Python 67%
TypeScript 18.2%
CSS 14.4%
1// -----------------------------------------------------------------------------2// House-Ka — Homes-for-sale aggregator (Canada outside Québec)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// account.tsx : shared account context — signed-in profile + ♥ favourites.5// One fetch when the app mounts; the listing page, header and account page6// all read the same state. Favourites live at the Groupe KA HUB (central7// "My Ka universe" store): each ♥ is optimistic on screen and pushed to the8// server, which relays to the hub. Signed out → ♥ redirects to KA ID login.9// -----------------------------------------------------------------------------10import {11 ReactNode, createContext, useCallback, useContext, useEffect, useState,12} from "react";13import {14 Listing, User, favItemFromListing, fetchFavorites, fetchMe, toggleFavorite,15} from "./api";1617interface AccountState {18 user: User | null;19 enabled: boolean; // SSO configured server-side20 loaded: boolean; // first fetch done21 favs: Set<string>; // favourite uids (hub mirror)22 toggleFav: (l: Listing) => void;23 refresh: () => void; // re-fetch profile + favourites (after login)24}2526const Ctx = createContext<AccountState>({27 user: null, enabled: false, loaded: false, favs: new Set(),28 toggleFav: () => {}, refresh: () => {},29});3031export const useAccount = () => useContext(Ctx);3233/** Redirects to the KA ID login, returning to the current page. */34export function kaLogin() {35 const next = window.location.pathname + window.location.search;36 window.location.href = `/api/auth/ka/login?next=${encodeURIComponent(next)}`;37}3839export function AccountProvider({ children }: { children: ReactNode }) {40 const [user, setUser] = useState<User | null>(null);41 const [enabled, setEnabled] = useState(false);42 const [loaded, setLoaded] = useState(false);43 const [favs, setFavs] = useState<Set<string>>(new Set());4445 const refresh = useCallback(() => {46 fetchMe()47 .then((r) => {48 setUser(r.user);49 setEnabled(r.enabled !== false);50 setLoaded(true);51 if (r.user && r.user.ka_id) {52 fetchFavorites()53 .then((f) => setFavs(new Set(f.ids)))54 .catch(() => setFavs(new Set()));55 } else {56 setFavs(new Set());57 }58 })59 .catch(() => { setUser(null); setLoaded(true); });60 }, []);6162 useEffect(() => { refresh(); }, [refresh]);6364 const toggleFav = useCallback((l: Listing) => {65 if (!user || !user.ka_id) { kaLogin(); return; } // ♥ = KA account required66 setFavs((prev) => {67 const next = new Set(prev);68 const on = !next.has(l.uid);69 if (on) next.add(l.uid); else next.delete(l.uid);70 toggleFavorite(on, favItemFromListing(l)).catch((e) => {71 if (String(e).includes("401")) kaLogin(); // session expired72 });73 return next;74 });75 }, [user]);7677 return (78 <Ctx.Provider value={{ user, enabled, loaded, favs, toggleFav, refresh }}>79 {children}80 </Ctx.Provider>81 );82}83