TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1"use client";2// Favoris et comparateur sans compte : stockés localement (localStorage), supprimables par l'utilisateur. Aucune donnée n'est envoyée au serveur.3import { useCallback, useEffect, useState } from "react";45const KEY_FAV = "toveli:favorites:v1";6const KEY_CMP = "toveli:compare:v1";7const KEY_SEARCH = "toveli:searches:v1";89function read<T>(k: string, fallback: T): T { if (typeof window === "undefined") return fallback; try { const v = localStorage.getItem(k); return v ? (JSON.parse(v) as T) : fallback; } catch { return fallback; } }10function write(k: string, v: unknown) { try { localStorage.setItem(k, JSON.stringify(v)); window.dispatchEvent(new Event("toveli:storage")); } catch { /* quota */ } }1112function useLocalList(key: string, max: number) {13 const [items, setItems] = useState<string[]>([]);14 useEffect(() => { const load = () => setItems(read<string[]>(key, [])); load(); window.addEventListener("toveli:storage", load); window.addEventListener("storage", load); return () => { window.removeEventListener("toveli:storage", load); window.removeEventListener("storage", load); }; }, [key]);15 const toggle = useCallback((uid: string) => { const cur = read<string[]>(key, []); const next = cur.includes(uid) ? cur.filter((x) => x !== uid) : [uid, ...cur].slice(0, max); write(key, next); setItems(next); }, [key, max]);16 const has = useCallback((uid: string) => items.includes(uid), [items]);17 const clear = useCallback(() => { write(key, []); setItems([]); }, [key]);18 return { items, toggle, has, clear };19}20/** Favoris : locaux par défaut ; si l'utilisateur est connecté, synchronisés avec le serveur (les favoris locaux sont poussés une fois, puis le serveur fait foi). */21export function useFavorites() {22 const local = useLocalList(KEY_FAV, 200);23 const [server, setServer] = useState<string[] | null>(null);24 useEffect(() => {25 let alive = true;26 fetch("/api/me").then((r) => r.json()).then(async (d) => {27 if (!alive || !d?.user) return;28 const cur = read<string[]>(KEY_FAV, []);29 if (cur.length) { await fetch("/api/me/favorites", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ uids: cur }) }).catch(() => {}); write(KEY_FAV, []); }30 const r = await fetch("/api/me/favorites").then((r) => r.json()).catch(() => null);31 if (alive && r?.items) setServer(r.items.map((i: { uid: string }) => i.uid));32 }).catch(() => {});33 return () => { alive = false; };34 }, []);35 if (server === null) return local;36 return {37 items: server,38 has: (uid: string) => server.includes(uid),39 toggle: (uid: string) => { const next = server.includes(uid) ? server.filter((x) => x !== uid) : [uid, ...server]; setServer(next); fetch("/api/me/favorites", { method: server.includes(uid) ? "DELETE" : "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ uids: [uid] }) }).catch(() => {}); },40 clear: () => { fetch("/api/me/favorites", { method: "DELETE", headers: { "content-type": "application/json" }, body: JSON.stringify({ uids: server }) }).catch(() => {}); setServer([]); },41 };42}43export function useCompare() { return useLocalList(KEY_CMP, 4); }4445export interface SavedSearch { id: string; label: string; href: string; createdAt: string; }46export function useSavedSearches() {47 const [items, setItems] = useState<SavedSearch[]>([]);48 useEffect(() => { const load = () => setItems(read<SavedSearch[]>(KEY_SEARCH, [])); load(); window.addEventListener("toveli:storage", load); return () => window.removeEventListener("toveli:storage", load); }, []);49 const save = useCallback((label: string, href: string) => { const cur = read<SavedSearch[]>(KEY_SEARCH, []); if (cur.some((s) => s.href === href)) return; const next = [{ id: Math.random().toString(36).slice(2), label, href, createdAt: new Date().toISOString() }, ...cur].slice(0, 50); write(KEY_SEARCH, next); setItems(next); }, []);50 const remove = useCallback((id: string) => { const next = read<SavedSearch[]>(KEY_SEARCH, []).filter((s) => s.id !== id); write(KEY_SEARCH, next); setItems(next); }, []);51 const clearAll = useCallback(() => { [KEY_FAV, KEY_CMP, KEY_SEARCH].forEach((k) => localStorage.removeItem(k)); window.dispatchEvent(new Event("toveli:storage")); }, []);52 return { items, save, remove, clearAll };53}54