Skip to main content
Store settings are managed at /admin/settings. All settings are stored in a single row in the store_settings table (row id = 1) and surfaced through a form split into four lego panels: WhatsApp, Social, Payment, and General.

store_settings Table

The store_settings table uses a structured single-row approach (not key-value pairs). The full schema:
export interface StoreSettings {
    id: number;                        // Always 1 — singleton row
    site_name: string;
    description: string | null;
    logo_url: string | null;
    whatsapp_number: string;           // International format without +
    whatsapp_default_message: string | null;
    social_links: {
        facebook?: string;
        instagram?: string;
        youtube?: string;
        tiktok?: string;
    } | null;
    location_address: string | null;
    location_city: string | null;
    location_map_url: string | null;
    bank_account_info: string | null;  // Multiline: Bank, Account, CLABE, Beneficiary
    payment_methods: {
        transfer: boolean;
        mercadopago: boolean;
        cash: boolean;
    } | null;
    hero_sliders: HeroSlider[] | null;             // MegaHero slider config
    featured_categories: FeaturedCategory[] | null; // Home page featured categories
    loyalty_config: LoyaltyConfig | null;           // Loyalty program rules
    loyalty_tiers_config: LoyaltyTier[] | null;     // Bronze/Silver/Gold/Platinum thresholds
    flash_deals_end: string | null;                 // ISO timestamp for flash deal expiry
    is_ai_assistant_enabled: boolean;              // Enable/disable Cesarin OS
    pilot_runbook_status: PilotRunbookItem[] | null;
}

Service Functions

getStoreSettings

export async function getStoreSettings(): Promise<StoreSettings | null>
Fetches the single settings row (id = 1). Returns null if the table doesn’t exist or the row is missing, logging a warning rather than throwing.

updateStoreSettings

export async function updateStoreSettings(
    settings: Partial<StoreSettings>
): Promise<StoreSettings>
Updates the singleton row (id = 1). The id field is stripped from the payload before the UPDATE call. Returns the full updated settings row.

AdminSettings Form Panels

Configured via WhatsAppSettings component:
  • whatsapp_number — Phone number in international format without + (e.g. 5212281234567)
  • whatsapp_default_message — Pre-filled message when customers tap the WhatsApp float button

Loyalty Configuration

The loyalty_config JSONB column controls the V-Coins loyalty program:
export interface LoyaltyConfig {
    points_per_currency: number;   // Points earned per MXN spent (default: 1)
    currency_per_point: number;    // MXN value of each redeemed point (default: 0.1)
    min_points_to_redeem: number;  // Minimum balance required to redeem (default: 100)
    max_points_per_order: number;  // Cap on points applied per order (default: 1000)
    points_expiry_days: number;    // Days before points expire (default: 365)
    enable_loyalty: boolean;       // Master switch
}
Default values used by AdminSettings when no settings are saved:
const DEFAULT_LOYALTY: LoyaltyConfig = {
    points_per_currency: 1,
    currency_per_point: 0.1,
    min_points_to_redeem: 100,
    max_points_per_order: 1000,
    points_expiry_days: 365,
    enable_loyalty: true,
};

SITE_CONFIG (Static Defaults)

The SITE_CONFIG in src/config/site.ts provides compile-time defaults. These are used as fallbacks when dynamic settings have not been configured:
export const SITE_CONFIG = {
    name: 'VSM Store',
    description: 'Tu tienda de vape y productos 420',
    logo: '/logo-vsm.png',
    whatsapp: {
        number: '5212281234567',
        defaultMessage: 'Hola, vengo de VSM Store y quiero hacer un pedido',
    },
    contact: {
        email: '[email protected]',
        phone: '2281234567',
    },
    location: {
        address: 'Av. Principal #123, Col. Centro',
        city: 'Xalapa',
        state: 'Veracruz',
        zipCode: '91000',
        country: 'México',
    },
    bankAccount: `Banco: BBVA\nCuenta: 0123456789\nCLABE: 012000001234567890\nBeneficiario: VSM Store`,
    social: {
        facebook: 'https://www.facebook.com/vsmstore',
        instagram: 'https://www.instagram.com/vsmstore',
        youtube: 'https://www.youtube.com/@vsmstore',
        tiktok: 'https://www.tiktok.com/@vsmstore',
        whatsapp: 'https://wa.me/5212281234567',
    },
    store: {
        currency: 'MXN',
        currencySymbol: '$',
        locale: 'es-MX',
        timezone: 'America/Mexico_City',
    },
} as const;
SITE_CONFIG is a compile-time constant. To change values in production without redeploying, use store_settings via the Admin Settings page. The app reads dynamic settings from store_settings at runtime and falls back to SITE_CONFIG if settings are unavailable.

Form State Management

AdminSettings uses prefix routing in handleChange to route input changes to the correct nested state:
Input name prefixTarget field
social_formData.social_links[key]
loyalty_formData.loyalty_config[key]
payment_formData.payment_methods[key]
(none)formData[name] directly
Save is triggered by handleSubmit, which calls updateMutation.mutateAsync() with the full form state merged with { id: STORE_SETTINGS_ID } (constant = 1).

Build docs developers (and LLMs) love