Files
TenerifeProp/docs/spec-issue-9.md

16 KiB

🎨 Technical Specification: Issue #9 - Property Navigation & Site Architecture

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                        FRONTEND                              │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐    │
│  │ index.html   │──▶│ property.html│   │ admin.html   │    │
│  │ (Catalog)    │   │ (Detail)     │   │ (Dashboard)  │    │
│  └──────┬───────┘   └──────┬───────┘   └──────────────┘    │
│         │                  │                                │
│  ┌──────▼───────┐   ┌──────▼───────┐                        │
│  │ app.js       │   │ property.js  │                        │
│  │ - renderCard │   │ - loadProp   │                        │
│  │ - filters    │   │ - gallery    │                        │
│  │ - map        │   │ - similar    │                        │
│  └──────┬───────┘   └──────┬───────┘                        │
│         │                  │                                │
│  ┌──────▼──────────────────▼───────┐                        │
│  │           api.js                │                        │
│  │  getProperties, getProperty     │                        │
│  │  getFeatured, trackEvent        │                        │
│  └──────────────┬─────────────────┘                        │
└─────────────────┼──────────────────────────────────────────┘
                  │ HTTP/JSON
┌─────────────────▼──────────────────────────────────────────┐
│                        BACKEND                               │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  Bun + Hono (src/server/index.ts)                    │   │
│  │                                                      │   │
│  │  GET  /api/properties          → list + filter       │   │
│  │  GET  /api/properties/:slug    → single + increment  │   │
│  │  GET  /api/properties/featured → featured list       │   │
│  │  GET  /api/cities              → distinct cities     │   │
│  │  POST /api/analytics/event     → track               │   │
│  └──────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  SQLite (data/tenerifeprop.db)                       │   │
│  │  tables: properties, leads, testimonials, faq,       │   │
│  │          services, settings, users, sessions          │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Component Changes

1. public/index.html — Property Card Navigation & Site Map

Current State: Cards already link to /property/${prop.slug} via <a href> in renderPropertyCard(). Map popup lacks click-through navigation.

Changes Required:

  • Add data-property-id and data-property-slug attributes to each property card wrapper for analytics tracking
  • Wire map marker popup click → window.location.href = '/property/' + slug
  • Add rel="canonical" meta tag to <head>
  • Add <link rel="sitemap" href="/sitemap.xml"> to <head>
  • Footer property links (lines 1886-1890) need href wired to #catalog with type filter
  • Hero floating cards need href to specific properties

Specific changes:

<!-- Hero float cards → link to properties -->
<a href="/property/terreno-urbano-adeje" class="hero-float hero-float-1">...</a>
<a href="/property/terreno-agricola-guimar" class="hero-float hero-float-2">...</a>

<!-- Footer property links → filter catalog -->
<li><a href="#catalog" data-filter="agricultural">Terrenos agrícolas</a></li>
<li><a href="#catalog" data-filter="urban">Terrenos urbanos</a></li>
...

<!-- Add sitemap link -->
<link rel="sitemap" type="application/xml" href="/sitemap.xml">

2. public/js/app.js — Card Click Tracking & Map Navigation

Current State: renderPropertyCard() generates cards with <a href="/property/${prop.slug}"> links already. Map markers show popup but don't navigate.

Changes Required:

  • Modify updateMapMarkers() to add click handler on popup that navigates to property detail
  • Add data-property-slug attribute to card root element for analytics
  • Add smooth scroll handler for footer filter links
  • Add navigateToProperty(slug) helper method
// In updateMapMarkers():
marker.on('popupopen', () => {
  const popupEl = marker.getPopup().getElement()
  if (popupEl) {
    popupEl.style.cursor = 'pointer'
    popupEl.addEventListener('click', () => {
      window.location.href = `/property/${prop.slug}`
    })
  }
})

// In renderPropertyCard(), add to root div:
<div class="col-lg-4 col-md-6" data-aos="fade-up" data-property-slug="${prop.slug}">

// New method:
navigateToProperty(slug) {
  window.location.href = `/property/${slug}`
}

// Footer filter link handler:
document.querySelectorAll('.footer-links a[data-filter]').forEach(link => {
  link.addEventListener('click', (e) => {
    e.preventDefault()
    const filter = e.target.dataset.filter
    window.location.href = `/#catalog`
    sessionStorage.setItem('pendingFilter', filter)
  })
})

// In init(), check for pending filter:
const pendingFilter = sessionStorage.getItem('pendingFilter')
if (pendingFilter) {
  sessionStorage.removeItem('pendingFilter')
  setTimeout(() => this.setFilter(pendingFilter), 500)
}

3. public/js/property.js — Breadcrumb & Similar Properties Navigation

Current State: property.js loads property by slug, renders gallery, features, utilities, similar properties. Similar properties link to property.html (static). Breadcrumb is hardcoded.

Changes Required:

  • Update renderSimilarProperties() to use dynamic slugs (already uses prop.slug)
  • Add updateBreadcrumb() method to set dynamic breadcrumb from property data
  • Add "Back to catalog" button with scroll-to-catalog behavior
  • Wire share buttons with dynamic URLs
// New method:
updateBreadcrumb() {
  const items = document.querySelectorAll('.breadcrumb-item a, .breadcrumb-item.active')
  if (items.length >= 4 && this.property) {
    items[2].textContent = this.getTypeLabel(this.property.type)
    items[3].textContent = this.property.title
  }
}

// Call in updatePage():
this.updateBreadcrumb()

// Update share methods to use dynamic data:
shareProperty() {
  const url = window.location.href
  const title = this.property?.title || 'Property'
  if (navigator.share) {
    navigator.share({ title, url })
  } else {
    navigator.clipboard.writeText(url)
    this.showNotification('Enlace copiado', 'success')
  }
}

4. public/js/api.js — Add Missing Endpoints

Current State: Has getProperties, getProperty, getFeaturedProperties, createLead, getLeads, getTestimonials, getFAQ, getServices, getSettings, trackEvent, getCities.

Changes Required:

  • Add getSitemap() method for sitemap generation
  • Add getNavigationData() method for consistent nav across pages
// New methods:
static async getSitemap() {
  const response = await fetch('/api/sitemap')
  return response.json()
}

static async getNavigationData(lang = 'es') {
  const [cities, featured] = await Promise.all([
    this.getCities(),
    this.getFeaturedProperties(lang)
  ])
  return { cities: cities.data, featured: featured.data }
}

5. public/property.html — Consistent Navigation & Breadcrumb

Current State: Navbar links point to index.html and index.html#section. Breadcrumb is static. Similar property cards link to property.html (no slug).

Changes Required:

  • Update all navbar links to use proper paths
  • Make breadcrumb dynamic via JS (see property.js changes)
  • Update similar property card links to use prop.slug
  • Add data-property-id meta tag for analytics
<!-- Add to <head> -->
<meta name="property-id" content="" id="metaPropertyId">

<!-- Update similar cards in HTML to use dynamic slugs via JS -->
<!-- property.js renderSimilarProperties() already generates correct links -->

<!-- Navbar links (already correct, using index.html) -->

6. src/server/index.ts — Sitemap & Navigation API

Current State: Has all property/lead/content endpoints. No sitemap endpoint.

Changes Required:

  • Add GET /api/sitemap endpoint returning XML sitemap
  • Add GET /sitemap.xml static route or dynamic generation
// New endpoint:
app.get('/api/sitemap', (c) => {
  const properties = db.query('SELECT slug, updated_at FROM properties WHERE published_at IS NOT NULL AND status = ?').all('active')
  
  let xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://tenerifeprop.com/</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://tenerifeprop.com/#catalog</loc>
    <priority>0.8</priority>
  </url>`

  properties.forEach((p: any) => {
    xml += `
  <url>
    <loc>https://tenerifeprop.com/property/${p.slug}</loc>
    <lastmod>${p.updated_at}</lastmod>
    <priority>0.7</priority>
  </url>`
  })

  xml += '\n</urlset>'
  
  c.header('Content-Type', 'application/xml')
  return c.body(xml)
})

7. Mobile Navigation — Responsive Improvements

Current State: Bootstrap navbar with collapse. Language switcher in navbar. No mobile-specific optimizations for property cards.

Changes Required:

  • Add mobile bottom navigation bar for property detail page
  • Ensure map is usable on mobile (touch-friendly)
  • Add swipe navigation for property gallery (already exists in property.js)
  • Add "sticky CTA" on mobile property pages
<!-- Add to property.html before </body> -->
<div class="mobile-bottom-nav d-md-none">
  <a href="tel:+34922123456" class="mobile-nav-item">
    <i class="bi bi-telephone"></i>
    <span>Llamar</span>
  </a>
  <a href="https://wa.me/34600123456" class="mobile-nav-item whatsapp">
    <i class="bi bi-whatsapp"></i>
    <span>WhatsApp</span>
  </a>
  <button class="mobile-nav-item" onclick="propertyPage.toggleFavorite()">
    <i class="bi bi-heart"></i>
    <span>Favorito</span>
  </button>
  <button class="mobile-nav-item" onclick="propertyPage.shareProperty()">
    <i class="bi bi-share"></i>
    <span>Compartir</span>
  </button>
</div>

Data Flow

User clicks property card (index.html)
  → window.location.href = '/property/{slug}'
  → property.html loads
  → property.js init()
    → getPropertySlug() from URL
    → API.getProperty(slug, lang)
    → Backend: SELECT * FROM properties WHERE slug = ?
    → Backend: views_count++
    → Response: { success: true, data: { ...property, title, description } }
    → updatePage() renders all sections
    → loadSimilarProperties() → API.getProperties({ type, city, limit: 3 })
    → renderSimilarProperties() with links back to /property/{slug}

API Contracts

Method Endpoint Input Output
GET /api/properties ?type=&city=&minPrice=&maxPrice=&lang=&limit=&offset= { success, data: Property[], total }
GET /api/properties/:slug ?lang= { success, data: Property }
GET /api/properties/featured ?lang= { success, data: Property[] }
GET /api/sitemap - XML sitemap
GET /api/cities - { success, data: string[] }
POST /api/analytics/event { type, session_id, ...data } 200 OK

Property Response Shape (from /api/properties/:slug):

{
  "success": true,
  "data": {
    "id": "prop-001",
    "slug": "terreno-urbano-adeje",
    "title": "Terreno Urbano en Adeje",
    "description": "...",
    "city": "Adeje",
    "zone": "Costa Adeje",
    "address": "Avda. de la Constitución",
    "price": 385000,
    "area": 2500,
    "type": "urban",
    "images": "[{\"url\":\"...\",\"alt\":\"...\"}]",
    "lat": 28.1227,
    "lng": -16.6942,
    "water": "available",
    "electricity": "available",
    "road": "asphalt",
    "is_featured": 1,
    "is_exclusive": 1,
    "views_count": 42
  }
}

Implementation Checklist

  • 1. Property Card Navigation

    • Add data-property-slug to card root in renderPropertyCard() (app.js)
    • Add click handler on map popup → navigate to property (app.js)
    • Wire hero floating cards to specific property slugs (index.html)
    • Wire footer property links to catalog with type filter (index.html + app.js)
  • 2. API Data Integration

    • Add getSitemap() method (api.js)
    • Add getNavigationData() method (api.js)
    • Add /api/sitemap endpoint returning XML (server/index.ts)
  • 3. Consistent Site Navigation

    • Update breadcrumb to be dynamic on property page (property.js)
    • Ensure navbar links are consistent across all pages (index.html, property.html)
    • Add "Back to catalog" link on property page
  • 4. User Workflow (B2B Client Journey)

    • Add pending filter persistence via sessionStorage (app.js)
    • Track property view events with trackEvent() (already exists, verify)
    • Add lead form pre-fill with property context (property.js)
  • 5. Site Map Implementation

    • Create /api/sitemap XML endpoint (server/index.ts)
    • Add <link rel="sitemap"> to index.html <head>
    • Include all published properties + main pages
  • 6. Data Connectivity

    • Verify similar properties use correct slugs (property.js)
    • Verify share buttons use dynamic URLs (property.js)
    • Add data-property-id meta tag to property.html
  • 7. Site Consistency

    • Unify CSS variables across index.html and property.html (already consistent)
    • Ensure footer is identical across pages
    • Verify language switcher works on all pages
  • 8. Mobile Navigation

    • Add mobile bottom navigation bar to property.html
    • Verify map is touch-friendly on mobile
    • Verify gallery swipe works (already exists)
    • Add responsive CSS for mobile bottom nav

Technical Debt Notes

  1. Duplicate HTML: index.html contains a nested/wrapper HTML structure (lines 1-15 are a wrapper DOCTYPE). Should be cleaned up but not blocking.

  2. Inline translations: Both index.html and property.html embed full translation objects inline. Should be consolidated into external JSON files loaded via api.js or i18n.js.

  3. Static property.html: The property page HTML contains hardcoded content that gets overwritten by JS. Consider using a minimal template skeleton.

  4. No server-side routing: Property pages use client-side URL matching (/property/:slug). The server serves property.html for all paths. Consider adding proper routing if SEO becomes critical.

  5. Mixed JS patterns: app.js uses vanilla class pattern, property.html inline script uses jQuery. Should standardize on one approach.

  6. Missing error states: No 404 page for invalid property slugs. property.js calls showError() but replaces entire <main> content.


Status: designed @SDETEngineer ready for test creation @lead-developer ready for implementation