The Obsidian Palace · LMXEngine · Built on Next.js 15 + Supabase + Stripe
- What Was Built
- Admin Portal — Features & How to Use
- Database Setup
- Environment Variables
- Deploy to Vercel
- Post-Deploy Checklist
- Storefront Routes
- Architecture Notes
| Route | Description |
|---|---|
/ |
Homepage with hero, featured products, collections teaser |
/shop |
Full product catalogue with real-time Supabase updates |
/collections |
Curated vault index — links dynamically to DB categories |
/collections/[slug] |
Individual collection page — filtered by category slug |
/product/[slug] |
Single product detail with variants & add-to-cart |
/account |
Customer order history and profile |
/login |
Supabase Auth email/password + magic link |
/cart |
Shopping bag drawer (client component) |
/checkout |
Stripe Checkout session redirect |
/checkout/success |
Post-payment confirmation page |
/about |
Brand story page |
/contact |
Contact form (Resend) |
| Route | Description |
|---|---|
/admin |
Command Center — live stats dashboard |
/admin/products |
Product list with inline actions |
/admin/products/new |
Create product with image upload |
/admin/products/[id] |
Edit product details, images, stock |
/admin/orders |
Order fulfilment table with status controls |
/admin/categories |
Category management |
/admin/users |
Customer directory |
/admin/settings |
Site settings and frontend content editor |
| Route | Description |
|---|---|
/api/checkout |
Creates Stripe Checkout session (server-side) |
/api/webhooks/stripe |
Receives and verifies Stripe events |
- Log in at
/loginwith your admin email - Your
profiles.rolemust be'admin'in Supabase - Navigate to
/admin— non-admin users are redirected to/
To make an account admin: In Supabase → Table Editor →
profiles→ find your row → setroletoadmin
Live stats refreshed on every page load:
- Total Revenue — sum of all
paidorders - Orders — total order count
- Low Stock — products with
inventory < 5 - Customers — total registered users
Quick Actions grid:
- → New Product
- → View Orders
- → Manage Categories
- → Site Settings
Low Stock Alerts panel — top 5 products by lowest inventory, click to edit
Recent Transactions table — last 8 orders with status badges and amounts
- Click "New Product" or go to
/admin/products/new - Fill in:
- Name (required)
- Description
- Price (in USD, e.g.
49.99) - Stock / Inventory quantity
- Category — dropdown populated from DB
- Images — drag & drop upload to Supabase Storage
- Active toggle — only active products appear in the store
- Click Save — page revalidates instantly
- Click any product row →
/admin/products/[id] - Same form, pre-filled with existing data
- Upload new images or remove old ones
- Save — changes reflect on storefront immediately (ISR revalidation)
- Delete button on the product list row
- Product is removed from DB and storefront
Status values: pending → paid → shipped → delivered | cancelled | refunded
Workflow:
- Order arrives as
pendingwhen checkout starts - Stripe webhook (
/api/webhooks/stripe) marks itpaidautomatically - Admin manually marks
shippedafter generating a label - Shippo label generation available via the order detail action button
- Tracking number is stored and visible to the customer in
/account
Important: Orders are immutable after payment per security rules. Status can advance but items cannot be changed.
- Add categories with
name,slug,description,image_url - Slug must be URL-safe (e.g.
skincare,lip-collection) - Categories appear in shop filter and
/collections/[slug]pages - Deleting a category does not delete products (FK set to NULL)
Edits content stored in site_settings and frontend_content tables:
- Hero headline and subtitle
- Store name and tagline
- Kill Switch — set
store_enabledtofalseto show a maintenance page
Supabase → SQL Editor → New Query → Paste → Run
Use DATABASE_FINAL.sql — this is the single source of truth. It:
- Adds all missing columns to existing tables
- Creates
variants,site_settings,frontend_contenttables - Sets exactly 4 RLS policies per table (SELECT / INSERT / UPDATE / DELETE)
- Uses
(select auth.uid())pattern — zero performance warnings - Creates all indexes, triggers, and the
admin_sales_statsview - Seeds default settings
After running the SQL and creating your account:
UPDATE public.profiles
SET role = 'admin'
WHERE email = 'your@email.com';The SQL creates the product-images bucket automatically. Configure storage policies via:
Supabase → Storage → product-images → Policies
Add:
SELECT:true(public read)INSERT/UPDATE/DELETE:(select role from profiles where id = auth.uid()) = 'admin'
Create .env.local (never commit this file):
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://zsahskxejgbrvfhobfyp.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sb_publishable_...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# Stripe
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Email (Resend)
RESEND_API_KEY=re_...
# Shipping (Shippo)
SHIPPO_API_KEY=shippo_live_...
# App
NEXT_PUBLIC_SITE_URL=https://dinacosmetic.storeAdd all of the above to:
Vercel → Project → Settings → Environment Variables
Set for Production, Preview, and Development.
# Install Vercel CLI (if not installed)
npm i -g vercel
# From project root
vercel --prodOr connect via GitHub:
- Go to vercel.com/new
- Import
lead-matrix/LMXEngine - Framework: Next.js (auto-detected)
- Add all environment variables
- Deploy
git push origin mainVercel auto-deploys on every push to main.
After deploying, register your webhook:
- Stripe Dashboard → Webhooks
- Add endpoint:
https://dinacosmetic.store/api/webhooks/stripe - Events to listen for:
checkout.session.completedpayment_intent.succeededpayment_intent.payment_failed
- Copy the Signing secret → set as
STRIPE_WEBHOOK_SECRETin Vercel
In Supabase → Authentication → URL Configuration:
- Site URL:
https://dinacosmetic.store - Redirect URLs:
https://dinacosmetic.store/** http://localhost:3000/**
□ DATABASE_FINAL.sql run successfully in Supabase SQL Editor
□ Admin account role set to 'admin' in profiles table
□ All environment variables added to Vercel
□ Stripe webhook registered with correct endpoint
□ Supabase Auth redirect URLs configured
□ product-images storage bucket is public
□ Test: Create a product via /admin/products/new
□ Test: Place a test order with Stripe test card 4242 4242 4242 4242
□ Test: Verify webhook fires and order status changes to 'paid'
□ Test: /admin Command Center shows correct stats
□ Test: /collections loads categories from DB
□ Test: /collections/[slug] filters products correctly
/ → /shop → /product/[slug] → (add to cart) → /checkout → /checkout/success
→ (Stripe webhook fires)
→ Order marked 'paid'
→ Confirmation email sent
/login → Supabase Auth → redirect to /account (or previous page)
/account → view orders, update profile
- All pricing calculated server-side — client never submits prices
- Stripe webhook verified with
STRIPE_WEBHOOK_SECRETsignature - Admin routes protected by middleware + server-side role check
SUPABASE_SERVICE_ROLE_KEYonly used in server components and API routes- RLS enabled on all tables with zero policy conflicts
- Product lists use
revalidate = 60(ISR — fresh every 60s) - Admin dashboard is fully dynamic (no cache)
- All
auth.uid()calls use(select auth.uid())pattern (single eval per query) - Exactly 4 RLS policies per table (no redundant evaluation)
- Indexed columns:
is_active,category_id,inventory,slug,status,created_at
ProductGridsubscribes to Supabase Realtime — product updates appear instantly- Admin orders page refreshes on status change
- Stored in Supabase Storage
product-imagesbucket (public CDN) - Uploaded via drag-and-drop in
ImageUploadcomponent next/imageused throughout withsizesattribute for responsive loading
Last updated: 2026-02-26 · LMXEngine / DINA COSMETIC