Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com
Python 51.9%
TypeScript 32.7%
JavaScript 4.8%
CSS 4.1%
Shell 2.9%
HTML 1.8%
SQL 1.5%
1/**2 * Trouve-KA — contenu client de la page « Mes favoris » (Mon univers Ka)3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 *6 * Liste vivante des favoris du membre, lue au hub Groupe KA via7 * /api/favorites (magasin partagé du fav-store — mêmes cœurs que les8 * résultats). Retrait immédiat (toggle optimiste, le hub tranche).9 */1011"use client";1213import { useEffect } from "react";14import Link from "next/link";15import { Favicon } from "@/components/favicon";16import {17 ensureLoaded,18 toggleFav,19 useFavorites,20 type Fav,21} from "@/components/fav-store";2223function domainOf(url: string | undefined): string {24 if (!url) return "";25 try {26 return new URL(url).hostname.replace(/^www\./, "");27 } catch {28 return "";29 }30}3132function FavCard({ fav }: { fav: Fav }) {33 const domain = domainOf(fav.url);34 return (35 <article className="group flex items-start gap-4 py-6 sm:gap-6">36 <div className="min-w-0 flex-1">37 {domain ? (38 <div className="flex items-center gap-2">39 <Favicon domain={domain} />40 <span className="gk-mono truncate text-xs font-medium text-green">41 {fav.subtitle || domain}42 </span>43 </div>44 ) : null}45 <h3 className="mt-1.5 text-[17px] font-bold leading-snug tracking-[-0.02em] sm:text-lg">46 <a47 href={fav.url || "#"}48 className="rounded-sm text-ink underline decoration-transparent decoration-[3px] underline-offset-[3px] transition-colors hover:decoration-lime focus-visible:decoration-lime"49 >50 {fav.title || fav.url}51 </a>52 </h3>53 {fav.price_label ? (54 <p className="mt-1.5 text-sm font-medium text-ink-2">55 {fav.price_label}56 </p>57 ) : null}58 <button59 type="button"60 onClick={() =>61 void toggleFav({62 url: fav.url || fav.item_id,63 title: fav.title,64 subtitle: fav.subtitle,65 image_url: fav.image_url,66 })67 }68 className="gk-display mt-3 inline-flex min-h-[36px] items-center gap-1.5 rounded-full border-[1.5px] border-ink bg-surface px-3.5 text-[12px] font-bold text-ink transition-colors hover:bg-[var(--accent-soft)]"69 >70 <svg71 width="12"72 height="12"73 viewBox="0 0 24 24"74 fill="currentColor"75 aria-hidden="true"76 >77 <path d="M12 21c-4.8-3.6-9-6.9-9-11a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 4.1-4.2 7.4-9 11Z" />78 </svg>79 Retirer80 </button>81 </div>82 {fav.image_url ? (83 // eslint-disable-next-line @next/next/no-img-element84 <img85 src={fav.image_url}86 alt=""87 loading="lazy"88 className="h-[76px] w-[76px] shrink-0 rounded-card border border-line object-cover sm:h-[110px] sm:w-[110px]"89 />90 ) : null}91 </article>92 );93}9495export function FavorisContent({ name }: { name: string }) {96 const favs = useFavorites();9798 useEffect(() => {99 // Liste fraîche à l'arrivée sur la page (le cache serveur reste à 30 s).100 void ensureLoaded(true);101 }, []);102103 const loading = favs.status === "idle" || favs.status === "loading";104105 return (106 <main id="contenu" className="mx-auto w-full max-w-3xl flex-1 px-4 py-8">107 <p className="klabel">Mon univers Ka</p>108 <h1 className="mt-2 text-3xl font-bold leading-tight tracking-[-0.03em] sm:text-4xl">109 Mes favoris110 </h1>111 <p className="mt-3 max-w-xl text-sm leading-relaxed text-ink-2">112 {name ? `${name}, voici` : "Voici"} les pages que vous avez gardées —113 la même liste vous suit sur toutes les plateformes du Groupe KA.114 </p>115116 {loading ? (117 <div aria-hidden="true" className="mt-6 divide-y divide-line">118 {Array.from({ length: 3 }, (_, i) => (119 <div key={i} className="animate-pulse space-y-2.5 py-6">120 <div className="h-3.5 w-1/3 rounded bg-[var(--line)]" />121 <div className="h-5 w-2/3 rounded bg-[var(--line)]" />122 <div className="h-9 w-24 rounded-full bg-[var(--line)]" />123 </div>124 ))}125 </div>126 ) : favs.status === "error" ? (127 <div className="mt-10 text-center">128 <p className="font-display font-bold text-ink">129 Le hub Groupe KA est momentanément injoignable.130 </p>131 <p className="mt-2 text-sm text-ink-2">132 Vos favoris sont en sécurité — réessayez dans quelques instants.133 </p>134 </div>135 ) : favs.items.length === 0 ? (136 <div className="rise mt-10 py-10 text-center">137 <p className="klabel">0 favori</p>138 <h2 className="mx-auto mt-4 max-w-xl text-2xl font-bold leading-tight tracking-[-0.03em] sm:text-3xl">139 Rien dans votre univers — <span className="hl">encore</span>.140 </h2>141 <p className="mx-auto mt-5 max-w-md text-sm leading-relaxed text-ink-2">142 Lancez une recherche et touchez le cœur ♥ d'un résultat pour143 le retrouver ici, et sur tous les sites du Groupe KA.144 </p>145 <Link href="/" className="btn btn-primary mt-8">146 Lancer une recherche147 </Link>148 </div>149 ) : (150 <div className="stagger mt-2 divide-y divide-line">151 {favs.items.map((fav) => (152 <FavCard key={fav.item_id || fav.url} fav={fav} />153 ))}154 </div>155 )}156 </main>157 );158}159