Avnyr cab80e6aef feat: add dashboard, projects, and persons pages with reusable components
Implemented the following:
- `DashboardPage`: displays an overview of stats, recent projects, and tabs for future analytics/reports.
- `ProjectsPage` and `PersonsPage`: include searchable tables, actions, and mobile-friendly card views.
- Integrated reusable components like `AuthLoading`, `DropdownMenu`, `Table`, and `Card`.
2025-05-16 14:43:14 +02:00

268 lines
10 KiB
TypeScript

"use client";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { toast } from "sonner";
export default function SettingsPage() {
const [activeTab, setActiveTab] = useState("profile");
const [isLoading, setIsLoading] = useState(false);
// Mock user data
const user = {
name: "Jean Dupont",
email: "jean.dupont@example.com",
avatar: "",
bio: "Développeur frontend passionné par les interfaces utilisateur et l'expérience utilisateur.",
notifications: {
email: true,
push: false,
projectUpdates: true,
groupChanges: true,
newMembers: false,
},
};
const { register, handleSubmit, formState: { errors } } = useForm({
defaultValues: {
name: user.name,
email: user.email,
bio: user.bio,
},
});
const onSubmitProfile = async (data: any) => {
setIsLoading(true);
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsLoading(false);
toast.success("Profil mis à jour avec succès");
};
const onSubmitNotifications = async (data: any) => {
setIsLoading(true);
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsLoading(false);
toast.success("Préférences de notification mises à jour avec succès");
};
const onExportData = async () => {
setIsLoading(true);
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsLoading(false);
toast.success("Vos données ont été exportées. Vous recevrez un email avec le lien de téléchargement.");
};
const onDeleteAccount = async () => {
setIsLoading(true);
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsLoading(false);
toast.success("Votre compte a été supprimé avec succès.");
};
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Paramètres</h1>
</div>
<Tabs defaultValue="profile" className="space-y-4" onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="profile">Profil</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="privacy">Confidentialité</TabsTrigger>
</TabsList>
<TabsContent value="profile" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Profil</CardTitle>
<CardDescription>
Gérez vos informations personnelles et votre profil.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
<Avatar className="h-16 w-16">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>{user.name.split(" ").map(n => n[0]).join("")}</AvatarFallback>
</Avatar>
<div>
<Button variant="outline" size="sm">
Changer d'avatar
</Button>
</div>
</div>
<Separator />
<form onSubmit={handleSubmit(onSubmitProfile)} className="space-y-4">
<div className="grid gap-2">
<Label htmlFor="name">Nom</Label>
<Input
id="name"
{...register("name", { required: "Le nom est requis" })}
/>
{errors.name && (
<p className="text-sm text-destructive">{errors.name.message}</p>
)}
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
{...register("email", {
required: "L'email est requis",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Adresse email invalide"
}
})}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<div className="grid gap-2">
<Label htmlFor="bio">Bio</Label>
<Textarea
id="bio"
{...register("bio")}
rows={4}
/>
</div>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Enregistrement..." : "Enregistrer les modifications"}
</Button>
</form>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
Configurez vos préférences de notification.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="email-notifications">Notifications par email</Label>
<p className="text-sm text-muted-foreground">
Recevez des notifications par email.
</p>
</div>
<Switch id="email-notifications" defaultChecked={user.notifications.email} />
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="push-notifications">Notifications push</Label>
<p className="text-sm text-muted-foreground">
Recevez des notifications push dans votre navigateur.
</p>
</div>
<Switch id="push-notifications" defaultChecked={user.notifications.push} />
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="project-updates">Mises à jour de projets</Label>
<p className="text-sm text-muted-foreground">
Soyez notifié des mises à jour de vos projets.
</p>
</div>
<Switch id="project-updates" defaultChecked={user.notifications.projectUpdates} />
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="group-changes">Changements de groupes</Label>
<p className="text-sm text-muted-foreground">
Soyez notifié des changements dans vos groupes.
</p>
</div>
<Switch id="group-changes" defaultChecked={user.notifications.groupChanges} />
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="new-members">Nouveaux membres</Label>
<p className="text-sm text-muted-foreground">
Soyez notifié lorsque de nouveaux membres rejoignent vos projets.
</p>
</div>
<Switch id="new-members" defaultChecked={user.notifications.newMembers} />
</div>
</div>
</CardContent>
<CardFooter>
<Button onClick={onSubmitNotifications} disabled={isLoading}>
{isLoading ? "Enregistrement..." : "Enregistrer les préférences"}
</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="privacy" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Confidentialité et données</CardTitle>
<CardDescription>
Gérez vos données personnelles et vos paramètres de confidentialité.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-4">
<div>
<h3 className="text-lg font-medium">Exporter vos données</h3>
<p className="text-sm text-muted-foreground">
Téléchargez une copie de vos données personnelles.
</p>
<Button
variant="outline"
className="mt-2"
onClick={onExportData}
disabled={isLoading}
>
{isLoading ? "Exportation..." : "Exporter mes données"}
</Button>
</div>
<Separator />
<div>
<h3 className="text-lg font-medium text-destructive">Supprimer votre compte</h3>
<p className="text-sm text-muted-foreground">
Supprimez définitivement votre compte et toutes vos données.
</p>
<Button
variant="destructive"
className="mt-2"
onClick={onDeleteAccount}
disabled={isLoading}
>
{isLoading ? "Suppression..." : "Supprimer mon compte"}
</Button>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}