# Property Detail Page - Component Map & Architecture Diagram

## 🎯 Page Component Hierarchy

```
PublicListingDetailPage (Server) [apps/web/app/[locale]/(public)/listings/[id]/page.tsx]
│
├─ JSON-LD Structured Data
│  ├─ JsonLd (Breadcrumb)
│  └─ JsonLd (Listing Schema)
│
└─ ListingDetailClient (Client) [apps/web/components/listings/listing-detail-client.tsx]
   │
   ├─ Breadcrumb Navigation
   │  └─ Link components
   │
   ├─ Header Section
   │  ├─ Title & Description
   │  ├─ Badge (SALE/RENT)
   │  ├─ Badge (Property Type)
   │  ├─ Price Display
   │  └─ AddToCompareButton
   │
   ├─ ImageGallery [MAIN FEATURE]
   │  [apps/web/components/listings/image-gallery.tsx]
   │  ├─ Main Image Display
   │  │  ├─ Previous Button
   │  │  ├─ Image (Next.js Image)
   │  │  ├─ Next Button
   │  │  └─ Counter Badge
   │  │
   │  └─ Thumbnail Navigation
   │     ├─ Thumbnail Item 1
   │     ├─ Thumbnail Item 2
   │     └─ Thumbnail Item N
   │
   ├─ Quick Stats Bar
   │  ├─ QuickStat (Area)
   │  ├─ QuickStat (Bedrooms)
   │  ├─ QuickStat (Bathrooms)
   │  ├─ QuickStat (Floors)
   │  └─ QuickStat (Direction)
   │
   ├─ Main Content (2/3 width - lg:col-span-2)
   │  │
   │  ├─ Description Card
   │  │  ├─ CardHeader
   │  │  └─ CardContent
   │  │
   │  ├─ Details Card
   │  │  ├─ CardHeader
   │  │  ├─ InfoItem (Property Type)
   │  │  ├─ InfoItem (Area)
   │  │  ├─ InfoItem (Bedrooms)
   │  │  ├─ InfoItem (Bathrooms)
   │  │  ├─ InfoItem (Floors)
   │  │  ├─ InfoItem (Direction)
   │  │  ├─ InfoItem (Year Built)
   │  │  ├─ InfoItem (Legal Status)
   │  │  └─ InfoItem (Project)
   │  │
   │  ├─ Amenities Card (conditional)
   │  │  ├─ CardHeader
   │  │  └─ Badge(s) for each amenity
   │  │
   │  └─ Map Card
   │     ├─ CardHeader
   │     └─ ListingMap (dynamic import)
   │
   └─ Sidebar (1/3 width - sticky)
      │
      ├─ Contact Card (sticky)
      │  ├─ Seller Avatar & Name
      │  ├─ Seller Phone
      │  ├─ Call Button
      │  ├─ Message Button
      │  └─ Agent Info (conditional)
      │
      ├─ AiEstimateButton
      │
      └─ Stats Card
         ├─ View Count
         ├─ Save Count
         ├─ Inquiry Count
         └─ Published Date
```

---

## 🖼️ Image Gallery Component Details

### File: `apps/web/components/listings/image-gallery.tsx`

```
ImageGallery (Client Component)
│
├─ Props:
│  ├─ media: PropertyMedia[]
│  └─ className?: string
│
├─ State:
│  └─ selectedIndex: number
│
├─ Layout:
│  │
│  ├─ Main Image Container
│  │  ├─ className: "aspect-video"
│  │  ├─ Image (Next.js)
│  │  ├─ Overlay: Previous Button
│  │  ├─ Overlay: Next Button
│  │  └─ Overlay: Counter Badge
│  │
│  └─ Thumbnail Container (if images.length > 1)
│     ├─ className: "flex gap-2 overflow-x-auto"
│     └─ ThumbnailButton (for each image)
│        ├─ Image (Next.js)
│        ├─ Border: selected ? primary : transparent
│        ├─ Opacity: 70% (unselected)
│        └─ Hover: opacity 100%
│
└─ Handlers:
   ├─ handlePrev()
   ├─ handleNext()
   └─ handleSelectIndex(index)
```

### Data Flow
```
Property.media (from API)
  ↓
Filter by type === 'image'
  ↓
Sort by order property
  ↓
[selectedIndex, setSelectedIndex] → selectedIndex
  ↓
Image.url at index → Main Display
All images → Thumbnails
selectedIndex → Highlighted Thumbnail
```

---

## 📱 Image Upload Component

### File: `apps/web/components/listings/image-upload.tsx`

```
ImageUpload (Client Component)
│
├─ Props:
│  ├─ images: ImageFile[]
│  ├─ onChange: (images: ImageFile[]) => void
│  ├─ maxFiles?: number (default: 20)
│  └─ className?: string
│
├─ State:
│  └─ isDragging: boolean
│
├─ Drag & Drop Zone
│  ├─ onDragOver → setIsDragging(true)
│  ├─ onDragLeave → setIsDragging(false)
│  ├─ onDrop → addFiles()
│  └─ onClick → inputRef.click()
│
├─ File Input
│  ├─ accept: "image/jpeg,image/png,image/webp"
│  ├─ multiple: true
│  └─ hidden: true
│
└─ Preview Grid
   └─ For each image:
      ├─ Image preview (URL.createObjectURL)
      ├─ Cover badge (first image)
      ├─ Delete button on hover
      └─ Cleanup on unmount (URL.revokeObjectURL)

Validation:
├─ Allowed types: JPEG, PNG, WebP
├─ Max size: 10MB per file
└─ Max count: 20 files
```

---

## 🧩 Related Components

### SearchResults & PropertyCard

```
SearchResults
│
└─ Grid of PropertyCards
   │
   └─ PropertyCard (for each listing)
      │
      ├─ Link to /listings/{id}
      │
      └─ Card
         ├─ Image Container
         │  ├─ Image (media[0])
         │  ├─ Badge: Transaction Type (overlay)
         │  ├─ Badge: Property Type (overlay)
         │  ├─ AddToCompareButton (overlay)
         │  └─ Badge: Media Count (bottom-right)
         │
         └─ Content
            ├─ Price
            ├─ Title
            ├─ Location
            └─ Badges (Area, Bedrooms, etc.)
```

---

## 🌐 Data Flow & API Mapping

### Server to Client Flow

```
1. Browser Request
   URL: /vi/listings/abc123
   │
   ↓
2. Next.js Route Handler
   [locale]/[id]/page.tsx (Server Component)
   │
   ├─ fetchListingById('abc123')
   │  └─ API: GET /api/v1/listings/abc123
   │     ↓
   │     ListingDetail {
   │       id: string
   │       property: {
   │         media: PropertyMedia[]  ← Images
   │       }
   │       seller: {...}
   │       agent: {...}
   │     }
   │
   ├─ generateMetadata()
   │  └─ Uses property.media[0] for OG image
   │
   ├─ generateJsonLd()
   │  └─ Structured data for SEO
   │
   └─ <ListingDetailClient listing={data} />
      │
      ↓
   3. Client Component (Hydrated)
      │
      ├─ <ImageGallery media={property.media} />
      │  └─ Local state: selectedIndex
      │
      └─ Other interactive components
```

---

## 🎨 Styling Architecture

### Tailwind CSS Structure

```
Root CSS Variables (globals.css)
│
├─ Colors (HSL format)
│  ├─ --primary
│  ├─ --secondary
│  ├─ --background
│  ├─ --foreground
│  ├─ --muted
│  ├─ --accent
│  └─ --card
│
├─ Spacing
│  └─ Uses standard Tailwind scale
│
└─ Radius
   └─ --radius

Tailwind Config (tailwind.config.ts)
│
├─ Extends theme
│  ├─ Colors mapped from CSS variables
│  └─ Border radius configuration
│
└─ Plugins
   └─ tailwindcss-animate
```

### Component-Level Patterns

```
ui/button.tsx
├─ CVA (Class Variance Authority)
│  ├─ Base classes
│  ├─ Variants
│  │  ├─ variant: default, outline, ghost, etc.
│  │  └─ size: sm, default, lg, icon
│  └─ defaultVariants
│
└─ Usage: <Button variant="default" size="lg" />

ui/badge.tsx
├─ CVA variants
│  ├─ default (primary)
│  ├─ secondary
│  ├─ outline
│  └─ ...colors (success, warning, info)
│
└─ Usage: <Badge variant="secondary">Text</Badge>
```

---

## 📊 State Management Patterns

### Gallery Local State
```
Component: ImageGallery
State: selectedIndex (number)
├─ Initialize: 0
├─ Update on: prev, next, thumbnail click
└─ Use: Display main image, highlight thumbnail
```

### Global State (Zustand)
```
Auth Store
├─ user: UserProfile | null
├─ isAuthenticated: boolean
├─ isLoading: boolean
├─ error: string | null
└─ Actions: login, logout, fetchProfile

Comparison Store
├─ selectedIds: string[] (persisted)
├─ listings: ListingDetail[]
├─ isLoading: boolean
├─ error: string | null
└─ Actions: addToCompare, removeFromCompare, etc.
```

---

## 🔗 Import Map

### File Structure References

```
apps/web/
│
├─ app/[locale]/(public)/listings/[id]/
│  └─ page.tsx ──────────────────────────── ENTRY POINT
│     │
│     └─ imports:
│        ├─ ListingDetailClient
│        ├─ JsonLd
│        ├─ fetchListingById
│        └─ formatting utilities
│
├─ components/
│  │
│  ├─ listings/
│  │  ├─ listing-detail-client.tsx ────────── CLIENT COMPONENT
│  │  │  ├─ imports: ImageGallery
│  │  │  ├─ imports: AddToCompareButton
│  │  │  ├─ imports: AiEstimateButton
│  │  │  └─ dynamic: ListingMap
│  │  │
│  │  ├─ image-gallery.tsx ───────────────── MAIN IMAGE DISPLAY
│  │  │  └─ imports: Next.js Image
│  │  │
│  │  └─ image-upload.tsx ──────────────────── FILE UPLOAD
│  │     └─ imports: Button component
│  │
│  ├─ ui/
│  │  ├─ button.tsx
│  │  ├─ badge.tsx
│  │  ├─ card.tsx
│  │  ├─ dialog.tsx
│  │  └─ ...other UI components
│  │
│  └─ search/
│     └─ property-card.tsx ──────────────── THUMBNAIL VIEW
│        └─ imports: ImageGallery pattern
│
└─ lib/
   ├─ listings-api.ts ────────────────────── API TYPES & FUNCTIONS
   │  └─ PropertyMedia interface
   │  └─ ListingDetail interface
   │
   ├─ auth-store.ts
   ├─ comparison-store.ts
   └─ utils.ts
```

---

## 📈 Component Complexity Levels

### Level 1: Simple UI Components
```
Badge, Button, Input, Label
├─ Props: basic props + variants
├─ State: none
└─ Interactions: click, focus, hover
```

### Level 2: Composite Components
```
Card (Header + Content + Footer)
ImageUpload (Drag-drop + Preview grid)
├─ Props: content children
├─ State: local state
└─ Interactions: click, drag, input
```

### Level 3: Feature Components
```
ImageGallery (Main + Thumbnails)
PropertyCard (Link + Image + Info)
├─ Props: data + callbacks
├─ State: selected index, UI state
└─ Interactions: navigation, filtering
```

### Level 4: Page Components
```
ListingDetailClient (Full page layout)
├─ Props: listing data
├─ State: multiple features
├─ Interactions: all user interactions
└─ Children: multiple feature components
```

---

## 🚀 Performance Considerations

### Image Optimization

```
Image Component Strategy:
├─ Next.js Image component
│  ├─ Automatic format selection (WebP, AVIF)
│  ├─ Responsive serving via srcset
│  └─ On-demand resizing
│
├─ Lazy Loading
│  ├─ Main image: priority={selectedIndex === 0}
│  └─ Thumbnails: no priority (lazy)
│
└─ Responsive Sizing
   ├─ sizes prop: tells browser image dimensions
   └─ Prevents layout shift
```

### Code Splitting

```
Dynamic Imports:
├─ ListingMap (heavy, maps library)
│  ├─ ssr: false (client-only)
│  └─ loading: placeholder component
│
└─ Other components: bundled with page
```

### State Optimization

```
Zustand:
├─ Selector pattern: useStore(state => state.field)
├─ Only re-render on selected state change
└─ Persist middleware: localStorage only on needed data
```

---

## 🔄 Navigation Flow

### User Journey - Image Gallery

```
1. User visits /vi/listings/123
   └─ Page loads with first image (priority=true)
   
2. User interacts with gallery:
   ├─ Click thumbnail
   │  └─ setSelectedIndex(index) → main image updates
   │
   ├─ Click next button
   │  └─ setSelectedIndex(i + 1) → wraps to 0
   │
   └─ Click prev button
      └─ setSelectedIndex(i - 1) → wraps to last
```

### User Journey - File Upload (Create/Edit Listing)

```
1. User opens listing form
   └─ ImageUpload component mounts

2. User adds images:
   ├─ Drag & drop files
   │  └─ addFiles() → filter + validate → onChange()
   │
   └─ Click to browse
      └─ Select files → same as drag & drop

3. User removes image:
   └─ removeImage(index) → cleanup URL → onChange()

4. User submits form:
   └─ Images uploaded via listingsApi.uploadMedia()
```

---

## 📋 Component Checklist

### Image Gallery Features
- [x] Main image display (responsive)
- [x] Previous/Next navigation
- [x] Image counter badge
- [x] Thumbnail navigation (scrollable)
- [x] Selected thumbnail highlighting
- [x] Empty state fallback
- [ ] Lightbox/modal zoom (NOT implemented)
- [ ] Keyboard navigation (NOT implemented)
- [ ] Touch gestures (NOT implemented)

### Image Upload Features
- [x] Drag & drop
- [x] Click to browse
- [x] File type validation
- [x] File size validation
- [x] Preview grid
- [x] Delete button
- [x] Cover photo indicator
- [x] URL cleanup on unmount
- [ ] Progress bar (NOT implemented)
- [ ] Multiple upload progress (NOT implemented)

### SEO & Metadata
- [x] Open Graph image
- [x] Twitter Card image
- [x] JSON-LD schema
- [x] Canonical URL
- [x] Alternate language links
- [x] Descriptive alt text

---

## 🛠️ Maintenance Guide

### Adding New Image Features

**Add to ImageGallery:**
1. Define new prop in interface
2. Update state if needed
3. Update render logic
4. Test responsive behavior
5. Update TypeScript types

**Example - Add zoom feature:**
```typescript
interface ImageGalleryProps {
  media: PropertyMedia[];
  className?: string;
  onImageClick?: (index: number) => void;  // NEW
}

// In component:
const [isZoomed, setIsZoomed] = useState(false);  // NEW

<Image
  onClick={() => setIsZoomed(true)}  // NEW
  cursor={isZoomed ? 'zoom-out' : 'zoom-in'}  // NEW
/>
```

### Updating Image Data Structure

**If PropertyMedia changes:**
1. Update interface in `lib/listings-api.ts`
2. Update API response mapping
3. Update gallery component to use new fields
4. Update tests
5. Update API documentation

---

## 📚 Related Documentation

See also:
- `PROPERTY_DETAIL_PAGE_ANALYSIS.md` - Comprehensive analysis
- `PROPERTY_DETAIL_QUICK_REFERENCE.md` - Code snippets & patterns

