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%
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