Introduce `getPublicUrl` in `S3Service` for generating public URLs. Replace custom file URL generation logic across services with the new method. Add media controller for file streaming and update related tests. Adjust frontend to display user roles instead of email in the sidebar. Update environment schema to include optional `API_URL`. Fix help page contact email.
302 lines
8.5 KiB
TypeScript
302 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ChevronRight,
|
|
Clock,
|
|
Heart,
|
|
HelpCircle,
|
|
History,
|
|
Home,
|
|
LayoutGrid,
|
|
LogIn,
|
|
LogOut,
|
|
PlusCircle,
|
|
Settings,
|
|
ShieldCheck,
|
|
TrendingUp,
|
|
User as UserIcon,
|
|
} from "lucide-react";
|
|
import Link from "next/link";
|
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
import * as React from "react";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarGroupLabel,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuSubItem,
|
|
} from "@/components/ui/sidebar";
|
|
import { useAuth } from "@/providers/auth-provider";
|
|
import { CategoryService } from "@/services/category.service";
|
|
import type { Category } from "@/types/content";
|
|
|
|
const mainNav = [
|
|
{
|
|
title: "Accueil",
|
|
url: "/",
|
|
icon: Home,
|
|
},
|
|
{
|
|
title: "Tendances",
|
|
url: "/trends",
|
|
icon: TrendingUp,
|
|
},
|
|
{
|
|
title: "Nouveautés",
|
|
url: "/recent",
|
|
icon: Clock,
|
|
},
|
|
];
|
|
|
|
export function AppSidebar() {
|
|
const pathname = usePathname();
|
|
const searchParams = useSearchParams();
|
|
const { user, logout, isAuthenticated } = useAuth();
|
|
const [categories, setCategories] = React.useState<Category[]>([]);
|
|
|
|
React.useEffect(() => {
|
|
CategoryService.getAll().then(setCategories).catch(console.error);
|
|
}, []);
|
|
|
|
return (
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader className="flex items-center justify-center py-4">
|
|
<Link href="/" className="flex items-center gap-2 font-bold text-xl">
|
|
<div className="bg-primary text-primary-foreground p-1 rounded">🐐</div>
|
|
<span className="group-data-[collapsible=icon]:hidden">MemeGoat</span>
|
|
</Link>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<SidebarGroup>
|
|
<SidebarMenu>
|
|
{mainNav.map((item) => (
|
|
<SidebarMenuItem key={item.title}>
|
|
<SidebarMenuButton
|
|
asChild
|
|
isActive={pathname === item.url}
|
|
tooltip={item.title}
|
|
>
|
|
<Link href={item.url}>
|
|
<item.icon />
|
|
<span>{item.title}</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
))}
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Explorer</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<Collapsible asChild className="group/collapsible">
|
|
<SidebarMenuItem>
|
|
<CollapsibleTrigger asChild>
|
|
<SidebarMenuButton tooltip="Catégories">
|
|
<LayoutGrid />
|
|
<span>Catégories</span>
|
|
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
</SidebarMenuButton>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub>
|
|
{categories.map((category) => (
|
|
<SidebarMenuSubItem key={category.id}>
|
|
<SidebarMenuSubButton
|
|
asChild
|
|
isActive={pathname === `/category/${category.slug}`}
|
|
>
|
|
<Link href={`/category/${category.slug}`}>
|
|
<span>{category.name}</span>
|
|
</Link>
|
|
</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
))}
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
</SidebarMenuItem>
|
|
</Collapsible>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Communauté</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild tooltip="Publier">
|
|
<Link href="/upload">
|
|
<PlusCircle />
|
|
<span>Publier</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Ma Bibliothèque</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
asChild
|
|
isActive={
|
|
pathname === "/profile" && searchParams.get("tab") === "favorites"
|
|
}
|
|
tooltip="Mes Favoris"
|
|
>
|
|
<Link href="/profile?tab=favorites">
|
|
<Heart />
|
|
<span>Mes Favoris</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
asChild
|
|
isActive={
|
|
pathname === "/profile" && searchParams.get("tab") === "memes"
|
|
}
|
|
tooltip="Mes Mèmes"
|
|
>
|
|
<Link href="/profile?tab=memes">
|
|
<History />
|
|
<span>Mes Mèmes</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
|
|
{isAuthenticated && user?.role === "admin" && (
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Administration</SidebarGroupLabel>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
asChild
|
|
isActive={pathname.startsWith("/admin")}
|
|
tooltip="Dashboard Admin"
|
|
>
|
|
<Link href="/admin">
|
|
<ShieldCheck />
|
|
<span>Admin</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
)}
|
|
</SidebarContent>
|
|
<SidebarFooter>
|
|
<SidebarMenu>
|
|
{isAuthenticated && user ? (
|
|
<SidebarMenuItem>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<SidebarMenuButton
|
|
size="lg"
|
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
>
|
|
<Avatar className="h-8 w-8 rounded-lg">
|
|
<AvatarImage src={user.avatarUrl} alt={user.username} />
|
|
<AvatarFallback className="rounded-lg">
|
|
{user.username.slice(0, 2).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
|
|
<span className="truncate font-semibold">
|
|
{user.displayName || user.username}
|
|
</span>
|
|
<span className="truncate text-xs">{user.role}</span>
|
|
</div>
|
|
<ChevronRight className="ml-auto size-4 group-data-[collapsible=icon]:hidden" />
|
|
</SidebarMenuButton>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
side="right"
|
|
align="end"
|
|
sideOffset={4}
|
|
>
|
|
<DropdownMenuLabel className="p-0 font-normal">
|
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
<Avatar className="h-8 w-8 rounded-lg">
|
|
<AvatarImage src={user.avatarUrl} alt={user.username} />
|
|
<AvatarFallback className="rounded-lg">
|
|
{user.username.slice(0, 2).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
<span className="truncate font-semibold">
|
|
{user.displayName || user.username}
|
|
</span>
|
|
<span className="truncate text-xs">{user.role}</span>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/profile" className="flex items-center gap-2">
|
|
<UserIcon className="size-4" />
|
|
<span>Profil</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/settings" className="flex items-center gap-2">
|
|
<Settings className="size-4" />
|
|
<span>Paramètres</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => logout()}>
|
|
<LogOut className="size-4 mr-2" />
|
|
<span>Déconnexion</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</SidebarMenuItem>
|
|
) : (
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild tooltip="Se connecter">
|
|
<Link href="/login">
|
|
<LogIn className="size-4" />
|
|
<span>Se connecter</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
)}
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild tooltip="Aide">
|
|
<Link href="/help">
|
|
<HelpCircle />
|
|
<span>Aide</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
);
|
|
}
|