394 lines
16 KiB
Markdown
394 lines
16 KiB
Markdown
## 🎨 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**:
|
|
```html
|
|
<!-- 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```html
|
|
<!-- 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```html
|
|
<!-- 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`):
|
|
```json
|
|
{
|
|
"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
|