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:

  1. Drag-and-drop or click to select images
  2. Upload to Supabase Storage (product-images bucket)
  3. Create product_images rows with position ordering
  4. Set primary image flag
  5. 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 updateOrderStatus Server 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 profiles with orders aggregates

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:

  1. Middleware checkmiddleware.ts redirects non-admin users away from /admin/*
  2. RLS policies — admin queries use is_admin() function for data access
  3. Server Actions — the admin Supabase client bypasses RLS with the service role key

To make a user an admin:

  1. Go to Supabase Dashboard > Authentication > Users
  2. Find the user and click their row
  3. Edit app_metadata to: {"role": "admin"}