Python 68.8%
TypeScript 18.6%
CSS 8.7%
JavaScript 3.3%
HTML 0.6%
1// -----------------------------------------------------------------------------2// Rent-Ka — Rental listings aggregator (Canada, outside Québec)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// components/ListingCard.tsx: listing card (results grid)5// -----------------------------------------------------------------------------6import { Link } from "react-router-dom";7import { Listing, fmtPrice, sourceName } from "../api";8import { useAccount } from "../account";9import { IcoCamera, IcoHeart } from "./Icons";10import SmartImg from "./SmartImg";11import FairValueBadge from "./FairValueBadge";12import KaScoreBadge from "./KaScoreBadge";1314export default function ListingCard({ l }: { l: Listing }) {15 const img = l.images && l.images.length > 0 ? l.images[0] : null;16 const { me, favs, toggleFav } = useAccount();17 const fav = favs.has(l.uid);18 return (19 <Link to={`/listing/${encodeURIComponent(l.uid)}`} className="card">20 <div className="card-img">21 <SmartImg src={img} width_={480} fallbackLabel={l.unit_type || undefined}22 alt={l.title} loading="lazy" decoding="async" />23 {l.unit_type && <span className="badge type">{l.unit_type}</span>}24 {l.ka_reco && (25 <span26 className="badge ka-reco"27 title="Matches your KA ID preferences — manage them at groupe-ka.com/mon-ka"28 >29 Recommended for you30 </span>31 )}32 {l.images.length > 1 && (33 <span className="badge right"><IcoCamera size={12} /> {l.images.length}</span>34 )}35 {me && (36 <button37 className={`fav-btn ${fav ? "on" : ""}`}38 aria-label={fav ? "Remove from saved" : "Save this rental"}39 aria-pressed={fav}40 onClick={(e) => { e.preventDefault(); e.stopPropagation(); toggleFav(l.uid); }}41 >42 <IcoHeart size={16} filled={fav} />43 </button>44 )}45 </div>46 <div className="card-body">47 <div className="card-price">48 {fmtPrice(l.price, l.price_label)} {l.price != null && <small>/ month</small>}49 <FairValueBadge verdict={l.fv_verdict} deviation={l.fv_deviation} compact />50 </div>51 <div className="card-title">{l.title || l.address}</div>52 <div className="card-meta">53 {l.sector && <span>{l.sector}</span>}54 {l.sector && l.city && <span className="sep" />}55 {l.city && <span>{l.city}</span>}56 {l.ks_global != null && <KaScoreBadge score={l.ks_global} />}57 </div>58 <div className="card-foot">59 <span className="source-tag">{sourceName(l.source)}</span>60 {l.availability && <span className="avail">{l.availability}</span>}61 </div>62 </div>63 </Link>64 );65}66