Admin Dashboard
Last updated on 2026-08-30
The admin panel is a complete store management interface that reads from and writes to the Supabase database via Server Actions. All admin routes are protected by middleware that checks for the admin role.
Admin Routes
| Route | Page | Operations |
|---|---|---|
/admin |
Dashboard | Revenue stats, recent orders, charts |
/admin/products |
Products List | View all, filter, search |
/admin/products/new |
New Product | Create with images and variants |
/admin/products/[id]/edit |
Edit Product | Update details, manage images |
/admin/orders |
Orders List | View all, filter by status |
/admin/orders/[id] |
Order Detail | Status updates, tracking, notes |
/admin/customers |
Customer List | View all with order stats |
/admin/customers/[id] |
Customer Detail | Profile, order history |
/admin/analytics |
Analytics | Revenue charts, product performance |
/admin/discounts |
Discount Codes | Create, edit, disable |
/admin/reviews |
Reviews | Moderate, respond, approve/reject |
/admin/returns |
Returns | Handle return requests |
/admin/settings |
Store Settings | Store configuration |
Admin Dashboard
The main dashboard shows:
- Revenue stats — total revenue, orders, customers, average order value (aggregate Supabase queries)
- Recent orders — last 5 orders with status badges
- Revenue chart — daily/weekly/monthly revenue from order data (Recharts)
- Top products — best-selling products by order count
All data comes from real database queries, not mock data.
Product Management
Creating Products
/admin/products/new provides a form with:
- Name, slug, description, long description
- Price, sale price, SKU, stock
- Category and subcategory selection
- Tags (comma-separated)
- Features list
- Specs (key-value pairs as JSON)
- Status (active, draft, archived)
The form submits via the createProduct Server Action in lib/actions/products.ts.
Image Upload
The ImageUploader component (components/admin/image-uploader.tsx) handles:
- Drag-and-drop or click to select images
- Upload to Supabase Storage (
product-imagesbucket) - Create
product_imagesrows with position ordering - Set primary image flag
- Reorder images via drag
// Upload flow
const { data } = await supabase.storage
.from("product-images")
.upload(`${productId}/${fileName}`, file)
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from("product-images")
.getPublicUrl(data.path)
// Create database record
await supabase.from("product_images").insert({
product_id: productId,
url: publicUrl,
position: nextPosition,
is_primary: isFirst,
})
Editing Products
/admin/products/[id]/edit pre-fills the form with current data and uses the updateProduct Server Action.
Order Management
Order List
Displays all orders with:
- Order number, customer name, email
- Status badge (pending, processing, shipped, delivered, cancelled)
- Payment status (pending, paid, refunded)
- Total amount, date
- Filterable by status
Order Detail
/admin/orders/[id] shows:
- Order items with images and prices
- Shipping and billing addresses
- Payment info (Stripe session and payment intent IDs)
- Status update dropdown (triggers
updateOrderStatusServer Action) - Tracking number input
- Order notes with add functionality
- Timeline of status changes
Status Updates
// lib/actions/orders.ts
export async function updateOrderStatus(orderId: string, status: string) {
const supabase = await createAdminClient()
await supabase
.from("orders")
.update({ status, updated_at: new Date().toISOString() })
.eq("id", orderId)
revalidatePath(`/admin/orders/${orderId}`)
}
Customer Directory
- List all users with profile data
- Order count and lifetime value per customer
- Click through to customer detail with full order history
- Data comes from joining
profileswithordersaggregates
Discount Code Management
CRUD operations on the discount_codes table:
| Field | Type | Description |
|---|---|---|
code |
text | Unique code (e.g., "SAVE20") |
type |
text | percentage or fixed |
value |
numeric | Discount amount or percentage |
min_order_amount |
numeric | Minimum order to apply |
usage_limit |
integer | Max uses (null = unlimited) |
usage_count |
integer | Current use count |
expires_at |
timestamptz | Expiration date |
status |
text | active or disabled |
Review Moderation
- View all reviews with status filters (pending, approved, flagged, rejected)
- Approve or reject reviews
- Write admin responses that appear on the product page
- Review status change triggers the rating recalculation trigger
Server Actions
All admin operations use Server Actions from lib/actions/:
// Example: lib/actions/products.ts
"use server"
import { createAdminClient } from "@/lib/supabase/admin"
import { revalidatePath } from "next/cache"
export async function createProduct(formData: FormData) {
const supabase = createAdminClient()
// ... insert into products table
revalidatePath("/admin/products")
}
Key points:
- All Server Actions use the admin Supabase client (service role)
revalidatePath()refreshes the page data after mutations- Form data is validated before database operations
- Error handling returns user-friendly messages
Access Control
Admin access requires:
- Middleware check —
middleware.tsredirects non-admin users away from/admin/* - RLS policies — admin queries use
is_admin()function for data access - Server Actions — the admin Supabase client bypasses RLS with the service role key
To make a user an admin:
- Go to Supabase Dashboard > Authentication > Users
- Find the user and click their row
- Edit
app_metadatato:{"role": "admin"}