diff --git a/src/app/components/ErrorComponent.js b/src/app/components/ErrorComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..7991a8550bd4ebc634e5304127302beb1cf30ca7 --- /dev/null +++ b/src/app/components/ErrorComponent.js @@ -0,0 +1,8 @@ +export default function ErrorComponent ({ name }) { + return ( + <div className="flex flex-col text-center"> + + <p className="text-text-filter mt-64 font-bold text-2xl">{name} não encontrado!</p> + </div> + ) + } \ No newline at end of file diff --git a/src/app/not-found.js b/src/app/not-found.js new file mode 100644 index 0000000000000000000000000000000000000000..a00af4438221a1a50153132635f2fddc942d7383 --- /dev/null +++ b/src/app/not-found.js @@ -0,0 +1,17 @@ +"use client" +import { useRouter } from "next/navigation"; +import Overlay from "./components/Overlay"; + +export default function Error(){ + const router = useRouter() + return ( + <Overlay> + + <div className="flex justify-center items-center mt-72 flex-col"> + <p className="text-8xl text-text-filter font-bold">404</p> + <p className="text-text-filter text-xl font-semibold">Página não encontrada</p> + <button onClick={() => router.push("/")} className="mt-4 bg-secondary text-lg text-white p-2 px-4 rounded-lg hover:bg-secondary-hover">Voltar para página inicial</button> + </div> + </Overlay> + ) +} \ No newline at end of file diff --git a/src/app/perfil/[id]/components/AboutCard.js b/src/app/perfil/[id]/components/AboutCard.js index a940e1bfadf826e095e3b533e72c21e552a1231b..f297a18d08352d9c419c7ce3c9fc4b263048f723 100644 --- a/src/app/perfil/[id]/components/AboutCard.js +++ b/src/app/perfil/[id]/components/AboutCard.js @@ -1,47 +1,24 @@ // components/Card.js -import { useState } from 'react'; -import { useEffect } from 'react'; -import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; -import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import NotFound from './NotFound'; /** * @param {Object} props - * @param {String} props.title Sobre * @param {String} props.content Descrição do usuário * @returns card com a descrição do usuário */ -const AboutCard = ({ title, content }) => { - const [expanded, setExpanded] = useState(false); - const [showButton, setShowButton] = useState(false); +const AboutCard = ({ content }) => { - /* Verificar se o conteúdo é maior que a altura inicial */ - useEffect(() => { - setShowButton(document.getElementById('content').scrollHeight > 50); - }, []); - - const toggleContent = () => { - setExpanded(!expanded); - }; return ( - <div className="mt-10 min-w-[200px] overflow-hidden transition-height bg-white"> + <div className="mt-6 min-w-[200px] overflow-hidden transition-height bg-white"> <div className='p-4 rounded-md min-w-[200px] min-h-[180px] ' > - <h2 className="text-main-text text-2xl font-semibold ">{title}</h2> - <div id="content" className="content pt-2"> - <p className={` ${expanded ? '' : 'line-clamp-3'} text-sm text-main-text `}>{content}</p> - </div> - <div className='flex justify-center'> - {/*Verifica se é para o botão aparecer e faz a lógica de Ver mais */} - {showButton && ( - <button - onClick={toggleContent} - className="bg-white text-main-text px-4 py-2 rounded transition-colors hover:bg-gray-200 text-base font-medium" - > - {expanded ? 'Ver menos' : 'Ver mais'} - {expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} - </button> - )} - </div> + <h2 className="text-main-text text-2xl font-semibold ">Sobre</h2> + {content ? <div id="content" className="content pt-2"> + <p className={` text-sm text-main-text `}>{content}</p> + </div> : + <NotFound name="descrição" /> + } + </div> </div> ); diff --git a/src/app/perfil/[id]/components/FollowersCards.js b/src/app/perfil/[id]/components/FollowersCards.js index 638381e7cf6219bb7be75bbe84131f8982e31e39..de65dde92412295b78a26fc1f25a1b37f1b398d3 100644 --- a/src/app/perfil/[id]/components/FollowersCards.js +++ b/src/app/perfil/[id]/components/FollowersCards.js @@ -3,6 +3,7 @@ import mecredApi from "@/axiosConfig" import { getStoredValue } from "@/app/handlers/localStorageHandler" import UsersPageCard from "@/app/components/UsersPageCard" import { Button } from "@mui/material" +import NotFound from "./NotFound" /** * @@ -49,6 +50,8 @@ export default function FollowersCards({ id, count }) { <div className="flex justify-between pt-10"> <div className="text-main-text text-2xl font-semibold inline-block ">Seguidores</div> </div> + {count === 0 ? + <NotFound name="seguidores" /> : <div className='p-3 my-5 rounded-md min-w-[200px] min-h-[180px] ' > <div className="flex flex-wrap justify-center"> {followers.map((item, i) => { @@ -64,6 +67,7 @@ export default function FollowersCards({ id, count }) { </div> </div> + } {numberCards+12 < count ? <Button fullWidth diff --git a/src/app/perfil/[id]/components/FollowingCards.js b/src/app/perfil/[id]/components/FollowingCards.js index f99fbc6e94b7a0fbc4dade420c7e2dc9b126198c..0c409686f9958795e4b71a1429e98290cfc84742 100644 --- a/src/app/perfil/[id]/components/FollowingCards.js +++ b/src/app/perfil/[id]/components/FollowingCards.js @@ -3,6 +3,7 @@ import mecredApi from "@/axiosConfig" import { getStoredValue } from "@/app/handlers/localStorageHandler" import UsersPageCard from "@/app/components/UsersPageCard" import { Button } from "@mui/material" +import NotFound from "./NotFound" /** * @@ -49,22 +50,25 @@ export default function FollowingCards({ id, count }) { <div className="flex justify-between pt-10"> <div className="text-main-text text-2xl font-semibold inline-block ">Seguindo</div> </div> - <div className='p-3 my-5 rounded-md min-w-[200px] min-h-[180px] ' > - <div className="flex flex-wrap justify-center"> - {following.map((item, i) => { - return ( + {count === 0 ? + <NotFound name="Seguindo" /> : + <div className='p-3 my-5 rounded-md min-w-[200px] min-h-[180px] ' > + <div className="flex flex-wrap justify-center"> + {following.map((item, i) => { + return ( item && <Fragment key={i}> - <UsersPageCard item={item["followable"]}/> + <UsersPageCard item={item["followable"]} /> </Fragment> - - ) - })} + ) + })} + + </div> </div> - </div> - {numberCards+12 < count ? + } + {numberCards + 12 < count ? <Button fullWidth disableElevation diff --git a/src/app/perfil/[id]/components/NotFound.js b/src/app/perfil/[id]/components/NotFound.js new file mode 100644 index 0000000000000000000000000000000000000000..ca50e0b734efe3bedd65bff31a31e7872a7e6252 --- /dev/null +++ b/src/app/perfil/[id]/components/NotFound.js @@ -0,0 +1,7 @@ +export default function NotFound({ name }) { + return ( + <div className="flex justify-center"> + <p className="pt-12 text-text-filter text-lg">{name === "Seguindo" ? `O usuário não segue ninguém ainda.` : `O usuário não possui ${name}`}</p> + </div> + ) +} \ No newline at end of file diff --git a/src/app/perfil/[id]/components/ProfileCollections.js b/src/app/perfil/[id]/components/ProfileCollections.js index 46513e5e157fa496cc1ea6d8ee9ee1cdac95563c..baa6c5a184221aa17b62a9d9d283aa707b4e4a3b 100644 --- a/src/app/perfil/[id]/components/ProfileCollections.js +++ b/src/app/perfil/[id]/components/ProfileCollections.js @@ -6,6 +6,7 @@ import DownloadButton from "@/app/components/DownloadButton"; import GroupCardsCollections from "@/app/components/GroupCardsCollections"; import Link from "next/link"; import mecredApi from "@/axiosConfig"; +import NotFound from "./NotFound"; /** * @returns Coleçoes dos usuários @@ -84,29 +85,32 @@ export default function ProfileCollections({ id, idLogin }) { Criar Nova Coleção </button>} </div> + {collections.length === 0 ? + <NotFound name="coleções" /> : - <div className="justify-center mt-5"> - {collections.map((item, index) => { - return ( - <div className="" key={index}> - <div key={item['id']} > - <div className="bg-white mb-10 pt-6 pl-4 mr-6 rounded-2xl"> - <p className="text-2xl max-md:text-center font-bold mb-0 text-text-color mr-2 inline-block">{item['name']}</p> - <div className="flex flex-wrap justify-between mr-8 max-md:flex-col "> - <p className=" ml-1 max-md:text-center text-main-text mb-0"> - por <Link href={"/perfil/" + item["owner"]["id"]}> {item["owner"]["name"]}</Link> - </p> - <div className="flex max-md:justify-center"> - {item['collection_items'].length !== 0 && <DownloadButton id={item['id']} objects={item['collection_items']} />} + <div className="justify-center mt-5"> + {collections.map((item, index) => { + return ( + <div className="" key={index}> + <div key={item['id']} > + <div className="bg-white mb-10 pt-6 pl-4 mr-6 rounded-2xl"> + <p className="text-2xl max-md:text-center font-bold mb-0 text-text-color mr-2 inline-block">{item['name']}</p> + <div className="flex flex-wrap justify-between mr-8 max-md:flex-col "> + <p className=" ml-1 max-md:text-center text-main-text mb-0"> + por <Link href={"/perfil/" + item["owner"]["id"]}> {item["owner"]["name"]}</Link> + </p> + <div className="flex max-md:justify-center"> + {item['collection_items'].length !== 0 && <DownloadButton id={item['id']} objects={item['collection_items']} />} + </div> </div> + <GroupCardsCollections cardsPerRow={4} data={item['collection_items']} /> </div> - <GroupCardsCollections cardsPerRow={4} data={item['collection_items']} /> </div> </div> - </div> - ); - })} - </div> + ); + })} + </div> + } {/** * verifica se o número de coleções é menor que o total de coleções * se sim, aparece o botão de ver mais @@ -125,7 +129,7 @@ export default function ProfileCollections({ id, idLogin }) { {"Ver mais..."} </p> </Button> - : <></>} + : <></>} </div> ); } diff --git a/src/app/perfil/[id]/components/ProfileHomologation.js b/src/app/perfil/[id]/components/ProfileHomologation.js index b9867a08f2e591a76d7d08de61d73fb8527b11f4..c3bf251ec516cf374b42b0453de57d76f953320e 100644 --- a/src/app/perfil/[id]/components/ProfileHomologation.js +++ b/src/app/perfil/[id]/components/ProfileHomologation.js @@ -11,8 +11,8 @@ import CardsHomologation from "./CardsHomologation"; */ export default function ProfileHomologation({ id }) { const [homologated, setHomologated] = useState([]) - const [expanded, setExpanded] = useState(false); const [numberCards, setNumberCards] = useState(0) + const [totalCount, setTotalCount] = useState(0) const token = getStoredValue("access_token") const client = getStoredValue("client") const uid = getStoredValue("uid") @@ -29,43 +29,46 @@ export default function ProfileHomologation({ id }) { 'Expires': 0 } }) - .then(({ data }) => { + .then(({ data, headers }) => { + setHomologated(prevData => [...prevData, ...data]); + setTotalCount(Number(headers["x-total-count"])) }); } fetchHomologation(id); - }, [client, id, token, numberCards, uid]); + }, [id, numberCards]); /** * Aumenta de 12 em 12 o número de recursos à serem homologados */ const toggleContent = () => { setNumberCards(numberCards + 12) - setExpanded(!expanded); }; return ( <Card className='p-3 my-10 rounded-md min-w-[200px] min-h-[180px] ' > <div className="text-main-text text-2xl font-semibold ">Homologação</div> - <div className={`flex content flex-wrap justify-center ${expanded ? "" : "overflow-y-hidden"}`}> + <div className={`flex content flex-wrap justify-center `}> {homologated.map((item, i) => { return <CardsHomologation item={item} key={i} /> })} </div> - <Divider className="mr-8 max-md:mr-0 mb-8"> - <Button - fullWidth - disableElevation - variant="outlined" - className="mt-2 border-secondary rounded-xl hover:border-secondary-hover text-gray-700 normal-case flex gap-2" - onClick={() => toggleContent()} - alt="Carregar mais" - > - {"Carregar mais"} - </Button> - </Divider> + {numberCards + 12 < totalCount ? + <Button + fullWidth + disableElevation + variant="outlined" + className="my-4 border-hidden hover:bg-transparent rounded-xl normal-case " + onClick={() => toggleContent()} + > + <p className="hover:bg-main px-4 text-gray-600 text-lg rounded-md"> + + {"Ver mais..."} + </p> + </Button> + : <></>} </Card> ); } \ No newline at end of file diff --git a/src/app/perfil/[id]/components/ProfileOptions.js b/src/app/perfil/[id]/components/ProfileOptions.js index cff7bfe86718214f0ec8fff0fc2b93a9d2be351e..130ec7b08e9879f71a51fcd6c788832159c7799f 100644 --- a/src/app/perfil/[id]/components/ProfileOptions.js +++ b/src/app/perfil/[id]/components/ProfileOptions.js @@ -8,19 +8,17 @@ import FollowersCards from "./FollowersCards"; /** * @returns Retorna para o usuário qual dos botões em SelectionButtons que ele solicitou * @param {Object} props - * @param {string} props.title Sobre * @param {string} props.content Descrição do usuário * @param {Number} props.optButton Qual dos botões foi selecionado pelo usuário (Sobre, Recursos, Coelções ou Homologação) * @param {Number} props.id id do usuário acessado * @param {Number} props.idLogin id do usuário logado */ -export default function ProfileOptions({ title, content, optButton, id, idLogin, followingCount, followersCount }) { +export default function ProfileOptions({ content, optButton, id, idLogin, followingCount, followersCount }) { - const opts = (op) => { switch (op) { case 0: - return <AboutCard title={title} content={content} /> + return <AboutCard content={content} /> case 1: return <ProfileResources id={id} idLogin={idLogin} /> case 2: @@ -32,7 +30,7 @@ export default function ProfileOptions({ title, content, optButton, id, idLogin, case 5: return <ProfileHomologation id={id} /> default: - return <AboutCard title={title} content={content} /> + return <AboutCard content={content} /> } } diff --git a/src/app/perfil/[id]/components/ProfileResources.js b/src/app/perfil/[id]/components/ProfileResources.js index 0ae9bffd932c226cad40479853934c2b0efa2e99..5db6614184823bf69c347e0ee21229d78977f28f 100644 --- a/src/app/perfil/[id]/components/ProfileResources.js +++ b/src/app/perfil/[id]/components/ProfileResources.js @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import mecredApi from "@/axiosConfig"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import { Button } from "@mui/material"; +import NotFound from "./NotFound"; /** * @returns Recursos do usuário @@ -65,7 +66,10 @@ export default function ProfileResources({ id, idLogin }) { </button> </Link>} </div> - <div className='p-3 my-5 rounded-md min-w-[200px] min-h-[180px] bg-white ' > + {totalCount === 0 ? + <NotFound name="recursos" /> : + + <div className='p-3 my-5 rounded-md min-w-[200px] min-h-[180px] bg-white ' > <div className="flex flex-wrap justify-center"> {resources.map((resource, index) => { return ( @@ -77,11 +81,12 @@ export default function ProfileResources({ id, idLogin }) { avatar={resource["publisher"]["avatar"]} image={resource["thumbnail"]} updated_at={resource["updated_at"]} - /> - ); - })} + /> + ); + })} </div> </div> + } {numberCards + 12 < totalCount ? <Button fullWidth diff --git a/src/app/perfil/[id]/components/SelectionButtons.js b/src/app/perfil/[id]/components/SelectionButtons.js index e17a8aded60de0df52c3748bcc452576c3c4c10a..f6b703665426434b00a3c8be1e2b84fc00082802 100644 --- a/src/app/perfil/[id]/components/SelectionButtons.js +++ b/src/app/perfil/[id]/components/SelectionButtons.js @@ -1,5 +1,7 @@ import Grid from "@mui/material/Grid" import { isLoggedIn } from '@/app/handlers/loginHandler'; +import { Tab, Tabs } from "@mui/material"; +import { useState } from "react"; /** * @return Botões de informações que o usuário pode acessar @@ -10,6 +12,11 @@ import { isLoggedIn } from '@/app/handlers/loginHandler'; * @param {Number} props.idLogin */ export default function SelectionButtons({ setOptButton, verifyCurator, idProfile, idLogin }) { + const [value, setValue] = useState(0) + const handleChange = (e, newValue) => { + setValue(newValue) + setOptButton(newValue) + } /** * Se o usuário estiver logado, vendo seu próprio perfil e for curador tem todas as opções disponíveis. @@ -22,22 +29,46 @@ export default function SelectionButtons({ setOptButton, verifyCurator, idProfil ["Sobre", "Meus Recursos", "Minhas Coleções", "Seguidores", "Seguindo"]) : ["Sobre", "Recursos", "Coleções", "Seguidores", "Seguindo"] return ( - <Grid container className='' > - { - buttons.map((item, index) => { - return ( - <Grid item key={index} xs={6} sm={12 / buttons.length} lg={12 / buttons.length}> - <button - className="h-12 text-main-text text-base normal-case font-bold w-[100%] hover:bg-gray-100 hover:text-secondary hover:border-b-2 hover:border-secondary focus:text-secondary focus:border-b-2 focus:border-secondary" - onClick={() => setOptButton(index)} - // title={item} - alt={item} - > - {item} - </button> - </Grid> - ) - })} - </Grid> + <div> + + <div className="max-lg:hidden"> + + <Tabs + value={value} + onChange={handleChange} + variant="fullWidth" + + > + {buttons.map((item, index) => { + + return ( + <Tab key={index} value={index} label={item} /> + ) + })} + + </Tabs> + </div> + <div className="lg:hidden"> + + <Tabs + value={value} + onChange={handleChange} + variant="scrollable" + scrollButtons="auto" + allowScrollButtonsMobile + + > + {buttons.map((item, index) => { + + return ( + <Tab key={index} value={index} label={item} /> + ) + })} + + </Tabs> + </div> + + </div> + ); }; \ No newline at end of file diff --git a/src/app/perfil/[id]/components/UserCard.js b/src/app/perfil/[id]/components/UserCard.js index 1c5490a9fd40ed60782dea1d2cdcb50a3f59a4c9..a9971c3c23c6706272cfa441e4c64c0fbc676a65 100644 --- a/src/app/perfil/[id]/components/UserCard.js +++ b/src/app/perfil/[id]/components/UserCard.js @@ -185,10 +185,10 @@ export default function UserCard({ profileData, idLogin}) { </div> {/* Botões de recursos, sobre, coleções e homologação, mostra quais botões para cada usuário */} <div className='rounded-md min-w-[200px] mt-10 bg-white'> - {<SelectionButtons setOptButton={setOptButton} verifyCurator={verifyCurator} idLogin={idLogin} idProfile={profileData["id"]} />} + {<SelectionButtons setOptButton={setOptButton} verifyCurator={verifyCurator} idLogin={idLogin} idProfile={profileData["id"]} />} </div> {/* Faz a escolha correta de qual botão o usuário clicou */} - <ProfileOptions title="Sobre" content={profileData["description"]} optButton={optButton} id={profileData["id"]} idLogin={idLogin} followersCount={followers} followingCount={following} /> + <ProfileOptions content={profileData["description"]} optButton={optButton} id={profileData["id"]} idLogin={idLogin} followersCount={followers} followingCount={following} /> </div> </> ); diff --git a/src/app/perfil/[id]/page.js b/src/app/perfil/[id]/page.js index 8e0d4b050d05c94fd268bf4893d57d0cde3f5d4c..c0789c4c32ddd3638192dc120735a02c30d46df7 100644 --- a/src/app/perfil/[id]/page.js +++ b/src/app/perfil/[id]/page.js @@ -5,9 +5,11 @@ import { useEffect, useState } from "react"; import Overlay from "../../components/Overlay"; import mecredApi from "@/axiosConfig"; import { isLoggedIn } from "@/app/handlers/loginHandler"; +import ErrorComponent from "@/app/components/ErrorComponent"; export default function Perfil({ params }) { const [profileData, setProfileData] = useState(null); + const [error, setError] = useState(false) const [idLogin, setIdLogin] = useState(0) const token = getStoredValue("access_token") const client = getStoredValue("client") @@ -41,7 +43,8 @@ export default function Perfil({ params }) { }) .then(({ data }) => { setProfileData(data); - }); + }) + .catch(() => setError(true)) } fetchUser(params.id) @@ -54,7 +57,8 @@ export default function Perfil({ params }) { .get(`/users/${id}`) .then(({ data }) => { setProfileData(data); - }); + }) + .catch(() => setError(true)) } fetchUser(params.id) @@ -62,10 +66,13 @@ export default function Perfil({ params }) { }, [params.id, client, token, uid]) + return ( <> <Overlay> - {profileData && <UserCard profileData={profileData} idLogin={idLogin} tab={optTab} />} + {error ? <ErrorComponent name="Usuário" /> : + + ( profileData && <UserCard profileData={profileData} idLogin={idLogin} tab={optTab} />)} </Overlay> </> ); diff --git a/src/app/recurso/[id]/page.js b/src/app/recurso/[id]/page.js index 381fb1e8a74ad89a05c104756feee7405394470c..ccfafb6e7e9efa7aa45f4b494f530155c8e7bbf2 100644 --- a/src/app/recurso/[id]/page.js +++ b/src/app/recurso/[id]/page.js @@ -12,10 +12,12 @@ import { getStoredValue } from "@/app/handlers/localStorageHandler"; import Loading from "@/app/components/Loading"; import ResourcePreview from "./components/resourcePreview"; import NeedLoginModal from "./components/needLoginModal"; +import ErrorComponent from "@/app/components/ErrorComponent"; export default function Recurso({ params }) { const [learningObject, setLearningObject] = useState(undefined); const [needLoginOpen, setNeedLoginOpen] = useState(false); + const [error, setError] = useState(false) const token = getStoredValue("access_token"); const client = getStoredValue("client"); @@ -40,13 +42,17 @@ export default function Recurso({ params }) { }) .then((response) => { setLearningObject(response.data); - }); + }) + .catch(() => {setError(true);}) }, [params.id, client, token, uid]); + return ( <> <Overlay> - {!learningObject ? ( + {!learningObject ? ( error ? + <ErrorComponent name="Recurso" /> : + <Loading scroll /> ) : ( <>