A minimalist, single‑purpose app to register one‑night reservations for a family guest room — built to be fast, clear, and safe for non‑technical users.
UI branding: “Habitación Familiar de Lisiani y Airton.”
Access control: Authentication is open to all Google accounts, but authorization to create [items] is restricted to an allowlist.
Live Demo: replace this link with your Vercel URL:
https://habitation-management.vercel.app
- One‑step reservation: guest name, party size, check‑in (always 1 night), optional breakfast, contacts, notes.
- Automatic pricing or manual lodging total (for discounts).
- Extra charges (consumo extra) can be added and are summed directly into the total.
- Deposit (50%) with paid / pending status.
- Calendar with occupancy colors (max 3 rooms):
- 1 reservation → pastel yellow; 2 → mellow orange; 3 → deep red (full).
- Day view with all reservations and actions (👁 view, ⚙ edit, 🗑️ delete with confirmation).
- Upcoming list from today onward (scroll if long).
- View / Edit never overlap: prompts to save/discard changes before switching.
- Reservation confirmation: share or download a styled confirmation PDF/image to send to the guest.
- Sign in with Google. Access is limited to allowlisted emails.
- Pick a date on the calendar.
- Fill guest details — name, party size, optional breakfast, phone/email, notes, optional consumo extra.
- Choose automatic or manual lodging total.
- Set deposit (50%) as paid or pending.
- Save — the reservation appears in the Day view, Upcoming list, and can generate a confirmation for the guest.
Calendar with occupancy colors
Edit reservation with extra charges
- Next.js 14 (App Router) — file‑based API routes for CRUD.
- React 18 + TypeScript — strict types across domain helpers and UI.
- NextAuth (Google) — per‑user data isolation; each account only accesses its own reservations.
- Cloudflare R2 (S3‑compatible) — simple storage: one JSON per reservation.
- Zod validation — schema‑validated inputs on both client and server.
- Pure pricing helpers — deterministic functions enable straightforward unit tests.
- Sign‑in allowlist: only emails listed in
ALLOWED_EMAILS
can authenticate. - Per‑user isolation: reservations are namespaced by
userId
; users only see their own data. - Private bucket: no public listing; access via scoped keys.
No admin key is required anywhere in the UI; access is controlled by the Google account allowlist.
.
├── app
│ ├── (ui)
│ │ └── ClientShell.tsx
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ │ └── reservations
│ │ ├── [id]
│ │ │ ├── ics
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ └── route.ts
│ ├── components
│ │ ├── AddReservationModal.tsx
│ │ ├── CalendarBoard.tsx
│ │ ├── Navbar.tsx
│ │ └── ReservationEditor.tsx
│ ├── layout.tsx
│ ├── page.tsx
│ └── sign-in
│ └── page.tsx
├── core
│ ├── entities.ts
│ └── usecases.ts
├── lib
│ ├── admin.ts
│ ├── allowlist.ts
│ ├── auth.client.tsx
│ ├── auth.config.ts
│ ├── pricing.ts
│ ├── s3.ts
│ ├── schema.ts
│ └── user.ts
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ └── icons
├── tailwind.config.ts
├── tsconfig.json
└── utils
└── ics.ts
type ReservationItem = {
id?: string
guestName: string
phone?: string
email?: string
partySize: number
checkIn: string
checkOut?: string
breakfastIncluded: boolean
nightlyRate: number
breakfastPerPersonPerNight: number
manualLodgingEnabled?: boolean
manualLodgingTotal?: number
extraCharges?: number
depositPaid: boolean
notes?: string
}
Automatic
lodging = nights * (nightlyPerPerson * partySize)
breakfast = breakfastIncluded ? nights * partySize * breakfastPerPersonPerNight : 0
total = lodging + breakfast + extraCharges
deposit = 0.5 * total
Manual lodging enabled
lodging = manualLodgingTotal
breakfast = breakfastIncluded ? nights * partySize * breakfastPerPersonPerNight : 0
total = lodging + breakfast + extraCharges
deposit = 0.5 * total
nights = checkOut - checkIn
(days). In v1: check‑out = check‑in + 1 day.- UI language & currency: pt‑BR, BRL.
npm i
npm run dev # http://localhost:3000
# production mode
npm run build && npm start
- Import the GitHub repository into Vercel (framework preset: Next.js).
- Add Environment Variables (see next section).
- Deploy.
- Add your Vercel domain to Google OAuth (origins + redirect URI).
Google Cloud Console → APIs & Services → Credentials
- Authorized JavaScript origins
http://localhost:3000
https://<your-project>.vercel.app
- Authorized redirect URIs
http://localhost:3000/api/auth/callback/google
https://<your-project>.vercel.app/api/auth/callback/google
# NextAuth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<dev-secret-32+chars>
GOOGLE_CLIENT_ID=<google-oauth-client-id>
GOOGLE_CLIENT_SECRET=<google-oauth-client-secret>
# Storage (R2 recommended)
STORAGE_PROVIDER=R2
BUCKET_NAME=<bucket>
CF_R2_ACCOUNT_ID=<id>
CF_R2_ACCESS_KEY_ID=<key>
CF_R2_SECRET_ACCESS_KEY=<secret>
# App access control
ALLOWED_EMAILS=mom@example.com,dad@example.com,me@gmail.com
# or
ALLOWED_DOMAIN=familia.com
GET /api/reservations?month=YYYY-MM
— list reservations for a month.POST /api/reservations
— create (requires a valid signed-in session).PUT /api/reservations/:id
— update (requires a valid signed-in session).DELETE /api/reservations/:id
— delete (requires a valid signed-in session).
# Create
curl -s -X POST https://<domain>/api/reservations \\
-H "Content-Type: application/json" \\
--cookie "next-auth.session-token=<SESSION_TOKEN>" \\
-d '{
"guestName": "Familia Souza",
"partySize": 3,
"checkIn": "2025-09-01",
"checkOut": "2025-09-02",
"breakfastIncluded": true,
"nightlyRate": 60,
"breakfastPerPersonPerNight": 10,
"extraCharges": 15
}'
- “Invalid Compact JWE” → set
NEXTAUTH_SECRET
. - Avatar blocked → add Google avatar hosts to
next.config.js
(see below). - 401 Unauthorized → you’re not signed in or not allowlisted.
- 403 Forbidden → email not in
ALLOWED_EMAILS
/ALLOWED_DOMAIN
. - 403 / 404 storage → bucket name or IAM policy incorrect.
- Google OAuth error → production origins/redirects not added.
module.exports = {
images: {
domains: [
'lh3.googleusercontent.com',
'lh4.googleusercontent.com',
'lh5.googleusercontent.com'
],
},
};
- Download per-reservation
.ics
event (to sync with Google/Apple/Outlook). - Multi‑night reservations (date ranges).
- CSV export & monthly totals.
- Calendar feed for month (
/calendar.ics
). - Share data between Google accounts.
- PWA (offline / installable).
GNU GENERAL PUBLIC LICENSE — see LICENSE
.