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// fiche/StickyListingCTA.tsx : barre CTA compacte du bas (mobile/tablette) —5// n'apparaît qu'une fois la rangée d'actions du héro sortie de l'écran,6// respecte la safe-area iOS, ne masque aucun contenu (padding de page).7// -----------------------------------------------------------------------------8import { useEffect, useState } from "react";9import { Listing, fmtPrice, sourceName } from "../api";10import { Ico } from "../components/Icons";11import { ComparaisonPrix, estLocation } from "./synthese";12import { NBSP } from "./ui";1314/** true dès que l'élément suivi est passé au-dessus du viewport (défilement). */15export function usePastElement(watch: React.RefObject<HTMLElement>): boolean {16 const [past, setPast] = useState(false);17 useEffect(() => {18 const check = () => {19 const el = watch.current;20 if (!el) return;21 setPast(el.getBoundingClientRect().bottom < 0);22 };23 check();24 window.addEventListener("scroll", check, { passive: true });25 window.addEventListener("resize", check);26 return () => { window.removeEventListener("scroll", check); window.removeEventListener("resize", check); };27 }, [watch]);28 return past;29}3031export default function StickyListingCTA({ l, cmp, show }: {32 l: Listing; cmp: ComparaisonPrix | null; show: boolean;33}) {34 return (35 <div className={`ik-cta-bar ${show ? "show" : ""}`} aria-hidden={!show}>36 <div className="ik-cta-txt">37 <div className="ik-cta-price">{fmtPrice(l.price, l.price_label)}{estLocation(l) && l.price != null && <small>{NBSP}/ mois</small>}</div>38 {cmp && <div className={`ik-cta-sub ${cmp.tone === "good" ? "good" : ""}`}>{cmp.court}</div>}39 </div>40 <a className="ik-btn ik-btn-primary" href={l.url} target="_blank" rel="noopener noreferrer" tabIndex={show ? 0 : -1}>41 Voir l'annonce <Ico name="external" size={15} />42 <span className="visually-hidden"> chez {sourceName(l.source)}</span>43 </a>44 </div>45 );46}47