SPB Git forge

spb/fabri-ka

Public

Agrégateur de produits québécois — www.fabri-ka.com

217commits 1branches 0releases
66.1 MBsize
maindefault branch
4 h agolast push
Python 38.4% HTML 30.3% TypeScript 17.2% CSS 11% JavaScript 3.2%
4.0 KB · 142 lines tsx
Raw Blame History
1// ---------------------------------------------------------------------------2// Fabri-Ka — favoris « Mon univers Ka » (magasin central du hub Groupe KA)3// Aucun stockage local : l'état vient du hub (GET /api/favorites) et chaque ♥4// y est poussé (POST /api/favorites/toggle), avec mise à jour optimiste et5// retour arrière si le hub refuse. Non connecté -> le clic ♥ envoie vers la6// connexion KA ID puis ramène sur la page en cours.7// ---------------------------------------------------------------------------8import {9  createContext,10  useCallback,11  useContext,12  useEffect,13  useState,14  type MouseEvent,15  type ReactNode,16} from 'react'17import { useLocation } from 'react-router-dom'18import {19  fetchFavorites,20  productFavItem,21  toggleFavorite,22  type FavoriteItem,23  type Product,24} from './api'25import { IconHeart } from './components/Icons'2627interface FavoritesState {28  /** premier chargement effectué (connecté ou non) */29  ready: boolean30  /** session KA ID active (sinon, toggle => redirection connexion) */31  loggedIn: boolean32  /** item_id (uid produit) des favoris, tels que lus au hub */33  ids: ReadonlySet<string>34  toggle: (item: FavoriteItem) => void35}3637const FavoritesContext = createContext<FavoritesState>({38  ready: false,39  loggedIn: false,40  ids: new Set(),41  toggle: () => {},42})4344export function useFavorites(): FavoritesState {45  return useContext(FavoritesContext)46}4748export function FavoritesProvider({ children }: { children: ReactNode }) {49  const location = useLocation()50  const [ready, setReady] = useState(false)51  const [loggedIn, setLoggedIn] = useState(false)52  const [ids, setIds] = useState<Set<string>>(new Set())5354  // Un seul chargement au montage : après la connexion SSO, la page se55  // recharge entièrement, donc l'état revient toujours frais du hub.56  useEffect(() => {57    const controller = new AbortController()58    fetchFavorites(controller.signal)59      .then((r) => {60        if (r === null) {61          setLoggedIn(false)62          setIds(new Set())63        } else {64          setLoggedIn(true)65          setIds(new Set(r.ids))66        }67        setReady(true)68      })69      .catch(() => {})70    return () => controller.abort()71  }, [])7273  const toggle = useCallback(74    (item: FavoriteItem) => {75      if (!loggedIn) {76        const next = location.pathname + location.search77        window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(next)}`78        return79      }80      const on = !ids.has(item.item_id)81      const optimistic = new Set(ids)82      if (on) optimistic.add(item.item_id)83      else optimistic.delete(item.item_id)84      setIds(optimistic)85      toggleFavorite(on, item)86        .then((ok) => {87          if (ok) return88          // le hub n'a pas pris le ♥ : on rétablit l'état précédent89          setIds((cur) => {90            const revert = new Set(cur)91            if (on) revert.delete(item.item_id)92            else revert.add(item.item_id)93            return revert94          })95        })96        .catch(() => {})97    },98    [ids, loggedIn, location.pathname, location.search]99  )100101  return (102    <FavoritesContext.Provider value={{ ready, loggedIn, ids, toggle }}>103      {children}104    </FavoritesContext.Provider>105  )106}107108/** Bouton ♥ d'un produit (cartes et fiche) — état lu/poussé au hub KA. */109export function FavButton({110  product,111  size = 17,112  className,113}: {114  product: Product115  size?: number116  className?: string117}) {118  const { ids, toggle } = useFavorites()119  const on = ids.has(product.uid)120  const label = on ? 'Retirer des favoris' : 'Ajouter aux favoris'121122  function onClick(e: MouseEvent<HTMLButtonElement>) {123    // les cartes produit sont des <Link> : le ♥ ne doit pas naviguer124    e.preventDefault()125    e.stopPropagation()126    toggle(productFavItem(product))127  }128129  return (130    <button131      type="button"132      className={`fav-btn${on ? ' fav-btn-on' : ''}${className ? ` ${className}` : ''}`}133      onClick={onClick}134      aria-pressed={on}135      aria-label={label}136      title={label}137    >138      <IconHeart size={size} filled={on} />139    </button>140  )141}142