// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
/**
* Bouton ♥ « Mon univers Ka » — favoris unifiés du Groupe KA (KA ID).
* · Connecté : bascule le favori au hub via POST /api/favorites/toggle
* (synchrone — le hub groupe-ka.com est le magasin central, rien en local).
* · Déconnecté : le clic envoie au SSO KA ID (retour sur la page courante).
* L'état initial est lu via GET /api/favorites, partagé entre tous les boutons
* de la page (une seule requête, mini-cache module 15 s, invalidé au toggle).
* Utilisable dans un parent (stopPropagation + preventDefault).
*/
"use client";
import { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
import { useLang } from "./LangContext";
export type FavPayload = {
item_id: string;
title: string;
subtitle?: string;
price_label?: string;
image_url?: string;
url?: string;
};
type FavState = { authed: boolean; ids: string[] };
// -- mini-cache module : une requête partagée par page, TTL 15 s --------------
const TTL_MS = 15_000;
let shared: { at: number; p: Promise } | null = null;
function loadFavs(): Promise {
const now = Date.now();
if (shared && now - shared.at < TTL_MS) return shared.p;
const p: Promise = fetch("/api/favorites")
.then(async (r) => {
if (r.status === 401) return { authed: false, ids: [] };
if (!r.ok) throw new Error(String(r.status));
const d = (await r.json()) as { favorites?: { item_id?: string }[] };
return {
authed: true,
ids: (d.favorites ?? []).map((f) => String(f.item_id ?? "")),
};
})
.catch(() => {
shared = null; // erreur transitoire : jamais mise en cache
return { authed: true, ids: [] };
});
shared = { at: now, p };
return p;
}
export default function FavButton({
item,
size = "sm",
className = "",
}: {
item: FavPayload;
size?: "sm" | "lg";
className?: string;
}) {
const { lang } = useLang();
const fr = lang === "fr";
const path = usePathname();
const [authed, setAuthed] = useState(null);
const [fav, setFav] = useState(false);
const [busy, setBusy] = useState(false);
const [flash, setFlash] = useState(false); // échec hub — bref signal visuel
useEffect(() => {
let alive = true;
loadFavs().then((st) => {
if (!alive) return;
setAuthed(st.authed);
setFav(st.ids.includes(item.item_id));
});
return () => {
alive = false;
};
}, [item.item_id]);
const onClick = async (e: React.MouseEvent) => {
// le bouton vit parfois dans un (carte de résultat) : ne pas naviguer
e.preventDefault();
e.stopPropagation();
if (busy) return;
if (authed === false) {
window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(path || "/")}`;
return;
}
setBusy(true);
try {
const r = await fetch("/api/favorites/toggle", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...item, action: fav ? "remove" : "add" }),
});
if (r.status === 401) {
window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(path || "/")}`;
return;
}
if (!r.ok) throw new Error(String(r.status));
const d = (await r.json()) as { favorited?: boolean };
setFav(!!d.favorited);
shared = null; // les autres boutons reliront une liste fraîche
} catch {
setFlash(true);
setTimeout(() => setFlash(false), 1200);
} finally {
setBusy(false);
}
};
const label = fav
? fr
? "Retirer de mes favoris"
: "Remove from my favourites"
: fr
? "Ajouter à mes favoris — Mon univers Ka"
: "Add to my favourites — My Ka universe";
const dims =
size === "lg" ? "h-11 w-11 text-[19px]" : "h-8 w-8 text-[14px]";
return (
);
}