Next.js App Router + TypeScript + Tailwind + Prisma.
Create .env.local (never commit secrets):
DATABASE_URL="file:./dev.db"
ADMIN_PASSWORD="your-dev-admin-password"
AGENT_COOKIE_SIGNING_KEY="change-me-for-dev"
SEED_AGENT_EMAIL=""
SEED_AGENT_PASSWORD=""
GUIDE_AGENT_EMAIL=""
GUIDE_AGENT_PASSWORD=""
EMAIL_MODE="log"
EMAIL_FROM="no-reply@v-property.co.za"
RESEND_API_KEY=""
EMAIL_ADMIN_TO=""
STRIPE_SECRET_KEY=""
STRIPE_WEBHOOK_SECRET=""
STRIPE_PRICE_ID_BASIC=""SEED_AGENT_EMAIL and SEED_AGENT_PASSWORD are optional. If both are set,
npm run prisma:seed creates one demo agent portal user linked to the first
seeded agent.
AGENT_COOKIE_SIGNING_KEY is required in production for agent session signing.
The development fallback key is for local/dev/test use only.
- Set
EMAIL_MODE="log"in local/dev to log outbound emails to the server console. - Set
EMAIL_MODE="resend"in production to deliver via Resend API. - Required in production (
EMAIL_MODE="resend"):EMAIL_FROMRESEND_API_KEY
EMAIL_ADMIN_TOis used for general enquiries (nolistingId).- Public enquiries are always stored in the
Leadtable, and admins can review the latest submissions at/admin/leads. - For controlled private beta, use
/admin/leadsas the fallback operational inbox wheneverEMAIL_MODE="log"is active or a notification email fails. - The lead inbox now shows lightweight operator counts and uses the audit log to track when listing enquiries are actioned and when general support enquiries are resolved.
- Ownership visibility in
/admin/leadsis derived from the latest admin handling event, so operators can see the current handler together with the latest recorded lead action and timestamp. /admin/leadsnow also shows age since received, age since last action, and a lightweight stale highlight for unresolved items that have had no activity for 24 hours or more.
- Set
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET, andSTRIPE_PRICE_ID_BASICin.env.local. STRIPE_PRICE_ID_BASICshould match your Stripe recurring price for the BASIC plan.- If
STRIPE_PRICE_ID_BASICis not set, seeding usesprice_placeholder_basicso billing checkout is disabled until configured.
Optional local webhook forwarding with Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe- Default:
DATABASE_URL="file:./dev.db" - Prisma commands:
npm run prisma:generatenpm run prisma:migratenpm run prisma:seed
Use a Postgres connection string in DATABASE_URL:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB_NAME?schema=public"Then run:
npm run prisma:generate
npm run prisma:migratenpm install
npm run prisma:generate
npm run prisma:migrate
npm run prisma:seed
npm run devFor screenshot automation, set these in .env.local:
GUIDE_AGENT_EMAILGUIDE_AGENT_PASSWORDADMIN_PASSWORD(optional; not used by the current capture script)
Run in two terminals:
# terminal 1
npm run dev
# terminal 2
npm run screenshots:guideScreenshots are written to docs/guide-images/YYYY-MM-DD/.
npm run sellers-guide:pdf- Output:
docs/SELLERS_GUIDE.pdf - If
reportlabis missing, install it with:
pip install reportlab- On Windows, the PDF script runs via
py.
- URL:
/admin/login - Password:
ADMIN_PASSWORDfrom.env.local - This is a temporary dev-only gate and must be replaced by real auth before production.
- Live verification:
npm run verify:admin-leads - The admin leads verification writes disposable screenshot/trace artifacts under
coverage/playwright/admin-leads-live/.
Set these environment variables in Vercel Project Settings:
DATABASE_URLADMIN_PASSWORDEMAIL_MODEEMAIL_FROMRESEND_API_KEYEMAIL_ADMIN_TOSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_PRICE_ID_BASIC
Choose one approach:
- Manual migration (recommended for control)
- Run
npx prisma migrate deployfrom CI or a release job before traffic switch. - Keep Vercel build command focused on app build.
- Build-time migration (small projects)
- Run migration before
next buildin your build command. - Example:
npx prisma migrate deploy && npm run build
Use npm run prisma:generate during build if your pipeline does not run postinstall.
- Health endpoint:
/api/health - Sitemap:
/sitemap.xml - Robots:
/robots.txt - Rate limiting is currently in-memory (
lib/rate-limit.ts) and best-effort for single-instance use; use a shared store in production.