feat: Adding animations
This commit is contained in:
@@ -1,14 +1,26 @@
|
|||||||
import { useMotionValue, useSpring } from "framer-motion";
|
import { useInView, useMotionValue, useSpring } from "framer-motion";
|
||||||
import { useEffect, useState } from "react";
|
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<HTMLSpanElement>(null);
|
||||||
|
const isInView = useInView(ref, { once, amount: 0.8 });
|
||||||
const motionValue = useMotionValue(0);
|
const motionValue = useMotionValue(0);
|
||||||
const spring = useSpring(motionValue, { stiffness: 60, damping: 20 });
|
const spring = useSpring(motionValue, { stiffness: 60, damping: 20 });
|
||||||
const [display, setDisplay] = useState(0);
|
const [display, setDisplay] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isInView) {
|
||||||
motionValue.set(value);
|
motionValue.set(value);
|
||||||
}, [value]);
|
} else if (!once) {
|
||||||
|
motionValue.set(0);
|
||||||
|
}
|
||||||
|
}, [isInView, value, motionValue, once]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = spring.on("change", (v) => {
|
const unsubscribe = spring.on("change", (v) => {
|
||||||
@@ -17,7 +29,7 @@ const AnimatedCounter = ({ value }: { value: number }) => {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [spring]);
|
}, [spring]);
|
||||||
|
|
||||||
return <span>{display}</span>;
|
return <span ref={ref}>{display}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AnimatedCounter;
|
export default AnimatedCounter;
|
||||||
@@ -1,18 +1,29 @@
|
|||||||
import { useMotionValue, useSpring } from "framer-motion";
|
import { useInView, useMotionValue, useSpring } from "framer-motion";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ProgressBar } from "react-bootstrap";
|
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<HTMLDivElement>(null);
|
||||||
|
const isInView = useInView(ref, { once, amount: 0.5 });
|
||||||
const motionValue = useMotionValue(0);
|
const motionValue = useMotionValue(0);
|
||||||
const spring = useSpring(motionValue, { stiffness: 60, damping: 20 });
|
const spring = useSpring(motionValue, { stiffness: 60, damping: 20 });
|
||||||
const [display, setDisplay] = useState(0);
|
const [display, setDisplay] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isInView) {
|
||||||
motionValue.set(value);
|
motionValue.set(value);
|
||||||
}, [value]);
|
} else if (!once) {
|
||||||
|
motionValue.set(0);
|
||||||
|
}
|
||||||
|
}, [isInView, value, motionValue, once]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// ✅ subscribe mantém o state sincronizado com o spring
|
|
||||||
const unsubscribe = spring.on("change", (v) => {
|
const unsubscribe = spring.on("change", (v) => {
|
||||||
setDisplay(Math.round(v));
|
setDisplay(Math.round(v));
|
||||||
});
|
});
|
||||||
@@ -20,11 +31,13 @@ const AnimatedProgressBar = ({ value, className }: { value: number; className?:
|
|||||||
}, [spring]);
|
}, [spring]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div ref={ref}>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
className={className}
|
className={className}
|
||||||
now={display}
|
now={display}
|
||||||
label={`${display}%`}
|
label={`${display}%`}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PiCheckCircleFill } from "react-icons/pi";
|
|||||||
import { useGetWorkshopsSearch } from "../../hooks/useGetWorkshopsSearch";
|
import { useGetWorkshopsSearch } from "../../hooks/useGetWorkshopsSearch";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../utils/imageSkeleton";
|
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../utils/imageSkeleton";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { dropdownTransition, dropdownVariants, slideFromTopTransition, slideFromTopVariants } from "../../utils/pageMotionVariants";
|
||||||
import { useGetCurrentUser } from "../../hooks/useGetCurrentUser";
|
import { useGetCurrentUser } from "../../hooks/useGetCurrentUser";
|
||||||
import AnimatedCounter from "../animatedNumberCount";
|
import AnimatedCounter from "../animatedNumberCount";
|
||||||
|
|
||||||
@@ -220,7 +221,7 @@ export default function Header() {
|
|||||||
<>
|
<>
|
||||||
|
|
||||||
{videosSearched.length > 0 ? (
|
{videosSearched.length > 0 ? (
|
||||||
<div className="d-flex flex-column">
|
<motion.div variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className="d-flex flex-column">
|
||||||
<span className="fs-1 text-center fw-bold mb-1 mt-3">Vídeos</span>
|
<span className="fs-1 text-center fw-bold mb-1 mt-3">Vídeos</span>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@@ -249,12 +250,12 @@ export default function Header() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{workshopsSearched.length > 0 ? (
|
{workshopsSearched.length > 0 ? (
|
||||||
<div className="d-flex flex-column">
|
<motion.div variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className="d-flex flex-column">
|
||||||
<span className="fs-1 text-center fw-bold mb-1 mt-3">Workshops</span>
|
<span className="fs-1 text-center fw-bold mb-1 mt-3">Workshops</span>
|
||||||
<div className="row g-3 mb-3">
|
<div className="row g-3 mb-3">
|
||||||
{workshopsSearched.length > 0 && (
|
{workshopsSearched.length > 0 && (
|
||||||
@@ -295,7 +296,7 @@ export default function Header() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
@@ -319,10 +320,11 @@ export default function Header() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showDropdown && (
|
{showDropdown && (
|
||||||
<motion.ul
|
<motion.ul
|
||||||
initial={{ opacity: 0, y: -10, scale: 0.98 }}
|
variants={dropdownVariants}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
initial="initial"
|
||||||
exit={{ opacity: 0, y: -10, scale: 0.98 }}
|
animate="animate"
|
||||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
exit="exit"
|
||||||
|
transition={dropdownTransition}
|
||||||
className="dropdown-menu dropdown-menu-end"
|
className="dropdown-menu dropdown-menu-end"
|
||||||
style={{ display: "block", zIndex: 2000, right: 0, top: "100%", marginTop: "0.25rem" }}>
|
style={{ display: "block", zIndex: 2000, right: 0, top: "100%", marginTop: "0.25rem" }}>
|
||||||
<li><Link className="dropdown-item" to="/profile"><LuCircleUser className="mb-1 me-2" size={24} />A minha conta</Link></li>
|
<li><Link className="dropdown-item" to="/profile"><LuCircleUser className="mb-1 me-2" size={24} />A minha conta</Link></li>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { LuArrowLeft } from "react-icons/lu";
|
|||||||
import { LuPlus } from "react-icons/lu";
|
import { LuPlus } from "react-icons/lu";
|
||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { slideFromLeftVariants, slideFromLeftOnMountTransition, pageTitleTransition, pageTitleVariants, slideFromBottomVariants, slideFromBottomTransition } from "../../../../utils/pageMotionVariants";
|
||||||
|
|
||||||
export default function CreateUser() {
|
export default function CreateUser() {
|
||||||
const [name, setName] = useState<string>("");
|
const [name, setName] = useState<string>("");
|
||||||
@@ -15,6 +17,7 @@ export default function CreateUser() {
|
|||||||
const [role_id, setRoleId] = useState("2");
|
const [role_id, setRoleId] = useState("2");
|
||||||
const [creating, setCreating] = useState(false);
|
const [creating, setCreating] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
//validação dos campos
|
//validação dos campos
|
||||||
|
|
||||||
@@ -113,13 +116,13 @@ export default function CreateUser() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-3 p-xl-0`}>
|
<div className={`${styles.container} p-3 p-xl-0`}>
|
||||||
<div className={"text-start"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className={"text-start"}>
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/admin/users"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Utilizadores</span> </Link></button>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/admin/users"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Utilizadores</span> </Link></button>
|
||||||
</div>
|
</motion.div>
|
||||||
<h1 className={styles.title}>Criar Utilizador</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Criar Utilizador</motion.h1>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<motion.div variants={slideFromBottomVariants} initial="initial" animate="animate" transition={slideFromBottomTransition} >
|
||||||
<div className="row g-3">
|
<div className="row g-3">
|
||||||
<div className="col-12 col-md-6 text-start">
|
<div className="col-12 col-md-6 text-start">
|
||||||
<label className="form-label fw-bold" htmlFor="name">Nome</label>
|
<label className="form-label fw-bold" htmlFor="name">Nome</label>
|
||||||
@@ -149,7 +152,7 @@ export default function CreateUser() {
|
|||||||
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
||||||
<button type="submit" className={`${styles.createButton}`} onClick={() => { create(); setCreating(true); }} disabled={creating}>{creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : <LuPlus className="mb-1 text-white" />} Adicionar</button>
|
<button type="submit" className={`${styles.createButton}`} onClick={() => { create(); setCreating(true); }} disabled={creating}>{creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : <LuPlus className="mb-1 text-white" />} Adicionar</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ import { LuArrowLeft, LuPlus } from "react-icons/lu";
|
|||||||
import { LuUpload } from "react-icons/lu";
|
import { LuUpload } from "react-icons/lu";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import { CgSpinner } from "react-icons/cg";
|
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() {
|
export default function CreateVideo() {
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
@@ -208,10 +217,12 @@ export default function CreateVideo() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-3 p-xl-0 px-2 px-md-5`}>
|
<div className={`${styles.container} p-3 p-xl-0 px-2 px-md-5`}>
|
||||||
<div className={"text-start"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className="text-start">
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/videos"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Vídeos</span> </Link></button>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/videos"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Vídeos</span> </Link></button>
|
||||||
</div>
|
</motion.div>
|
||||||
<h1 className={styles.title}>Adicionar Vídeo</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Adicionar Vídeo</motion.h1>
|
||||||
|
<div>
|
||||||
|
<motion.div variants={slideFromBottomVariants} initial="initial" animate="animate" transition={slideFromBottomTransition}>
|
||||||
<div className="row mx-auto g-4">
|
<div className="row mx-auto g-4">
|
||||||
<div className="col-12 px-1 text-start">
|
<div className="col-12 px-1 text-start">
|
||||||
<label className="form-label fw-bold" htmlFor="title">Título</label>
|
<label className="form-label fw-bold" htmlFor="title">Título</label>
|
||||||
@@ -302,6 +313,8 @@ export default function CreateVideo() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button type="button" className={`${styles.btnAdicionarVideo} btn btn-primary mt-5`} onClick={() => { createVideo() }} disabled={creating}>{creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : <LuPlus />} Adicionar vídeo</button>
|
<button type="button" className={`${styles.btnAdicionarVideo} btn btn-primary mt-5`} onClick={() => { createVideo() }} disabled={creating}>{creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : <LuPlus />} Adicionar vídeo</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ import "react-datepicker/dist/react-datepicker.css";
|
|||||||
import { pt } from "date-fns/locale";
|
import { pt } from "date-fns/locale";
|
||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import {
|
||||||
|
pageTitleTransition,
|
||||||
|
pageTitleVariants,
|
||||||
|
slideFromBottomTransition,
|
||||||
|
slideFromBottomVariants,
|
||||||
|
slideFromLeftOnMountTransition,
|
||||||
|
slideFromLeftVariants,
|
||||||
|
} from "../../../../utils/pageMotionVariants";
|
||||||
|
|
||||||
export default function CreateWorkshop() {
|
export default function CreateWorkshop() {
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
@@ -92,10 +101,12 @@ export default function CreateWorkshop() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-3 p-xl-0 px-2 px-md-5`}>
|
<div className={`${styles.container} p-3 p-xl-0 px-2 px-md-5`}>
|
||||||
<div className={"text-start"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className="text-start">
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/workshops"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Workshops</span> </Link></button>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/workshops"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Workshops</span> </Link></button>
|
||||||
</div>
|
</motion.div>
|
||||||
<h1 className={styles.title}>Adicionar Workshop</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Adicionar Workshop</motion.h1>
|
||||||
|
<div>
|
||||||
|
<motion.div variants={slideFromBottomVariants} initial="initial" animate="animate" transition={slideFromBottomTransition}>
|
||||||
<div className="row mx-auto g-4">
|
<div className="row mx-auto g-4">
|
||||||
<div className="col-12 px-1 text-start">
|
<div className="col-12 px-1 text-start">
|
||||||
<label className={`${styles.label} form-label fw-bold`} htmlFor="title">Título</label>
|
<label className={`${styles.label} form-label fw-bold`} htmlFor="title">Título</label>
|
||||||
@@ -171,6 +182,8 @@ export default function CreateWorkshop() {
|
|||||||
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
||||||
<button type="submit" onClick={createWorkshop} className={`${styles.btnAdicionarWorkshop}`} disabled={isLoading}><LuPlus className="mb-1 text-white" /> {creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : "Adicionar Workshop"}</button>
|
<button type="submit" onClick={createWorkshop} className={`${styles.btnAdicionarWorkshop}`} disabled={isLoading}><LuPlus className="mb-1 text-white" /> {creating ? <CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} /> : "Adicionar Workshop"}</button>
|
||||||
</div>
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,8 @@ import styles from "./styles.module.css";
|
|||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import { LuUpload, LuPlus, LuCheck, LuTrash2, LuArrowLeft } from "react-icons/lu";
|
import { LuUpload, LuPlus, LuCheck, LuTrash2, LuArrowLeft } from "react-icons/lu";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
|
import { slideFromLeftVariants, slideFromLeftOnMountTransition, pageTitleTransition, pageTitleVariants, slideFromBottomVariants, slideFromBottomTransition } from "../../../../utils/pageMotionVariants";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
export default function editVideo() {
|
export default function editVideo() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -247,17 +249,15 @@ export default function editVideo() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`${styles.container} p-3 p-xl-0`}>
|
<div className={`${styles.container} p-3 p-xl-0`}>
|
||||||
<div className={"text-start"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className={"text-start"}>
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/videos"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Vídeos</span> </Link></button>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/videos"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Vídeos</span> </Link></button>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
{video ? (
|
{video ? (
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
|
|
||||||
|
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<span className={styles.title}>Alterar dados do vídeo</span>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Alterar dados do vídeo</motion.h1>
|
||||||
<div className="row mx-auto g-4">
|
<motion.div variants={slideFromBottomVariants} initial="initial" animate="animate" transition={slideFromBottomTransition} className="row mx-auto g-4">
|
||||||
<form method="patch" onSubmit={update}>
|
<form method="patch" onSubmit={update}>
|
||||||
<input type="hidden" name="video_id" value={video?.id} />
|
<input type="hidden" name="video_id" value={video?.id} />
|
||||||
<div className="col-12 px-1 text-start mb-3">
|
<div className="col-12 px-1 text-start mb-3">
|
||||||
@@ -337,7 +337,7 @@ export default function editVideo() {
|
|||||||
<button type="button" className={`${styles.deleteButton}`} onClick={() => handleDeleteVideo()}>Apagar <LuTrash2 className="mb-1 text-white" /></button>
|
<button type="button" className={`${styles.deleteButton}`} onClick={() => handleDeleteVideo()}>Apagar <LuTrash2 className="mb-1 text-white" /></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import "react-datepicker/dist/react-datepicker.css";
|
|||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../../utils/imageSkeleton";
|
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() {
|
export default function Workshop() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -192,19 +194,19 @@ export default function Workshop() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-3 p-xl-0`}>
|
<div className={`${styles.container} p-3 p-xl-0`}>
|
||||||
<div className={"text-start mb-3"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className={"text-start mb-3"}>
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}>
|
||||||
<Link className={`${styles.link} text-decoration-none`} to="/workshops">
|
<Link className={`${styles.link} text-decoration-none`} to="/workshops">
|
||||||
<LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Workshops</span>
|
<LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Workshops</span>
|
||||||
</Link>
|
</Link>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
{workshop ? (
|
{workshop ? (
|
||||||
<>
|
<>
|
||||||
<div className={`${styles.container} d-flex flex-column gap-2`}>
|
<div className={`${styles.container} d-flex flex-column gap-2`}>
|
||||||
<div className="row mt-4 g-3 gx-md-4 gx-lg-5 ms-0">
|
<div className="row mt-4 g-3 gx-md-4 gx-lg-5 ms-0">
|
||||||
<div className={`${styles.thumbnailWrapper} col-12 col-lg-3 align-self-center align-self-sm-center px-0 mt-0`}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftTransition} className={`${styles.thumbnailWrapper} col-12 col-lg-3 align-self-center align-self-sm-center px-0 mt-0`}>
|
||||||
<img
|
<img
|
||||||
src={`${API_URL}/storage/${workshop.image}`}
|
src={`${API_URL}/storage/${workshop.image}`}
|
||||||
alt={workshop.title}
|
alt={workshop.title}
|
||||||
@@ -212,8 +214,8 @@ export default function Workshop() {
|
|||||||
style={imageSkeletonFadeStyle}
|
style={imageSkeletonFadeStyle}
|
||||||
onLoad={onImageSkeletonLoad}
|
onLoad={onImageSkeletonLoad}
|
||||||
/>
|
/>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="col-12 col-lg-9 d-flex flex-column justify-content-between gap-2">
|
<motion.div variants={slideFromRightVariants} initial="initial" animate="animate" transition={slideFromRightTransition} className="col-12 col-lg-9 d-flex flex-column justify-content-between gap-2">
|
||||||
<div className="d-flex flex-column flex-wrap gap-1 justify-content-center justify-content-md-start">
|
<div className="d-flex flex-column flex-wrap gap-1 justify-content-center justify-content-md-start">
|
||||||
{role === 1 ? (
|
{role === 1 ? (
|
||||||
<span className={`${styles.statusWorkshop} text-center d-inline-block py-1 mb-1 mb-sm-0 ${workshop.status === "pending" ? "alert alert-primary" : workshop.status === "realized" ? "alert alert-success" : "alert alert-danger"}`} style={{ width: "fit-content" }}>
|
<span className={`${styles.statusWorkshop} text-center d-inline-block py-1 mb-1 mb-sm-0 ${workshop.status === "pending" ? "alert alert-primary" : workshop.status === "realized" ? "alert alert-success" : "alert alert-danger"}`} style={{ width: "fit-content" }}>
|
||||||
@@ -253,12 +255,12 @@ export default function Workshop() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{listagemInscritos ? (
|
{listagemInscritos ? (
|
||||||
<div className={`${styles.users} mt-4`}>
|
<motion.div variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className={`${styles.users} mt-4`}>
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<button type="button" className={`${styles.btnClose} d-flex float-end p-1 mb-1`} onClick={() => setListagemInscritos(false)}><LuX className={`${styles.iconClose}`} /> </button>
|
<button type="button" className={`${styles.btnClose} d-flex float-end p-1 mb-1`} onClick={() => setListagemInscritos(false)}><LuX className={`${styles.iconClose}`} /> </button>
|
||||||
<table className="table table-striped table-hover align-middle mt-3">
|
<table className="table table-striped table-hover align-middle mt-3">
|
||||||
@@ -292,12 +294,12 @@ export default function Workshop() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!formEdit && role === 1 ? (
|
{!formEdit && role === 1 ? (
|
||||||
<div className={`${styles.buttonsContainer} d-flex flex-wrap gap-2 justify-content-center mt-5`}>
|
<motion.div variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className={`${styles.buttonsContainer} d-flex flex-wrap gap-2 justify-content-center mt-5`}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${styles.updateButton} bg-primary`}
|
className={`${styles.updateButton} bg-primary`}
|
||||||
@@ -317,14 +319,14 @@ export default function Workshop() {
|
|||||||
{workshop.status === "pending" ? (
|
{workshop.status === "pending" ? (
|
||||||
<button type="button" className={`${styles.cancelButton}`} onClick={handleCancelWorkshop}><LuTrash2 className="mb-1 btn-danger" /> Cancelar Workshop</button>
|
<button type="button" className={`${styles.cancelButton}`} onClick={handleCancelWorkshop}><LuTrash2 className="mb-1 btn-danger" /> Cancelar Workshop</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{formEdit ? (
|
{formEdit ? (
|
||||||
<form className={`${styles.formEdit} mt-5`} onSubmit={update}>
|
<motion.form variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className={`${styles.formEdit} mt-5`} onSubmit={update}>
|
||||||
<span className={styles.subtitle}>Alterar detalhes do workshop</span>
|
<span className={styles.subtitle}>Alterar detalhes do workshop</span>
|
||||||
<div className="row mx-auto g-4 mt-1">
|
<div className="row mx-auto g-4 mt-1">
|
||||||
<div className="col-12 px-1 text-start">
|
<div className="col-12 px-1 text-start">
|
||||||
@@ -453,7 +455,7 @@ export default function Workshop() {
|
|||||||
<button type="submit" className={`${styles.updateButton} bg-primary`} ><LuCheck className="mb-1" /> Submeter dados</button>
|
<button type="submit" className={`${styles.updateButton} bg-primary`} ><LuCheck className="mb-1" /> Submeter dados</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</motion.form>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { LuCheck, LuTrash2, LuX, LuPencil, LuArrowLeft, LuCalendar, LuClock3 } f
|
|||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../../utils/imageSkeleton";
|
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../../utils/imageSkeleton";
|
||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import Swal from "sweetalert2";
|
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() {
|
export default function User() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -161,17 +163,17 @@ export default function User() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-1 p-xl-0`}>
|
<div className={`${styles.container} p-1 p-xl-0`}>
|
||||||
<div className={"text-start mt-5"}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className={"text-start mt-5"}>
|
||||||
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/admin/users"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Utilizadores</span> </Link></button>
|
<button className={`${styles.button} border-0 bg-transparent fs-5`}><Link className={`${styles.link} text-decoration-none`} to="/admin/users"><LuArrowLeft className={`${styles.LinkIcon} mb-1 fs-4 align-items-center`} /> <span className={styles.linkText}>Utilizadores</span> </Link></button>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<span className={styles.title}>Dados do Utilizador </span>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Dados do Utilizador </motion.h1>
|
||||||
|
|
||||||
<div className="row mt-5 justify-content-between px-2">
|
<div className="row mt-5 justify-content-between px-2">
|
||||||
<div className="col-12 col-lg-8 p-2 rounded-3">
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className="col-12 col-lg-8 p-2 rounded-3">
|
||||||
<div className={`${styles.userCard} d-flex flex-column flex-md-row g-5 p-4 bg-white h-100`}>
|
<div className={`${styles.userCard} d-flex flex-column flex-md-row g-5 p-4 bg-white h-100`}>
|
||||||
<div className="col-12 col-md-6 d-flex flex-column text-start flex-column mt-xl-0">
|
<div className="col-12 col-md-6 d-flex flex-column text-start flex-column mt-xl-0">
|
||||||
<span className={`badge text-uppercase bg-primary fw-bold px-4 py-3 fs-6 mb-3 d-flex d-md-none align-self-center ${user.role_id === 1 ? 'bg-primary bg-gradient text-white' : 'bg-secondary bg-gradient text-white'} `} style={{ width: "fit-content" }}>{user.role_id === 1 ? "Administrador" : "Utilizador"}</span>
|
<span className={`badge text-uppercase bg-primary fw-bold px-4 py-3 fs-6 mb-3 d-flex d-md-none align-self-center ${user.role_id === 1 ? 'bg-primary bg-gradient text-white' : 'bg-secondary bg-gradient text-white'} `} style={{ width: "fit-content" }}>{user.role_id === 1 ? "Administrador" : "Utilizador"}</span>
|
||||||
@@ -192,8 +194,8 @@ export default function User() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="col-12 col-lg-4 p-2">
|
<motion.div variants={slideFromRightVariants} initial="initial" animate="animate" transition={slideFromRightTransition} className="col-12 col-lg-4 p-2">
|
||||||
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-6" style={{ color: "var(--bg-primary-color-opacity)" }}>Vídeos assistidos</span>
|
<span className="fw-normal fs-6" style={{ color: "var(--bg-primary-color-opacity)" }}>Vídeos assistidos</span>
|
||||||
@@ -206,15 +208,49 @@ export default function User() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div className="mt-3">
|
||||||
|
{formEdit ? (
|
||||||
|
<motion.div variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className="my-3">
|
||||||
|
<span className={styles.title}>Editar Utilizador</span>
|
||||||
|
|
||||||
|
<form method="patch" onSubmit={update} id="formEditUser">
|
||||||
|
<div className="row g-3 mt-2">
|
||||||
|
<input type="hidden" name="user_id" value={user?.id} />
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<label className="form-label fw-bold" htmlFor="name">Nome</label>
|
||||||
|
<input type="text" className="form-control py-2" id="name" name="name" defaultValue={user?.name} />
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<label className="form-label fw-bold" htmlFor="email">Email</label>
|
||||||
|
<input type="email" className="form-control py-2" id="email" name="email" defaultValue={user?.email} />
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<label className="form-label fw-bold" htmlFor="role_id">Cargo</label>
|
||||||
|
<select className="form-select py-2" id="role_id" name="role_id" defaultValue={user?.role_id}>
|
||||||
|
<option value="1">Administrador</option>
|
||||||
|
<option value="2">Utilizador</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 p-2 mt-4 mb-3">
|
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
||||||
<span className={`${styles.subtitle} text-start`}>Workshops inscrito</span>
|
<button type="button" className={`${styles.closeFormButton}`} onClick={() => setFormEdit(false)}> Cancelar <LuX className={`${styles.icon} mb-1`} /></button>
|
||||||
|
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
</motion.div>
|
||||||
|
) : null}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div variants={slideFromBottomVariants} initial="initial" animate="animate" transition={slideFromBottomTransition} className="col-12 p-2 mt-4 mb-3">
|
||||||
|
<span className={`${styles.subtitle} text-start`}>Workshops inscrito</span>
|
||||||
|
</motion.div>
|
||||||
{workshopsParticipated.length > 0 ? (
|
{workshopsParticipated.length > 0 ? (
|
||||||
workshopsParticipated.map((workshop) => (
|
workshopsParticipated.map((workshop) => (
|
||||||
<div key={workshop.id} className="col-12 col-sm-6 col-lg-4 p-2">
|
<div key={workshop.id} className="col-12 col-sm-6 col-lg-4 p-2">
|
||||||
<div className={`${styles.boxWorkshop} text-start pb-3`}>
|
<motion.div variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className={`${styles.boxWorkshop} text-start pb-3`}>
|
||||||
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
||||||
<img
|
<img
|
||||||
src={`${API_URL}/storage/${workshop.image}`}
|
src={`${API_URL}/storage/${workshop.image}`}
|
||||||
@@ -243,49 +279,17 @@ export default function User() {
|
|||||||
Ver detalhes
|
Ver detalhes
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="col-12 text-center ps-1">
|
<motion.div variants={slideFromBottomVariants} whileInView="animate" viewport={viewportOnce} transition={slideFromBottomTransition} className="col-12 text-center ps-1">
|
||||||
<span className="text-muted fs-5">Este utilizador ainda não participou em nenhum workshop</span>
|
<span className="text-muted fs-5">Este utilizador ainda não participou em nenhum workshop</span>
|
||||||
</div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5">
|
|
||||||
{formEdit ? (
|
|
||||||
<div className="my-3">
|
|
||||||
<span className={styles.title}>Editar Utilizador</span>
|
|
||||||
|
|
||||||
<form method="patch" onSubmit={update} id="formEditUser">
|
|
||||||
<div className="row g-3 mt-2">
|
|
||||||
<input type="hidden" name="user_id" value={user?.id} />
|
|
||||||
<div className="col-12 col-md-6 text-start">
|
|
||||||
<label className="form-label fw-bold" htmlFor="name">Nome</label>
|
|
||||||
<input type="text" className="form-control py-2" id="name" name="name" defaultValue={user?.name} />
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 text-start">
|
|
||||||
<label className="form-label fw-bold" htmlFor="email">Email</label>
|
|
||||||
<input type="email" className="form-control py-2" id="email" name="email" defaultValue={user?.email} />
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 text-start">
|
|
||||||
<label className="form-label fw-bold" htmlFor="role_id">Cargo</label>
|
|
||||||
<select className="form-select py-2" id="role_id" name="role_id" defaultValue={user?.role_id}>
|
|
||||||
<option value="1">Administrador</option>
|
|
||||||
<option value="2">Utilizador</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="buttonsContainer mt-4 d-flex gap-2 justify-content-center" >
|
|
||||||
<button type="button" className={`${styles.closeFormButton}`} onClick={() => setFormEdit(false)}> Cancelar <LuX className={`${styles.icon} mb-1`} /></button>
|
|
||||||
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center alert alert-danger mt-5 align-items-center">
|
<div className="text-center alert alert-danger mt-5 align-items-center">
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ import { LuPencil, LuSettings2, LuPlus } from "react-icons/lu";
|
|||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import { Form, Pagination, Table } from "react-bootstrap";
|
import { Form, Pagination, Table } from "react-bootstrap";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
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() {
|
export default function Users() {
|
||||||
|
|
||||||
@@ -22,6 +38,7 @@ export default function Users() {
|
|||||||
const [listTotal, setListTotal] = useState(0);
|
const [listTotal, setListTotal] = useState(0);
|
||||||
const [loadingUsers, setLoadingUsers] = useState(false);
|
const [loadingUsers, setLoadingUsers] = useState(false);
|
||||||
const debouncedSearch = useDebounce(search, 500);
|
const debouncedSearch = useDebounce(search, 500);
|
||||||
|
const listContentKey = `${currentPage}-${debouncedSearch}-${selectedUsers}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
index(debouncedSearch);
|
index(debouncedSearch);
|
||||||
@@ -79,19 +96,18 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-3 p-xl-0`}>
|
<div className={`${styles.container} p-2 p-sm-4 p-lg-0`}>
|
||||||
<h1 className={styles.title}>Utilizadores</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Utilizadores</motion.h1>
|
||||||
|
|
||||||
<div className="row py-3 g-4 justify-content-between">
|
<div className="row py-3 g-4 justify-content-between">
|
||||||
<div className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2 ">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftTransition} className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2">
|
||||||
<Form.Control type="text" placeholder="Pesquise por nome ou email..."
|
<Form.Control type="text" placeholder="Pesquise por nome ou email..."
|
||||||
value={search} onChange={(e) => {
|
value={search} onChange={(e) => {
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
}} />
|
}} />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
</div>
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightTransition} className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
||||||
|
|
||||||
<div className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
|
||||||
<div
|
<div
|
||||||
className="btn-group flex-grow-1 position-relative"
|
className="btn-group flex-grow-1 position-relative"
|
||||||
onMouseEnter={() => setShowFilterDropdown(true)}
|
onMouseEnter={() => setShowFilterDropdown(true)}
|
||||||
@@ -104,10 +120,11 @@ export default function Users() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showFilterDropdown && (
|
{showFilterDropdown && (
|
||||||
<motion.ul
|
<motion.ul
|
||||||
initial={{ opacity: 0, y: -10, scale: 0.98 }}
|
variants={dropdownVariants}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
initial="initial"
|
||||||
exit={{ opacity: 0, y: -10, scale: 0.98 }}
|
animate="animate"
|
||||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
exit="exit"
|
||||||
|
transition={dropdownTransition}
|
||||||
className="dropdown-menu text-center w-100"
|
className="dropdown-menu text-center w-100"
|
||||||
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
||||||
>
|
>
|
||||||
@@ -142,18 +159,29 @@ export default function Users() {
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4">
|
<motion.div variants={slideFromRightVariants} initial="initial" animate="animate" transition={slideFromRightOnMountTransition} className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4">
|
||||||
<Link to="/admin/create-user" className={`${styles.createButton} text-decoration-none`}><LuPlus className="mb-1" /> Novo Utilizador</Link>
|
<Link to="/admin/create-user" className={`${styles.createButton} text-decoration-none`}><LuPlus className="mb-1" /> Novo Utilizador</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
{loadingUsers ? (
|
{loadingUsers ? (
|
||||||
<div className="text-center mt-5">
|
<motion.div key="loading" variants={fadeVariants} initial="initial" animate="animate" exit="exit" transition={fadeTransition} className="text-center mt-5">
|
||||||
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
||||||
</div>
|
</motion.div>
|
||||||
) : users.length > 0 ? (
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key={listContentKey}
|
||||||
|
variants={listContentVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
exit="exit"
|
||||||
|
transition={listContentTransition}
|
||||||
|
>
|
||||||
|
{users.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<div className={`${styles.table} mt-3 mb-2`}>
|
<div className={`${styles.table} mt-3 mb-2`}>
|
||||||
<Table responsive className="mb-0">
|
<Table responsive className="mb-0">
|
||||||
@@ -229,6 +257,10 @@ export default function Users() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,18 @@ import { useState } from "react";
|
|||||||
import type { ApiErrorResponse } from "../../../types";
|
import type { ApiErrorResponse } from "../../../types";
|
||||||
import { Navigate } from "react-router";
|
import { Navigate } from "react-router";
|
||||||
import AccordionFAQS from "../../../components/accordion FAQ´s/accordionFAQS";
|
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() {
|
export default function Contactos() {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
@@ -64,9 +76,9 @@ export default function Contactos() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container} >
|
<div className={styles.container} >
|
||||||
<h1 className={styles.title}>Contactos</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Contactos</motion.h1>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-6 text-start d-flex flex-column gap-3 px-4 mt-3 mt-lg-0">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftTransition} className="col-12 col-lg-6 text-start d-flex flex-column gap-3 px-4 mt-3 mt-lg-0">
|
||||||
<div className="text-center text-sm-start">
|
<div className="text-center text-sm-start">
|
||||||
<h2 className={styles.subtitle}>Os nossos dados</h2>
|
<h2 className={styles.subtitle}>Os nossos dados</h2>
|
||||||
<span >Pode contactar-nos através do nosso email, telefone ou morada.</span>
|
<span >Pode contactar-nos através do nosso email, telefone ou morada.</span>
|
||||||
@@ -91,8 +103,8 @@ export default function Contactos() {
|
|||||||
<span className={styles.contactData}>Rua da Escola, 123, Lisboa, Portugal</span></div>
|
<span className={styles.contactData}>Rua da Escola, 123, Lisboa, Portugal</span></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="col-12 col-lg-6 text-start d-flex flex-column gap-4 px-4 mt-5 mt-lg-0">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightTransition} className="col-12 col-lg-6 text-start d-flex flex-column gap-4 px-4 mt-5 mt-lg-0">
|
||||||
<div className="text-center text-sm-start">
|
<div className="text-center text-sm-start">
|
||||||
<h2 className={styles.subtitle}>Formulário de contacto</h2>
|
<h2 className={styles.subtitle}>Formulário de contacto</h2>
|
||||||
<span>Preencha o formulário abaixo para entrar em contacto connosco</span>
|
<span>Preencha o formulário abaixo para entrar em contacto connosco</span>
|
||||||
@@ -142,16 +154,16 @@ export default function Contactos() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="col-12 mt-5 text-center">
|
<motion.div variants={slideFromBottomVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromBottomTransition} className="col-12 mt-5 text-center">
|
||||||
<span className={`${styles.title}`}>Perguntas Frequentes</span>
|
<span className={`${styles.title}`}>Perguntas Frequentes</span>
|
||||||
<div className="row mt-4">
|
<div className="row mt-4">
|
||||||
<div className="col-12 mx-auto">
|
<div className="col-12 mx-auto">
|
||||||
<AccordionFAQS />
|
<AccordionFAQS />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div >
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ import { Link } from "react-router";
|
|||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import AnimatedProgressBar from "../../../components/animatedProgressBar";
|
import AnimatedProgressBar from "../../../components/animatedProgressBar";
|
||||||
import AnimatedCounter from "../../../components/animatedNumberCount";
|
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 { useGetCurrentUser } from "../../../hooks/useGetCurrentUser"
|
||||||
|
|
||||||
import { useGetVideosLength } from "../../../hooks/useGetVideosLength";
|
import { useGetVideosLength } from "../../../hooks/useGetVideosLength";
|
||||||
@@ -190,7 +200,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<div className={`${styles.container} mx-2 mx-sm-0 p-2 p-sm-4 p-lg-0`}>
|
<div className={`${styles.container} mx-2 mx-sm-0 p-2 p-sm-4 p-lg-0`}>
|
||||||
|
|
||||||
<div className={`${styles.containerVideos} px-2 p-sm-4 mx-sm-1 mx-lg-0`}>
|
<motion.div variants={slideFromLeftVariants} initial="initial" animate="animate" transition={slideFromLeftOnMountTransition} className={`${styles.containerVideos} px-2 p-sm-4 mx-sm-1 mx-lg-0`}>
|
||||||
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
||||||
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4 px-0 `}>{role === 1 ? "Vídeos ativos" : "Continuar Formação"}</h2>
|
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4 px-0 `}>{role === 1 ? "Vídeos ativos" : "Continuar Formação"}</h2>
|
||||||
<Link to="/videos" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-4`}>Ver vídeos <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
<Link to="/videos" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-4`}>Ver vídeos <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
||||||
@@ -198,7 +208,7 @@ export default function Home() {
|
|||||||
|
|
||||||
{role !== 1 && (
|
{role !== 1 && (
|
||||||
<>
|
<>
|
||||||
<AnimatedProgressBar value={progressoVideos} className={`${styles.progressBar}`} />
|
<AnimatedProgressBar value={progressoVideos ?? 0} className={`${styles.progressBar}`} />
|
||||||
|
|
||||||
{progressoVideos === 100 && (
|
{progressoVideos === 100 && (
|
||||||
<div className="text-center mt-3">
|
<div className="text-center mt-3">
|
||||||
@@ -237,9 +247,9 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="ms-0 px-lg-4 mt-4">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightOnMountTransition02} className="ms-0 px-lg-4 mt-4">
|
||||||
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
||||||
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4`}>Próximos workshops</h2>
|
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4`}>Próximos workshops</h2>
|
||||||
<Link to="/workshops" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-sm-4`}>Ver workshops <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
<Link to="/workshops" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-sm-4`}>Ver workshops <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
||||||
@@ -290,11 +300,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-8 mt-5 px-3">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftInViewTransition03} className="col-12 col-lg-8 mt-5 px-3">
|
||||||
<form onSubmit={sendMail} className="pt-2 px-1">
|
<form onSubmit={sendMail} className="pt-2 px-1">
|
||||||
<div className="row g-3 bg-white p-4 mt-3" style={{ borderRadius: "var(--border-radius)" }}>
|
<div className="row g-3 bg-white p-4 mt-3" style={{ borderRadius: "var(--border-radius)" }}>
|
||||||
<div className="text-center text-sm-start mb-4">
|
<div className="text-center text-sm-start mb-4">
|
||||||
@@ -342,23 +352,33 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="col-12 col-lg-4 mt-lg-5 px-sm-0 ps-lg-4 pe-lg-3 px-2">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightInViewTransition04} className="col-12 col-lg-4 mt-lg-5 px-sm-0 ps-lg-4 pe-lg-3 px-2">
|
||||||
<div className="h-100 pt-4 px-1 px-lg-0">
|
<div className="h-100 pt-4 px-1 px-lg-0">
|
||||||
<div className={`${styles.userVideosWatched} text-start justify-content-evenly d-flex flex-column h-100 p-4 mx-sm-2 ms-lg-2 me-lg-0`}>
|
<div className={`${styles.userVideosWatched} text-start justify-content-evenly d-flex flex-column h-100 p-4 mx-sm-2 ms-lg-2 me-lg-0`}>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-3 text-white" > {role === 1 ? "Vídeos ativos" : "Vídeos assistidos"}</span>
|
<span className="fw-normal fs-3 text-white" > {role === 1 ? "Vídeos ativos" : "Vídeos assistidos"}</span>
|
||||||
<span className=" fw-bold text-white" style={{ fontSize: "4rem" }}>{role === 1 ? <AnimatedCounter value={videosCount} /> : <AnimatedCounter value={videosWatched} />}/<AnimatedCounter value={videosCount} /></span>
|
<span className=" fw-bold text-white" style={{ fontSize: "4rem" }}>
|
||||||
|
{role === 1
|
||||||
|
? <AnimatedCounter value={videosCount} />
|
||||||
|
: <> <AnimatedCounter value={videosWatched} />/<AnimatedCounter value={videosCount} /></>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-3" style={{ color: "var(--bg-grey)" }}>{role === 1 ? "Workshops agendados" : "Workshops inscrito"}</span>
|
<span className="fw-normal fs-3" style={{ color: "var(--bg-grey)" }}>{role === 1 ? "Workshops agendados" : "Workshops inscrito"}</span>
|
||||||
<span className="fw-bold text-white" style={{ fontSize: "4rem" }}>{role === 1 ? <AnimatedCounter value={workshopsCount} /> : <AnimatedCounter value={workshopsInscribed} />}/{<AnimatedCounter value={workshopsCount} />}</span>
|
<span className="fw-bold text-white" style={{ fontSize: "4rem" }}>
|
||||||
</div>
|
{role === 1
|
||||||
|
? <AnimatedCounter value={workshopsCount} />
|
||||||
|
: <> <AnimatedCounter value={workshopsInscribed} />/<AnimatedCounter value={workshopsCount} /></>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,27 @@ import { LuCheck, LuKeyRound, LuPencil, LuX, LuArrowUpRight, LuClock3, LuCalenda
|
|||||||
import { CgSpinner } from "react-icons/cg";
|
import { CgSpinner } from "react-icons/cg";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import AnimatedProgressBar from "../../../components/animatedProgressBar";
|
import AnimatedProgressBar from "../../../components/animatedProgressBar";
|
||||||
|
import AnimatedCounter from "../../../components/animatedNumberCount";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
||||||
import { PiCheckCircleFill } from "react-icons/pi";
|
import { PiCheckCircleFill } from "react-icons/pi";
|
||||||
import Swal from "sweetalert2";
|
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() {
|
export default function Profile() {
|
||||||
const [role, setRole] = useState(0);
|
const [role, setRole] = useState(0);
|
||||||
@@ -197,11 +215,11 @@ export default function Profile() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<span className={styles.title}>Os meus dados</span>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={styles.title}>Os meus dados</motion.h1>
|
||||||
<div>
|
<div>
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<div className="row mt-5 justify-content-between">
|
<div className="row mt-5 justify-content-between">
|
||||||
<div className="col-12 col-lg-8 p-3 rounded-3">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftTransitionNoDelay} className="col-12 col-lg-8 p-3 rounded-3">
|
||||||
<div className={`${styles.userCard} p-4 bg-white h-100`}>
|
<div className={`${styles.userCard} p-4 bg-white h-100`}>
|
||||||
<div className="row justify-content-between">
|
<div className="row justify-content-between">
|
||||||
<div className="col-12 d-flex text-start mt-xl-0 justify-content-between flex-grow-1 mb-3">
|
<div className="col-12 d-flex text-start mt-xl-0 justify-content-between flex-grow-1 mb-3">
|
||||||
@@ -213,46 +231,46 @@ export default function Profile() {
|
|||||||
<div className="col-12 d-flex flex-column flex-md-row gap-0 gap-md-2 text-end align-items-start align-items-md-end mt-md-0 mx-auto mx-md-0 flex-wrap">
|
<div className="col-12 d-flex flex-column flex-md-row gap-0 gap-md-2 text-end align-items-start align-items-md-end mt-md-0 mx-auto mx-md-0 flex-wrap">
|
||||||
<span className="d-flex justify-content-center flex-wrap flex-sm-nowrap border border-secondary-subtle py-2 px-2 rounded-3 fs-6 fw-medium py-0" style={{ maxWidth: "fit-content", height: "fit-content" }}>Membro desde: <b className="fw-bold fs-6">{new Date(userData?.created_at as string).toLocaleDateString('pt-PT')}</b></span>
|
<span className="d-flex justify-content-center flex-wrap flex-sm-nowrap border border-secondary-subtle py-2 px-2 rounded-3 fs-6 fw-medium py-0" style={{ maxWidth: "fit-content", height: "fit-content" }}>Membro desde: <b className="fw-bold fs-6">{new Date(userData?.created_at as string).toLocaleDateString('pt-PT')}</b></span>
|
||||||
<div className="d-flex flex-wrap flex-grow-1 justify-content-evenly justify-content-md-end flex-wrap mt-3 mx-auto">
|
<div className="d-flex flex-wrap flex-grow-1 justify-content-evenly justify-content-md-end flex-wrap mt-3 mx-auto">
|
||||||
<a className={`${styles.updateButton} text-decoration-none text-primary text-center px-3`} onClick={() => { setFormEdit(true); }}> Editar <LuPencil className={`${styles.icon} mb-1 text-primary`} /></a>
|
<a className={`${styles.updateButton} text-decoration-none text-primary text-center px-3`} onClick={() => { setFormEdit(true); setFormEditPassword(false); }}> Editar <LuPencil className={`${styles.icon} mb-1 text-primary`} /></a>
|
||||||
<a className={`${styles.passwordButton} text-decoration-none text-danger px-1 px-md-3
|
<a className={`${styles.passwordButton} text-decoration-none text-danger px-1 px-md-3
|
||||||
`} onClick={() => { setFormEditPassword(true); }}> Alterar password <LuKeyRound className={`${styles.icon} mb-1 text-danger`} /></a>
|
`} onClick={() => { setFormEditPassword(true); setFormEdit(false); }}> Alterar password <LuKeyRound className={`${styles.icon} mb-1 text-danger`} /></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="col-12 col-lg-4 p-3">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightTransition} className="col-12 col-lg-4 p-3">
|
||||||
{role === 1 ? (
|
{role === 1 ? (
|
||||||
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Vídeos ativos</span>
|
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Vídeos ativos</span>
|
||||||
<span className="fs-2 fw-bold text-white">{videosCount}</span>
|
<span className="fs-2 fw-bold text-white"><AnimatedCounter value={videosCount} /></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Workshops agendados</span>
|
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Workshops agendados</span>
|
||||||
<span className="fs-2 fw-bold text-white">{workshopsCount}</span>
|
<span className="fs-2 fw-bold text-white"><AnimatedCounter value={workshopsCount} /></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
<div className={`${styles.userVideosWatched} text-start d-flex flex-column h-100 gap-3 p-4`}>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Vídeos assistidos</span>
|
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Vídeos assistidos</span>
|
||||||
<span className="fs-2 fw-bold text-white">{videosWatched}/{videosCount}</span>
|
<span className="fs-2 fw-bold text-white"><AnimatedCounter value={videosWatched} />/<AnimatedCounter value={videosCount} /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Workshops inscrito</span>
|
<span className="fw-normal fs-6" style={{ color: "var(--bg-grey)" }}>Workshops inscrito</span>
|
||||||
<span className="fs-2 fw-bold text-white">{workshopsCount}</span>
|
<span className="fs-2 fw-bold text-white"><AnimatedCounter value={workshopsCount} /></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{formEdit ? (
|
{formEdit ? (
|
||||||
<div className={`${styles.containerForm} my-3 px-2 p-sm-4`}>
|
<motion.div variants={slideFromTopVariants} initial="initial" whileInView="animate" transition={slideFromTopTransition} className={`${styles.containerForm} my-3 px-2 p-sm-4`}>
|
||||||
<span className={styles.title}>Editar dados</span>
|
<span className={styles.title}>Editar dados</span>
|
||||||
|
|
||||||
<form method="patch" onSubmit={update} id="formEditUser">
|
<form method="patch" onSubmit={update} id="formEditUser">
|
||||||
@@ -273,9 +291,9 @@ export default function Profile() {
|
|||||||
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</motion.div>
|
||||||
) : formEditPassword ? (
|
) : formEditPassword ? (
|
||||||
<div className={`${styles.containerForm} my-3 px-2 p-sm-4`}>
|
<motion.div variants={slideFromTopVariants} initial="initial" whileInView="animate" transition={slideFromTopTransitionDelayed} className={`${styles.containerForm} my-3 px-2 p-sm-4`}>
|
||||||
<span className={styles.title}>Alterar password</span>
|
<span className={styles.title}>Alterar password</span>
|
||||||
|
|
||||||
<form method="patch" onSubmit={updatePassword} id="formEditPassword">
|
<form method="patch" onSubmit={updatePassword} id="formEditPassword">
|
||||||
@@ -300,10 +318,10 @@ export default function Profile() {
|
|||||||
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
<button type="submit" className={`${styles.submitFormButton}`} >Submeter dados <LuCheck className="mb-1" /></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className={`${styles.containerVideos} mt-4 px-2 p-sm-4 mx-1`}>
|
<motion.div variants={slideFromBottomVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromBottomInViewTransition} className={`${styles.containerVideos} mt-4 px-2 p-sm-4 mx-1`}>
|
||||||
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
||||||
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4 px-0 `}>{role === 1 ? "Vídeos ativos" : "Continuar Formação"}</h2>
|
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4 px-0 `}>{role === 1 ? "Vídeos ativos" : "Continuar Formação"}</h2>
|
||||||
<Link to="/videos" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-4`}>Ver vídeos <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
<Link to="/videos" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-4`}>Ver vídeos <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
||||||
@@ -311,7 +329,7 @@ export default function Profile() {
|
|||||||
|
|
||||||
{role !== 1 && (
|
{role !== 1 && (
|
||||||
<>
|
<>
|
||||||
<AnimatedProgressBar value={progressoVideos} className={`${styles.progressBar}`} />
|
<AnimatedProgressBar value={progressoVideos ?? 0} className={`${styles.progressBar}`} />
|
||||||
|
|
||||||
{progressoVideos === 100 && (
|
{progressoVideos === 100 && (
|
||||||
<div className="text-center mt-3">
|
<div className="text-center mt-3">
|
||||||
@@ -323,8 +341,8 @@ export default function Profile() {
|
|||||||
|
|
||||||
<div className="row mt-4 px-2">
|
<div className="row mt-4 px-2">
|
||||||
{videosCount > 0 ? videos.map((video: Video) => (
|
{videosCount > 0 ? videos.map((video: Video) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2">
|
<motion.div key={video.id} variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="col-12 col-sm-6 col-lg-4 p-2">
|
||||||
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`} key={video.id}>
|
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`}>
|
||||||
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
||||||
{role === 1 && (
|
{role === 1 && (
|
||||||
<Link to={`/admin/edit-video/${video.id}`}> <LuPencil className={`${styles.iconEdit} text-decoration-none`} /> </Link>
|
<Link to={`/admin/edit-video/${video.id}`}> <LuPencil className={`${styles.iconEdit} text-decoration-none`} /> </Link>
|
||||||
@@ -342,23 +360,23 @@ export default function Profile() {
|
|||||||
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
)) : videosCount === 0 ? (
|
)) : videosCount === 0 ? (
|
||||||
<div className="col-12 text-start ps-1">
|
<div className="col-12 text-start ps-1">
|
||||||
<span className="text-muted fs-5">Nenhum vídeo encontrado</span>
|
<span className="text-muted fs-5">Nenhum vídeo encontrado</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="ms-0 px-lg-4 mt-4">
|
<div className="ms-0 px-lg-4 mt-4">
|
||||||
<div className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
<motion.div variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="d-flex flex-column flex-sm-row justify-content-between align-items-center px-0" >
|
||||||
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4`}>{role === 1 ? "Workshops agendados" : "Workshops Inscrito"}</h2>
|
<h2 className={`${styles.subtitle} text-center text-sm-start mt-4 mt-sm-3 mb-4`}>{role === 1 ? "Workshops agendados" : "Workshops Inscrito"}</h2>
|
||||||
<Link to="/workshops" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-sm-4`}>Ver workshops <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
<Link to="/workshops" className={`${styles.link} text-decoration-none fw-semibold fs-5 mt-sm-3 mb-sm-4`}>Ver workshops <LuArrowUpRight className="mb-1 me-2" size={25} /></Link>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="row mt-4 mt-sm-1 px-2 mb-5">
|
<div className="row mt-4 mt-sm-1 px-2 mb-5">
|
||||||
{role !== 1 && workshopsCount > 0 ? workshops.map((workshop: Workshop) => (
|
{role !== 1 && workshopsCount > 0 ? workshops.map((workshop: Workshop) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
<motion.div key={workshop.id} variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
||||||
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
||||||
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
||||||
<img
|
<img
|
||||||
@@ -382,13 +400,13 @@ export default function Profile() {
|
|||||||
<Link to={`${role === 1 ? `/admin/edit-workshop/${workshop.id}` : `/workshop/${workshop.id}`}`} className={`${styles.linkWorkshop} d-block text-center py-2 px-5 text-decoration-none`}> Detalhes </Link>
|
<Link to={`${role === 1 ? `/admin/edit-workshop/${workshop.id}` : `/workshop/${workshop.id}`}`} className={`${styles.linkWorkshop} d-block text-center py-2 px-5 text-decoration-none`}> Detalhes </Link>
|
||||||
|
|
||||||
{role !== 1 ? (
|
{role !== 1 ? (
|
||||||
<button type="button" className={`${styles.btncancelarInscricao} btn text-center py-2 text-decoration-none`} onClick={() => cancelarInscricao(workshop.id)} key={workshop.id}>
|
<button type="button" className={`${styles.btncancelarInscricao} btn text-center py-2 text-decoration-none`} onClick={() => cancelarInscricao(workshop.id)}>
|
||||||
Anular inscrição
|
Anular inscrição
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
)
|
)
|
||||||
) : workshopsCount === 0 ? (
|
) : workshopsCount === 0 ? (
|
||||||
<div className="col-12 text-center px-0 mt-3">
|
<div className="col-12 text-center px-0 mt-3">
|
||||||
@@ -397,7 +415,7 @@ export default function Profile() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{role === 1 && workshopsCount > 0 ? nextWorkshops.map((workshop: Workshop) => (
|
{role === 1 && workshopsCount > 0 ? nextWorkshops.map((workshop: Workshop) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
<motion.div key={workshop.id} variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
||||||
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
||||||
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
||||||
<img
|
<img
|
||||||
@@ -421,7 +439,7 @@ export default function Profile() {
|
|||||||
<Link to={`${role === 1 ? `/admin/edit-workshop/${workshop.id}` : `/workshop/${workshop.id}`}`} className={`${styles.linkWorkshop} d-block text-center py-2 px-5 text-decoration-none`}> Detalhes </Link>
|
<Link to={`${role === 1 ? `/admin/edit-workshop/${workshop.id}` : `/workshop/${workshop.id}`}`} className={`${styles.linkWorkshop} d-block text-center py-2 px-5 text-decoration-none`}> Detalhes </Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
)
|
)
|
||||||
) : workshopsCount === 0 ? (
|
) : workshopsCount === 0 ? (
|
||||||
<div className="col-12 text-center px-0 mt-3">
|
<div className="col-12 text-center px-0 mt-3">
|
||||||
|
|||||||
@@ -169,6 +169,24 @@
|
|||||||
color: var(--text-white);
|
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 {
|
.titleVideo {
|
||||||
color: var(--text-white);
|
color: var(--text-white);
|
||||||
font-size: var(--size-font-text);
|
font-size: var(--size-font-text);
|
||||||
@@ -227,10 +245,6 @@
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon{
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkWorkshop {
|
.linkWorkshop {
|
||||||
display: block;
|
display: block;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { useGetVideosSearch } from "../../../hooks/useGetVideosSearch";
|
|||||||
import { useGetWorkshopsSearch } from "../../../hooks/useGetWorkshopsSearch";
|
import { useGetWorkshopsSearch } from "../../../hooks/useGetWorkshopsSearch";
|
||||||
import { PiCheckCircleFill } from "react-icons/pi";
|
import { PiCheckCircleFill } from "react-icons/pi";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
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() {
|
export default function Search() {
|
||||||
const [videos, setVideos] = useState<Video[]>([]);
|
const [videos, setVideos] = useState<Video[]>([]);
|
||||||
@@ -76,14 +79,14 @@ export default function Search() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<h1 className={`${styles.subtitle} mb-4`}>Resultados da pesquisa: "{query}"</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={`${styles.subtitle} mb-4`}>Resultados da pesquisa: "{query}"</motion.h1>
|
||||||
|
|
||||||
{videos.length > 0 ? (
|
{videos.length > 0 ? (
|
||||||
<div className="row g-3 p-0">
|
<div className="row g-3 p-0">
|
||||||
<h2 className={`${styles.title} text-center text-md-start`}>Vídeos</h2>
|
<motion.h2 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={`${styles.title} text-start`}>Vídeos</motion.h2>
|
||||||
<span className="text-muted text-start mt-0">{videos.length === 1 ? `Foi encontrado ${videos.length} vídeo na sua pesquisa.` : `Foram encontrados ${videos.length} vídeos na sua pesquisa.`}</span>
|
<motion.span variants={slideFromTopVariants} initial="initial" animate="animate" transition={slideFromTopTransition} className="text-muted text-start mt-0">{videos.length === 1 ? `Foi encontrado ${videos.length} vídeo na sua pesquisa.` : `Foram encontrados ${videos.length} vídeos na sua pesquisa.`}</motion.span>
|
||||||
{videos.map((video) => (
|
{videos.map((video) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2">
|
<motion.div variants={slideFromBottomVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromBottomTransition} className="col-12 col-sm-6 col-lg-4 p-2">
|
||||||
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`} key={video.id}>
|
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`} key={video.id}>
|
||||||
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
@@ -102,17 +105,17 @@ export default function Search() {
|
|||||||
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{workshops.length > 0 ? (
|
{workshops.length > 0 ? (
|
||||||
<div className="row g-3 p-0 mt-3">
|
<div className="row g-3 p-0 mt-3">
|
||||||
<h2 className={`${styles.title} text-center text-md-start`}>Workshops</h2>
|
<motion.h2 variants={pageTitleVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={pageTitleTransition} className={`${styles.title} text-start`}>Workshops</motion.h2>
|
||||||
<span className="text-muted text-start mt-0">{workshops.length === 1 ? `Foi encontrado ${workshops.length} workshop na sua pesquisa.` : `Foram encontrados ${workshops.length} workshops na sua pesquisa.`}</span>
|
<motion.span variants={slideFromTopVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromTopTransition} className="text-muted text-start mt-0">{workshops.length === 1 ? `Foi encontrado ${workshops.length} workshop na sua pesquisa.` : `Foram encontrados ${workshops.length} workshops na sua pesquisa.`}</motion.span>
|
||||||
{workshops.map((workshop) => (
|
{workshops.map((workshop) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2 position-relative" key={workshop.id}>
|
<motion.div variants={slideFromBottomVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromBottomTransition} className="col-12 col-sm-6 col-lg-4 p-2 position-relative" key={workshop.id}>
|
||||||
<div className={`${styles.boxWorkshop} text-start pb-3`}>
|
<div className={`${styles.boxWorkshop} text-start pb-3`}>
|
||||||
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
||||||
<img
|
<img
|
||||||
@@ -136,7 +139,7 @@ export default function Search() {
|
|||||||
</div>
|
</div>
|
||||||
<Link to={`/workshop/${workshop.id}`} className={`${styles.linkWorkshop} d-block text-center mt-3 py-2 px-5 mx-auto text-decoration-none`}> Detalhes </Link>
|
<Link to={`/workshop/${workshop.id}`} className={`${styles.linkWorkshop} d-block text-center mt-3 py-2 px-5 mx-auto text-decoration-none`}> Detalhes </Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ export default function Video() {
|
|||||||
<Link to={`/video/${previousVideo}`} className={`${styles.previousButton} fs-6 text-start`}><b><LuChevronLeft size={25} title="Vídeo anterior" /> Anterior</b></Link>
|
<Link to={`/video/${previousVideo}`} className={`${styles.previousButton} fs-6 text-start`}><b><LuChevronLeft size={25} title="Vídeo anterior" /> Anterior</b></Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
|
||||||
{nextVideo && (
|
{nextVideo && (
|
||||||
<Link to={`/video/${nextVideo}`} className={`${styles.nextButton} fs-6 text-end`}><b>Próximo <LuChevronRight size={25} title="Próximo vídeo" /> </b></Link>
|
<Link to={`/video/${nextVideo}`} className={`${styles.nextButton} fs-6 text-end`}><b>Próximo <LuChevronRight size={25} title="Próximo vídeo" /> </b></Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,6 +12,24 @@ import { useDebounce } from "../../../hooks/useDebounce";
|
|||||||
import { PiCheckCircleFill } from "react-icons/pi";
|
import { PiCheckCircleFill } from "react-icons/pi";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
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() {
|
export default function Videos() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -28,6 +46,7 @@ export default function Videos() {
|
|||||||
const [loadingVideos, setLoadingVideos] = useState(false);
|
const [loadingVideos, setLoadingVideos] = useState(false);
|
||||||
const videosToShow = videos;
|
const videosToShow = videos;
|
||||||
const [role, setRole] = useState(0);
|
const [role, setRole] = useState(0);
|
||||||
|
const listContentKey = `${currentPage}-${debouncedSearch}-${selectedCategoryId}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchVideos = async () => {
|
const fetchVideos = async () => {
|
||||||
@@ -92,7 +111,7 @@ export default function Videos() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-2 p-sm-4 p-lg-0`}>
|
<div className={`${styles.container} p-2 p-sm-4 p-lg-0`}>
|
||||||
<h1 className={`${styles.title} mt-1`}>Vídeos</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={`${styles.title} mt-1`}>Vídeos</motion.h1>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-center mt-5 d-flex flex-column gap-2 align-items-center">
|
<div className="text-center mt-5 d-flex flex-column gap-2 align-items-center">
|
||||||
@@ -101,14 +120,14 @@ export default function Videos() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="row py-3 g-4 justify-content-between">
|
<div className="row py-3 g-4 justify-content-between">
|
||||||
<div className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftTransition} className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2">
|
||||||
<Form.Control type="text" placeholder="Pesquisar vídeos..."
|
<Form.Control type="text" placeholder="Pesquisar vídeos..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightTransition} className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
||||||
<div
|
<div
|
||||||
className="btn-group flex-grow-1 position-relative"
|
className="btn-group flex-grow-1 position-relative"
|
||||||
onMouseEnter={() => setShowFilterDropdown(true)}
|
onMouseEnter={() => setShowFilterDropdown(true)}
|
||||||
@@ -126,10 +145,11 @@ export default function Videos() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showFilterDropdown && (
|
{showFilterDropdown && (
|
||||||
<motion.ul
|
<motion.ul
|
||||||
initial={{ opacity: 0, y: -10, scale: 0.98 }}
|
variants={dropdownVariants}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
initial="initial"
|
||||||
exit={{ opacity: 0, y: -10, scale: 0.98 }}
|
animate="animate"
|
||||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
exit="exit"
|
||||||
|
transition={dropdownTransition}
|
||||||
className="dropdown-menu text-center w-100"
|
className="dropdown-menu text-center w-100"
|
||||||
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
||||||
>
|
>
|
||||||
@@ -204,27 +224,37 @@ export default function Videos() {
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
|
|
||||||
{role === 1 && (
|
{role === 1 && (
|
||||||
<div className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4">
|
<motion.div variants={slideFromRightVariants} initial="initial" animate="animate" transition={slideFromRightOnMountTransition} className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4">
|
||||||
<Link to="/admin/create-video" className={`${styles.btnAdicionarVideo} text-decoration-none`}><LuPlus className="mb-1" /> Adicionar vídeo</Link>
|
<Link to="/admin/create-video" className={`${styles.btnAdicionarVideo} text-decoration-none`}><LuPlus className="mb-1" /> Adicionar vídeo</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`${styles.containerVideos} mt-3`}>
|
<div className={`${styles.containerVideos} mt-3`}>
|
||||||
<div className="row g-3 p-0">
|
<AnimatePresence mode="wait">
|
||||||
{ loadingVideos ? (
|
{loadingVideos ? (
|
||||||
<div className="col-12 text-center mt-5">
|
<motion.div key="loading" variants={fadeVariants} initial="initial" animate="animate" exit="exit" transition={fadeTransition} className="col-12 text-center mt-5">
|
||||||
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
||||||
</div>
|
</motion.div>
|
||||||
) : videosToShow.length > 0 ? (
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key={listContentKey}
|
||||||
|
variants={listContentVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
exit="exit"
|
||||||
|
transition={listContentTransition}
|
||||||
|
className="row g-3 p-0"
|
||||||
|
>
|
||||||
|
{videosToShow.length > 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="row g-3 p-0">
|
<div className="row g-3 p-0">
|
||||||
{videos.map((video) => (
|
{videos.map((video) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2">
|
<motion.div key={video.id} variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="col-12 col-sm-6 col-lg-4 p-2">
|
||||||
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`} key={video.id}>
|
<Link to={`/video/${video.id}`} className={`${styles.linkVideo} text-decoration-none text-black`}>
|
||||||
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
<div className={`${styles.boxVideo} position-relative`} data-category={video.categories?.map((category) => category.id).join(', ')} >
|
||||||
{role === 1 && (
|
{role === 1 && (
|
||||||
<Link to={`/admin/edit-video/${video.id}`}> <LuPencil className={`${styles.iconEdit} text-decoration-none`} /> </Link>
|
<Link to={`/admin/edit-video/${video.id}`}> <LuPencil className={`${styles.iconEdit} text-decoration-none`} /> </Link>
|
||||||
@@ -243,7 +273,7 @@ export default function Videos() {
|
|||||||
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
{video.watched && <span className="d-block badge text-success text-start fs-6 position-absolute top-0 start-0 bg-success-subtle text-success px-3 py-2" style={{ borderRadius: "0 0 var(--border-radius) 0" }}><PiCheckCircleFill className="mb-1 me-1" /> Visto</span>}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -287,11 +317,9 @@ export default function Videos() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,24 @@ import Swal from "sweetalert2";
|
|||||||
import { Form, Pagination } from "react-bootstrap";
|
import { Form, Pagination } from "react-bootstrap";
|
||||||
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../../utils/imageSkeleton";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
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() {
|
export default function Workshops() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -25,6 +43,7 @@ export default function Workshops() {
|
|||||||
const [loadingWorkshops, setLoadingWorkshops] = useState(false);
|
const [loadingWorkshops, setLoadingWorkshops] = useState(false);
|
||||||
const [role, setRole] = useState(0);
|
const [role, setRole] = useState(0);
|
||||||
const [userId, setUserId] = useState(0);
|
const [userId, setUserId] = useState(0);
|
||||||
|
const listContentKey = `${currentPage}-${debouncedSearch}-${selectedWorkshopStatus}`;
|
||||||
/* const [searchLoading, setSearchLoading] = useState(false); */
|
/* const [searchLoading, setSearchLoading] = useState(false); */
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -152,17 +171,17 @@ export default function Workshops() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} p-4 p-lg-0`}>
|
<div className={`${styles.container} p-2 p-sm-4 p-lg-0`}>
|
||||||
<h1 className={`${styles.title} mt-1 mb-0 mb-sm-3`}>Workshops</h1>
|
<motion.h1 variants={pageTitleVariants} initial="initial" animate="animate" transition={pageTitleTransition} className={`${styles.title} mt-1 mb-0 mb-sm-3`}>Workshops</motion.h1>
|
||||||
|
|
||||||
<div className="row py-3 g-4 justify-content-between">
|
<div className="row py-3 g-4 justify-content-between">
|
||||||
<div className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2">
|
<motion.div variants={slideFromLeftVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromLeftTransition} className="col-12 col-sm-7 col-md-8 col-lg-6 d-flex gap-2 text-start px-2">
|
||||||
<Form.Control type="text" placeholder="Pesquisar workshops..."
|
<Form.Control type="text" placeholder="Pesquisar workshops..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</motion.div>
|
||||||
<div className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
<motion.div variants={slideFromRightVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={slideFromRightTransition} className="col-12 col-sm-5 col-md-4 col-lg-3 d-flex mt-2 mt-sm-4">
|
||||||
{/* <label htmlFor="filter" className="form-label fw-bold text-start">Filtrar workshops</label> */}
|
{/* <label htmlFor="filter" className="form-label fw-bold text-start">Filtrar workshops</label> */}
|
||||||
<div
|
<div
|
||||||
className="btn-group flex-grow-1 position-relative"
|
className="btn-group flex-grow-1 position-relative"
|
||||||
@@ -176,10 +195,11 @@ export default function Workshops() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showFilterDropdown && (
|
{showFilterDropdown && (
|
||||||
<motion.ul
|
<motion.ul
|
||||||
initial={{ opacity: 0, y: -10, scale: 0.98 }}
|
variants={dropdownVariants}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
initial="initial"
|
||||||
exit={{ opacity: 0, y: -10, scale: 0.98 }}
|
animate="animate"
|
||||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
exit="exit"
|
||||||
|
transition={dropdownTransition}
|
||||||
className="dropdown-menu text-center w-100"
|
className="dropdown-menu text-center w-100"
|
||||||
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
style={{ display: "block", zIndex: 4000, top: "100%", marginTop: "0.25rem" }}
|
||||||
>
|
>
|
||||||
@@ -228,23 +248,34 @@ export default function Workshops() {
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
{role === 1 ? (
|
{role === 1 ? (
|
||||||
<div className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4" style={{ minHeight: '40px' }}>
|
<motion.div variants={slideFromRightVariants} initial="initial" animate="animate" transition={slideFromRightOnMountTransition} className="col-12 col-sm col-md-12 col-lg-3 text-end align-content-center mt-4 mt-lg-4" style={{ minHeight: '40px' }}>
|
||||||
<Link to="/admin/create-workshop" className={`${styles.btnAdicionarWorkshop} text-decoration-none`}><LuPlus className="mb-1" />Adicionar workshop</Link>
|
<Link to="/admin/create-workshop" className={`${styles.btnAdicionarWorkshop} text-decoration-none`}><LuPlus className="mb-1" />Adicionar workshop</Link>
|
||||||
</div>
|
</motion.div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row py-3 g-4">
|
<div className="row py-3 g-4">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
{loadingWorkshops ? (
|
{loadingWorkshops ? (
|
||||||
<div className="col-12 text-center mt-5">
|
<motion.div key="loading" variants={fadeVariants} initial="initial" animate="animate" exit="exit" transition={fadeTransition} className="col-12 text-center mt-5">
|
||||||
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
<CgSpinner className={`${styles.animateSpin} text-2xl fs-3`} />
|
||||||
</div>
|
</motion.div>
|
||||||
) : workshops.length > 0 ? (
|
) : (
|
||||||
|
<motion.div
|
||||||
|
key={listContentKey}
|
||||||
|
variants={listContentVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
exit="exit"
|
||||||
|
transition={listContentTransition}
|
||||||
|
className="row g-4 p-0 w-100 mx-0 mt-0"
|
||||||
|
>
|
||||||
|
{workshops.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{workshops.map((workshop) => (
|
{workshops.map((workshop) => (
|
||||||
<div className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
<motion.div key={workshop.id} variants={cardInViewVariants} initial="initial" whileInView="animate" viewport={viewportOnce} transition={cardInViewTransition} className="col-12 col-sm-6 col-lg-4 p-2 position-relative">
|
||||||
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
<div className={`${styles.boxWorkshop} d-flex flex-column text-start pb-3 h-100`}>
|
||||||
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
<div className={`${styles.thumbnailWorkshop} position-relative`}>
|
||||||
<img
|
<img
|
||||||
@@ -306,7 +337,7 @@ export default function Workshops() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
<div className="d-flex justify-content-center align-items-center gap-3 py-3">
|
<div className="d-flex justify-content-center align-items-center gap-3 py-3">
|
||||||
<Pagination>
|
<Pagination>
|
||||||
@@ -348,6 +379,9 @@ export default function Workshops() {
|
|||||||
<span className="text-muted fs-5">Nenhum workshop encontrado com o filtro selecionado</span>
|
<span className="text-muted fs-5">Nenhum workshop encontrado com o filtro selecionado</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|
||||||
Reference in New Issue
Block a user