SPB Git

spb/fabri-ka

Public

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

8.8 KB · 260 lines tsx
1import { useEffect, useState } from 'react'2import { Link, NavLink, Route, Routes, useLocation, useNavigate } from 'react-router-dom'3import { fetchMe, logout, type Me } from './api'4import BottomNav from './components/BottomNav'5import SearchBar from './components/SearchBar'6import { FavoritesProvider, useFavorites } from './favorites'7import { productFavItem } from './api'8import { Ico, IcoHeart } from './components/KaIcons'9import { useCurrentListing } from './fiche/current'10import GroupeKaBadge from './ka/GroupeKaBadge'11import KaFooter from './ka/KaFooter'12import About from './pages/About'13import Catalog from './pages/Catalog'14import Contact from './pages/Contact'15import Home from './pages/Home'16import Inscrire from './pages/Inscrire'17import NotFound from './pages/NotFound'18import ProductDetail from './pages/ProductDetail'19import Profile from './pages/Profile'20import Stats from './pages/Stats'21import StoreDetail from './pages/StoreDetail'22import Stores from './pages/Stores'2324const HEADER_SEARCH_ID = 'header-search-input'2526/** Bouton « Se connecter avec KA » (KA ID — SSO Groupe KA) ou avatar + menu. */27function AccountMenu() {28  const location = useLocation()29  const navigate = useNavigate()30  const [enabled, setEnabled] = useState(false)31  const [me, setMe] = useState<Me | null>(null)32  const [open, setOpen] = useState(false)3334  useEffect(() => {35    fetchMe()36      .then((r) => {37        setEnabled(r.enabled)38        setMe(r.user)39      })40      .catch(() => {})41  }, [location.pathname])4243  if (!enabled) return null4445  if (!me) {46    const next = location.pathname + location.search47    return (48      <div className="site-header-account">49        <a50          className="login-btn"51          href={`/api/auth/ka/login?next=${encodeURIComponent(next)}`}52        >53          <span className="login-ka" aria-hidden="true">KA</span>54          Se connecter avec KA55        </a>56        <a57          className="signup-link"58          href="https://www.groupe-ka.com/connexion"59          target="_blank"60          rel="noopener noreferrer"61        >62          Créer un compte63        </a>64      </div>65    )66  }6768  return (69    <div className="site-header-account">70      <button71        className="account-btn"72        onClick={() => setOpen(!open)}73        aria-expanded={open}74        aria-haspopup="menu"75        aria-label={`Compte : ${me.name || me.email}`}76      >77        {me.picture ? (78          <img src={me.picture} alt="" referrerPolicy="no-referrer" />79        ) : (80          <span className="account-initial">81            {(me.name || me.email).charAt(0).toUpperCase()}82          </span>83        )}84      </button>85      {open && (86        <>87          <div88            className="account-backdrop"89            onClick={() => setOpen(false)}90            aria-hidden="true"91          />92          <div className="account-menu" role="menu">93            <div className="account-id">94              <b>{me.name || 'Mon compte'}</b>95              <span>{me.email}</span>96              <span className="account-kaid">{me.ka_id}</span>97              <span className="account-note">98                Compte Groupe KA — le même KA-ID sur toutes les plateformes99              </span>100            </div>101            <Link102              to="/profil"103              role="menuitem"104              className="account-link"105              onClick={() => setOpen(false)}106            >107              Mon profil108            </Link>109            <a110              href="https://www.groupe-ka.com/compte"111              target="_blank"112              rel="noopener noreferrer"113              role="menuitem"114              className="account-link"115              onClick={() => setOpen(false)}116            >117              Mes favoris — Mon univers Ka ↗118            </a>119            <button120              role="menuitem"121              onClick={async () => {122                await logout()123                setMe(null)124                setOpen(false)125                navigate('/')126              }}127            >128              Se déconnecter129            </button>130          </div>131        </>132      )}133    </div>134  )135}136137/** Actions du header sur une fiche produit : favoris + partage (compactes).138    Le produit affiché est publié par la page fiche (fiche/current.ts). */139function FicheHeaderActions() {140  const p = useCurrentListing()141  const { ids, toggle } = useFavorites()142  if (!p) return null143  const fav = ids.has(p.uid)144  const share = async () => {145    const url = window.location.origin + window.location.pathname146    try {147      if (navigator.share) await navigator.share({ title: document.title, url })148      else await navigator.clipboard.writeText(url)149    } catch { /* annulé */ }150  }151  return (152    <div className="hdr-actions">153      <button type="button" className={`hdr-btn ${fav ? 'on' : ''}`} aria-pressed={fav}154              aria-label={fav ? 'Retirer des favoris' : 'Ajouter aux favoris'} onClick={() => toggle(productFavItem(p))}>155        <IcoHeart size={18} filled={fav} />156      </button>157      <button type="button" className="hdr-btn" aria-label="Partager" onClick={share}>158        <Ico name="share" size={17} />159      </button>160    </div>161  )162}163164function Header() {165  const location = useLocation()166  // fiche produit : header compact (56 px), sans badge ni recherche ; favoris + partage167  const isFiche = /^\/produits\/[^/]+$/.test(location.pathname)168169  // "/" focuses the desktop header search.170  useEffect(() => {171    function onKey(e: KeyboardEvent) {172      if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return173      const target = e.target as HTMLElement | null174      const tag = target?.tagName ?? ''175      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return176      if (target?.isContentEditable) return177      const input = document.getElementById(HEADER_SEARCH_ID)178      if (input instanceof HTMLInputElement) {179        e.preventDefault()180        input.focus()181        input.select()182      }183    }184    window.addEventListener('keydown', onKey)185    return () => window.removeEventListener('keydown', onKey)186  }, [])187188  return (189    <header className={`site-header ${isFiche ? 'header--fiche' : ''}`}>190      <div className="site-header-inner">191        <div className="header-brand">192          <Link to="/" className="logo" aria-label="Fabri-Ka — Accueil">193            Fabri<span className="logo-ka">·Ka</span>194          </Link>195          {/* badge Groupe KA — desktop et mobile (masqué < 480 px, repris196              par le pied de page KaFooter) */}197          {!isFiche && <GroupeKaBadge />}198        </div>199        {isFiche && <FicheHeaderActions />}200        <nav className="site-nav" aria-label="Navigation principale">201          <NavLink to="/produits">Produits</NavLink>202          <NavLink to="/boutiques">Boutiques</NavLink>203          <NavLink to="/stats">Stats</NavLink>204          <NavLink to="/inscrire">Inscrire mon atelier</NavLink>205          <NavLink to="/a-propos">À propos</NavLink>206          <NavLink to="/contact">Contact</NavLink>207        </nav>208        <div className="site-header-search">209          {/* remount on navigation so the field reflects the current query */}210          <SearchBar211            compact212            shortcutHint213            inputId={HEADER_SEARCH_ID}214            key={location.pathname + location.search}215            initialValue={216              location.pathname === '/produits'217                ? new URLSearchParams(location.search).get('q') ?? ''218                : ''219            }220            placeholder="Rechercher…"221          />222        </div>223        <AccountMenu />224      </div>225    </header>226  )227}228229export default function App() {230  return (231    <FavoritesProvider>232      <div className="app">233        <Header />234        <main className="site-main">235          <Routes>236            <Route path="/" element={<Home />} />237            <Route path="/produits" element={<Catalog />} />238            <Route path="/produits/:uid" element={<ProductDetail />} />239            {/* Pages programmatiques SEO — mêmes URLs que le rendu serveur */}240            <Route path="/categorie/:cslug" element={<Catalog />} />241            <Route path="/categorie/:cslug/region/:rslug" element={<Catalog />} />242            <Route path="/region/:rslug" element={<Catalog />} />243            <Route path="/boutiques" element={<Stores />} />244            <Route path="/boutiques/:id" element={<StoreDetail />} />245            <Route path="/stats" element={<Stats />} />246            <Route path="/profil" element={<Profile />} />247            <Route path="/inscrire" element={<Inscrire />} />248            <Route path="/a-propos" element={<About />} />249            <Route path="/contact" element={<Contact />} />250            <Route path="*" element={<NotFound />} />251          </Routes>252        </main>253        {/* Pied de page commun Groupe KA (ka-ui) */}254        <KaFooter siteId="fabri-ka" localLegal={[{ label: 'Documentation', href: '/doc/' }]} />255        <BottomNav />256      </div>257    </FavoritesProvider>258  )259}260
Files · bf2ebbda7aecf5e4a611a753cbb29f3cc4d34429
Menu

Theme