# VaultDXB

VaultDXB is a private Dubai off-market and distressed property deal marketplace connecting brokers, investors, and admins.

## Run & Operate

- `pnpm --filter @workspace/api-server run dev` — run the API server (port varies)
- `pnpm --filter @workspace/vaultdxb run dev` — run the frontend (port varies)
- `pnpm run typecheck` — full typecheck across all packages
- `pnpm run build` — typecheck + build all packages
- `pnpm --filter @workspace/api-spec run codegen` — regenerate API hooks and Zod schemas from the OpenAPI spec
- `pnpm --filter @workspace/db run push` — push DB schema changes (dev only)
- Required env: `DATABASE_URL` — Postgres connection string
- Optional env: `STRIPE_SECRET_KEY` — for live Stripe checkout (mocked if absent)
- Optional env: `STRIPE_WEBHOOK_SECRET` — for Stripe webhooks
- Optional env: `JWT_SECRET` — JWT signing secret (defaults to dev key)

## Stack

- pnpm workspaces, Node.js 24, TypeScript 5.9
- Frontend: React + Vite, wouter routing, shadcn/ui, framer-motion
- API: Express 5, JWT auth (bcryptjs + jsonwebtoken)
- DB: PostgreSQL + Drizzle ORM
- Validation: Zod (`zod/v4`), `drizzle-zod`
- Payments: Stripe checkout
- API codegen: Orval (from OpenAPI spec)

## Where things live

- `lib/api-spec/openapi.yaml` — source of truth for API contracts
- `lib/db/src/schema/` — database tables (users, deals, access-requests, payments)
- `artifacts/api-server/src/routes/` — backend route handlers
- `artifacts/api-server/src/middlewares/auth.ts` — JWT middleware
- `artifacts/vaultdxb/src/pages/` — all frontend pages
- `artifacts/vaultdxb/src/lib/auth.tsx` — AuthContext + JWT management

## Seed Accounts

| Role     | Email                    | Password      |
|----------|--------------------------|---------------|
| Admin    | admin@vaultdxb.com       | Admin12345    |
| Broker   | broker@vaultdxb.com      | Broker12345   |
| Investor | investor@vaultdxb.com    | Investor12345 |

## Architecture decisions

- JWT stored in localStorage (`vaultdxb_token`) and attached via `setAuthTokenGetter` from api-client-react's custom-fetch
- Discount % auto-calculated server-side: `((market - asking) / market) * 100`
- Urgency score auto-calculated from discount, timeline, and urgency reason (max 100)
- VIP-only deals show blurred/locked UI for Free tier investors
- Stripe checkout uses AED currency; falls back to mock session when `STRIPE_SECRET_KEY` not set
- Admin can manually override membership without Stripe

## Product

- **Brokers** submit off-market deals; admin approves/rejects
- **Investors** browse approved deals with filters; Free tier sees VIP deals locked
- **VIP/Elite** members see full deal details and broker contact info
- **Admin** manages deals, access requests, and user memberships
- **Pricing page** offers Stripe checkout for VIP (AED 999/mo) and Elite (AED 2999/mo)

## User preferences

- Dark luxury UI: deep navy/obsidian (#12162b) background, gold (#D4AF37) accents
- Always dark mode (no toggle)
- No emojis in UI

## Gotchas

- After schema changes: run `pnpm run typecheck:libs` before typechecking api-server
- After OpenAPI changes: run codegen then fix `lib/api-zod/src/index.ts` to only export from `./generated/api` (orval overwrites it with stale exports)
- `numeric` Drizzle columns map to `string` in TypeScript — always convert with `String()` before insert
- Express 5: params are `string | string[]`, always parse with `Array.isArray` guard

## Pointers

- See the `pnpm-workspace` skill for workspace structure, TypeScript setup, and package details
