wrongmove: guard property cards against null backend fields (fix BUY crash)

BUY listings can come back from the API with null `total_price`, `qm`,
`qmprice`, `rooms`, and `last_seen` even though the TypeScript types
declare them non-nullable. The cards called `.toLocaleString()` and
`.split('T')` on those values, throwing TypeError and tripping the
ErrorBoundary — which is the "BUY part of the page crashes" symptom.

Coerce numeric fields via a `safeNum` helper (0 fallback), gate the
"Nd ago" line on a finite last_seen, and apply the same guards in
PropertyCard, PropertyCardCompact, SwipeCard, and the price-history
section of ListingDetail. Added regression tests asserting both card
variants render with all-null backend fields without throwing.
This commit is contained in:
Viktor Barzin 2026-05-10 21:17:41 +00:00
parent 73823bd381
commit 0b5308200e
6 changed files with 97 additions and 30 deletions

View file

@ -16,8 +16,15 @@ export function PropertyCardCompact({
avgPricePerSqm,
onClick,
}: PropertyCardCompactProps) {
const isGoodDeal = avgPricePerSqm && property.qmprice > 0 && property.qmprice < avgPricePerSqm * 0.9;
const isExpensive = avgPricePerSqm && property.qmprice > avgPricePerSqm * 1.1;
// BUY listings may have null numeric fields; coerce so renders don't throw.
const safeNum = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
const safeTotalPrice = safeNum(property.total_price);
const safeQm = safeNum(property.qm);
const safeQmprice = safeNum(property.qmprice);
const safeRooms = safeNum(property.rooms);
const isGoodDeal = avgPricePerSqm && safeQmprice > 0 && safeQmprice < avgPricePerSqm * 0.9;
const isExpensive = avgPricePerSqm && safeQmprice > avgPricePerSqm * 1.1;
const priceIndicator = isGoodDeal
? { dotColor: 'bg-[var(--deal-good)]', label: 'Good deal' }
@ -37,7 +44,7 @@ export function PropertyCardCompact({
{property.photo_thumbnail && (
<img
src={property.photo_thumbnail}
alt={`${property.rooms}-bed, £${property.total_price.toLocaleString()}`}
alt={`${safeRooms}-bed, £${safeTotalPrice.toLocaleString()}`}
className="w-full h-full object-cover"
/>
)}
@ -48,7 +55,7 @@ export function PropertyCardCompact({
{/* Price bold */}
<div className="flex items-center gap-1.5">
<span className="font-bold text-base">
£{property.total_price.toLocaleString()}
£{safeTotalPrice.toLocaleString()}
{property.listing_type !== 'BUY' && (
<span className="text-muted-foreground font-normal text-sm">/mo</span>
)}
@ -62,10 +69,10 @@ export function PropertyCardCompact({
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span className="flex items-center gap-1">
<Bed className="h-3.5 w-3.5" />
{property.rooms} bed
{safeRooms} bed
</span>
<span>·</span>
<span>{property.qm} m²</span>
<span>{safeQm} m²</span>
</div>
{/* Location */}