Skip to main content
The VSM Store admin panel is not a separate application. It lives inside the same React SPA as the storefront, separated by route prefix (/admin/*), lazy loading, and its own layout components. No storefront Header or Footer renders inside admin routes.

Architecture

Code Split Chunk

All admin pages are bundled into a single lazy-loaded chunk named admin-panel, configured in vite.config.ts via manualChunks. The chunk only loads when a user first navigates to /admin.

AdminGuard

Every admin route is wrapped with AdminGuard, which verifies the authenticated user exists in the admin_users table. Non-admins are redirected; unauthenticated users go to /login.

AdminLayout

Admin pages render inside AdminLayout — a distinct shell with a collapsible sidebar, breadcrumb header, omnisearch, and system-pulse indicator. No storefront chrome is present.

Supabase RLS

Row Level Security policies on every table ensure admin operations are gated at the database level, not only in the UI.

How Access Works

1

User logs in

The user authenticates via Supabase Auth. AuthContext loads their session and customer_profiles record.
2

Navigate to /admin

React Router renders AdminGuard wrapping AdminLayout. The guard calls checkIsAdmin(user.id) from admin-auth.service.ts.
3

admin_users table check

checkIsAdmin queries the admin_users table:
export async function checkIsAdmin(userId: string): Promise<boolean> {
    const { data, error } = await supabase
        .from('admin_users')
        .select('id, role')
        .eq('id', userId)
        .single();

    if (error || !data) return false;
    return true;
}
4

Guard decision

  • If admin → renders AdminLayout with the requested page.
  • If not admin → shows an “Access Denied” screen with a link back to the storefront.
  • If not logged in → <Navigate to="/login" replace />.
  • If check times out after 8 seconds → shows a debug/retry screen.
The guard has an 8-second timeout safety net. If Supabase does not respond within that window, the user sees a retry dialog rather than an infinite spinner.

Granting Admin Access

Insert a row into admin_users where id matches the user’s Supabase Auth UUID:
-- Grant admin access to a specific user
INSERT INTO admin_users (id, role)
VALUES ('<supabase-auth-uuid>', 'admin');

-- Verify the record
SELECT * FROM admin_users WHERE id = '<supabase-auth-uuid>';
The id column must exactly match the UUID from auth.users. Mismatched IDs will result in checkIsAdmin returning false and the user being denied access.

Admin Routes

All routes are prefixed with /admin and rendered inside AdminGuard + AdminLayout.
RouteComponentDescription
/adminAdminDashboardMain dashboard — metrics, charts, AI insights
/admin/productsAdminProductsProduct list with filtering and bulk actions
/admin/products/newAdminProductFormCreate a new product
/admin/products/:idAdminProductFormEdit an existing product
/admin/ordersAdminOrdersOrder management with Kanban and list views
/admin/categoriesAdminCategoriesCategory tree management
/admin/customersAdminCustomersCustomer directory
/admin/customers/:idAdminCustomerDetailsFull customer profile and history
/admin/couponsAdminCouponsCoupon CRUD
/admin/settingsAdminSettingsStore configuration
/admin/monitoringAdminMonitoringSystem health and live users

AdminLayout Structure

The sidebar is organized into five sections:
SectionItems
OperacionesDashboard, Pedidos, Clientes
InventarioProductos, Categorías, Marcas, Etiquetas, Atributos, Batch Manager
VitrinaEditor Home, MegaHero Sliders, Ofertas Flash, Testimonios
RetenciónV-Coins (Lealtad), Cupones, Ruleta de Premios
SistemaCesarin OS, Configuración, Monitoreo
The Monitoreo item shows a color-coded live ping in the sidebar based on current system pulse:
  • Green dot → optimal
  • Amber dot → busy (>10 active orders)
  • Red dot → alert (any products with stock < 5)

Code Splitting Configuration

// vite.config.ts — manualChunks
{
  'vendor-react': ['react', 'react-dom', 'react-router-dom'],
  'vendor-query': ['@tanstack/react-query'],
  'vendor-supabase': ['@supabase/supabase-js'],
  'vendor-icons': ['lucide-react'],
  'admin-panel': [/src\/pages\/admin\//],  // Entire admin panel
  'legal-pages': [/src\/pages\/legal\//],
}
The admin-panel chunk is loaded lazily via React.lazy() in App.tsx, so the admin JavaScript is never downloaded by regular storefront visitors.

Build docs developers (and LLMs) love