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

4.8 KiB

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)

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

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