5.9 KiB
5.9 KiB
Plan: Admin Dashboard Auth + CRUD + Enhancements
TL;DR
Add password authentication to the admin dashboard, enable full CRUD for personas/pricing, and add useful admin features like order analytics and configurator settings management.
Phase 1: Admin Authentication (Database-backed with Prisma + SQLite)
Steps
-
Setup Prisma + SQLite
- Install
prismaand@prisma/client - Create
prisma/schema.prismawith SQLite provider - Models:
AdminUser(id, username, passwordHash, createdAt),AppSettings(key, value) - Run
npx prisma db pushto create the database - Create
src/lib/prisma.tssingleton Prisma client
- Install
-
Create seed script
prisma/seed.ts- Creates default admin user with bcrypt-hashed password
- Generates and stores JWT secret in
AppSettingstable - Run with
npx prisma db seed
-
Create admin auth API routes
src/app/api/admin/login/route.tsPOST, accepts{ username, password }, verifies bcrypt hash from DB, returns JWT in httpOnly cookie (JWT secret from DB)src/app/api/admin/verify/route.tsGET, checks JWT cookie validitysrc/app/api/admin/logout/route.tsPOST, clears auth cookiesrc/app/api/admin/change-password/route.tsPOST, accepts{ currentPassword, newPassword }, updates hash in DB
-
Create admin middleware
src/middleware.ts- Protects all
/admin/*routes (except/admin/login/) - Checks for valid JWT cookie, redirects to
/admin/login/if missing/invalid - Note: middleware can't use Prisma directly (edge runtime), so JWT secret needs to be in env OR verify via API call
- Protects all
-
Create admin login page
src/app/admin/login/page.tsx- Username + password form (styled to match existing admin design)
- Calls login API, redirects to
/admin/on success - Shows error on wrong credentials
-
Add JWT_SECRET to .env.local (only this one password is in DB)
- Alternative: store JWT secret in DB and load at startup into a module-level cache
Dependencies to install
prisma(dev),@prisma/client,bcryptjs,@types/bcryptjs(dev),jose
Phase 2: Full CRUD for Pricing Items + Personas
Steps (depends on Phase 1)
-
Add/Remove pricing items in admin Update
src/app/admin/page.tsx- "Add Item" button with fields: id (auto-slug from label), label, price
- Delete button per row (with confirmation)
- Update
usePricingStoreto supportaddItem()andremoveItem()actions
-
Persona management section New section in admin page
- List current personas with edit capability (label, description, colors)
- Add new persona form
- Delete persona button
- Create
usePersonaStoreor extendusePricingStoreto manage persona definitions - Sync persona list with ConfigPanel's
PERSONA_OPTIONS(currently hardcoded)
Phase 3: Additional Admin Features
Steps (parallel with Phase 2)
-
Orders dashboard section New section in admin page
- Show orders from Stripe API (list recent payments)
- Display: order ID, customer email, amount, status, date
- New API route
src/app/api/admin/orders/route.tsto fetch from Stripe
-
Analytics overview cards at top of admin page
- Total revenue (from Stripe)
- Number of orders
- Most popular persona
- Most popular color
-
Configurator settings section
- Edit default color (
#96a2b6) - Toggle available color options
- Set min/max price boundaries
- Edit default color (
-
Logout button in admin header
Relevant Files
src/app/admin/page.tsxMain admin dashboard (add CRUD UI, analytics)src/app/admin/login/page.tsxNew login pagesrc/app/api/admin/login/route.tsNew auth APIsrc/app/api/admin/verify/route.tsNew verify APIsrc/app/api/admin/orders/route.tsNew orders APIsrc/middleware.tsNew, protects admin routessrc/lib/prisma.tsNew, Prisma client singletonprisma/schema.prismaNew, database schema (AdminUser, AppSettings)prisma/seed.tsNew, seeds default admin usersrc/store/usePricingStore.tsAddaddItem(),removeItem()actionssrc/components/ConfigPanel.tsxPersona options currently hardcoded (PERSONA_OPTIONS const).env.localAdd ADMIN_PASSWORD, ADMIN_JWT_SECRET
Verification
- Visit
/admin/without login → redirected to/admin/login/ - Enter wrong password → error message shown
- Enter correct password → redirected to
/admin/ - Add a pricing item → appears in configurator pricing breakdown
- Remove a pricing item → disappears from pricing
- Edit persona → reflected in ConfigPanel
- Orders section shows real Stripe payment data
- Refresh admin page → still logged in (cookie persists)
- Run
npx vitest run→ all existing tests pass
Decisions
- Database: Prisma + SQLite file-based, no external server needed, real ORM
- Admin password stored as bcrypt hash in DB (not in env vars)
- JWT secret stored in AppSettings table in DB
- JWT in httpOnly cookie secure, no localStorage tokens
- jose library for JWT lightweight, edge-compatible (works in Next.js middleware)
- Personas need to move from hardcoded array to store-driven breaking change in ConfigPanel
- Stripe orders fetched via Stripe API directly no local DB needed for orders
Further Considerations
- Persona storage: Currently hardcoded in ConfigPanel. Moving to a store means ConfigPanel reads from store dynamically. This is required for admin CRUD to work. Recommended: create persona store with localStorage persistence (same pattern as pricing store)
- Stripe orders vs local orders: Currently orders only exist client-side. To show in admin, we fetch from Stripe's payment intents list. Recommended: use Stripe API directly, no local DB needed
- Multi-admin support: Currently single password. If needed later, can upgrade to NextAuth with credentials provider. Recommended: start simple, upgrade if needed