123 lines
3.0 KiB
TypeScript
123 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import {
|
|
DEBUG_FEATURES,
|
|
getDebugFeatureDefaultVisible,
|
|
type DebugFeature,
|
|
} from "@/config/debug-features";
|
|
|
|
const STORAGE_KEY = "nomadcna:dev-debug-overrides";
|
|
const IS_DEV = process.env.NODE_ENV === "development";
|
|
|
|
type Overrides = Record<string, boolean>;
|
|
|
|
interface DevDebugContextValue {
|
|
isDev: boolean;
|
|
panelOpen: boolean;
|
|
setPanelOpen: (open: boolean) => void;
|
|
features: DebugFeature[];
|
|
overrides: Overrides;
|
|
isFeatureVisible: (id: string) => boolean;
|
|
setFeatureVisible: (id: string, visible: boolean) => void;
|
|
resetAll: () => void;
|
|
}
|
|
|
|
const DevDebugContext = createContext<DevDebugContextValue | null>(null);
|
|
|
|
function readOverrides(): Overrides {
|
|
if (!IS_DEV || typeof window === "undefined") return {};
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return {};
|
|
const parsed = JSON.parse(raw) as Overrides;
|
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function writeOverrides(overrides: Overrides) {
|
|
if (!IS_DEV || typeof window === "undefined") return;
|
|
try {
|
|
if (Object.keys(overrides).length === 0) {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
} else {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(overrides));
|
|
}
|
|
} catch {
|
|
// ignore quota / private mode
|
|
}
|
|
}
|
|
|
|
export function DevDebugProvider({ children }: { children: ReactNode }) {
|
|
const [panelOpen, setPanelOpen] = useState(false);
|
|
const [overrides, setOverrides] = useState<Overrides>({});
|
|
|
|
useEffect(() => {
|
|
setOverrides(readOverrides());
|
|
}, []);
|
|
|
|
const isFeatureVisible = useCallback(
|
|
(id: string) => {
|
|
if (!IS_DEV) return getDebugFeatureDefaultVisible(id);
|
|
if (id in overrides) return overrides[id];
|
|
return getDebugFeatureDefaultVisible(id);
|
|
},
|
|
[overrides]
|
|
);
|
|
|
|
const setFeatureVisible = useCallback((id: string, visible: boolean) => {
|
|
if (!IS_DEV) return;
|
|
setOverrides((prev) => {
|
|
const defaultVisible = getDebugFeatureDefaultVisible(id);
|
|
const next = { ...prev };
|
|
if (visible === defaultVisible) {
|
|
delete next[id];
|
|
} else {
|
|
next[id] = visible;
|
|
}
|
|
writeOverrides(next);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const resetAll = useCallback(() => {
|
|
if (!IS_DEV) return;
|
|
setOverrides({});
|
|
writeOverrides({});
|
|
}, []);
|
|
|
|
const value = useMemo<DevDebugContextValue>(
|
|
() => ({
|
|
isDev: IS_DEV,
|
|
panelOpen,
|
|
setPanelOpen,
|
|
features: DEBUG_FEATURES,
|
|
overrides,
|
|
isFeatureVisible,
|
|
setFeatureVisible,
|
|
resetAll,
|
|
}),
|
|
[panelOpen, overrides, isFeatureVisible, setFeatureVisible, resetAll]
|
|
);
|
|
|
|
return <DevDebugContext.Provider value={value}>{children}</DevDebugContext.Provider>;
|
|
}
|
|
|
|
export function useDevDebug() {
|
|
const context = useContext(DevDebugContext);
|
|
if (!context) {
|
|
throw new Error("useDevDebug must be used within DevDebugProvider");
|
|
}
|
|
return context;
|
|
}
|