Files
glass/docs/superpowers/specs/2026-05-10-admin-panel-v2-design.md
T

111 lines
4.8 KiB
Markdown

# Admin Panel v2 — Design Spec
**Date:** 2026-05-10
**Features:** User management, configurable pricing, new order contact fields
---
## Goal
Extend the existing Осколкам.Нет admin panel with three capabilities: full admin user management (invite/delete) via Supabase Edge Functions, dynamic calculator pricing stored in the database, and three new contact fields on orders (Telegram, Max messenger, address).
---
## Architecture
Three independent feature blocks, each with isolated DB changes, hooks, and UI. No changes to the public LeadForm. The Estimator (public) reads prices from DB via a new `usePricing` hook. User management uses Edge Functions as a secure server-side proxy so `service_role` is never exposed in the browser bundle.
**Tech stack:** React 19 + Vite, Supabase (PostgreSQL + Auth + Edge Functions), react-router-dom v6, inline styles, lucide-react icons, vitest + @testing-library/react.
---
## Database Changes
### `settings` table (new)
```sql
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO settings (key, value) VALUES
('price_200', '2000'),
('price_300', '3000');
ALTER TABLE settings ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can read settings" ON settings FOR SELECT USING (true);
CREATE POLICY "Auth users can update settings" ON settings FOR UPDATE USING (auth.role() = 'authenticated');
```
### `orders` table additions
```sql
ALTER TABLE orders
ADD COLUMN telegram_username TEXT,
ADD COLUMN max_username TEXT,
ADD COLUMN address TEXT;
```
---
## Edge Functions
Located in `supabase/functions/`. Each function:
- Reads `Authorization` header, verifies the JWT via Supabase Auth
- Returns 401 if unauthenticated
- Uses the built-in `service_role` (available via `SUPABASE_SERVICE_ROLE_KEY` env var in the Supabase runtime)
| Function | Method | Body | Action |
|---|---|---|---|
| `list-admin-users` | GET | — | `auth.admin.listUsers()``[{id, email, created_at, last_sign_in_at}]` |
| `invite-admin-user` | POST | `{email}` | `auth.admin.inviteUserByEmail(email)` — Supabase sends the invite email |
| `delete-admin-user` | POST | `{userId}` | `auth.admin.deleteUser(userId)` |
Deploy with: `supabase functions deploy list-admin-users && supabase functions deploy invite-admin-user && supabase functions deploy delete-admin-user`
---
## Frontend
### New files
| File | Responsibility |
|---|---|
| `src/hooks/usePricing.js` | Public hook — reads `price_200` and `price_300` from `settings` table. Returns `{prices: {200: number, 300: number}, loading}`. Used by `Estimator.jsx`. |
| `src/admin/settings/useSettings.js` | Admin hook — reads and updates prices in `settings`. Returns `{prices, loading, error, updatePrice(key, value)}`. |
| `src/admin/settings/SettingsPage.jsx` | Two number inputs (цена 200 мкм, цена 300 мкм) with a save button. Shows current prices, optimistic update on save. |
| `src/admin/users/useAdminUsers.js` | Hook — calls Edge Functions via `supabase.functions.invoke()`. Returns `{users, loading, error, inviteUser(email), deleteUser(userId)}`. |
| `src/admin/users/UserManagementPage.jsx` | Table of current admin users (email, created_at, last_sign_in). Invite form (email input + button). Delete button per row with confirmation dialog. |
### Modified files
| File | Change |
|---|---|
| `src/admin/layout/Sidebar.jsx` | Add nav items: «Пользователи» → `/admin/users` (Users icon), «Настройки» → `/admin/settings` (Settings icon) |
| `src/App.jsx` | Add lazy-loaded routes for `/admin/users` and `/admin/settings` inside the existing admin auth guard |
| `src/components/Estimator.jsx` | Replace hardcoded `costPerSqM` with `usePricing()`. Show skeleton/fallback while prices load. |
| `src/admin/orders/OrderModal.jsx` | Add three fields: `telegram_username` (text input, placeholder `@username`), `max_username` (text input, placeholder `@username`), `address` (textarea) |
| `src/admin/orders/useOrders.js` | Include `telegram_username`, `max_username`, `address` in `select('*')` (already covered), `createOrder`, and `updateOrder` payloads |
---
## Scope Exclusions
- Public `LeadForm.jsx` is **not** modified — new fields are admin-only
- Only two thickness options exist (200 µm, 300 µm) — no UI for adding/removing thickness tiers
- User roles beyond "authenticated = admin" are out of scope — all authenticated users have equal access
- Password reset is handled by Supabase's built-in email flow, not from the panel
---
## Testing
Each new hook gets a vitest unit test following the existing pattern (vi.hoisted() for mock variables, vi.mock() for supabase, renderHook + act). UI components get smoke tests (renders without crash, key elements present).
Files:
- `src/hooks/usePricing.test.js`
- `src/admin/settings/useSettings.test.js`
- `src/admin/users/useAdminUsers.test.js`