Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)
Python 47.5%
HTML 27.9%
TypeScript 15.5%
CSS 7.2%
JavaScript 2%
1// -----------------------------------------------------------------------------2// Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// account.tsx : contexte de compte partagé — profil connecté + favoris ♥5// Un seul fetch au montage de l'app ; le cœur des cartes, la fiche et le6// menu compte lisent le même état. Les favoris vivent au HUB Groupe KA7// (magasin central « Mon univers Ka ») : le toggle est optimiste à l'écran8// et poussé au serveur qui relaie au hub. Non connecté -> clic ♥ redirige9// vers la connexion KA ID (retour sur la page courante).10// -----------------------------------------------------------------------------11import {12 ReactNode, createContext, useCallback, useContext, useEffect, useState,13} from "react";14import {15 Listing, User, favItemFromListing, fetchFavorites, fetchMe, toggleFavorite,16} from "./api";1718interface AccountState {19 user: User | null;20 enabled: boolean; // connexion configurée côté serveur21 loaded: boolean; // premier fetch terminé22 favs: Set<string>; // uids favoris (miroir du hub)23 toggleFav: (l: Listing) => void;24 refresh: () => void; // re-fetch profil + favoris (après login)25}2627const Ctx = createContext<AccountState>({28 user: null, enabled: false, loaded: false, favs: new Set(),29 toggleFav: () => {}, refresh: () => {},30});3132export const useAccount = () => useContext(Ctx);3334/** Redirige vers la connexion KA ID, avec retour sur la page courante. */35export function kaLogin() {36 const next = window.location.pathname + window.location.search;37 window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(next)}`;38}3940export function AccountProvider({ children }: { children: ReactNode }) {41 const [user, setUser] = useState<User | null>(null);42 const [enabled, setEnabled] = useState(false);43 const [loaded, setLoaded] = useState(false);44 const [favs, setFavs] = useState<Set<string>>(new Set());4546 const refresh = useCallback(() => {47 fetchMe()48 .then((r) => {49 setUser(r.user);50 setEnabled(r.enabled !== false);51 setLoaded(true);52 if (r.user && r.user.ka_id) {53 fetchFavorites()54 .then((f) => setFavs(new Set(f.ids)))55 .catch(() => setFavs(new Set()));56 } else {57 setFavs(new Set());58 }59 })60 .catch(() => { setUser(null); setLoaded(true); });61 }, []);6263 useEffect(() => { refresh(); }, [refresh]);6465 const toggleFav = useCallback((l: Listing) => {66 if (!user || !user.ka_id) { kaLogin(); return; } // ♥ = compte KA requis67 setFavs((prev) => {68 const next = new Set(prev);69 const on = !next.has(l.uid);70 if (on) next.add(l.uid); else next.delete(l.uid);71 toggleFavorite(on, favItemFromListing(l)).catch((e) => {72 if (String(e).includes("401")) kaLogin(); // session expirée73 });74 return next;75 });76 }, [user]);7778 return (79 <Ctx.Provider value={{ user, enabled, loaded, favs, toggleFav, refresh }}>80 {children}81 </Ctx.Provider>82 );83}84