Role
Full-Stack Developer (lead)
Client
Concept Dash
Duration
Dec 2025 – Aug 2026 (~9 months)
Real-estate developers each want their own branded sales website, and each one wants roughly the same things: a cinematic hero, a 3D walkthrough, a 360° tour of the show unit, a live availability list, and a lead form. Building six of those as six projects means six codebases drifting apart. This repo is the answer to that: a single Next.js App Router application where `NEXT_PUBLIC_SITE_VARIANT` picks one entry out of `app/config/siteConfig.js` at build time, and that entry *is* the site. The config's `layout` array is a list of section descriptors; `HomePageClient` maps each `type` through a `SECTION_COMPONENTS` lookup and renders them in order, handing `section.data` straight to the component. Adding a customer is one config entry and one data file — no routing work, no new pages. Six variants ship from it today, across developments in Canada and the UAE.
The interesting engineering is in the 360° layer. `app/components/panorama/` wraps Photo Sphere Viewer into nine focused modules driven entirely by a data object: a viewer that handles pano transitions and hotspot navigation, a debounced inventory search with full keyboard navigation, a minimap whose compass cone is driven by `requestAnimationFrame` against the live camera yaw, floor switching, and a cinematic loading overlay. Because placing hotspots by hand in a JSON file is miserable, the repo also contains `PanoCalibrator` — an in-app authoring mode that lets you aim the camera at a doorway, click, and copy the resulting JSON to the clipboard. It is gated off public builds. The commit history records the grind that tool made possible: 87 markers calibrated across a two-floor property.
Beyond the tour there is a neighbourhood explorer built on Leaflet with a deliberately server-side places layer: `app/api/places/nearby/route.js` proxies every lookup so the Google key never reaches the browser, responses are cached so cost is independent of visitor count, and a provider outage degrades to a committed snapshot instead of a broken page. Live unit availability comes from Supabase with realtime channels and deep-linkable `?unitId=` URLs; enquiries go through a multipart form route into AWS SES with hand-built HTML email templates. A separate `build:static` script produces a credential-free static export for client demos, sitting alongside the normal server build without polluting `next.config.ts`.
The repo also carries a genuinely unusual amount of written reasoning — `docs/architecture.md` documents the stack *as installed* rather than as intended, flags that the README is stale, and names the module that is the problem child and why.
Tech Stack
Features
- Config-driven multi-tenancyOne environment variable selects a site config whose `layout` array is rendered section-by-section through a component lookup, so a new property site is a config entry rather than a new codebase.
- Modular 360° tour engineNine composable panorama modules — viewer, controls, search, minimap, floor switch, loader, shortcuts — all driven from a single tour data object, so seven of them need no change between properties.
- In-app hotspot calibratorAn authoring mode that lets an operator aim the camera at a doorway, click to place a marker, and copy the generated JSON to the clipboard; it is excluded from public builds.
- Live inventory with deep linksSupabase realtime channels keep unit availability in sync, and `?unitId=` search params open the site directly on a specific unit's enquiry state.
- Cached, key-safe neighbourhood explorerA Leaflet map fed by a server-side places proxy that keeps the API key off the client, caches responses so cost does not scale with traffic, and falls back to a committed snapshot when the provider is down.
- Lead capture into AWS SESA multipart contact route with file attachments, country lookup and unit-booking context, rendered into custom HTML email templates and delivered through Nodemailer over SES.
- Credential-free static exportA dedicated `build:static` script produces a static bundle for client demos without touching the committed Next config, with Netlify headers and Node version pinned.
