SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
2.7 KB · 66 lines tsx
Raw Blame History
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