feat: request optimizations

This commit is contained in:
Xavier Oliveira
2026-05-28 11:23:57 +01:00
parent 68f99798ce
commit 996d44f33d
38 changed files with 1258 additions and 1116 deletions
@@ -3,17 +3,17 @@ import { LuUser, LuMenu, LuSearch, LuLogOut, LuUsers, LuMail, LuGraduationCap, L
import { useEffect, useState } from "react";
import { useNavigate, Link } from "react-router";
import { Button, NavLink, Offcanvas } from "react-bootstrap";
import type { User, Workshop } from "../../types";
import type { Workshop } from "../../types";
import type { Video } from "../../types";
import { CgSpinner } from "react-icons/cg";
import { useDebounce } from "../../hooks/useDebounce";
import { useGetVideos } from "../../hooks/useGetVideos";
import { useGetVideosLength } from "../../hooks/useGetVideosLength";
import { useGetVideosSearch } from "../../hooks/useGetVideosSearch";
import { PiCheckCircleFill } from "react-icons/pi";
import { useGetWorkshopsSearch } from "../../hooks/useGetWorkshopsSearch";
import { imageSkeletonFadeStyle, onImageSkeletonLoad } from "../../utils/imageSkeleton";
import { motion, AnimatePresence } from "framer-motion";
import { useGetCurrentUser } from "../../hooks/useGetCurrentUser";
export default function Header() {
const [showMenu, setShowMenu] = useState(false);
@@ -23,31 +23,28 @@ export default function Header() {
const [searchCompleted, setSearchCompleted] = useState(false);
const [videosSearched, setVideosSearched] = useState<Video[]>([]);
const [workshopsSearched, setWorkshopsSearched] = useState<Workshop[]>([]);
const user = JSON.parse(localStorage.getItem("user") || "{}") as User;
const isAdmin = user.role_id === 1;
const debouncedSearch = useDebounce(search, 500);
const [showDropdown, setShowDropdown] = useState(false);
const { getVideos } = useGetVideos();
const { getVideosLength } = useGetVideosLength();
const { getVideosSearch } = useGetVideosSearch();
const { getWorkshopsSearch } = useGetWorkshopsSearch();
const [videosStats, setVideosStats] = useState({
videos: 0,
videosWatched: 0
});
const { getCurrentUser } = useGetCurrentUser();
const [role, setRole] = useState(0);
const [videosWatched, setVideosWatched] = useState(0);
const [videosCount, setVideosCount] = useState(0);
const navigate = useNavigate();
useEffect(() => {
if (isAdmin) return;
const fetchProgressVideos = async () => {
const videosLengthData = await getVideosLength();
if ("videos" in videosLengthData) {
setVideosStats(videosLengthData as { videos: number, videosWatched: number });
}
const fetchAll = async () => {
const videosWatched = localStorage.getItem("videosWatched");
setVideosWatched(videosWatched ? parseInt(videosWatched) : 0);
const videosCount = localStorage.getItem("videosCount");
setVideosCount(videosCount ? parseInt(videosCount) : 0);
const userData = await getCurrentUser();
setRole(userData.data.role_id);
};
fetchProgressVideos();
fetchAll();
}, []);
const handleCloseMenu = () => setShowMenu(false);
@@ -132,7 +129,13 @@ export default function Header() {
<Offcanvas.Header >
<Offcanvas.Title className={"w-100 d-flex justify-content-between"}>
<Link className={`${styles.logoLink} justify-content-start`} to="/dashboard">
<img className={styles.logo} src="/src/assets/logo.png" alt="Logo" />
<img
className={styles.logo}
src="/src/assets/logo.png"
alt="Logo"
style={imageSkeletonFadeStyle}
onLoad={onImageSkeletonLoad}
/>
</Link>
<button type="button" className={`${styles.sidebarClose} align-items-center justify-content-center d-xl-none`} onClick={handleCloseMenu}> <LuPanelLeftClose size={24} /> </button>
</Offcanvas.Title>
@@ -150,7 +153,7 @@ export default function Header() {
<li className={`${styles.navItem} text-start`}>
<NavLink className={styles.navLink} href="/workshops"> <LuGraduationCap className="me-2" size={24} />Workshops</NavLink>
</li>
{isAdmin && (
{role === 1 && (
<li className={`${styles.navItem} text-start`}>
<NavLink className={styles.navLink} href="/admin/users"> <LuUsers className="me-2" size={24} />Utilizadores</NavLink>
</li>
@@ -161,10 +164,10 @@ export default function Header() {
</ul>
</nav>
{!isAdmin && videosStats.videos > 0 && (
{role !== 1 && videosCount > 0 && (
<div className="d-flex d-sm-none justify-content-center">
<span className="fw-semibold">Vídeos assistidos</span>
<span className={`${styles.badge} badge align-content-center ms-3`}>{videosStats.videosWatched}/{videosStats.videos}</span>
<span className={`${styles.badge} badge align-content-center ms-3`}>{videosWatched}/{videosCount}</span>
</div>
)}
@@ -177,11 +180,11 @@ export default function Header() {
<nav className="navbar px-3">
<div className={styles.headerRight}>
{!isAdmin && videosStats.videos > 0 && (
{role !== 1 && videosCount > 0 && (
<>
<div className="d-none d-sm-flex align-items-center gap-1 text-muted">
<span className="fw-semibold">Vídeos assistidos</span>
<span className={`${styles.badge} badge align-content-center`}>{videosStats.videosWatched}/{videosStats.videos}</span>
<span className={`${styles.badge} badge align-content-center`}>{videosWatched}/{videosCount}</span>
</div>
</>
)}