feat: add admin authentication and management features
- Implemented Prisma schema with models for AdminUser, AppSettings, and Snapshot. - Created seed script to initialize the database with an admin user and JWT secret. - Developed admin login page with form handling and error management. - Added API routes for admin login, logout, change password, and JWT verification. - Integrated Stripe for payment intent management in admin orders. - Established middleware for protecting admin routes with JWT authentication. - Created Zustand stores for managing persona and snapshot states.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# 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
|
||||
1. **Setup Prisma + SQLite**
|
||||
- Install `prisma` and `@prisma/client`
|
||||
- Create `prisma/schema.prisma` with SQLite provider
|
||||
- Models: `AdminUser` (id, username, passwordHash, createdAt), `AppSettings` (key, value)
|
||||
- Run `npx prisma db push` to create the database
|
||||
- Create `src/lib/prisma.ts` — singleton Prisma client
|
||||
|
||||
2. **Create seed script** `prisma/seed.ts`
|
||||
- Creates default admin user with bcrypt-hashed password
|
||||
- Generates and stores JWT secret in `AppSettings` table
|
||||
- Run with `npx prisma db seed`
|
||||
|
||||
3. **Create admin auth API routes**
|
||||
- `src/app/api/admin/login/route.ts` — POST, accepts `{ username, password }`, verifies bcrypt hash from DB, returns JWT in httpOnly cookie (JWT secret from DB)
|
||||
- `src/app/api/admin/verify/route.ts` — GET, checks JWT cookie validity
|
||||
- `src/app/api/admin/logout/route.ts` — POST, clears auth cookie
|
||||
- `src/app/api/admin/change-password/route.ts` — POST, accepts `{ currentPassword, newPassword }`, updates hash in DB
|
||||
|
||||
4. **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
|
||||
|
||||
5. **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
|
||||
|
||||
6. **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)
|
||||
6. **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 `usePricingStore` to support `addItem()` and `removeItem()` actions
|
||||
|
||||
7. **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 `usePersonaStore` or extend `usePricingStore` to manage persona definitions
|
||||
- Sync persona list with ConfigPanel's `PERSONA_OPTIONS` (currently hardcoded)
|
||||
|
||||
## Phase 3: Additional Admin Features
|
||||
|
||||
### Steps (parallel with Phase 2)
|
||||
8. **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.ts` to fetch from Stripe
|
||||
|
||||
9. **Analytics overview cards** at top of admin page
|
||||
- Total revenue (from Stripe)
|
||||
- Number of orders
|
||||
- Most popular persona
|
||||
- Most popular color
|
||||
|
||||
10. **Configurator settings** section
|
||||
- Edit default color (`#96a2b6`)
|
||||
- Toggle available color options
|
||||
- Set min/max price boundaries
|
||||
|
||||
11. **Logout button** in admin header
|
||||
|
||||
## Relevant Files
|
||||
- `src/app/admin/page.tsx` — Main admin dashboard (add CRUD UI, analytics)
|
||||
- `src/app/admin/login/page.tsx` — New login page
|
||||
- `src/app/api/admin/login/route.ts` — New auth API
|
||||
- `src/app/api/admin/verify/route.ts` — New verify API
|
||||
- `src/app/api/admin/orders/route.ts` — New orders API
|
||||
- `src/middleware.ts` — New, protects admin routes
|
||||
- `src/lib/prisma.ts` — New, Prisma client singleton
|
||||
- `prisma/schema.prisma` — New, database schema (AdminUser, AppSettings)
|
||||
- `prisma/seed.ts` — New, seeds default admin user
|
||||
- `src/store/usePricingStore.ts` — Add `addItem()`, `removeItem()` actions
|
||||
- `src/components/ConfigPanel.tsx` — Persona options currently hardcoded (PERSONA_OPTIONS const)
|
||||
- `.env.local` — Add ADMIN_PASSWORD, ADMIN_JWT_SECRET
|
||||
|
||||
## Verification
|
||||
1. Visit `/admin/` without login → redirected to `/admin/login/`
|
||||
2. Enter wrong password → error message shown
|
||||
3. Enter correct password → redirected to `/admin/`
|
||||
4. Add a pricing item → appears in configurator pricing breakdown
|
||||
5. Remove a pricing item → disappears from pricing
|
||||
6. Edit persona → reflected in ConfigPanel
|
||||
7. Orders section shows real Stripe payment data
|
||||
8. Refresh admin page → still logged in (cookie persists)
|
||||
9. 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
|
||||
1. **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)**
|
||||
2. **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**
|
||||
3. **Multi-admin support**: Currently single password. If needed later, can upgrade to NextAuth with credentials provider. **Recommended: start simple, upgrade if needed**
|
||||
Reference in New Issue
Block a user