"use client"; import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { authFetch } from "@/lib/auth"; // ─── Dev mode detection ───────────────────────────────────────────── /** * Build-time hint — may be `false` even in source-dev if the build happened * without the env var. The runtime check via `/api/dev-mode` is authoritative. */ const BUILD_TIME_HINT = process.env.NEXT_PUBLIC_SF_DEV === "1"; /** * Exported for static guards that run before the runtime check resolves. * Defaults to the build-time hint, then gets upgraded by the API response. * Components that need the authoritative value should use `useDevOverrides().isDevMode`. */ export let IS_DEV_MODE = BUILD_TIME_HINT; // ─── Override keys ────────────────────────────────────────────────── /** * Each override is a named boolean toggle. * Add new overrides here — they automatically appear in the Admin panel. */ export interface DevOverrideMap { /** Force the onboarding wizard to render regardless of auth state. */ forceOnboarding: boolean; } export type DevOverrideKey = keyof DevOverrideMap; export interface DevOverrideEntry { key: DevOverrideKey; label: string; description: string; /** Keyboard shortcut label shown in the UI, e.g. "Ctrl+Shift+1" */ shortcutLabel: string; } /** Registry of all available overrides, their labels, and shortcuts. */ export const DEV_OVERRIDE_REGISTRY: DevOverrideEntry[] = [ { key: "forceOnboarding", label: "Onboarding wizard", description: "Force the onboarding gate to render even when credentials are valid", shortcutLabel: "Ctrl+Shift+1", }, ]; // ─── Default state ────────────────────────────────────────────────── const DEFAULT_OVERRIDES: DevOverrideMap = { forceOnboarding: false, }; // ─── Context ──────────────────────────────────────────────────────── interface DevOverridesContextValue { /** Whether the host is source-dev (runtime-checked via /api/dev-mode). */ isDevMode: boolean; /** Whether dev-mode overrides are globally enabled (the master toggle). */ enabled: boolean; setEnabled: (enabled: boolean) => void; /** Individual override values. Only effective when `enabled` is true. */ overrides: DevOverrideMap; /** Toggle an individual override. */ toggle: (key: DevOverrideKey) => void; /** Resolve an override: returns true only when master + individual are both on. */ isActive: (key: DevOverrideKey) => boolean; } const DevOverridesContext = createContext( null, ); // ─── Provider ─────────────────────────────────────────────────────── export function DevOverridesProvider({ children }: { children: ReactNode }) { const [enabled, setEnabled] = useState(false); const [overrides, setOverrides] = useState(DEFAULT_OVERRIDES); const [isDevMode, setIsDevMode] = useState(BUILD_TIME_HINT); // Fetch authoritative dev-mode flag from the server at mount useEffect(() => { authFetch("/api/dev-mode", { cache: "no-store" }) .then((res) => res.json()) .then((data: { isDevMode?: boolean }) => { if (data.isDevMode) { setIsDevMode(true); IS_DEV_MODE = true; } }) .catch(() => { // Non-critical — fall back to build-time hint }); }, []); const toggle = useCallback((key: DevOverrideKey) => { setOverrides((prev) => ({ ...prev, [key]: !prev[key] })); }, []); const isActive = useCallback( (key: DevOverrideKey) => enabled && overrides[key], [enabled, overrides], ); // ─── Global keyboard shortcuts ────────────────────────────────── useEffect(() => { if (!isDevMode) return; function handleKeyDown(e: KeyboardEvent) { // Only fire when master toggle is on if (!enabled) return; // Ctrl+Shift+1 → toggle forceOnboarding if (e.ctrlKey && e.shiftKey && e.key === "1") { e.preventDefault(); setOverrides((prev) => ({ ...prev, forceOnboarding: !prev.forceOnboarding, })); } } window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [enabled, isDevMode]); const value = useMemo( () => isDevMode ? { isDevMode, enabled, setEnabled, overrides, toggle, isActive } : { isDevMode: false, enabled: false, setEnabled: () => {}, overrides: DEFAULT_OVERRIDES, toggle: () => {}, isActive: () => false, }, [isDevMode, enabled, overrides, toggle, isActive], ); return ( {children} ); } // ─── Hook ─────────────────────────────────────────────────────────── export function useDevOverrides(): DevOverridesContextValue { const ctx = useContext(DevOverridesContext); if (!ctx) { throw new Error( "useDevOverrides must be used within ", ); } return ctx; }