Friends-only guest room booking app.
- Product rename across UI + emails to The Grow Room.
- Mobile-first clickable calendar selection:
- first tap = check-in
- second tap = check-out
- highlights for today, check-in, check-out, in-range, and unavailable
- jump-to-today button when current view excludes today
- strict inline validation for 5-night max and unavailable ranges
- 24-hour door-code safety flow:
- approval email does not include codes
- guest sees codes only within 24h of check-in
- arrival details email is sent once in the 24h window
- admin can manually send arrival email now for testing
- Guidebook improvements:
- updated cleaning-contribution copy
- pay-it-forward block
- photo gallery grid with tap-to-enlarge preview
- Admin QR page:
/admin/qrwith print-friendly guidebook QR.
- Next.js App Router + TypeScript
- NextAuth + Prisma Adapter
- Prisma + Postgres (local via Docker, production via managed Postgres)
- Tailwind CSS
- Install dependencies:
npm install- Create env file:
cp .env.example .env- Set required env vars in
.env:
NEXTAUTH_SECRETENCRYPTION_KEYADMIN_EMAIL_1ADMIN_EMAIL_2CRON_SECRETDATABASE_URL
- Start local Postgres:
docker compose up -d postgres- Prepare DB and seed:
npm run db:generate
npm run db:deploy
npm run db:seed- Start app:
npm run devOr run one command:
npm run dev:localNotes:
- This repo now uses Postgres for local and production.
- SQLite file databases are not suitable for Vercel production (no persistent writable local DB file on serverless instances).
- Seed creates admins from
ADMIN_EMAIL_1andADMIN_EMAIL_2. - After login as admin, go to
/adminand use Invite allowlist to add/remove allowed emails and assign roles.
Public (no auth required):
/- public landing page for The Grow Room/request-invite- invite request form/faq- FAQ page (direct linkable)/login- magic-link sign-in page
Invite-only (auth required):
/calendar- availability + booking request flow/bookings- guest booking status, cancel/reschedule/guidebook- authenticated guidebook
Admin-only:
/admin- approvals, blocks, settings, invite allowlist, invite requests/admin/suggestions- suggestions triage list (filters + detail links)/admin/suggestions/[id]- suggestion detail + triage editor/admin/qr- print-friendly guidebook QR
Security notes:
- Booking and admin pages redirect unauthenticated users to
/login. - Public pages never expose address, door codes, guest identities, or booking data.
- Suggestions are invite-only and visible only to the author and admins.
- Logged-in users can open
/suggestionsto propose sustainable improvements to The Grow Room. - Suggestions are intended to reduce owner effort (guest-doable, another-guest-doable, or owner effort under ~15 minutes).
- Optional product link is supported for things like supply or fixture suggestions.
Eligibility rule:
- A user can only submit suggestions after at least one completed stay.
- "Completed stay" means:
- booking status is
APPROVED - checkout (
endDate) is earlier than the current time
- booking status is
- Users without a completed stay can still open
/suggestionsand view the page, but they see:Suggestions are available after your first stay.
Admin triage:
- Admins review suggestions in
/admin/suggestions. - Filter by status and category, then click a row to open
/admin/suggestions/[id]. - Admins can update:
- status (
NEW,REVIEWING,ACCEPTED,DONE,DECLINED) - category
- owner effort tag
- internal admin notes
- status (
- Suggestions are audited (
SUGGESTION_CREATED,SUGGESTION_ADMIN_UPDATED) in the existing audit log.
- Guest submits
/request-invitewith name, email, social link, shared connection, trip purpose, and requested dates. - App stores the request in
InviteRequest(status starts asPENDING). - Admin reviews requests in
/adminand approves or denies. - Approve:
- request marked
APPROVED - user is added/upserted in the allowlist (
User.allowed = true) - app sends a "You're invited" email with a magic sign-in link (or logs email in dev if Resend is not configured)
- Deny:
- request marked
DENIED - no denial email is sent automatically
- Reveal logic:
canRevealCodes(booking, now)is true only when:- booking is
APPROVED - current time is at or after
check-in - 24h
- booking is
- Guests outside that window see:
Door codes unlock 24 hours before check-in. You'll see them here and get an email then.
- Time reference is based on the app server clock (single consistent source).
Route:
POST /api/cron/checkout-reminders
Auth:
- send header
Authorization: Bearer <CRON_SECRET> - Vercel cron header also supported
Example local trigger:
curl -X POST \
-H "Authorization: Bearer $CRON_SECRET" \
http://localhost:3000/api/cron/checkout-remindersThe route sends:
- arrival-detail emails for approved bookings entering the 24h window (once, tracked by
arrivalEmailSentAt) - checkout reminder emails (once, tracked by
reminderSentAt)
- Push the repo to GitHub (or connect the local repo directly in Vercel).
- Create a Vercel project and set the framework to Next.js.
- Configure environment variables in Vercel (Production):
NEXTAUTH_URL=https://schedule.joelenquist.comNEXTAUTH_SECRETDATABASE_URL(managed Postgres connection string)ENCRYPTION_KEYCRON_SECRETADMIN_EMAIL_1,ADMIN_EMAIL_2(orADMIN_EMAILS)RESEND_API_KEY,EMAIL_FROM(for magic links + notifications)- optional Google OAuth / Google Calendar vars if used
- Add custom domain
schedule.joelenquist.comin Vercel Project Settings -> Domains. - Update DNS so
schedule.joelenquist.compoints to Vercel (follow Vercel's DNS target/CNAME instructions shown in the dashboard). - Run Prisma migrations against the production database before first use:
DATABASE_URL="postgresql://..." npm run db:deploy- (Optional) Seed admin users/settings after migrations:
DATABASE_URL="postgresql://..." ADMIN_EMAIL_1="you@example.com" ADMIN_EMAIL_2="spouse@example.com" ENCRYPTION_KEY="..." npm run db:seed- Verify the cron route is protected and working:
vercel.jsonschedulesPOST /api/cron/checkout-reminders- route requires
CRON_SECRETvia bearer header (Vercel cron is also supported)
Production notes:
- Keep the booking app invite-only; only
/,/request-invite,/faq, and/loginare public. - Do not store production door codes in client-visible config; they remain admin/email-path only and subject to the 24h reveal rule.
- Use a managed Postgres provider (Neon, Supabase, Vercel Postgres-compatible offerings, etc.) for Vercel deployment.
- Admin:
/admin-> Global settings -> add/remove photo URLs. - Stored in
Settings.photosJsonas JSON array. - Guests view photos on
/guidebookin a responsive grid.
- Admin-only page:
/admin/qr - Contains:
- title: The Grow Room Guide
- QR code to
${NEXTAUTH_URL}/guidebook - URL text
- one-liner:
Scan for guidebook + checklist
- Use browser print to create a paper card.
- If
RESEND_API_KEY+EMAIL_FROMare configured, real email is sent. - Otherwise, full email payload is logged to terminal (dev fallback).