How the site works, what it’s built with, how to run it locally, testing, and API endpoints.
This is a production-style admin dashboard for a Resident Experience Platform: multiple properties, onboarding per property, services marketplace, and analytics. The dashboard is its own route: select a property in the sidebar, then use Onboarding, Marketplace, or Analytics for that property.
Dashboard (/dashboard) includes the sidebar (property selector + sections) and Docs. Onboarding is scoped to the selected property: residents and tasks belong to that property. Marketplace and Analytics are placeholders.
Data is stored in PostgreSQL via Prisma. The UI calls REST APIs under /api/*. There is no authentication in this demo.
Package manager: pnpm.
Prerequisites: Node 20+, pnpm, Docker.
git clone <repo-url> && cd portfolio pnpm install cp .env.example .env # set DATABASE_URL pnpm db:up && pnpm db:migrate && pnpm db:seed pnpm dev
Open http://localhost:3000, then go to /dashboard. Optional: pnpm db:studio, pnpm db:reset.
Vitest + React Testing Library + jsdom.
pnpm test # Watch pnpm test:run # CI
JSON responses: success { data: T }, errors { error: { code, message, details? } }.
GET /api/propertiesList all properties. Response: { id, name, slug, address }[].
GET /api/onboarding/residents?propertyId=:idList residents for a property. propertyId required.
GET /api/onboarding/categoriesList task categories.
GET /api/onboarding/tasks?residentId=:idList tasks for a resident.
POST /api/onboarding/tasksCreate task (body: residentId, categoryId, title, optional status, owner, dueDate).
PATCH /api/onboarding/tasks/:idUpdate task. Idempotent completion.
DELETE /api/onboarding/tasks/:idPOST /api/onboarding/resetReset onboarding demo data.
GET /api/marketplace/servicesList services. Optional query: category, enabled=false to include disabled.
POST /api/marketplace/servicesCreate service (body: name, description?, category, priceCents, enabled?).
PATCH /api/marketplace/services/:idUpdate service (partial).
DELETE /api/marketplace/services/:idDelete service (fails if it has orders).
GET /api/marketplace/orders?propertyId=:idList orders for residents of the property. propertyId required.
GET /api/marketplace/orders/:idGet order with items and resident.