Skip to content

Buyer Portal

The buyer portal is the primary user-facing application. It’s an Astro 5 SSR application serving both public and authenticated content. Prospective buyers browse units, register with OTP, save favorites, download documents, and contact agents.

RoutePageAccess
/Home / LandingPublic
/unitsUnit listing with filtersPublic
/units/[id]Unit detailPublic
/profileBuyer profile (edit name, contact, preferences)Auth required
/brochureProject brochure downloadPublic
/featuresFeatures/amenitiesPublic
/locationLocation / mapPublic
/imprintLegal imprintPublic
/privacyPrivacy policyPublic
ComponentFilePurpose
UnitListingcomponents/islands/UnitListing.tsxFilterable grid/list unit view
UnitDetailPopupcomponents/islands/UnitDetailPopup.tsxFull unit detail modal
UnitFilterBarcomponents/islands/UnitFilterBar.tsxPrice, rooms, status filters
LoginFormcomponents/islands/LoginForm.tsxOTP email/WhatsApp login flow
BuyerProfileFormcomponents/islands/BuyerProfileForm.tsxProfile editor
FavoriteButtoncomponents/islands/FavoriteButton.tsxSave/unsave toggle
FinancingCalculatorcomponents/islands/FinancingCalculator.tsxMonthly payment calculator
ImageBrowsercomponents/islands/ImageBrowser.tsxFullscreen image lightbox
BrochureDownloadscomponents/islands/BrochureDownloads.tsxDocument download manager
PriceRangeSlidercomponents/islands/PriceRangeSlider.tsxDual-handle price range
CookieConsentcomponents/islands/CookieConsent.tsxGDPR consent banner
PresetTesterOverlaycomponents/islands/PresetTesterOverlay.tsxTheme preset preview (dev)

On the main branch, portal data (units, agents, pages, settings) is fetched from Directus API using a static token. On spike/cf-runtime, it reads directly from the tenant-scoped PostgreSQL tables.

  1. User enters email or phone number
  2. OTP is sent via Resend (email) or Meta Cloud API (WhatsApp)
  3. User enters OTP, server verifies SHA-256 hash
  4. Lucia session stored in Postgres (sessions, 30-day expiry). Cookie refreshed when Lucia extends expiry — same session id (no per-request rotation). HTTP-only cookie set
  5. Cloudflare Turnstile protects OTP send endpoints from abuse

The portal resolves tenant from the Host header using resolveHost(), which joins portal_domains → portals → projects → organizations. Falls back to Lucia user attributes when hostname resolution fails (e.g., local dev).