From ff6fc1c6b3b36cc6dd965add0213154a1408e7ae Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Wed, 14 Jan 2026 20:50:38 +0100 Subject: [PATCH] feat(ui): enhance mobile user experience and authentication handling Add `UserNavMobile` component for improved mobile navigation. Update dashboard and profile pages to include authentication checks with loading states and login prompts. Introduce category-specific content tabs on the profile page. Apply sidebar enhancements, including new sections for user favorites and memes. --- frontend/src/app/(dashboard)/layout.tsx | 44 ++++++++++------- frontend/src/app/(dashboard)/profile/page.tsx | 49 ++++++++++++++++--- frontend/src/app/(dashboard)/upload/page.tsx | 46 +++++++++++++++-- frontend/src/components/app-sidebar.tsx | 35 ++++++++++++- frontend/src/components/user-nav-mobile.tsx | 39 +++++++++++++++ 5 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 frontend/src/components/user-nav-mobile.tsx diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx index 181c20b..89e2f02 100644 --- a/frontend/src/app/(dashboard)/layout.tsx +++ b/frontend/src/app/(dashboard)/layout.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { AppSidebar } from "@/components/app-sidebar"; import { MobileFilters } from "@/components/mobile-filters"; import { SearchSidebar } from "@/components/search-sidebar"; +import { UserNavMobile } from "@/components/user-nav-mobile"; import { SidebarInset, SidebarProvider, @@ -16,26 +17,31 @@ export default function DashboardLayout({ modal: React.ReactNode; }) { return ( - - - -
-
- -
-
-
- {children} - {modal} -
+ + + + +
+
+ +
+ MemeGoat +
+ +
+
+ {children} + {modal} +
+ + + +
- + -
- - - -
-
+ + + ); } diff --git a/frontend/src/app/(dashboard)/profile/page.tsx b/frontend/src/app/(dashboard)/profile/page.tsx index cc02784..d1c1746 100644 --- a/frontend/src/app/(dashboard)/profile/page.tsx +++ b/frontend/src/app/(dashboard)/profile/page.tsx @@ -1,19 +1,23 @@ "use client"; -import { Calendar, LogOut, Settings } from "lucide-react"; +import { Calendar, LogIn, LogOut, Settings } from "lucide-react"; import Link from "next/link"; -import { redirect } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import * as React from "react"; import { ContentList } from "@/components/content-list"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useAuth } from "@/providers/auth-provider"; import { ContentService } from "@/services/content.service"; import { FavoriteService } from "@/services/favorite.service"; +import { Spinner } from "@/components/ui/spinner"; export default function ProfilePage() { const { user, isAuthenticated, isLoading, logout } = useAuth(); + const searchParams = useSearchParams(); + const tab = searchParams.get("tab") || "memes"; const fetchMyMemes = React.useCallback( (params: { limit: number; offset: number }) => @@ -26,9 +30,36 @@ export default function ProfilePage() { [], ); - if (isLoading) return null; + if (isLoading) { + return ( +
+ +
+ ); + } + if (!isAuthenticated || !user) { - redirect("/login"); + return ( +
+ + +
+ +
+ Profil inaccessible + + Vous devez être connecté pour voir votre profil, vos mèmes et vos + favoris. + +
+ + + +
+
+ ); } return ( @@ -79,10 +110,14 @@ export default function ProfilePage() { - + - Mes Mèmes - Mes Favoris + + Mes Mèmes + + + Mes Favoris + diff --git a/frontend/src/app/(dashboard)/upload/page.tsx b/frontend/src/app/(dashboard)/upload/page.tsx index 2567fbf..2049f8f 100644 --- a/frontend/src/app/(dashboard)/upload/page.tsx +++ b/frontend/src/app/(dashboard)/upload/page.tsx @@ -1,15 +1,16 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Image as ImageIcon, Loader2, Upload, X } from "lucide-react"; +import { Image as ImageIcon, Loader2, LogIn, Upload, X } from "lucide-react"; import NextImage from "next/image"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import * as React from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import * as z from "zod"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, @@ -27,8 +28,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { useAuth } from "@/providers/auth-provider"; import { CategoryService } from "@/services/category.service"; import { ContentService } from "@/services/content.service"; +import { Spinner } from "@/components/ui/spinner"; import type { Category } from "@/types/content"; const uploadSchema = z.object({ @@ -42,6 +45,7 @@ type UploadFormValues = z.infer; export default function UploadPage() { const router = useRouter(); + const { isAuthenticated, isLoading } = useAuth(); const [categories, setCategories] = React.useState([]); const [file, setFile] = React.useState(null); const [preview, setPreview] = React.useState(null); @@ -57,8 +61,42 @@ export default function UploadPage() { }); React.useEffect(() => { - CategoryService.getAll().then(setCategories).catch(console.error); - }, []); + if (isAuthenticated) { + CategoryService.getAll().then(setCategories).catch(console.error); + } + }, [isAuthenticated]); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!isAuthenticated) { + return ( +
+ + +
+ +
+ Connexion requise + + Vous devez être connecté pour partager vos meilleurs mèmes avec la + communauté. + +
+ + + +
+
+ ); + } const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0]; diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx index 13e7536..49905bf 100644 --- a/frontend/src/components/app-sidebar.tsx +++ b/frontend/src/components/app-sidebar.tsx @@ -3,7 +3,9 @@ import { ChevronRight, Clock, + Heart, HelpCircle, + History, Home, LayoutGrid, LogIn, @@ -14,7 +16,7 @@ import { User as UserIcon, } from "lucide-react"; import Link from "next/link"; -import { usePathname } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import * as React from "react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { @@ -68,6 +70,7 @@ const mainNav = [ export function AppSidebar() { const pathname = usePathname(); + const searchParams = useSearchParams(); const { user, logout, isAuthenticated } = useAuth(); const [categories, setCategories] = React.useState([]); @@ -149,6 +152,36 @@ export function AppSidebar() { + + + Ma Bibliothèque + + + + + + Mes Favoris + + + + + + + + Mes Mèmes + + + + + diff --git a/frontend/src/components/user-nav-mobile.tsx b/frontend/src/components/user-nav-mobile.tsx new file mode 100644 index 0000000..c70387f --- /dev/null +++ b/frontend/src/components/user-nav-mobile.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { LogIn, User as UserIcon } from "lucide-react"; +import Link from "next/link"; +import * as React from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { useAuth } from "@/providers/auth-provider"; + +export function UserNavMobile() { + const { user, isAuthenticated, isLoading } = useAuth(); + + if (isLoading) { + return
; + } + + if (!isAuthenticated || !user) { + return ( + + ); + } + + return ( + + ); +}