diff --git a/src/assets/assets.ts b/src/assets/assets.ts index 8b1c3ad..affaecc 100644 --- a/src/assets/assets.ts +++ b/src/assets/assets.ts @@ -14,6 +14,8 @@ const images = { 'https://res.cloudinary.com/xbu/image/upload/v1708539494/xbu_assets/noData_gs71zg.svg', emptyFavorites: 'https://res.cloudinary.com/xbu/image/upload/v1725365540/xbu_assets/undraw_with_love_re_1q3m_yxcely.svg', + collections: + 'https://res.cloudinary.com/xbu/image/upload/v1729604473/xbu_assets/collections_frakbc.svg', }; export const { @@ -25,4 +27,5 @@ export const { PageNotFound, NoData, emptyFavorites, + collections, } = images; diff --git a/src/assets/collections.svg b/src/assets/collections.svg new file mode 100644 index 0000000..dce0db6 --- /dev/null +++ b/src/assets/collections.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/filters/AsideFilter.tsx b/src/components/filters/AsideFilter.tsx index e69de29..cc13302 100644 --- a/src/components/filters/AsideFilter.tsx +++ b/src/components/filters/AsideFilter.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Flex, Icon } from '@chakra-ui/react'; +import { CgOptions } from 'react-icons/cg'; + +export function AsideFilter() { + return ( + <> + + + + Filtrar por: + + + + + ); +} diff --git a/src/components/forms/FormCreateUser.tsx b/src/components/forms/FormCreateUser.tsx index 51d980a..b3e542f 100644 --- a/src/components/forms/FormCreateUser.tsx +++ b/src/components/forms/FormCreateUser.tsx @@ -27,7 +27,7 @@ export function FormCreateUser() { setUsername(value); } - function handleSubmit(e) { + function handleSubmit(e: React.FormEvent) { e.preventDefault(); mutateAsync(token); } @@ -38,50 +38,45 @@ export function FormCreateUser() { return ( <> - - - - Elegir nombre de usuario - - - - xbu.com/ - - - - +
+ + + + Elegir nombre de usuario + + + + xbu.com/ + + + + + - +
); } diff --git a/src/components/modals/ModalCollection.tsx b/src/components/modals/ModalCollection.tsx new file mode 100644 index 0000000..b4ce607 --- /dev/null +++ b/src/components/modals/ModalCollection.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Button, + Flex, + FormControl, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + useColorModeValue, +} from '@chakra-ui/react'; + +import { useCreateCollections } from '@hooks/queries'; +import { useAuth } from '@contexts/AuthContext'; + +interface ModalCollectionType { + isOpen: boolean; + onClose: () => void; + refetch: () => void; +} + +export function ModalCollection({ isOpen, onClose, refetch }: ModalCollectionType) { + const [name, setName] = useState(''); + const bgColorBox = useColorModeValue('white', 'gray.900'); + const bgColorInput = useColorModeValue('gray.100', 'gray.800'); + const { currentUser } = useAuth(); + const uid = currentUser?.uid; + const navigate = useNavigate(); + const { mutate, isPending, isSuccess } = useCreateCollections(uid); + + function handleNameCollection(e: React.ChangeEvent) { + const { value } = e.target; + + setName(value); + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + mutate(name); + } + + useEffect(() => { + if (isSuccess) { + onClose(); + refetch(); + setName(''); + navigate('/my-collections'); + } + }, [isSuccess, navigate, onClose]); + + return ( + <> + + + + Crear nueva colección + + +
+ + + Nombre + + + + +
+
+
+
+ + ); +} diff --git a/src/components/nav/menu/MenuProfile.tsx b/src/components/nav/menu/MenuProfile.tsx index 3fce03b..b4143aa 100644 --- a/src/components/nav/menu/MenuProfile.tsx +++ b/src/components/nav/menu/MenuProfile.tsx @@ -56,6 +56,13 @@ export function MenuProfile({ displayName, photoURL, username }: MenuType) { Crear Publicación + + Mis Colecciones + patchToggleFavorite(userId, body, isFavorite), - onError: (error) => { - console.error('Error updating favorite status'); - }, }); } @@ -278,6 +278,33 @@ function useAllFavoriteByUser(userId: string | undefined) { }); } +function useCreateCollections(userId: string | undefined) { + return useMutation({ + mutationKey: [keys.createCollections], + mutationFn: (name: string | undefined) => postCollections(userId, name), + }); +} + +function useCollections(userId: string | undefined) { + return useQuery({ + queryKey: [keys.allCollections], + queryFn: () => getFindAllCollections(userId), + refetchOnWindowFocus: true, + retry: false, + }); +} + +function useDeleteCollections() { + return useMutation({ + mutationKey: [keys.deleteCollections], + mutationFn: ([id, collectionId]: [string | undefined, string]) => + deleteCollections(id, collectionId), + onError: async (error) => { + console.error('Error en el servidor:', error); + }, + }); +} + function useDeleteBook() { return useMutation({ mutationKey: [keys.deleteBook], @@ -328,6 +355,9 @@ export { useRelatedBooks, useMoreBooksAuthors, useFavoriteBook, + useCollections, + useCreateCollections, + useDeleteCollections, // Usuarios useUserRegister, diff --git a/src/pages/profile/MyCollections.tsx b/src/pages/profile/MyCollections.tsx new file mode 100644 index 0000000..4923b01 --- /dev/null +++ b/src/pages/profile/MyCollections.tsx @@ -0,0 +1,155 @@ +import React from 'react'; +import { + Box, + Button, + Flex, + Icon, + Image, + useColorModeValue, + useDisclosure, +} from '@chakra-ui/react'; +import { ScrollRestoration } from 'react-router-dom'; +import { TbPlaylistAdd } from 'react-icons/tb'; + +import { ContainerTitle } from '@components/layout/ContainerTitle'; +import { MainHead } from '@components/layout/Head'; +import { collections } from '@assets/assets'; +import { MyContainer } from '@components/ui/MyContainer'; +import { ModalCollection } from '@components/modals/ModalCollection'; +import { useCollections, useDeleteCollections } from '@hooks/queries'; +import { useAuth } from '@contexts/AuthContext'; +import { parseDate } from '@utils/utils'; + +export function MyCollections() { + const bgColorButton = useColorModeValue('green.500', 'green.700'); + const grayColor = useColorModeValue('#E2E8F0', '#2D3748'); + const { isOpen, onOpen, onClose } = useDisclosure(); + const { currentUser } = useAuth(); + const uid = currentUser?.uid; + const { data, refetch } = useCollections(uid); + const { mutate, isSuccess } = useDeleteCollections(); + let collectionsUI; + + function deleteCollection(id: string) { + mutate([uid, id]); + } + + if (isSuccess) { + refetch(); + } + + if (!data || !data?.collections || data?.collections.length === 0) { + collectionsUI = ( + + + + Aún no tienes colecciones creadas + + + ); + } else { + collectionsUI = ( + + {data?.collections.map(({ id, name, createdAt }) => ( + + + + {name} + {parseDate(createdAt)} + + + + + ))} + + ); + } + + return ( + <> + + + + + + + {data?.totalCollections} Colecciones + + + + {collectionsUI} + + ); +} diff --git a/src/routes.tsx b/src/routes.tsx index aaae6cc..0cdb56f 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -14,6 +14,7 @@ import { PrivateRoute } from '@components/nav/PrivateRoute'; import { MyAccount } from '@pages/profile/account/MyAccount'; import { Profile } from '@pages/profile/Profile'; import { RouteWatcher } from '@hooks/RouteWatcher'; +import { MyCollections } from '@pages/profile/MyCollections'; const PrivacyPolicies = lazy(() => import('@pages/PrivacyPolicies')); const Explore = lazy(() => import('@pages/Explore')); @@ -123,6 +124,14 @@ export const routes = createBrowserRouter([ ), }, + { + path: '/my-collections', + element: ( + + + + ), + }, { path: '*', element: , diff --git a/src/services/api.ts b/src/services/api.ts index cbbbbec..7cf1a5c 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -71,6 +71,24 @@ async function patchToggleFavorite( }); } +async function getFindAllCollections(userId: string | undefined) { + return await fetchData(`${API_URL}/users/${userId}/my-collections`); +} + +async function postCollections(userId: string | undefined, body: any) { + return await fetchData(`${API_URL}/users/${userId}/my-collections`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name: body }), + }); +} + +async function deleteCollections(id: string | undefined, collectionId: string) { + return await fetchData(`${API_URL}/users/${id}/my-collections/${collectionId}`, { + method: 'DELETE', + }); +} + async function postBook(books: any) { return await fetchData(`${API_URL}/books`, { method: 'POST', @@ -153,6 +171,9 @@ export { getRelatedBooks, getMoreBooksAuthors, patchToggleFavorite, + getFindAllCollections, + postCollections, + deleteCollections, postBook, deleteBook, updateBook, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 57bf46d..f2712ab 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -23,6 +23,9 @@ const keys = { checkUser: 'CheckUser', userData: 'UserData', userFavoriteBooks: 'UserFavoriteBooks', + createCollections: 'CreateCollections', + allCollections: 'AllCollections', + deleteCollections: 'DeleteCollections', deleteAccount: 'DeleteAccount', };