All checks were successful
Deploy to Netlify / Deploy to Netlify (push) Successful in 2m30s
225 lines
7.5 KiB
TypeScript
225 lines
7.5 KiB
TypeScript
import { Moon, Palette, Save, Sun, Undo } from "lucide-react";
|
|
import {
|
|
Card,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
CardContent,
|
|
} from "~/components/ui/card";
|
|
import { Label } from "~/components/ui/label";
|
|
import {
|
|
cn,
|
|
colors,
|
|
DEFAULT_COLOR,
|
|
DEFAULT_EMOJI,
|
|
getColorClass,
|
|
getSubjectColor,
|
|
getSubjectEmoji,
|
|
} from "~/lib/utils";
|
|
import { Badge } from "../ui/badge";
|
|
import { Button } from "../ui/button";
|
|
import { Separator } from "../ui/separator";
|
|
import { updateUserPreferences, useSubjects, type User } from "~/lib/api";
|
|
import EmojiInput from "./emoji-input";
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { useDisplayedTheme } from "../theme-provider";
|
|
|
|
export default function Preferences({ user }: { user: User }) {
|
|
const [preferences, setPreferences] = useState(user.preferences || []);
|
|
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
|
const { theme, setTheme } = useDisplayedTheme();
|
|
const queryClient = useQueryClient();
|
|
|
|
// TODO: Add a loading state and error handling
|
|
const { subjects, isLoading, isError } = useSubjects();
|
|
|
|
const resetPreferences = () => {
|
|
setPreferences(user.preferences || []);
|
|
setUnsavedChanges(false);
|
|
};
|
|
|
|
const setPref = (subjectName: string, key: string, value: string) => {
|
|
const existingPreference = preferences.find((p) => p.name === subjectName);
|
|
const updatedPreferences = existingPreference
|
|
? preferences.map((p) =>
|
|
p.name === subjectName ? { ...p, [key]: value } : p
|
|
)
|
|
: [
|
|
...preferences,
|
|
Object.assign(
|
|
{ name: subjectName, emoji: DEFAULT_EMOJI, color: DEFAULT_COLOR },
|
|
{
|
|
name: subjectName,
|
|
[key]: value,
|
|
}
|
|
),
|
|
];
|
|
if (!unsavedChanges) {
|
|
toast("Vos changements n'ont pas été sauvegardés", {
|
|
description:
|
|
"N'oubliez pas de sauvegarder vos préférences en bas de la page.",
|
|
duration: 3000,
|
|
});
|
|
setUnsavedChanges(true);
|
|
}
|
|
setPreferences(updatedPreferences);
|
|
};
|
|
|
|
function savePreferences() {
|
|
updateUserPreferences(
|
|
// Filter out any incomplete preferences
|
|
preferences.filter((p) => p.name && p.emoji && p.color)
|
|
)
|
|
.then(() => {
|
|
// Invalidate the user query to refresh the data
|
|
queryClient.removeQueries({ queryKey: ["user"] });
|
|
|
|
toast.success("Vos préférences ont été sauvegardé avec succès !");
|
|
setUnsavedChanges(false);
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
"Erreur lors de la sauvegarde de vos préférences :",
|
|
error
|
|
);
|
|
toast.error("Échec de la sauvegarde. Veuillez réessayer.");
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Theme card: darmode light mode */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Moon className="h-5 w-5" />
|
|
Thème
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Personnalisez l'apparence de l'application.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* TODO: Save user theme */}
|
|
{/* Theme toggle button */}
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
className="w-full"
|
|
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
>
|
|
{theme === "dark" ? (
|
|
<>
|
|
<Sun className="mr-2 h-4 w-4" />
|
|
<span>Activer le mode clair</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Moon className="mr-2 h-4 w-4" />
|
|
<span>Activer le mode sombre</span>
|
|
</>
|
|
)}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Subject Customization Section */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Palette className="h-5 w-5" />
|
|
Personnalisation
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Choisissez les couleurs et emojis des matières.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{subjects
|
|
.sort((a, b) => a.localeCompare(b))
|
|
.map((subjectName) => ({
|
|
subjectName,
|
|
subjectEmoji: getSubjectEmoji(subjectName, preferences),
|
|
subjectColor: getSubjectColor(subjectName, preferences),
|
|
}))
|
|
.map(({ subjectName, subjectEmoji, subjectColor }) => (
|
|
<div key={subjectName} className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-base font-medium truncate">
|
|
{subjectName}
|
|
</Label>
|
|
<Badge
|
|
className={cn(
|
|
"px-3 py-1 truncate",
|
|
getColorClass(subjectColor)
|
|
)}
|
|
>
|
|
{subjectEmoji} {subjectName}
|
|
</Badge>
|
|
</div>
|
|
{/* Color selector */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label>Couleur</Label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{colors.map((colorName) => (
|
|
<button
|
|
key={colorName}
|
|
type="button"
|
|
className={cn(
|
|
"w-4 h-4 rounded-full border",
|
|
subjectColor === colorName
|
|
? "ring-2 ring-offset-2 ring-primary"
|
|
: "ring-0",
|
|
getColorClass(colorName)
|
|
)}
|
|
onClick={() =>
|
|
setPref(subjectName, "color", colorName)
|
|
}
|
|
title={colorName}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{/* Emoji selector */}
|
|
<div className="space-y-2">
|
|
<Label>Emoji</Label>
|
|
<div className="relative">
|
|
<EmojiInput
|
|
type="text"
|
|
defaultValue={subjectEmoji}
|
|
onChange={(emoji) =>
|
|
setPref(subjectName, "emoji", emoji)
|
|
}
|
|
className="w-full bg-transparent border border-gray-300 rounded-md px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Separator className="my-4" />
|
|
</div>
|
|
))}
|
|
<Button
|
|
className="w-full"
|
|
onClick={() => savePreferences()}
|
|
disabled={!unsavedChanges}
|
|
>
|
|
<Save className="h-4 w-4 mr-2" />
|
|
Enregistrer vos préférences
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
className="w-full text-white"
|
|
onClick={resetPreferences}
|
|
disabled={!unsavedChanges}
|
|
>
|
|
<Undo className="h-4 w-4 mr-2" />
|
|
Annuler les modifications en cours
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|