A single-folder, zero-config demo of the Aadhavan event-design platform. Your original React-via-CDN prototype is now wired to a working Flask backend with SQLite storage, session auth, and a full admin dashboard.
⚠ Note on stack divergence. Your PRD/spec targets Next.js 14 + Prisma + Supabase + Cloudinary + Twilio + Resend. That stack is incompatible with the prototype's CDN-React + Babel-standalone architecture. To keep the frontend you gave me intact, this demo trades those services for:
PRD target Demo substitute Next.js 14 App Router Flask (serves the CDN-React pages directly) PostgreSQL + Prisma SQLite + SQLAlchemy Cloudinary Local uploads/folder + PillowTwilio WhatsApp Console log stub (hook point documented below) Resend email Console log stub NextAuth JWT cookies Flask secure-cookie sessions + bcrypt API routes, schema, auth flow, sanitization, and rate-limit behavior all match the spec. This is designed to validate flows end-to-end before the eventual Next.js port.
# 1. Install dependencies (one time)
pip install -r requirements.txt
# 2. Seed the owner account + default settings (one time)
python seed.py
# 3. Run the server
python app.pyOpen:
- Public site → http://localhost:5000
- Admin dashboard → http://localhost:5000/admin/
Default login (change it after first sign-in):
- Email:
owner@aadhavan.com - Password:
aadhavan2025
aadhavan/
├── app.py # Flask app + all API routes (public, admin, auth)
├── models.py # SQLAlchemy schema
├── seed.py # Creates owner user + default settings
├── requirements.txt
├── .env.example # Copy to .env for production tweaks
├── README.md # You are here
│
├── data/ # SQLite DB lives here (auto-created)
├── uploads/ # Uploaded gallery photos (auto-created)
│
└── static/
├── Aadhavan.html # Public SPA shell (unchanged from prototype)
├── styles.css # 44KB site styles (unchanged)
├── icons.jsx # SVG icon library (unchanged)
├── home.jsx # Home page (unchanged)
├── shared.jsx # Nav, Footer, Calendar — modified to hit /api/calendar
├── book.jsx # Book Now — modified to POST /api/inquiries
├── gallery.jsx # Gallery — modified to fetch /api/gallery (with demo fallback)
├── about_contact.jsx # Contact — modified to POST /api/contact
└── admin/
├── index.html # Admin SPA shell
├── admin.css # Admin styling
└── admin.jsx # Login + Dashboard + Inquiries + Calendar + Photos + Messages
All routes return JSON. See app.py for exact shapes.
GET /api/calendar?month=N&year=N Blocked dates for a month
GET /api/gallery?category=X Uploaded photos
POST /api/inquiries Submit a booking inquiry
POST /api/contact Submit a contact-form message
GET /api/settings/public Business phone, hours, socials
POST /api/auth/login Email + password → session cookie
POST /api/auth/logout
GET /api/auth/session Who am I?
PATCH /api/auth/password Change password
GET /api/admin/inquiries List (with ?status=PENDING|CONFIRMED|CLOSED)
GET /api/admin/inquiries/:id
PATCH /api/admin/inquiries/:id Update status / owner notes
DELETE /api/admin/inquiries/:id
GET /api/admin/calendar All blocked dates with notes
POST /api/admin/calendar/block Block a date
PATCH /api/admin/calendar/block/:id Edit note
DELETE /api/admin/calendar/block/:id Unblock
GET /api/admin/photos
POST /api/admin/photos/upload Multipart file upload
PATCH /api/admin/photos/:id
DELETE /api/admin/photos/:id
GET /api/admin/messages
PATCH /api/admin/messages/:id Mark read / unread
DELETE /api/admin/messages/:id
GET /api/admin/stats Dashboard stats + 6-month trend
GET/PATCH /api/admin/settings
- bcrypt for password hashing (12 salt rounds)
- Rate limits (in-memory, per IP):
POST /api/inquiries— 3/hourPOST /api/contact— 5/hourPOST /api/auth/login— 5 attempts / 15 min
- Input sanitization via
bleach— strips all HTML, trims, caps at 2000 chars - Security headers on every response:
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy,Permissions-Policy - Session cookies are HttpOnly and signed with
SECRET_KEY - File uploads capped at 12 MB; only
.jpg/.jpeg/.png/.webp/.gifaccepted; images >2000px on the long edge are auto-resized via Pillow
- Public visitor browses site, picks a service, clicks an open date on the
calendar — backed by a live
GET /api/calendar?month=…fetch that reloads whenever the viewed month changes. - They fill the inquiry modal — name, phone, requirements — and submit. Backend validates, rate-limits, sanitizes, saves, and logs a notification stub (swap in Twilio/Resend at the marked hook points).
- Past dates and blocked dates are rejected with a friendly error. Same for short phone numbers, malformed emails on contact form, and fourth+ submission within an hour.
- Owner logs in at
/admin/, sees dashboard stats (this-month vs last-month deltas, popular event types, 6-month trend). - Owner clicks any inquiry row → drawer opens with full details, one-click
Call / WhatsApp buttons, status dropdown (
PENDING→CONFIRMED→CLOSED), and a private notes textarea. - Owner blocks
2026-08-15with a note. The public/api/calendarpicks it up instantly — the next visitor's month-fetch sees that date as unavailable and cannot submit an inquiry for it. - Owner uploads a photo → Pillow reads dimensions, oversize images are
resized, file saved to
uploads/<uuid>.ext, metadata to DB. Public gallery shows it prepended to the demo placeholder set. - Owner marks contact messages read / unread, deletes inquiries, changes own password.
When you're ready to port:
- Replace Flask with Next.js 14 App Router. The React components inside
the JSX files are already close to Next.js-ready — they just need to become
proper
.tsxfiles with imports instead of globalwindowassignments. - Replace SQLAlchemy models with the Prisma schema already written in
your
AADHAVAN_BACKEND_SPEC.md. Field names are intentionally aligned (fullName,eventDate,serviceTypeetc.) so API payloads port 1:1. - Swap SQLite for Supabase Postgres. Update
DATABASE_URL; runprisma migrate deploy. - Swap
uploads/for Cloudinary. The upload handler inapp.py(api_admin_upload_photo) is the single hook point — replace the local file save with acloudinary.uploader.upload_streamcall. - Swap notification stubs for Twilio + Resend.
notify_new_inquiry()andnotify_new_contact_message()are isolated at the top ofapp.py— drop in the real API calls from the spec'slib/whatsapp.tsandlib/email.ts. - Swap Flask sessions for NextAuth JWT. NextAuth.js v5 credential provider config is already written out in the backend spec — copy it in.
These are intentional simplifications for the single-folder demo — noted here so nothing surprises you later.
- Color palette: follows the prototype's
v2lilac + orange, NOT the PRD's ivory + rose-gold + walnut. Change the variables instatic/styles.csslines 1–30 to flip back. - Inquiry form: the prototype collects only name + phone + requirements.
The PRD specifies more fields (email, guest count, venue, contact method).
Schema supports all of them — just add the inputs to
book.jsx. - Contact numbers: Indian (
+91 80748 96611), per the prototype, not the PRD's Memphis (+1 901 555 1234). Change via Settings page once logged in as admin. - Feedback section on the home page uses
localStorage(kept from the prototype). It's not wired to the backend; if you want persistent feedback, add aPOST /api/feedbackroute. - No server-side email/WhatsApp notifications — they log to console. The Thank-You screen's WhatsApp link is client-initiated (user taps it).
Port already in use: export PORT=5001 && python app.py
Forgot the admin password: delete data/aadhavan.db and re-run
python seed.py. (Wipes all inquiries too — for a prod fix, add a password
reset script that just bcrypts a new password for the existing user.)
Uploaded photos not showing on public gallery: hard-refresh (Ctrl+Shift+R). The gallery component fetches once on mount; if you uploaded a photo after navigating to Gallery, navigate away and back.
Changes to JSX not appearing: the files are loaded and transpiled at runtime by Babel-standalone — hard refresh to bypass browser cache.
Built to match the behaviors documented in AADHAVAN_PRD.docx and
AADHAVAN_BACKEND_SPEC.md. Every deviation above is intentional and noted.