UI & Feature update - Alpha #9
@@ -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"}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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é.
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user