UI & Feature update - Alpha #9

Merged
Mathis merged 22 commits from dev into prod 2026-01-14 22:40:06 +01:00
9 changed files with 115 additions and 60 deletions
Showing only changes of commit 02d70f27ea - Show all commits

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
@@ -11,7 +12,6 @@ import {
} from "@/components/ui/table";
import { CategoryService } from "@/services/category.service";
import type { Category } from "@/types/content";
import { Skeleton } from "@/components/ui/skeleton";
export default function AdminCategoriesPage() {
const [categories, setCategories] = useState<Category[]>([]);
@@ -20,14 +20,16 @@ export default function AdminCategoriesPage() {
useEffect(() => {
CategoryService.getAll()
.then(setCategories)
.catch(err => console.error(err))
.catch((err) => console.error(err))
.finally(() => setLoading(false));
}, []);
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">Catégories ({categories.length})</h2>
<h2 className="text-3xl font-bold tracking-tight">
Catégories ({categories.length})
</h2>
</div>
<div className="rounded-md border bg-card">
<Table>
@@ -41,10 +43,17 @@ export default function AdminCategoriesPage() {
<TableBody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
<TableRow key={i}>
<TableCell><Skeleton className="h-4 w-[150px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[150px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[250px]" /></TableCell>
<TableCell>
<Skeleton className="h-4 w-[150px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[150px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[250px]" />
</TableCell>
</TableRow>
))
) : categories.length === 0 ? (
@@ -56,7 +65,9 @@ export default function AdminCategoriesPage() {
) : (
categories.map((category) => (
<TableRow key={category.id}>
<TableCell className="font-medium whitespace-nowrap">{category.name}</TableCell>
<TableCell className="font-medium whitespace-nowrap">
{category.name}
</TableCell>
<TableCell className="whitespace-nowrap">{category.slug}</TableCell>
<TableCell className="text-muted-foreground">
{category.description || "Aucune description"}

View File

@@ -1,6 +1,12 @@
"use client";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Download, Eye, Image as ImageIcon, Trash2, Video } from "lucide-react";
import { useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
@@ -11,12 +17,6 @@ import {
} from "@/components/ui/table";
import { ContentService } from "@/services/content.service";
import type { Content } from "@/types/content";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Skeleton } from "@/components/ui/skeleton";
import { Eye, Download, Image as ImageIcon, Video, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
export default function AdminContentsPage() {
const [contents, setContents] = useState<Content[]>([]);
@@ -27,9 +27,9 @@ export default function AdminContentsPage() {
ContentService.getExplore({ limit: 20 })
.then((res) => {
setContents(res.data);
setTotalCount(res.total);
setTotalCount(res.totalCount);
})
.catch(err => console.error(err))
.catch((err) => console.error(err))
.finally(() => setLoading(false));
}, []);
@@ -38,8 +38,8 @@ export default function AdminContentsPage() {
try {
await ContentService.removeAdmin(id);
setContents(contents.filter(c => c.id !== id));
setTotalCount(prev => prev - 1);
setContents(contents.filter((c) => c.id !== id));
setTotalCount((prev) => prev - 1);
} catch (error) {
console.error(error);
}
@@ -48,7 +48,9 @@ export default function AdminContentsPage() {
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">Contenus ({totalCount})</h2>
<h2 className="text-3xl font-bold tracking-tight">
Contenus ({totalCount})
</h2>
</div>
<div className="rounded-md border bg-card">
<Table>
@@ -65,12 +67,23 @@ export default function AdminContentsPage() {
<TableBody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
<TableRow key={i}>
<TableCell><Skeleton className="h-10 w-[200px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[100px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[100px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[80px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[100px]" /></TableCell>
<TableCell>
<Skeleton className="h-10 w-[200px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[80px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
</TableRow>
))
) : contents.length === 0 ? (
@@ -93,16 +106,18 @@ export default function AdminContentsPage() {
</div>
<div>
<div className="font-semibold">{content.title}</div>
<div className="text-xs text-muted-foreground">{content.type} {content.mimeType}</div>
<div className="text-xs text-muted-foreground">
{content.type} {content.mimeType}
</div>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline">{content.category.name}</Badge>
</TableCell>
<TableCell>
@{content.author.username}
<Badge variant="outline">
{content.category?.name || "Sans catégorie"}
</Badge>
</TableCell>
<TableCell>@{content.author.username}</TableCell>
<TableCell>
<div className="flex flex-col gap-1 text-xs">
<div className="flex items-center gap-1">

View File

@@ -1,11 +1,11 @@
"use client";
import { AlertCircle, FileText, LayoutGrid, Users } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { adminService, type AdminStats } from "@/services/admin.service";
import { Users, FileText, LayoutGrid, AlertCircle } from "lucide-react";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";
import { type AdminStats, adminService } from "@/services/admin.service";
export default function AdminDashboardPage() {
const [stats, setStats] = useState<AdminStats | null>(null);

View File

@@ -1,6 +1,12 @@
"use client";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
TableBody,
@@ -11,12 +17,6 @@ import {
} from "@/components/ui/table";
import { UserService } from "@/services/user.service";
import type { User } from "@/types/user";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { fr } from "date-fns/locale";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { Trash2 } from "lucide-react";
export default function AdminUsersPage() {
const [users, setUsers] = useState<User[]>([]);
@@ -29,19 +29,24 @@ export default function AdminUsersPage() {
setUsers(res.data);
setTotalCount(res.totalCount);
})
.catch(err => {
.catch((err) => {
console.error(err);
})
.finally(() => setLoading(false));
}, []);
const handleDelete = async (uuid: string) => {
if (!confirm("Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.")) return;
if (
!confirm(
"Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.",
)
)
return;
try {
await UserService.removeUserAdmin(uuid);
setUsers(users.filter(u => u.uuid !== uuid));
setTotalCount(prev => prev - 1);
setUsers(users.filter((u) => u.uuid !== uuid));
setTotalCount((prev) => prev - 1);
} catch (error) {
console.error(error);
}
@@ -50,7 +55,9 @@ export default function AdminUsersPage() {
return (
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">Utilisateurs ({totalCount})</h2>
<h2 className="text-3xl font-bold tracking-tight">
Utilisateurs ({totalCount})
</h2>
</div>
<div className="rounded-md border bg-card">
<Table>
@@ -67,12 +74,23 @@ export default function AdminUsersPage() {
<TableBody>
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
/* biome-ignore lint/suspicious/noArrayIndexKey: skeleton items don't have unique IDs */
<TableRow key={i}>
<TableCell><Skeleton className="h-4 w-[150px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[200px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[50px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[80px]" /></TableCell>
<TableCell><Skeleton className="h-4 w-[100px]" /></TableCell>
<TableCell>
<Skeleton className="h-4 w-[150px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[200px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[50px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[80px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
</TableRow>
))
) : users.length === 0 ? (

View File

@@ -1,10 +1,10 @@
import { HelpCircle } from "lucide-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { HelpCircle } from "lucide-react";
export default function HelpPage() {
const faqs = [
@@ -48,10 +48,8 @@ export default function HelpPage() {
<h2 className="text-xl font-semibold mb-6">Foire Aux Questions</h2>
<Accordion type="single" collapsible className="w-full">
{faqs.map((faq, index) => (
<AccordionItem key={index} value={`item-${index}`}>
<AccordionTrigger className="text-left">
{faq.question}
</AccordionTrigger>
<AccordionItem key={faq.question} value={`item-${index}`}>
<AccordionTrigger className="text-left">{faq.question}</AccordionTrigger>
<AccordionContent className="text-muted-foreground leading-relaxed">
{faq.answer}
</AccordionContent>

View File

@@ -4,6 +4,7 @@ import { Calendar, Camera, LogIn, LogOut, Settings } from "lucide-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import * as React from "react";
import { toast } from "sonner";
import { ContentList } from "@/components/content-list";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
@@ -20,7 +21,6 @@ import { useAuth } from "@/providers/auth-provider";
import { ContentService } from "@/services/content.service";
import { FavoriteService } from "@/services/favorite.service";
import { UserService } from "@/services/user.service";
import { toast } from "sonner";
export default function ProfilePage() {
const { user, isAuthenticated, isLoading, logout, refreshUser } = useAuth();
@@ -32,7 +32,9 @@ export default function ProfilePage() {
fileInputRef.current?.click();
};
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const file = event.target.files?.[0];
if (!file) return;

View File

@@ -9,7 +9,11 @@ export const metadata: Metadata = {
export default function RecentPage() {
return (
<React.Suspense fallback={<div className="p-8 text-center">Chargement des nouveautés...</div>}>
<React.Suspense
fallback={
<div className="p-8 text-center">Chargement des nouveautés...</div>
}
>
<HomeContent defaultSort="recent" />
</React.Suspense>
);

View File

@@ -24,8 +24,8 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/providers/auth-provider";
import { UserService } from "@/services/user.service";
@@ -52,7 +52,7 @@ export default function SettingsPage() {
if (user) {
form.reset({
displayName: user.displayName || "",
bio: (user as any).bio || "",
bio: user.bio || "",
});
}
}, [user, form]);
@@ -107,7 +107,8 @@ export default function SettingsPage() {
<CardHeader>
<CardTitle>Informations personnelles</CardTitle>
<CardDescription>
Mettez à jour vos informations publiques. Ces données seront visibles par les autres utilisateurs.
Mettez à jour vos informations publiques. Ces données seront visibles par
les autres utilisateurs.
</CardDescription>
</CardHeader>
<CardContent>
@@ -117,7 +118,11 @@ export default function SettingsPage() {
<FormItem>
<FormLabel>Nom d'utilisateur</FormLabel>
<FormControl>
<Input value={user.username} disabled className="bg-zinc-50 dark:bg-zinc-900" />
<Input
value={user.username}
disabled
className="bg-zinc-50 dark:bg-zinc-900"
/>
</FormControl>
<FormDescription>
Le nom d'utilisateur ne peut pas être modifié.

View File

@@ -9,7 +9,9 @@ export const metadata: Metadata = {
export default function TrendsPage() {
return (
<React.Suspense fallback={<div className="p-8 text-center">Chargement des tendances...</div>}>
<React.Suspense
fallback={<div className="p-8 text-center">Chargement des tendances...</div>}
>
<HomeContent defaultSort="trend" />
</React.Suspense>
);