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-idanddata-property-slugattributes 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
#catalogwith 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-slugattribute 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 usesprop.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-idmeta 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/sitemapendpoint returning XML sitemap - Add
GET /sitemap.xmlstatic 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-slugto card root inrenderPropertyCard()(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)
- Add
-
2. API Data Integration
- Add
getSitemap()method (api.js) - Add
getNavigationData()method (api.js) - Add
/api/sitemapendpoint returning XML (server/index.ts)
- Add
-
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/sitemapXML endpoint (server/index.ts) - Add
<link rel="sitemap">to index.html<head> - Include all published properties + main pages
- Create
-
6. Data Connectivity
- Verify similar properties use correct slugs (property.js)
- Verify share buttons use dynamic URLs (property.js)
- Add
data-property-idmeta 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
-
Duplicate HTML:
index.htmlcontains a nested/wrapper HTML structure (lines 1-15 are a wrapper DOCTYPE). Should be cleaned up but not blocking. -
Inline translations: Both
index.htmlandproperty.htmlembed full translation objects inline. Should be consolidated into external JSON files loaded viaapi.jsori18n.js. -
Static property.html: The property page HTML contains hardcoded content that gets overwritten by JS. Consider using a minimal template skeleton.
-
No server-side routing: Property pages use client-side URL matching (
/property/:slug). The server servesproperty.htmlfor all paths. Consider adding proper routing if SEO becomes critical. -
Mixed JS patterns:
app.jsuses vanilla class pattern,property.htmlinline script uses jQuery. Should standardize on one approach. -
Missing error states: No 404 page for invalid property slugs.
property.jscallsshowError()but replaces entire<main>content.
Status: designed @SDETEngineer ready for test creation @lead-developer ready for implementation