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`.
This commit is contained in:
10
frontend/app/settings/layout.tsx
Normal file
10
frontend/app/settings/layout.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DashboardLayout } from "@/components/dashboard-layout";
|
||||
import { AuthLoading } from "@/components/auth-loading";
|
||||
|
||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<AuthLoading>
|
||||
<DashboardLayout>{children}</DashboardLayout>
|
||||
</AuthLoading>
|
||||
);
|
||||
}
|
||||
268
frontend/app/settings/page.tsx
Normal file
268
frontend/app/settings/page.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user