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 — cœur ♥ « Mon univers Ka » d'un résultat de recherche3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 *6 * Toggle du favori au hub Groupe KA (via /api/favorites/toggle). Connecté :7 * cœur plein/contour ; non connecté : le tap envoie au flux « Se connecter8 * avec KA ID » existant (retour sur la page courante). Zone tactile 44 px9 * via ::after (pattern du socle mobile KA).10 */1112"use client";1314import { useEffect, useState } from "react";15import { cn } from "@/lib/utils";16import { ensureLoaded, isFavorite, toggleFav, useFavorites } from "@/components/fav-store";1718interface FavButtonProps {19 url: string;20 title: string;21 subtitle?: string;22 imageUrl?: string | null;23 className?: string;24}2526export function FavButton({ url, title, subtitle, imageUrl, className }: FavButtonProps) {27 const favs = useFavorites();28 const [busy, setBusy] = useState(false);2930 useEffect(() => {31 void ensureLoaded();32 }, []);3334 const fav = isFavorite(favs, url);35 const anon = favs.status === "anon";3637 const onClick = async () => {38 if (busy) return;39 if (anon) {40 const here = window.location.pathname + window.location.search;41 window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(here)}`;42 return;43 }44 setBusy(true);45 await toggleFav({46 url,47 title,48 subtitle,49 image_url: imageUrl || undefined,50 });51 setBusy(false);52 };5354 return (55 <button56 type="button"57 onClick={onClick}58 disabled={busy}59 aria-pressed={fav}60 aria-label={61 anon62 ? "Se connecter avec KA ID pour ajouter aux favoris"63 : fav64 ? "Retirer des favoris"65 : "Ajouter aux favoris"66 }67 title={68 anon69 ? "Se connecter avec KA ID"70 : fav71 ? "Retirer de Mon univers Ka"72 : "Ajouter à Mon univers Ka"73 }74 className={cn(75 "relative inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-[1.5px] transition-colors after:absolute after:-inset-1.5 after:content-['']",76 fav77 ? "border-ink bg-ink text-lime"78 : "border-transparent text-ink-2 hover:border-ink hover:text-ink",79 busy && "opacity-60",80 className,81 )}82 >83 <svg84 width="15"85 height="15"86 viewBox="0 0 24 24"87 fill={fav ? "currentColor" : "none"}88 stroke="currentColor"89 strokeWidth="2"90 strokeLinejoin="round"91 aria-hidden="true"92 >93 <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" />94 </svg>95 </button>96 );97}98