SPB Git

spb/fabri-ka Public

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

HTML 57.9% Python 18.6% TypeScript 15.6% CSS 7.8%
4.2 KB · 122 lines tsx
Raw Blame History
1import { useEffect } from 'react'2import { Link, NavLink, Route, Routes, useLocation } from 'react-router-dom'3import BottomNav from './components/BottomNav'4import SearchBar from './components/SearchBar'5import About from './pages/About'6import Catalog from './pages/Catalog'7import Home from './pages/Home'8import NotFound from './pages/NotFound'9import ProductDetail from './pages/ProductDetail'10import Stats from './pages/Stats'11import StoreDetail from './pages/StoreDetail'12import Stores from './pages/Stores'1314const HEADER_SEARCH_ID = 'header-search-input'1516function Header() {17  const location = useLocation()1819  // "/" focuses the desktop header search.20  useEffect(() => {21    function onKey(e: KeyboardEvent) {22      if (e.key !== '/' || e.metaKey || e.ctrlKey || e.altKey) return23      const target = e.target as HTMLElement | null24      const tag = target?.tagName ?? ''25      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return26      if (target?.isContentEditable) return27      const input = document.getElementById(HEADER_SEARCH_ID)28      if (input instanceof HTMLInputElement) {29        e.preventDefault()30        input.focus()31        input.select()32      }33    }34    window.addEventListener('keydown', onKey)35    return () => window.removeEventListener('keydown', onKey)36  }, [])3738  return (39    <header className="site-header">40      <div className="site-header-inner">41        <Link to="/" className="logo" aria-label="Fabri-Ka — Accueil">42          Fabri<span className="logo-dot">·</span>Ka43        </Link>44        <nav className="site-nav" aria-label="Navigation principale">45          <NavLink to="/produits">Produits</NavLink>46          <NavLink to="/boutiques">Boutiques</NavLink>47          <NavLink to="/stats">Stats</NavLink>48          <NavLink to="/a-propos">À propos</NavLink>49        </nav>50        <div className="site-header-search">51          {/* remount on navigation so the field reflects the current query */}52          <SearchBar53            compact54            shortcutHint55            inputId={HEADER_SEARCH_ID}56            key={location.pathname + location.search}57            initialValue={58              location.pathname === '/produits'59                ? new URLSearchParams(location.search).get('q') ?? ''60                : ''61            }62            placeholder="Rechercher…"63          />64        </div>65      </div>66    </header>67  )68}6970function Footer() {71  return (72    <footer className="site-footer">73      <div className="site-footer-inner">74        <div className="footer-brand">75          <span className="logo logo-footer">76            Fabri<span className="logo-dot">·</span>Ka77          </span>78          <p>Tous les produits québécois. Un seul endroit.</p>79        </div>80        <nav className="footer-col" aria-label="Explorer">81          <span className="footer-col-title">Explorer</span>82          <Link to="/produits">Produits</Link>83          <Link to="/boutiques">Boutiques</Link>84          <Link to="/produits?sort=recent">Nouveautés</Link>85          <Link to="/stats">Stats</Link>86        </nav>87        <nav className="footer-col" aria-label="Fabri-Ka">88          <span className="footer-col-title">Fabri-Ka</span>89          <Link to="/a-propos">À propos</Link>90          <a href="mailto:contact@spboucher.ai">Contact</a>91        </nav>92      </div>93      <p className="footer-legal">94        © 2026 Simon-Pierre Boucher — Fabri-Ka agrège des catalogues publics;95        les prix et disponibilités appartiennent aux boutiques sources.96      </p>97    </footer>98  )99}100101export default function App() {102  return (103    <div className="app">104      <Header />105      <main className="site-main">106        <Routes>107          <Route path="/" element={<Home />} />108          <Route path="/produits" element={<Catalog />} />109          <Route path="/produits/:uid" element={<ProductDetail />} />110          <Route path="/boutiques" element={<Stores />} />111          <Route path="/boutiques/:id" element={<StoreDetail />} />112          <Route path="/stats" element={<Stats />} />113          <Route path="/a-propos" element={<About />} />114          <Route path="*" element={<NotFound />} />115        </Routes>116      </main>117      <Footer />118      <BottomNav />119    </div>120  )121}122