// ----------------------------------------------------------------------------- // Rent-Ka — Rental listings aggregator (Canada, outside Québec) // Author: Simon-Pierre Boucher — contact@spboucher.ai // App.tsx: global layout (header + live ticker + ink footer) and routing // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { NavLink, Route, Routes, useLocation } from "react-router-dom"; import { fetchAuthConfig, fetchFacets, fetchSources, fetchStats, logout, registerSourceNames, sourceName, PROVINCE_NAMES, } from "./api"; import { AccountProvider, useAccount } from "./account"; import CookieConsent from "./components/CookieConsent"; import { IcoChart, IcoCompass, IcoDoc, IcoFolder, IcoHeart, IcoHouse, IcoLock, IcoMap, IcoSearch, IcoUser, } from "./components/Icons"; import { LogoIcon } from "./components/Logo"; import GroupeKaBadge from "./ka/GroupeKaBadge"; import KaFooter from "./ka/KaFooter"; import ContactPage from "./pages/Contact"; import Home from "./pages/Home"; import ListingPage from "./pages/Listing"; import BotPage from "./pages/Bot"; import JusteValeurPage from "./pages/JusteValeur"; import KaScoresPage from "./pages/KaScores"; import PasserellePage from "./pages/Passerelle"; import PrivacyPage from "./pages/Privacy"; import ProfilePage from "./pages/Profile"; import PublicProfilePage from "./pages/PublicProfile"; import BienvenuePage from "./pages/Bienvenue"; import FavorisPage from "./pages/Favoris"; import GestionPage from "./pages/Gestion"; import GestionPublicPage from "./pages/GestionPublic"; import TermsPage from "./pages/Terms"; import SourcesPage from "./pages/Sources"; import StatsPage from "./pages/Stats"; import VillePage, { VillesPage } from "./pages/Ville"; function Ticker() { const [items, setItems] = useState([]); useEffect(() => { Promise.all([fetchStats(), fetchFacets(), fetchSources()]) .then(([stats, facets, src]) => { registerSourceNames(src.sources); const parts: string[] = [`${stats.total} active rentals`]; const provs = stats.provinces || {}; for (const [code, n] of Object.entries(provs)) { if ((n as number) > 0) parts.push(`${PROVINCE_NAMES[code] || code} ${n}`); } if (stats.avg_price != null) parts.push(`Average rent $${Math.round(stats.avg_price).toLocaleString("en-CA")}`); for (const s of facets.sources.slice(0, 10)) parts.push(`${sourceName(s.source)} · ${s.n}`); parts.push("Updated automatically"); setItems(parts); }) .catch(() => setItems(["Rent-Ka — rentals across Canada"])); }, []); if (items.length === 0) return null; // content doubled for a continuous scroll loop return ( ); } const NAV_LINKS = [ { to: "/", label: "Rentals", icon: , end: true }, { to: "/?view=map", label: "Map", icon: , end: false, force: true }, { to: "/cities", label: "Cities", icon: , end: false }, { to: "/stats", label: "Stats", icon: , end: false }, { to: "/sources", label: "Sources", icon: , end: false }, { to: "/contact", label: "Contact", icon: , end: false }, { to: "/terms", label: "Terms", icon: , end: false }, { to: "/privacy", label: "Privacy", icon: , end: false }, ]; /** "Sign in" button or avatar + account menu (Google sign-in) */ function AccountMenu() { const [enabled, setEnabled] = useState(false); const { me, refresh } = useAccount(); const [menuOpen, setMenuOpen] = useState(false); useEffect(() => { fetchAuthConfig().then((c) => setEnabled(c.ka || c.google)).catch(() => {}); }, []); // menu open: close on Escape (outside tap goes through the backdrop) useEffect(() => { if (!menuOpen) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setMenuOpen(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [menuOpen]); if (!enabled) return null; if (!me) { return ( Sign in ); } return (
{menuOpen && ( <> {/* Backdrop rendered at the root (createPortal): the header's backdrop-filter re-parented position:fixed → veil clipped to header. */} {createPortal(
setMenuOpen(false)} aria-hidden="true" />, document.body, )}
{me.name || "My account"} {me.email} {me.ka_id && {me.ka_id}}
setMenuOpen(false)} > My profile {me.role === "locataire" && ( setMenuOpen(false)} > My saved rentals )} {me.role === "gestionnaire" && ( setMenuOpen(false)} > My manager page )} {!me.role && ( setMenuOpen(false)} > Choose my profile )}
)}
); } function Header() { const [open, setOpen] = useState(false); const location = useLocation(); // close the menu on every navigation + lock scrolling underneath useEffect(() => { setOpen(false); }, [location]); useEffect(() => { document.documentElement.classList.toggle("ka-scroll-lock", open); return () => { document.documentElement.classList.remove("ka-scroll-lock"); }; }, [open]); return ( <>
Rent·Ka Apartments and homes for rent across Canada.
{/* mobile drawer menu */}
{/* fixed ✕ of the panel: visible regardless of scroll; the header burger is hidden while open */} {NAV_LINKS.map((l, i) => ( `mm-link ${isActive && !l.force ? "active" : ""}`} onClick={() => setOpen(false)} > {l.label} ))}
Independent aggregator — updated automatically; every listing links back to the original ad.
{open &&
setOpen(false)} aria-hidden="true" />} ); } /** Footer: local navigation row + shared Groupe KA footer. */ function Footer() { return ( <>
Rent-Ka — Apartments and homes for rent across Canada. Independent aggregator: every listing links back to the original ad. Rentals Cities Stats Sources Contact
); } /** Bottom navigation bar (mobile) — active icon in accent. */ function MobileTabBar() { const location = useLocation(); const isMap = new URLSearchParams(location.search).get("view") === "map"; const tabs = [ { to: "/", label: "Search", icon: , on: location.pathname === "/" && !isMap }, { to: "/?view=map", label: "Map", icon: , on: location.pathname === "/" && isMap }, { to: "/favorites", label: "Saved", icon: , on: location.pathname.startsWith("/favorites") }, { to: "/profile", label: "Profile", icon: , on: location.pathname.startsWith("/profile") }, ]; return ( ); } export default function App() { return (
} /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />

Page not found

The requested link does not exist.

} />