From e2c22f025f600e4f66f5fbe0439bafda3701c6da Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 22 Feb 2026 00:49:32 +0000 Subject: [PATCH] Expand swipe card to 50/50 photo/details split with all info - Card now fills available height with photo carousel in top half and property details in bottom half - Details section shows: price, beds/sqm/price-per-sqm, agency, available date, all POI distances, and price history summary - Fix DialogTitle accessibility warning in ListingDetailSheet and MobileBottomSheet (add sr-only Drawer.Title) --- .../src/components/ListingDetailSheet.tsx | 1 + frontend/src/components/MobileBottomSheet.tsx | 1 + frontend/src/components/SwipeCard.tsx | 59 +++++++++++++++---- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/ListingDetailSheet.tsx b/frontend/src/components/ListingDetailSheet.tsx index 82b6da0..53734b1 100644 --- a/frontend/src/components/ListingDetailSheet.tsx +++ b/frontend/src/components/ListingDetailSheet.tsx @@ -46,6 +46,7 @@ export function ListingDetailSheet({ + Listing Details
{isLoading && ( diff --git a/frontend/src/components/MobileBottomSheet.tsx b/frontend/src/components/MobileBottomSheet.tsx index 3a62d54..a69e467 100644 --- a/frontend/src/components/MobileBottomSheet.tsx +++ b/frontend/src/components/MobileBottomSheet.tsx @@ -77,6 +77,7 @@ export function MobileBottomSheet({ className="fixed inset-x-0 bottom-0 z-40 flex flex-col rounded-t-xl bg-background border-t shadow-lg" style={{ maxHeight: '85vh' }} > + Property Listings {/* Drag handle */}
diff --git a/frontend/src/components/SwipeCard.tsx b/frontend/src/components/SwipeCard.tsx index e7add7a..ea685ed 100644 --- a/frontend/src/components/SwipeCard.tsx +++ b/frontend/src/components/SwipeCard.tsx @@ -2,7 +2,7 @@ import { useRef, useState, useCallback, useEffect } from 'react'; import { animated, useSpring } from '@react-spring/web'; import { useDrag } from '@use-gesture/react'; import useEmblaCarousel from 'embla-carousel-react'; -import { Bed, Maximize2, ExternalLink, ChevronLeft, ChevronRight } from 'lucide-react'; +import { Bed, Maximize2, ExternalLink, ChevronLeft, ChevronRight, Building2, Calendar } from 'lucide-react'; import type { PropertyFeature } from '@/types'; interface SwipeCardProps { @@ -98,12 +98,13 @@ export function SwipeCard({ feature, onSwipe, onTap, isTop, stackIndex }: SwipeC touchAction: 'none', position: 'absolute' as const, width: '100%', + height: '100%', zIndex: 10 - stackIndex, top: stackIndex * 8, }} className="cursor-grab active:cursor-grabbing" > -
+
{/* Color overlay */} {isTop && ( )} - {/* Photo carousel */} -
+ {/* Photo carousel — top half */} +
{photos.length > 1 ? ( <>
@@ -159,16 +160,18 @@ export function SwipeCard({ feature, onSwipe, onTap, isTop, stackIndex }: SwipeC
- {/* Details */} -
-
+ {/* Details — bottom half */} +
+ {/* Price */} +
£{p.total_price.toLocaleString()} {p.listing_type !== 'BUY' && ( - /mo + /mo )}
-
+ {/* Key stats */} +
{p.rooms} bed @@ -178,10 +181,24 @@ export function SwipeCard({ feature, onSwipe, onTap, isTop, stackIndex }: SwipeC £{p.qmprice}/m²
+ {/* Agency & availability */} +
+ {p.agency && ( + + {p.agency} + + )} + {p.available_from && ( + + Available {p.available_from} + + )} +
+ {/* POI distances */} {p.poi_distances && p.poi_distances.length > 0 && ( -
- {p.poi_distances.slice(0, 4).map((d) => ( +
+ {p.poi_distances.map((d) => ( )} + + {/* Price history */} + {p.price_history && p.price_history.length > 1 && ( +
+ {p.price_history.length} price changes + {(() => { + const first = p.price_history[0]?.price; + const last = p.price_history[p.price_history.length - 1]?.price; + if (first && last && first !== last) { + const diff = last - first; + return ( + + ({diff < 0 ? '' : '+'}£{diff.toLocaleString()}) + + ); + } + return null; + })()} +
+ )}