// ============================================================================== // Author: Simon-Pierre Boucher // File: favorites.tsx // Desc: Favoris ♥ « Mon univers Ka » — le hub Groupe KA (groupe-ka.com) est // le magasin central des favoris du groupe (aucun stockage local ni // navigateur) : lecture/écriture via l'API locale /api/favorites qui // relaie au hub. FavProvider charge la liste une fois ; FavButton = // cœur sur les cartes de restos et la fiche détail. Non connecté → // clic = « Se connecter avec KA ID ». Calqué sur foodka/favorites.tsx. // ============================================================================== import { ReactNode, createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { FavItem, Restaurant, fmtPrice, toggleFavorite } from "./api"; interface FavState { ready: boolean; // premier chargement terminé connected: boolean; // session KA ID active ids: Set; // uid des restos en favoris items: FavItem[]; toggle: (item: FavItem) => void; } const FavContext = createContext({ ready: false, connected: false, ids: new Set(), items: [], toggle: () => {}, }); /** Restaurant -> item de favori du hub (nom, ville, prix médian, image, url). */ export function restoFavItem(r: Restaurant): FavItem { const ms = r.menu_summary; return { item_id: r.uid, title: r.name || "Restaurant", subtitle: [r.city, r.region].filter(Boolean).join(" · ") || r.address || "", price_label: ms?.price_median != null ? `${fmtPrice(ms.price_median)} · plat médian` : (r.price_range || ""), image_url: ms?.image || (r.images ?? [])[1] || (r.images ?? [])[0] || "", url: `https://www.resto-ka.com/resto/${encodeURIComponent(r.uid)}`, }; } export function FavProvider({ children }: { children: ReactNode }) { const [ready, setReady] = useState(false); const [connected, setConnected] = useState(false); const [items, setItems] = useState([]); useEffect(() => { fetch("/api/favorites", { credentials: "same-origin" }) .then(async (res) => { if (res.status === 401) return; // pas connecté : cœurs vides if (!res.ok) throw new Error(`API ${res.status}`); const data = (await res.json()) as { items?: FavItem[] }; setConnected(true); setItems(data.items ?? []); }) .catch(() => {}) // hub muet : cœurs vides .finally(() => setReady(true)); }, []); const toggle = useCallback((item: FavItem) => { if (!connected) { // non connecté : passage par la connexion KA ID (hub Groupe KA) window.location.assign("/api/auth/ka/login"); return; } const prev = items; const on = !prev.some((i) => i.item_id === item.item_id); // optimiste : le hub confirme (sinon on rétablit) setItems(on ? [...prev, item] : prev.filter((i) => i.item_id !== item.item_id)); toggleFavorite(on, item).catch(() => setItems(prev)); }, [connected, items]); const ids = useMemo( () => new Set(items.map((i) => i.item_id)), [items]); return ( {children} ); } export const useFav = () => useContext(FavContext); /** Cœur ♥ — cartes de resto (par défaut) et fiche détail (`big`). */ export function FavButton({ r, big = false }: { r: Restaurant; big?: boolean }) { const { connected, ids, toggle } = useFav(); const on = ids.has(r.uid); const label = !connected ? "Se connecter avec KA ID pour ajouter aux favoris" : on ? "Retirer de mes favoris (Mon univers Ka)" : "Ajouter à mes favoris (Mon univers Ka)"; return ( ); }