# UI/UX Redesign Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Redesign the realestate-crawler frontend from sidebar-filter layout to horizontal-filter-bar layout with React Router, redesigned property cards, refined visual system, and tabbed listing detail. **Architecture:** Replace the sidebar FilterPanel with a horizontal FilterBar component. Add react-router-dom for URL-based navigation and deep linking. Extract shared utilities to eliminate duplication. Refine the color palette from plain shadcn neutral to a teal-accented property search theme. All changes are frontend-only — no backend API changes needed. **Tech Stack:** React 19, TypeScript, Tailwind CSS 4, shadcn/ui, react-router-dom, Mapbox GL, Vite --- ## Phase 1: Foundation (Router + Shared Utils + Color System) ### Task 1: Install react-router-dom **Files:** - Modify: `frontend/package.json` **Step 1: Install the dependency** Run: `cd /Users/viktorbarzin/code/realestate-crawler/frontend && npm install react-router-dom` **Step 2: Verify installation** Run: `grep react-router-dom package.json` Expected: `"react-router-dom": "^7..."` **Step 3: Commit** ```bash cd /Users/viktorbarzin/code/realestate-crawler git add frontend/package.json frontend/package-lock.json git commit -m "chore: add react-router-dom dependency" ``` --- ### Task 2: Extract shared utility functions Currently `formatDuration`, `formatCurrency`, `formatDate`, `TravelModeIcon`, and `isTerminalStatus` are duplicated across PropertyCard.tsx, ListingDetail.tsx, MobileBottomSheet.tsx, StatsBar.tsx, TaskIndicator.tsx, TaskProgressDrawer.tsx, useTaskProgress.ts, and App.tsx. **Files:** - Create: `frontend/src/utils/format.ts` - Create: `frontend/src/utils/taskUtils.ts` - Modify: `frontend/src/components/PropertyCard.tsx` — remove local formatDuration/formatCurrency/formatDate, import from utils - Modify: `frontend/src/components/ListingDetail.tsx` — same - Modify: `frontend/src/components/MobileBottomSheet.tsx` — same - Modify: `frontend/src/components/StatsBar.tsx` — remove local formatCurrency, import from utils - Modify: `frontend/src/App.tsx` — remove local isTerminalStatus, import from utils - Modify: `frontend/src/components/TaskIndicator.tsx` — remove local isTerminalStatus/taskStateToResult, import - Modify: `frontend/src/components/TaskProgressDrawer.tsx` — remove local isTerminalStatus/taskStateToResult, import - Modify: `frontend/src/hooks/useTaskProgress.ts` — remove local isTerminalStatus, import **Step 1: Create `frontend/src/utils/format.ts`** Extract these functions by reading them from PropertyCard.tsx (lines ~18-55) and consolidating: - `formatCurrency(value: number): string` — formats as £X,XXX - `formatDuration(seconds: number): string` — formats as Xmin / Xhr Xmin - `formatDate(dateStr: string): string` — formats as "3d ago" / "2w ago" etc. - `formatPricePerSqm(price: number, sqm: number | null): string | null` **Step 2: Create `frontend/src/utils/taskUtils.ts`** Extract from App.tsx and TaskIndicator.tsx: - `isTerminalStatus(status: string): boolean` - `taskStateToResult(ts: TaskState): TaskResult` **Step 3: Update all importing files** to use the shared utils instead of local definitions. Search for each function name and replace local definitions with imports. **Step 4: Run tests** Run: `cd /Users/viktorbarzin/code/realestate-crawler/frontend && npx vitest run` Expected: All existing tests pass **Step 5: Commit** ```bash git add frontend/src/utils/format.ts frontend/src/utils/taskUtils.ts frontend/src/components/ frontend/src/App.tsx frontend/src/hooks/ git commit -m "refactor: extract shared utility functions to eliminate duplication" ``` --- ### Task 3: Update color palette in index.css Replace the achromatic neutral palette with a teal-accented property search palette. **Files:** - Modify: `frontend/src/index.css:44-77` (the `:root` block) **Step 1: Update the `:root` CSS variables** Change the `:root` block to use teal-accented colors. Key changes: - `--primary`: change from `oklch(0.205 0 0)` (pure black) to a dark slate `oklch(0.208 0.042 265.755)` (slate 900 with hint of blue) - `--accent`: change from neutral gray to teal `oklch(0.627 0.14 175.5)` (teal 600) - `--ring`: update to teal ring color - Add custom properties for deal indicators: - `--color-deal-good: oklch(0.696 0.17 162.48)` (emerald 500) - `--color-deal-above: oklch(0.795 0.184 86.047)` (amber 500) Also add these to the `@theme inline` block: ```css --color-deal-good: var(--deal-good); --color-deal-above: var(--deal-above); ``` **Step 2: Verify Vite HMR picks up the changes** The running Vite dev server should hot-reload. Check the browser at localhost:5173. **Step 3: Commit** ```bash git add frontend/src/index.css git commit -m "style: update color palette to teal-accented property search theme" ``` --- ### Task 4: Set up React Router with URL-based filter state **Files:** - Modify: `frontend/src/main.tsx` — wrap App in BrowserRouter - Create: `frontend/src/hooks/useFilterParams.ts` — hook that syncs filter state with URL search params - Modify: `frontend/src/App.tsx` — use useFilterParams instead of local useState for queryParameters and viewMode **Step 1: Update main.tsx** ```tsx import { BrowserRouter } from 'react-router-dom'; // Wrap in ``` **Step 2: Create useFilterParams hook** This hook: - Reads filter values from URL search params on mount - Returns `[filterValues, setFilterValues]` where setFilterValues updates both state and URL - Reads `viewMode` from the URL pathname (`/map`, `/split`, `/list`, `/saved`) - Falls back to DEFAULT_FILTER_VALUES for missing params - Handles the `/callback` route for auth Key URL params: `type`, `minPrice`, `maxPrice`, `minBeds`, `maxBeds`, `minSqm`, `maxSqm`, `minPriceSqm`, `maxPriceSqm`, `furnish`, `district`, `lastSeenDays`, `availableFrom`, `sort`, `metric` **Step 3: Update App.tsx** - Import `useFilterParams` and `useNavigate` from react-router-dom - Replace `useState(null)` for queryParameters with the hook - Replace `useState('map')` for viewMode with URL-derived state - Keep the `/callback` route check but use React Router's Routes/Route **Step 4: Test that the app still renders with the router** Navigate to `http://localhost:5173/map` in the browser and verify it loads. **Step 5: Run tests** Run: `cd /Users/viktorbarzin/code/realestate-crawler/frontend && npx vitest run` Fix any tests that break due to missing router context (wrap test renders in ``). **Step 6: Commit** ```bash git add frontend/src/main.tsx frontend/src/hooks/useFilterParams.ts frontend/src/App.tsx git commit -m "feat: add React Router with URL-based filter state and deep linking" ``` --- ## Phase 2: Filter Bar (Replace Sidebar) ### Task 5: Create the FilterBar component This is the core layout change. Create a new horizontal FilterBar that replaces the sidebar FilterPanel. **Files:** - Create: `frontend/src/components/FilterBar.tsx` - Create: `frontend/src/components/FilterChips.tsx` **Step 1: Create FilterBar.tsx** A horizontal bar with: - Compact dropdown selectors for: Price Range (min-max), Bedrooms (min-max), Min Size - A "More Filters" button that opens a Popover with: Max Size, Price/m² range, Furnishing toggles, District input, Available From date, Last Seen Days, POI travel time filters - Right-aligned action buttons: "Show Listings" (primary) and "Scrape New" (secondary) - Uses the same Zod schema and react-hook-form as the current FilterPanel - Emits the same `onSubmit(action, parameters)` callback The bar layout: `flex items-center gap-2 px-4 h-10 bg-muted/50 border-b` Use shadcn Popover for dropdown selectors (not full Select components — we want compact inline triggers). **Step 2: Create FilterChips.tsx** A component that renders active filter values as removable chips: ```tsx
{chips.map(chip => ( {chip.label} ))}
``` Only renders when there are active non-default filters. **Step 3: Run tests** Run: `npx vitest run` **Step 4: Commit** ```bash git add frontend/src/components/FilterBar.tsx frontend/src/components/FilterChips.tsx git commit -m "feat: create horizontal FilterBar and FilterChips components" ``` --- ### Task 6: Integrate FilterBar into App.tsx layout **Files:** - Modify: `frontend/src/App.tsx` — replace FilterPanel sidebar with FilterBar - Modify: `frontend/src/components/Header.tsx` — add Rent/Buy toggle, remove elements that moved **Step 1: Update the desktop layout in App.tsx** Replace the current desktop layout structure: ``` sidebar (FilterPanel w-80) + main content ``` With: ``` FilterBar (full-width, below header) FilterChips (full-width, conditional) main content (full-width) StatusBar (full-width) ``` Remove the `` render from the desktop layout. Keep it for mobile as a sheet/modal (or replace with FilterBar in a full-screen modal). **Step 2: Update Header.tsx** - Add Rent/Buy toggle tabs to the header (currently in FilterPanel) - The toggle should call the filter update function to switch listing_type **Step 3: Move VisualizationCard "Color by" control into StatsBar** The metric selector (Color by dropdown) currently sits below the filter panel. Move it into the StatsBar component, next to the view mode toggle. **Step 4: Update mobile layout** Replace the mobile filter Sheet (sidebar slide-in) with a full-screen Dialog/modal containing the FilterBar fields stacked vertically with a sticky "Apply Filters" button at the bottom. **Step 5: Verify in browser** Navigate to localhost:5173 and verify: - Filter bar appears below header - Map takes full viewport width - Filters work correctly - Mobile filter opens as modal **Step 6: Run tests** Run: `npx vitest run` **Step 7: Commit** ```bash git add frontend/src/App.tsx frontend/src/components/Header.tsx frontend/src/components/StatsBar.tsx git commit -m "feat: integrate FilterBar into layout, remove sidebar, full-width map" ``` --- ## Phase 3: Property Cards Redesign ### Task 7: Redesign PropertyCard component **Files:** - Modify: `frontend/src/components/PropertyCard.tsx` **Step 1: Redesign the full card variant** Update the card layout to match the design: - Larger image carousel (16:10 aspect ratio via `aspect-[16/10]`) - Heart and external link buttons overlaid on the image (absolute positioned, top-right) - Price as the dominant element below image (`text-xl font-bold tracking-tight`) - Deal indicator as a colored dot + text (using new `--color-deal-good` / `--color-deal-above` CSS vars) - Key metrics on one line with middle dots: `2 bed · 65 m² · £38/m²` - Location below metrics - POI travel times as compact inline badges with mode icons - Agency + freshness de-emphasized at bottom **Step 2: Update compact card variant** Ensure PropertyCardCompact also follows the new visual hierarchy (price dominant, location secondary). **Step 3: Verify in browser** Check both full and compact card rendering. **Step 4: Run tests** Run: `npx vitest run -- --grep PropertyCard` **Step 5: Commit** ```bash git add frontend/src/components/PropertyCard.tsx frontend/src/components/PropertyCardCompact.tsx git commit -m "style: redesign PropertyCard with better visual hierarchy" ``` --- ### Task 8: Redesign ListingDetail with tabbed sections **Files:** - Modify: `frontend/src/components/ListingDetail.tsx` - Modify: `frontend/src/components/ListingDetailSheet.tsx` **Step 1: Update ListingDetailSheet to be larger** Change the drawer to take 60% viewport width on desktop (currently `sm:!max-w-lg` → `sm:!max-w-2xl`) and 90vh on mobile. **Step 2: Add tabbed sections to ListingDetail** Use shadcn Tabs component to organize content into: - **Overview** tab: Key features list + full description (default active) - **Travel** tab: POI distances table with all travel modes - **Price History** tab: Price history timeline (currently inline) - **Details** tab: Property attributes grid (type, furnishing, council tax, lease, service charge, available from) Keep the photo gallery and header (price, metrics, actions) above the tabs. **Step 3: Verify in browser** Open a listing detail sheet and verify tabs work. **Step 4: Commit** ```bash git add frontend/src/components/ListingDetail.tsx frontend/src/components/ListingDetailSheet.tsx git commit -m "feat: redesign listing detail with tabbed sections" ``` --- ## Phase 4: Polish & Cleanup ### Task 9: Add Error Boundary **Files:** - Create: `frontend/src/components/ErrorBoundary.tsx` - Modify: `frontend/src/main.tsx` — wrap App in ErrorBoundary **Step 1: Create a simple error boundary** ```tsx import { Component, type ReactNode } from 'react'; interface Props { children: ReactNode; } interface State { hasError: boolean; error: Error | null; } export class ErrorBoundary extends Component { state: State = { hasError: false, error: null }; static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return (

Something went wrong

{this.state.error?.message}

); } return this.props.children; } } ``` **Step 2: Wrap App in main.tsx** **Step 3: Commit** ```bash git add frontend/src/components/ErrorBoundary.tsx frontend/src/main.tsx git commit -m "feat: add error boundary to prevent white-screen crashes" ``` --- ### Task 10: Clean up deprecated FilterPanel references **Files:** - Optionally delete or mark deprecated: `frontend/src/components/FilterPanel.tsx` - Clean up any remaining references to FilterPanel in App.tsx - Remove unused sidebar CSS variables from index.css if no longer needed **Step 1: Remove FilterPanel from imports and usage in App.tsx** If FilterPanel is no longer used anywhere (replaced by FilterBar), remove the import and any dead code. **Step 2: Run final test suite** Run: `npx vitest run` Expected: All tests pass **Step 3: Build check** Run: `npx tsc -b && npx vite build` Expected: No type errors, successful build **Step 4: Commit** ```bash git add -A git commit -m "chore: clean up deprecated FilterPanel references" ``` --- ## Phase 5: Verification ### Task 11: End-to-end visual verification **Step 1: Start the full dev environment** Start backend services (Docker compose or local) and the frontend. **Step 2: Log in and verify all views** - Map view: hexgrid renders, clicking cells shows popups - Filter bar: all dropdowns work, chips appear/remove correctly - URL state: changing filters updates URL, refreshing preserves filters - Split view: map + list side by side - List view: redesigned cards with correct hierarchy - Listing detail: tabbed sections work - Mobile: bottom sheet, filter modal, swipe review - Saved view: liked listings display correctly **Step 3: Take screenshots of before/after** Use Chrome CDP at localhost:9222 to capture final state. **Step 4: Final commit with any fixes** --- ## Summary of Files Changed | Action | File | |--------|------| | Create | `frontend/src/utils/format.ts` | | Create | `frontend/src/utils/taskUtils.ts` | | Create | `frontend/src/hooks/useFilterParams.ts` | | Create | `frontend/src/components/FilterBar.tsx` | | Create | `frontend/src/components/FilterChips.tsx` | | Create | `frontend/src/components/ErrorBoundary.tsx` | | Modify | `frontend/package.json` (add react-router-dom) | | Modify | `frontend/src/main.tsx` (router + error boundary) | | Modify | `frontend/src/index.css` (color palette) | | Modify | `frontend/src/App.tsx` (layout restructure, router integration) | | Modify | `frontend/src/components/Header.tsx` (Rent/Buy toggle) | | Modify | `frontend/src/components/StatsBar.tsx` (add metric selector) | | Modify | `frontend/src/components/PropertyCard.tsx` (visual redesign) | | Modify | `frontend/src/components/PropertyCardCompact.tsx` (visual updates) | | Modify | `frontend/src/components/ListingDetail.tsx` (tabbed layout) | | Modify | `frontend/src/components/ListingDetailSheet.tsx` (larger drawer) | | Modify | `frontend/src/components/TaskIndicator.tsx` (use shared utils) | | Modify | `frontend/src/components/TaskProgressDrawer.tsx` (use shared utils) | | Modify | `frontend/src/components/MobileBottomSheet.tsx` (use shared utils) | | Modify | `frontend/src/hooks/useTaskProgress.ts` (use shared utils) | | Deprecate | `frontend/src/components/FilterPanel.tsx` |