diff --git a/frontend-plataforma-tutoriais/src/components/animatedNumberCount/index.tsx b/frontend-plataforma-tutoriais/src/components/animatedNumberCount/index.tsx index 06c3f7d..7c62c45 100644 --- a/frontend-plataforma-tutoriais/src/components/animatedNumberCount/index.tsx +++ b/frontend-plataforma-tutoriais/src/components/animatedNumberCount/index.tsx @@ -1,14 +1,26 @@ -import { useMotionValue, useSpring } from "framer-motion"; -import { useEffect, useState } from "react"; +import { useInView, useMotionValue, useSpring } from "framer-motion"; +import { useEffect, useRef, useState } from "react"; -const AnimatedCounter = ({ value }: { value: number }) => { +type AnimatedCounterProps = { + value: number; + /** Só anima na primeira vez que entra no ecrã (default: true) */ + once?: boolean; +}; + +const AnimatedCounter = ({ value, once = true }: AnimatedCounterProps) => { + const ref = useRef(null); + const isInView = useInView(ref, { once, amount: 0.8 }); const motionValue = useMotionValue(0); const spring = useSpring(motionValue, { stiffness: 60, damping: 20 }); const [display, setDisplay] = useState(0); useEffect(() => { - motionValue.set(value); - }, [value]); + if (isInView) { + motionValue.set(value); + } else if (!once) { + motionValue.set(0); + } + }, [isInView, value, motionValue, once]); useEffect(() => { const unsubscribe = spring.on("change", (v) => { @@ -17,7 +29,7 @@ const AnimatedCounter = ({ value }: { value: number }) => { return unsubscribe; }, [spring]); - return {display}; + return {display}; }; -export default AnimatedCounter; \ No newline at end of file +export default AnimatedCounter; diff --git a/frontend-plataforma-tutoriais/src/components/animatedProgressBar/index.tsx b/frontend-plataforma-tutoriais/src/components/animatedProgressBar/index.tsx index 3eacb63..44c7d6d 100644 --- a/frontend-plataforma-tutoriais/src/components/animatedProgressBar/index.tsx +++ b/frontend-plataforma-tutoriais/src/components/animatedProgressBar/index.tsx @@ -1,18 +1,29 @@ -import { useMotionValue, useSpring } from "framer-motion"; -import { useEffect, useState } from "react"; +import { useInView, useMotionValue, useSpring } from "framer-motion"; +import { useEffect, useRef, useState } from "react"; import { ProgressBar } from "react-bootstrap"; -const AnimatedProgressBar = ({ value, className }: { value: number; className?: string }) => { +type AnimatedProgressBarProps = { + value: number; + className?: string; + once?: boolean; +}; + +const AnimatedProgressBar = ({ value, className, once = true }: AnimatedProgressBarProps) => { + const ref = useRef(null); + const isInView = useInView(ref, { once, amount: 0.5 }); const motionValue = useMotionValue(0); const spring = useSpring(motionValue, { stiffness: 60, damping: 20 }); const [display, setDisplay] = useState(0); useEffect(() => { - motionValue.set(value); - }, [value]); + if (isInView) { + motionValue.set(value); + } else if (!once) { + motionValue.set(0); + } + }, [isInView, value, motionValue, once]); useEffect(() => { - // ✅ subscribe mantém o state sincronizado com o spring const unsubscribe = spring.on("change", (v) => { setDisplay(Math.round(v)); }); @@ -20,12 +31,14 @@ const AnimatedProgressBar = ({ value, className }: { value: number; className?: }, [spring]); return ( - +
+ +
); }; -export default AnimatedProgressBar; \ No newline at end of file +export default AnimatedProgressBar; diff --git a/frontend-plataforma-tutoriais/src/components/header/index.tsx b/frontend-plataforma-tutoriais/src/components/header/index.tsx index 0de9128..52c1b2d 100644 --- a/frontend-plataforma-tutoriais/src/components/header/index.tsx +++ b/frontend-plataforma-tutoriais/src/components/header/index.tsx @@ -14,6 +14,7 @@ import { PiCheckCircleFill } from "react-icons/pi"; import { useGetWorkshopsSearch } from "../../hooks/useGetWorkshopsSearch"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../utils/imageSkeleton"; import { motion, AnimatePresence } from "framer-motion"; +import { dropdownTransition, dropdownVariants, slideFromTopTransition, slideFromTopVariants } from "../../utils/pageMotionVariants"; import { useGetCurrentUser } from "../../hooks/useGetCurrentUser"; import AnimatedCounter from "../animatedNumberCount"; @@ -220,7 +221,7 @@ export default function Header() { <> {videosSearched.length > 0 ? ( -
+ Vídeos
@@ -249,12 +250,12 @@ export default function Header() { ))}
-
+ ) : null} {workshopsSearched.length > 0 ? ( -
+ Workshops
{workshopsSearched.length > 0 && ( @@ -295,7 +296,7 @@ export default function Header() { ))}
-
+ ) : null} @@ -319,12 +320,13 @@ export default function Header() { {showDropdown && ( + style={{ display: "block", zIndex: 2000, right: 0, top: "100%", marginTop: "0.25rem" }}>
  • A minha conta
  • Sair
  • diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/createUser/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/createUser/index.tsx index c437ece..d50c5de 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/createUser/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/createUser/index.tsx @@ -6,6 +6,8 @@ import { LuArrowLeft } from "react-icons/lu"; import { LuPlus } from "react-icons/lu"; import { CgSpinner } from "react-icons/cg"; import Swal from "sweetalert2"; +import { motion } from "framer-motion"; +import { slideFromLeftVariants, slideFromLeftOnMountTransition, pageTitleTransition, pageTitleVariants, slideFromBottomVariants, slideFromBottomTransition } from "../../../../utils/pageMotionVariants"; export default function CreateUser() { const [name, setName] = useState(""); @@ -15,6 +17,7 @@ export default function CreateUser() { const [role_id, setRoleId] = useState("2"); const [creating, setCreating] = useState(false); + async function create() { //validação dos campos @@ -113,13 +116,13 @@ export default function CreateUser() { return (
    -
    + -
    -

    Criar Utilizador

    + + Criar Utilizador
    -
    +
    @@ -149,7 +152,7 @@ export default function CreateUser() {
    -
    +
    ) diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/createVideo/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/createVideo/index.tsx index 280ec14..22b2239 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/createVideo/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/createVideo/index.tsx @@ -7,6 +7,15 @@ import { LuArrowLeft, LuPlus } from "react-icons/lu"; import { LuUpload } from "react-icons/lu"; import Swal from "sweetalert2"; import { CgSpinner } from "react-icons/cg"; +import { motion } from "framer-motion"; +import { + pageTitleTransition, + pageTitleVariants, + slideFromBottomTransition, + slideFromBottomVariants, + slideFromLeftOnMountTransition, + slideFromLeftVariants, +} from "../../../../utils/pageMotionVariants"; export default function CreateVideo() { const [title, setTitle] = useState(""); @@ -208,10 +217,12 @@ export default function CreateVideo() { return (
    -
    + -
    -

    Adicionar Vídeo

    + + Adicionar Vídeo +
    +
    @@ -302,6 +313,8 @@ export default function CreateVideo() { )} + +
    ) } diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/createWorkshop/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/createWorkshop/index.tsx index 66b5d8a..08ceafc 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/createWorkshop/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/createWorkshop/index.tsx @@ -8,6 +8,15 @@ import "react-datepicker/dist/react-datepicker.css"; import { pt } from "date-fns/locale"; import { CgSpinner } from "react-icons/cg"; import Swal from "sweetalert2"; +import { motion } from "framer-motion"; +import { + pageTitleTransition, + pageTitleVariants, + slideFromBottomTransition, + slideFromBottomVariants, + slideFromLeftOnMountTransition, + slideFromLeftVariants, +} from "../../../../utils/pageMotionVariants"; export default function CreateWorkshop() { const [title, setTitle] = useState(""); @@ -92,10 +101,12 @@ export default function CreateWorkshop() { return (
    -
    + -
    -

    Adicionar Workshop

    + + Adicionar Workshop +
    +
    @@ -171,6 +182,8 @@ export default function CreateWorkshop() {
    + +
    ) } \ No newline at end of file diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/editVideo/[id].tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/editVideo/[id].tsx index 07b27b1..6aa509e 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/editVideo/[id].tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/editVideo/[id].tsx @@ -6,6 +6,8 @@ import styles from "./styles.module.css"; import { CgSpinner } from "react-icons/cg"; import { LuUpload, LuPlus, LuCheck, LuTrash2, LuArrowLeft } from "react-icons/lu"; import Swal from "sweetalert2"; +import { slideFromLeftVariants, slideFromLeftOnMountTransition, pageTitleTransition, pageTitleVariants, slideFromBottomVariants, slideFromBottomTransition } from "../../../../utils/pageMotionVariants"; +import { motion } from "framer-motion"; export default function editVideo() { const { id } = useParams(); @@ -247,17 +249,15 @@ export default function editVideo() {
    ) : (
    -
    + -
    + {video ? (
    - -
    - Alterar dados do vídeo -
    + Alterar dados do vídeo +
    @@ -337,7 +337,7 @@ export default function editVideo() {
    -
    +
    diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/editWorkshop/[id].tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/editWorkshop/[id].tsx index 347e82c..3eeae02 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/editWorkshop/[id].tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/editWorkshop/[id].tsx @@ -10,6 +10,8 @@ import "react-datepicker/dist/react-datepicker.css"; import Swal from "sweetalert2"; import styles from "./styles.module.css"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../../utils/imageSkeleton"; +import { motion } from "framer-motion"; +import { slideFromLeftVariants, slideFromLeftOnMountTransition, slideFromLeftTransition, slideFromRightVariants, slideFromRightTransition, slideFromTopTransition, slideFromTopVariants } from "../../../../utils/pageMotionVariants"; export default function Workshop() { const { id } = useParams(); @@ -192,19 +194,19 @@ export default function Workshop() { return (
    -
    + -
    + {workshop ? ( <>
    -
    + {workshop.title} -
    -
    + +
    {role === 1 ? ( @@ -253,12 +255,12 @@ export default function Workshop() { ) : null}
    -
    +
    {listagemInscritos ? ( -
    +
    @@ -292,12 +294,12 @@ export default function Workshop() {
    -
    + ) : null}
    {!formEdit && role === 1 ? ( -
    + ) : null} -
    + ) : null} {formEdit ? ( -
    + Alterar detalhes do workshop
    @@ -453,7 +455,7 @@ export default function Workshop() {
    - +
    ) : null} ) : ( diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/user/[id].tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/user/[id].tsx index c9a3d9a..a248852 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/user/[id].tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/user/[id].tsx @@ -7,6 +7,8 @@ import { LuCheck, LuTrash2, LuX, LuPencil, LuArrowLeft, LuCalendar, LuClock3 } f import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../../utils/imageSkeleton"; import { CgSpinner } from "react-icons/cg"; import Swal from "sweetalert2"; +import { motion } from "framer-motion"; +import { slideFromLeftVariants, slideFromLeftOnMountTransition, pageTitleTransition, pageTitleVariants, slideFromBottomVariants, slideFromBottomTransition, slideFromRightVariants, slideFromRightTransition, slideFromTopVariants, slideFromTopTransition, viewportOnce, cardInViewTransition, cardInViewVariants } from "../../../../utils/pageMotionVariants"; export default function User() { const { id } = useParams(); @@ -161,17 +163,17 @@ export default function User() { return (
    -
    + -
    + {user ? (
    - Dados do Utilizador + Dados do Utilizador
    -
    +
    {user.role_id === 1 ? "Administrador" : "Utilizador"} @@ -192,8 +194,8 @@ export default function User() {
    -
    -
    + +
    Vídeos assistidos @@ -206,15 +208,49 @@ export default function User() {
    -
    + -
    + + {formEdit ? ( + + Editar Utilizador + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    +
    + ) : null} +
    + + Workshops inscrito -
    + {workshopsParticipated.length > 0 ? ( workshopsParticipated.map((workshop) => (
    -
    +
    -
    +
    )) ) : ( -
    + Este utilizador ainda não participou em nenhum workshop -
    + )}
    -
    - {formEdit ? ( -
    - Editar Utilizador -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - ) : null} -
    ) : (
    diff --git a/frontend-plataforma-tutoriais/src/pages/private/admin/users/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/admin/users/index.tsx index ef2ed51..5b3c1fb 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/admin/users/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/admin/users/index.tsx @@ -8,6 +8,22 @@ import { LuPencil, LuSettings2, LuPlus } from "react-icons/lu"; import { CgSpinner } from "react-icons/cg"; import { Form, Pagination, Table } from "react-bootstrap"; import { AnimatePresence, motion } from "framer-motion"; +import { + dropdownTransition, + dropdownVariants, + fadeTransition, + fadeVariants, + listContentTransition, + listContentVariants, + pageTitleTransition, + pageTitleVariants, + slideFromLeftTransition, + slideFromLeftVariants, + slideFromRightOnMountTransition, + slideFromRightTransition, + slideFromRightVariants, + viewportOnce, +} from "../../../../utils/pageMotionVariants"; export default function Users() { @@ -22,6 +38,7 @@ export default function Users() { const [listTotal, setListTotal] = useState(0); const [loadingUsers, setLoadingUsers] = useState(false); const debouncedSearch = useDebounce(search, 500); + const listContentKey = `${currentPage}-${debouncedSearch}-${selectedUsers}`; useEffect(() => { index(debouncedSearch); @@ -79,19 +96,18 @@ export default function Users() { } return ( -
    -

    Utilizadores

    +
    + Utilizadores
    -
    + { setSearch(e.target.value); }} /> + -
    - -
    +
    setShowFilterDropdown(true)} @@ -104,10 +120,11 @@ export default function Users() { {showFilterDropdown && ( @@ -142,18 +159,29 @@ export default function Users() { )}
    -
    + -
    + Novo Utilizador -
    +
    - {loadingUsers ? ( -
    - -
    - ) : users.length > 0 ? ( +
    + + {loadingUsers ? ( + + + + ) : ( + + {users.length > 0 ? (
    @@ -229,6 +257,10 @@ export default function Users() { )} )} + + )} + + ) } \ No newline at end of file diff --git a/frontend-plataforma-tutoriais/src/pages/private/contactos/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/contactos/index.tsx index 95ecb28..0f81486 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/contactos/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/contactos/index.tsx @@ -6,6 +6,18 @@ import { useState } from "react"; import type { ApiErrorResponse } from "../../../types"; import { Navigate } from "react-router"; import AccordionFAQS from "../../../components/accordion FAQ´s/accordionFAQS"; +import { motion } from "framer-motion"; +import { + pageTitleTransition, + pageTitleVariants, + slideFromBottomTransition, + slideFromBottomVariants, + slideFromLeftTransition, + slideFromLeftVariants, + slideFromRightTransition, + slideFromRightVariants, + viewportOnce, +} from "../../../utils/pageMotionVariants"; export default function Contactos() { const token = localStorage.getItem("token"); @@ -64,9 +76,9 @@ export default function Contactos() { return (
    -

    Contactos

    + Contactos
    -
    +

    Os nossos dados

    Pode contactar-nos através do nosso email, telefone ou morada. @@ -91,8 +103,8 @@ export default function Contactos() { Rua da Escola, 123, Lisboa, Portugal
    -
    -
    + +

    Formulário de contacto

    Preencha o formulário abaixo para entrar em contacto connosco @@ -142,16 +154,16 @@ export default function Contactos() {
    -
    + -
    + Perguntas Frequentes
    -
    + ); diff --git a/frontend-plataforma-tutoriais/src/pages/private/dashboard/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/dashboard/index.tsx index d40b8ab..cf49b0d 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/dashboard/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/dashboard/index.tsx @@ -7,6 +7,16 @@ import { Link } from "react-router"; import { CgSpinner } from "react-icons/cg"; import AnimatedProgressBar from "../../../components/animatedProgressBar"; import AnimatedCounter from "../../../components/animatedNumberCount"; +import { motion } from "framer-motion"; +import { + slideFromLeftOnMountTransition, + slideFromLeftInViewTransition03, + slideFromLeftVariants, + slideFromRightOnMountTransition02, + slideFromRightInViewTransition04, + slideFromRightVariants, + viewportOnce, +} from "../../../utils/pageMotionVariants"; /* import { useGetCurrentUser } from "../../../hooks/useGetCurrentUser" import { useGetVideosLength } from "../../../hooks/useGetVideosLength"; @@ -190,7 +200,7 @@ export default function Home() { return (
    -
    +

    {role === 1 ? "Vídeos ativos" : "Continuar Formação"}

    Ver vídeos @@ -198,13 +208,13 @@ export default function Home() { {role !== 1 && ( <> - + - {progressoVideos === 100 && ( -
    - Parabéns! A sua formação está completa! -
    - )} + {progressoVideos === 100 && ( +
    + Parabéns! A sua formação está completa! +
    + )} )} @@ -237,9 +247,9 @@ export default function Home() {
    ) : null}
    -
    + -
    +

    Próximos workshops

    Ver workshops @@ -261,11 +271,11 @@ export default function Home() {

    {workshop.time_start.slice(0, 5)} - {workshop.time_end.slice(0, 5)}

    -

    {workshop.title}

    -
    - {workshop.date.split('-').reverse().join('-')} - {workshop.time_start.slice(0, 5).split(':').join('h')} - {workshop.time_end.slice(0, 5).split(':').join('h')} -
    +

    {workshop.title}

    +
    + {workshop.date.split('-').reverse().join('-')} + {workshop.time_start.slice(0, 5).split(':').join('h')} - {workshop.time_end.slice(0, 5).split(':').join('h')} +
    Detalhes @@ -290,11 +300,11 @@ export default function Home() {
    ) : null} - +
    -
    +
    @@ -342,23 +352,33 @@ export default function Home() {
    -
    + -
    +
    {role === 1 ? "Vídeos ativos" : "Vídeos assistidos"} - {role === 1 ? : }/ + + {role === 1 + ? + : <> / + } +
    {role === 1 ? "Workshops agendados" : "Workshops inscrito"} - {role === 1 ? : }/{} + + {role === 1 + ? + : <> / + } +
    -
    +
    diff --git a/frontend-plataforma-tutoriais/src/pages/private/profile/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/profile/index.tsx index 5d1b08a..d9a0b8f 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/profile/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/profile/index.tsx @@ -6,9 +6,27 @@ import { LuCheck, LuKeyRound, LuPencil, LuX, LuArrowUpRight, LuClock3, LuCalenda import { CgSpinner } from "react-icons/cg"; import { Link } from "react-router"; import AnimatedProgressBar from "../../../components/animatedProgressBar"; +import AnimatedCounter from "../../../components/animatedNumberCount"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton"; import { PiCheckCircleFill } from "react-icons/pi"; import Swal from "sweetalert2"; +import { motion } from "framer-motion"; +import { + cardInViewTransition, + cardInViewVariants, + pageTitleTransition, + pageTitleVariants, + slideFromBottomInViewTransition, + slideFromBottomVariants, + slideFromLeftTransitionNoDelay, + slideFromLeftVariants, + slideFromRightTransition, + slideFromRightVariants, + slideFromTopTransition, + slideFromTopTransitionDelayed, + slideFromTopVariants, + viewportOnce, +} from "../../../utils/pageMotionVariants"; export default function Profile() { const [role, setRole] = useState(0); @@ -197,11 +215,11 @@ export default function Profile() { return (
    - Os meus dados + Os meus dados
    -
    +
    @@ -213,46 +231,46 @@ export default function Profile() {
    Membro desde: {new Date(userData?.created_at as string).toLocaleDateString('pt-PT')}
    -
    -
    + + {role === 1 ? (
    Vídeos ativos - {videosCount} +
    Workshops agendados - {workshopsCount} +
    ) : (
    Vídeos assistidos - {videosWatched}/{videosCount} + /
    Workshops inscrito - {workshopsCount} +
    )} -
    +
    {formEdit ? ( -
    + Editar dados
    @@ -273,9 +291,9 @@ export default function Profile() {
    -
    + ) : formEditPassword ? ( -
    + Alterar password
    @@ -300,10 +318,10 @@ export default function Profile() {
    -
    + ) : null} -
    +

    {role === 1 ? "Vídeos ativos" : "Continuar Formação"}

    Ver vídeos @@ -311,7 +329,7 @@ export default function Profile() { {role !== 1 && ( <> - + {progressoVideos === 100 && (
    @@ -323,8 +341,8 @@ export default function Profile() {
    {videosCount > 0 ? videos.map((video: Video) => ( -
    - + +
    category.id).join(', ')} > {role === 1 && ( @@ -342,23 +360,23 @@ export default function Profile() { {video.watched && Visto}
    -
    + )) : videosCount === 0 ? (
    Nenhum vídeo encontrado
    ) : null}
    -
    +
    -
    +

    {role === 1 ? "Workshops agendados" : "Workshops Inscrito"}

    Ver workshops -
    +
    {role !== 1 && workshopsCount > 0 ? workshops.map((workshop: Workshop) => ( -
    +
    Detalhes {role !== 1 ? ( - ) : null}
    -
    + ) ) : workshopsCount === 0 ? (
    @@ -397,7 +415,7 @@ export default function Profile() { ) : null} {role === 1 && workshopsCount > 0 ? nextWorkshops.map((workshop: Workshop) => ( -
    +
    Detalhes
    -
    + ) ) : workshopsCount === 0 ? (
    diff --git a/frontend-plataforma-tutoriais/src/pages/private/profile/styles.module.css b/frontend-plataforma-tutoriais/src/pages/private/profile/styles.module.css index 0d7003d..783048b 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/profile/styles.module.css +++ b/frontend-plataforma-tutoriais/src/pages/private/profile/styles.module.css @@ -169,6 +169,24 @@ color: var(--text-white); } +.iconEdit { + color: var(--text-white); + background-color: var(--bg-primary-color); + font-size: 2.3rem; + font-weight: 500; + transition: all 0.3s ease; + z-index: 1000; + position: absolute; + top: 10px; + right: 10px; + border-radius: var(--border-radius); + padding: 10px; + transition: all 0.3s ease; + &:hover { + background-color: var(--neutral-color); + } +} + .titleVideo { color: var(--text-white); font-size: var(--size-font-text); @@ -227,10 +245,6 @@ height: 150px; } -.icon{ - color: var(--text-primary-color); -} - .linkWorkshop { display: block; width: 160px; diff --git a/frontend-plataforma-tutoriais/src/pages/private/search/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/search/index.tsx index 84c4e23..c04237e 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/search/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/search/index.tsx @@ -9,6 +9,9 @@ import { useGetVideosSearch } from "../../../hooks/useGetVideosSearch"; import { useGetWorkshopsSearch } from "../../../hooks/useGetWorkshopsSearch"; import { PiCheckCircleFill } from "react-icons/pi"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton"; +import { motion } from "framer-motion"; +import { pageTitleTransition, pageTitleVariants, slideFromBottomTransition, slideFromBottomVariants, slideFromTopTransition, slideFromTopVariants, viewportOnce } from "../../../utils/pageMotionVariants"; + export default function Search() { const [videos, setVideos] = useState([]); @@ -76,14 +79,14 @@ export default function Search() { return (
    -

    Resultados da pesquisa: "{query}"

    + Resultados da pesquisa: "{query}" {videos.length > 0 ? (
    -

    Vídeos

    - {videos.length === 1 ? `Foi encontrado ${videos.length} vídeo na sua pesquisa.` : `Foram encontrados ${videos.length} vídeos na sua pesquisa.`} + Vídeos + {videos.length === 1 ? `Foi encontrado ${videos.length} vídeo na sua pesquisa.` : `Foram encontrados ${videos.length} vídeos na sua pesquisa.`} {videos.map((video) => ( -
    +
    category.id).join(', ')} > {isAdmin && ( @@ -102,17 +105,17 @@ export default function Search() { {video.watched && Visto}
    -
    + ))}
    ) : null} {workshops.length > 0 ? (
    -

    Workshops

    - {workshops.length === 1 ? `Foi encontrado ${workshops.length} workshop na sua pesquisa.` : `Foram encontrados ${workshops.length} workshops na sua pesquisa.`} + Workshops + {workshops.length === 1 ? `Foi encontrado ${workshops.length} workshop na sua pesquisa.` : `Foram encontrados ${workshops.length} workshops na sua pesquisa.`} {workshops.map((workshop) => ( -
    +
    Detalhes
    -
    +
    ))}
    ) : null} diff --git a/frontend-plataforma-tutoriais/src/pages/private/video/[id].tsx b/frontend-plataforma-tutoriais/src/pages/private/video/[id].tsx index d60465e..75117d1 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/video/[id].tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/video/[id].tsx @@ -168,6 +168,8 @@ export default function Video() { Anterior )} +
    + {nextVideo && ( Próximo )} diff --git a/frontend-plataforma-tutoriais/src/pages/private/videos/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/videos/index.tsx index 8d968e7..e534c81 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/videos/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/videos/index.tsx @@ -12,6 +12,24 @@ import { useDebounce } from "../../../hooks/useDebounce"; import { PiCheckCircleFill } from "react-icons/pi"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton"; import { AnimatePresence, motion } from "framer-motion"; +import { + cardInViewTransition, + cardInViewVariants, + dropdownTransition, + dropdownVariants, + fadeTransition, + fadeVariants, + listContentTransition, + listContentVariants, + pageTitleTransition, + pageTitleVariants, + slideFromLeftTransition, + slideFromRightOnMountTransition, + slideFromRightTransition, + slideFromRightVariants, + slideFromLeftVariants, + viewportOnce, +} from "../../../utils/pageMotionVariants"; export default function Videos() { const [loading, setLoading] = useState(true); @@ -28,6 +46,7 @@ export default function Videos() { const [loadingVideos, setLoadingVideos] = useState(false); const videosToShow = videos; const [role, setRole] = useState(0); + const listContentKey = `${currentPage}-${debouncedSearch}-${selectedCategoryId}`; useEffect(() => { const fetchVideos = async () => { @@ -92,7 +111,7 @@ export default function Videos() { return (
    -

    Vídeos

    + Vídeos {error && (
    @@ -101,14 +120,14 @@ export default function Videos() { )}
    -
    + setSearch(e.target.value)} /> -
    + -
    +
    setShowFilterDropdown(true)} @@ -126,10 +145,11 @@ export default function Videos() { {showFilterDropdown && ( @@ -204,27 +224,37 @@ export default function Videos() { )}
    -
    + {role === 1 && ( -
    + Adicionar vídeo -
    + )}
    -
    - { loadingVideos ? ( -
    + + {loadingVideos ? ( + -
    - ) : videosToShow.length > 0 ? ( + + ) : ( + + {videosToShow.length > 0 ? (
    {videos.map((video) => ( -
    - + +
    category.id).join(', ')} > {role === 1 && ( @@ -243,7 +273,7 @@ export default function Videos() { {video.watched && Visto}
    -
    + ))}
    @@ -287,11 +317,9 @@ export default function Videos() { )} )} - - - - -
    + + )} +
    ) diff --git a/frontend-plataforma-tutoriais/src/pages/private/workshops/index.tsx b/frontend-plataforma-tutoriais/src/pages/private/workshops/index.tsx index 71dd395..1082e94 100644 --- a/frontend-plataforma-tutoriais/src/pages/private/workshops/index.tsx +++ b/frontend-plataforma-tutoriais/src/pages/private/workshops/index.tsx @@ -11,6 +11,24 @@ import Swal from "sweetalert2"; import { Form, Pagination } from "react-bootstrap"; import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton"; import { AnimatePresence, motion } from "framer-motion"; +import { + cardInViewTransition, + cardInViewVariants, + dropdownTransition, + dropdownVariants, + fadeTransition, + fadeVariants, + listContentTransition, + listContentVariants, + pageTitleTransition, + pageTitleVariants, + slideFromLeftTransition, + slideFromLeftVariants, + slideFromRightOnMountTransition, + slideFromRightTransition, + slideFromRightVariants, + viewportOnce, +} from "../../../utils/pageMotionVariants"; export default function Workshops() { const [loading, setLoading] = useState(true); @@ -25,6 +43,7 @@ export default function Workshops() { const [loadingWorkshops, setLoadingWorkshops] = useState(false); const [role, setRole] = useState(0); const [userId, setUserId] = useState(0); + const listContentKey = `${currentPage}-${debouncedSearch}-${selectedWorkshopStatus}`; /* const [searchLoading, setSearchLoading] = useState(false); */ useEffect(() => { @@ -152,17 +171,17 @@ export default function Workshops() { } return ( -
    -

    Workshops

    +
    + Workshops
    -
    + setSearch(e.target.value)} /> -
    -
    + + {/* */}
    {showFilterDropdown && ( @@ -228,23 +248,34 @@ export default function Workshops() { )}
    -
    + {role === 1 ? ( -
    + Adicionar workshop -
    + ) : null}
    - {loadingWorkshops ? ( -
    - -
    - ) : workshops.length > 0 ? ( + + {loadingWorkshops ? ( + + + + ) : ( + + {workshops.length > 0 ? ( <> {workshops.map((workshop) => ( -
    +
    -
    +
    ))}
    @@ -348,6 +379,9 @@ export default function Workshops() { Nenhum workshop encontrado com o filtro selecionado
    ) : null} + + )} +
    ) diff --git a/frontend-plataforma-tutoriais/src/utils/pageMotionVariants.ts b/frontend-plataforma-tutoriais/src/utils/pageMotionVariants.ts new file mode 100644 index 0000000..d76e749 --- /dev/null +++ b/frontend-plataforma-tutoriais/src/utils/pageMotionVariants.ts @@ -0,0 +1,70 @@ +import type { Transition, Variants } from "framer-motion"; + +export const viewportOnce = { once: true }; + +export const pageTitleVariants: Variants = { + initial: { opacity: 0, y: -50 }, + animate: { opacity: 1, y: 0 }, +}; + +export const slideFromLeftVariants: Variants = { + initial: { opacity: 0, x: -50 }, + animate: { opacity: 1, x: 0 }, +}; + +export const slideFromRightVariants: Variants = { + initial: { opacity: 0, x: 50 }, + animate: { opacity: 1, x: 0 }, +}; + +export const slideFromBottomVariants: Variants = { + initial: { opacity: 0, y: 70 }, + animate: { opacity: 1, y: 0 }, +}; + +export const slideFromTopVariants: Variants = { + initial: { opacity: 0, y: -50 }, + animate: { opacity: 1, y: 0 }, +}; + +export const fadeVariants: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, +}; + +export const cardInViewVariants: Variants = { + initial: { opacity: 0, y: 50 }, + animate: { opacity: 1, y: 0 }, +}; + +export const dropdownVariants: Variants = { + initial: { opacity: 0, y: -10, scale: 0.98 }, + animate: { opacity: 1, y: 0, scale: 1 }, + exit: { opacity: 0, y: -10, scale: 0.98 }, +}; + +export const listContentVariants: Variants = { + initial: { opacity: 0, y: 70 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 30 }, +}; + +export const pageTitleTransition: Transition = { duration: 0.5, delay: 0.1 }; +export const slideFromLeftTransition: Transition = { duration: 0.5, delay: 0.2 }; +export const slideFromLeftTransitionNoDelay: Transition = { duration: 0.5 }; +export const slideFromLeftOnMountTransition: Transition = { duration: 0.5, delay: 0.1 }; +export const slideFromRightOnMountTransition02: Transition = { duration: 0.5, delay: 0.2 }; +export const slideFromLeftInViewTransition03: Transition = { duration: 0.5, delay: 0.3 }; +export const slideFromRightTransition: Transition = { duration: 0.5, delay: 0.3 }; +export const slideFromRightInViewTransition04: Transition = { duration: 0.5, delay: 0.4 }; +export const slideFromBottomTransition: Transition = { duration: 0.5, delay: 0.4 }; +export const slideFromBottomInViewTransition: Transition = { duration: 0.5, delay: 0.1 }; +export const slideFromRightOnMountTransition: Transition = { duration: 0.5, delay: 0.5 }; +export const slideFromTopTransition: Transition = { duration: 0.5 }; +export const slideFromTopTransitionDelayed: Transition = { duration: 0.5, delay: 0.1 }; +export const cardInViewTransition: Transition = { duration: 0.5, delay: 0.1 }; +export const dropdownTransition: Transition = { duration: 0.2, ease: "easeOut" }; +export const listContentTransition: Transition = { duration: 0.4, ease: "easeOut" }; +export const fadeTransition: Transition = { duration: 0.15 }; +