vape and 420 — each with its own category tree and URL namespace. Products are served from Supabase PostgreSQL with Row Level Security and filtered by is_active = true, status = 'active', and stock > 0 before reaching the storefront.
Routes
| Route | Component | Description |
|---|---|---|
/ | Home | Landing page with hero, flash deals, featured grid |
/vape/:slug | SectionSlugResolver | Vape product or category |
/420/:slug | SectionSlugResolver | 420 product or category |
/buscar | SearchResults | Live product search |
SectionSlugResolver
SectionSlugResolver (src/pages/SectionSlugResolver.tsx) is the single router entry point for both sections. Given a :slug URL parameter, it determines whether the slug belongs to a product or a category and renders the appropriate page component.
Extract slug and section
The component reads
section from the route path prefix (vape or 420) and :slug from the URL params.Attempt product lookup
Calls
getProductBySlug(slug, section) from products.service.ts. If a product is returned, renders <ProductDetail />.Attempt category lookup
If no product matches, queries the
categories table for a matching slug within the same section. If found, renders <CategoryPage />.Category Structure
Categories are stored in thecategories table with a self-referential parent_id for hierarchy. There are 13 categories split across two sections:
Vape Section
- Mods
- Atomizadores
- Líquidos
- Coils
- Accesorios Vape
420 Section
- Vaporizers
- Fumables
- Comestibles
- Concentrados
- Tópicos
- Accesorios 420
Category TypeScript interface:
Product Flags and Time Expiry
Products support three boolean promotion flags, each with an optional expiry timestamp:The
_until fields are informational — the admin panel uses them to schedule badge expiry. The storefront filters purely on the boolean flags; expiry enforcement is handled by the admin automation layer.Product Status
| Status | Storefront behavior |
|---|---|
active | Visible and purchasable |
legacy | May appear in search but addItem will still add it |
discontinued | Blocked from addItem in cart.store.ts |
coming_soon | Listed but not available for purchase |
useProducts() Hook
The useProducts hook is a React Query wrapper over getProducts(). Query results are cached for 2 minutes (staleTime: 1000 * 60 * 2).
GetProductsOptions interface (from products.service.ts):
filter is set, pagination is applied with .limit(limit). Without filter, server-side range pagination uses .range(offset, offset + limit - 1).
Specialized hooks
Product Variants
Products may have variants stored inproduct_variants, nested via the Supabase join in getProducts(). The mapProductVariations() utility normalizes the joined data structure:
sku, price, stock, and images. Variant options link to product_attribute_values (e.g., "Nicotina": "3mg").
AI Strategy Fields
TheProduct type includes AI-specific fields populated by the product-intelligence edge function:
