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, useState } from 'react'2import { Link } from 'react-router-dom'3import { Facets, fetchFacets, fetchStats, formatInt, Stats } from '../api'4import CountUp from '../components/CountUp'5import { categoryIcon, IconArrowRight } from '../components/Icons'6import Rail from '../components/Rail'7import SearchBar from '../components/SearchBar'8import Skeleton from '../components/Skeleton'910interface RailDef {11 title: string12 category?: string13 seeAllHref: string14}1516function pickCategory(17 facets: Facets | null,18 keywords: string[]19): string | undefined {20 if (!facets) return undefined21 const found = facets.categories.find((c) =>22 keywords.some(23 (k) =>24 c.key.toLowerCase().includes(k) || c.label.toLowerCase().includes(k)25 )26 )27 return found?.key28}2930export default function Home() {31 const [stats, setStats] = useState<Stats | null>(null)32 const [facets, setFacets] = useState<Facets | null>(null)3334 useEffect(() => {35 const controller = new AbortController()36 fetchStats(controller.signal)37 .then(setStats)38 .catch(() => {})39 fetchFacets(controller.signal)40 .then(setFacets)41 .catch(() => {})42 return () => controller.abort()43 }, [])4445 const rails: RailDef[] = [46 { title: 'Nouveautés', seeAllHref: '/produits?sort=recent' },47 ]48 const erable = pickCategory(facets, ['érable', 'erable', 'miel'])49 if (erable) {50 rails.push({51 title: 'Érable & miel',52 category: erable,53 seeAllHref: `/produits?category=${encodeURIComponent(erable)}`,54 })55 }56 const beaute = pickCategory(facets, ['beauté', 'beaute', 'soins'])57 if (beaute) {58 rails.push({59 title: 'Beauté & soins',60 category: beaute,61 seeAllHref: `/produits?category=${encodeURIComponent(beaute)}`,62 })63 }64 const maison = pickCategory(facets, ['maison', 'déco', 'deco'])65 if (maison) {66 rails.push({67 title: 'Maison & déco',68 category: maison,69 seeAllHref: `/produits?category=${encodeURIComponent(maison)}`,70 })71 }7273 return (74 <div className="page home">75 <section className="hero">76 <p className="hero-eyebrow">Le répertoire des produits d'ici</p>77 <h1 className="hero-title">78 Tous les produits québécois.79 <br />80 <em>Un seul endroit.</em>81 </h1>82 <p className="hero-sub">83 Fabri-Ka rassemble les catalogues des boutiques en ligne d'ici —84 fabriqué, conçu et vendu au Québec.85 </p>8687 <div className="hero-search">88 <SearchBar />89 </div>9091 <div className="hero-stats">92 {stats ? (93 <>94 <div className="stat">95 <span className="stat-number">96 <CountUp value={stats.totals.products} />97 </span>98 <span className="stat-label">Produits</span>99 </div>100 <div className="stat">101 <span className="stat-number">102 <CountUp value={stats.totals.stores_live} />103 </span>104 <span className="stat-label">Boutiques</span>105 </div>106 <div className="stat">107 <span className="stat-number">108 <CountUp value={stats.totals.regions} />109 </span>110 <span className="stat-label">Régions</span>111 </div>112 </>113 ) : (114 <>115 <Skeleton width="90px" height="3rem" />116 <Skeleton width="90px" height="3rem" />117 <Skeleton width="90px" height="3rem" />118 </>119 )}120 </div>121 <Link className="hero-stats-link" to="/stats">122 Statistiques <IconArrowRight size={14} />123 </Link>124 </section>125126 <section className="home-categories" aria-label="Catégories">127 <header className="section-header">128 <h2>Parcourir par catégorie</h2>129 <Link className="section-see-all" to="/produits">130 Tout le catalogue <IconArrowRight size={15} />131 </Link>132 </header>133 <div className="category-chips">134 {facets ? (135 facets.categories.map((c) => (136 <Link137 key={c.key}138 className="chip"139 to={`/produits?category=${encodeURIComponent(c.key)}`}140 >141 <span className="chip-icon" aria-hidden="true">142 {categoryIcon(c.label)}143 </span>144 {c.label}145 <span className="chip-count">{formatInt(c.n)}</span>146 </Link>147 ))148 ) : (149 <>150 <Skeleton width="110px" height="2.5rem" radius="999px" />151 <Skeleton width="140px" height="2.5rem" radius="999px" />152 <Skeleton width="120px" height="2.5rem" radius="999px" />153 <Skeleton width="100px" height="2.5rem" radius="999px" />154 </>155 )}156 </div>157 </section>158159 {rails.map((r) => (160 <Rail161 key={r.title}162 title={r.title}163 query={164 r.category165 ? { category: r.category, per_page: 12 }166 : { sort: 'recent', per_page: 12 }167 }168 seeAllHref={r.seeAllHref}169 />170 ))}171 </div>172 )173}174