hidden dashboard routing, dev and build env files

This commit is contained in:
Achintya Rajan
2025-10-06 19:21:34 -07:00
parent b1f3553ae4
commit 6529ed4d62
35 changed files with 208 additions and 114 deletions

View File

@@ -0,0 +1,2 @@
NODE_ENV=development
NEXT_PUBLIC_BASE_URL=""

View File

@@ -0,0 +1,2 @@
NODE_ENV=production
NEXT_PUBLIC_BASE_URL="ui/"

View File

@@ -46,6 +46,21 @@ interface MenuItem {
icon?: React.ReactNode;
}
/** ---------- Base URL helpers ---------- */
/** Normalizes NEXT_PUBLIC_BASE_URL into either "" or "/something" (no trailing slash). */
const getBasePath = () => {
const raw = process.env.NEXT_PUBLIC_BASE_URL ?? "";
const trimmed = raw.replace(/^\/+|\/+$/g, ""); // strip leading/trailing slashes
return trimmed ? `/${trimmed}` : "";
};
/** Joins base path with a relative path like "virtual-keys" or "/virtual-keys" -> "/base/virtual-keys" */
const withBase = (relativePath: string) => {
const base = getBasePath(); // "" or "/ui" (no trailing slash)
const rel = relativePath.replace(/^\/+/, ""); // drop any leading slash
return `${base}/${rel}`.replace(/\/{2,}/g, "/"); // collapse accidental doubles
};
const Sidebar2: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelectedKey, collapsed = false }) => {
const menuItems: MenuItem[] = [
{ key: "1", page: "api-keys", label: "Virtual Keys", icon: <KeyOutlined style={{ fontSize: 18 }} /> },
@@ -217,8 +232,11 @@ const Sidebar2: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelect
return "1";
};
// Match Virtual Keys path with base prefix (e.g., "/virtual-keys" or "/ui/virtual-keys")
const virtualKeysPath = withBase("virtual-keys");
const selectedMenuKey =
pathname === "/dashboard/virtual-keys"
pathname === virtualKeysPath
? "1"
: pageParam
? findMenuItemKey(pageParam)
@@ -226,16 +244,19 @@ const Sidebar2: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelect
? findMenuItemKey(defaultSelectedKey)
: "1";
// Root-only routing helper: always replace everything after the domain
const rootWithPage = (p: string) => ({ pathname: "/", query: { page: p } });
// Root-only routing helper: always replace everything after the domain, honoring base path
const rootWithPage = (p: string) => ({
pathname: getBasePath() || "/",
query: { page: p },
});
// Convert to AntD Menu items:
// - "Virtual Keys" still routes to /virtual-keys
// - All other items (and children) route to "/?page=<page>"
// - "Virtual Keys" routes to "/<BASE>/virtual-keys"
// - All other items (and children) route to "/<BASE>/?page=<page>"
const antdItems = filteredMenuItems.map((item) => {
const isVirtualKeys = item.key === "1";
const label = isVirtualKeys ? (
<Link href="/virtual-keys">Virtual Keys</Link>
<Link href={withBase("virtual-keys")}>Virtual Keys</Link>
) : (
<Link href={rootWithPage(item.page)}>{item.label}</Link>
);

View File

@@ -0,0 +1,32 @@
"use client";
import Sidebar2 from "@/app/(dashboard)/components/Sidebar2";
import Sidebar from "@/components/leftnav";
import React from "react";
import useFeatureFlags from "@/hooks/useFeatureFlags";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
interface SidebarProviderProps {
defaultSelectedKey: string;
sidebarCollapsed: boolean;
setPage: (page: string) => void;
}
const SidebarProvider = ({ defaultSelectedKey, sidebarCollapsed, setPage }: SidebarProviderProps) => {
const { accessToken, userRole } = useAuthorized();
const { refactoredUIFlag, setRefactoredUIFlag } = useFeatureFlags();
return refactoredUIFlag ? (
<Sidebar2 accessToken={accessToken} userRole={userRole} />
) : (
<Sidebar
accessToken={accessToken}
setPage={setPage}
userRole={userRole}
defaultSelectedKey={defaultSelectedKey}
collapsed={sidebarCollapsed}
/>
);
};
export default SidebarProvider;

View File

@@ -29,9 +29,9 @@ import {
} from "@/components/networking";
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
import BulkCreateUsersButton from "@/components/bulk_create_users_button";
import { fetchTeams } from "@/app/dashboard/virtual-keys/networking";
import useTeams from "@/app/dashboard/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { fetchTeams } from "@/app/(dashboard)/virtual-keys/networking";
import useTeams from "@/app/(dashboard)/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
// Helper function to generate UUID compatible across all environments
const generateUUID = (): string => {

View File

@@ -3,8 +3,8 @@
import React from "react";
import Navbar from "@/components/navbar";
import { ThemeProvider } from "@/contexts/ThemeContext";
import Sidebar2 from "@/app/dashboard/components/Sidebar2";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import Sidebar2 from "@/app/(dashboard)/components/Sidebar2";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function Layout({ children }: { children: React.ReactNode }) {
const { accessToken, userRole } = useAuthorized();

View File

@@ -6,9 +6,9 @@ import { Modal, Form } from "antd";
import { getPossibleUserRoles, Organization } from "@/components/networking";
import { rolesWithWriteAccess } from "@/utils/roles";
import { Team } from "@/components/key_team_helpers/key_list";
import CreateKeyModal from "@/app/dashboard/virtual-keys/components/CreateKeyModal/CreateKeyModal";
import CreateUserModal from "@/app/dashboard/components/modals/CreateUserModal";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import CreateKeyModal from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/CreateKeyModal";
import CreateUserModal from "@/app/(dashboard)/components/modals/CreateUserModal";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
interface CreateKeyProps {
team: Team | null;

View File

@@ -17,7 +17,7 @@ import SchemaFormFields from "@/components/common_components/check_openapi_schem
import { Team } from "@/components/key_team_helpers/key_list";
import React from "react";
import { DefaultOptionType } from "rc-select/lib/Select";
import { ModelAliases } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/types";
import { ModelAliases } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/types";
export interface OptionalSettingsSectionProps {
form: FormInstance;

View File

@@ -6,7 +6,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
import TeamDropdown from "@/components/common_components/team_dropdown";
import React from "react";
import { Team } from "@/components/key_team_helpers/key_list";
import { UserOption } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/types";
import { UserOption } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/types";
interface OwnershipSectionProps {
team: Team | null;

View File

@@ -5,7 +5,7 @@ import { Text } from "@tremor/react";
import { keyCreateCall, keyCreateServiceAccountCall } from "@/components/networking";
import React, { useEffect, useState } from "react";
import { Team } from "@/components/key_team_helpers/key_list";
import SaveKeyModal from "@/app/dashboard/virtual-keys/components/SaveKeyModal";
import SaveKeyModal from "@/app/(dashboard)/virtual-keys/components/SaveKeyModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import {
useGuardrailsAndPrompts,
@@ -13,15 +13,15 @@ import {
useTeamModels,
useUserModels,
useUserSearch,
} from "@/app/dashboard/virtual-keys/components/CreateKeyModal/hooks";
import OwnershipSection from "@/app/dashboard/virtual-keys/components/CreateKeyModal/CreateKeyForm/OwnershipSection";
import KeyDetailsSection from "@/app/dashboard/virtual-keys/components/CreateKeyModal/CreateKeyForm/KeyDetailsSection";
import OptionalSettingsSection from "@/app/dashboard/virtual-keys/components/CreateKeyModal/CreateKeyForm/OptionalSettingsSection";
import { ModelAliases } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/types";
import { getPredefinedTags, prepareFormValues } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/utils";
import { fetchTeams } from "@/app/dashboard/virtual-keys/networking";
import useTeams from "@/app/dashboard/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
} from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/hooks";
import OwnershipSection from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/CreateKeyForm/OwnershipSection";
import KeyDetailsSection from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/CreateKeyForm/KeyDetailsSection";
import OptionalSettingsSection from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/CreateKeyForm/OptionalSettingsSection";
import { ModelAliases } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/types";
import { getPredefinedTags, prepareFormValues } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/utils";
import { fetchTeams } from "@/app/(dashboard)/virtual-keys/networking";
import useTeams from "@/app/(dashboard)/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export interface CreateKeyModalProps {
isModalVisible: boolean;

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { fetchGuardrails, fetchPrompts } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { fetchGuardrails, fetchPrompts } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export const useGuardrailsAndPrompts = () => {
const [guardrails, setGuardrails] = useState<string[]>([]);

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { getMCPAccessGroups } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { getMCPAccessGroups } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export const useMcpAccessGroups = () => {
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import type { Team } from "@/components/key_team_helpers/key_list";
import { fetchTeamModels } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { fetchTeamModels } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export const useTeamModels = (selectedTeam: Team | null) => {
const { userId: userID, userRole, accessToken } = useAuthorized();

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { getUserModelNames } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { getUserModelNames } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export const useUserModels = () => {
const { userId: userID, userRole, accessToken } = useAuthorized();

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { debounce } from "lodash";
import { searchUserOptionsByEmail } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import { searchUserOptionsByEmail } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
type User = { user_id: string; user_email: string; role?: string };
export interface UserOption {

View File

@@ -6,7 +6,7 @@ import {
User,
userFilterUICall,
} from "@/components/networking";
import { ModelAvailableResponse, UserOption } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/types";
import { ModelAvailableResponse, UserOption } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/types";
export const fetchGuardrails = async (accessToken: string) => {
try {

View File

@@ -1,5 +1,5 @@
import { mapDisplayToInternalNames } from "@/components/callback_info_helpers";
import { ModelAliases } from "@/app/dashboard/virtual-keys/components/CreateKeyModal/types";
import { ModelAliases } from "@/app/(dashboard)/virtual-keys/components/CreateKeyModal/types";
export const getPredefinedTags = (data: any[] | null) => {
let allTags = [];

View File

@@ -16,9 +16,9 @@ import { Organization, userListCall } from "@/components/networking";
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
import FilterComponent, { FilterOption } from "@/components/molecules/filter";
import { useFilterLogic } from "@/components/key_team_helpers/filter_logic";
import useTeams from "@/app/dashboard/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import KeyInfoView from "@/app/dashboard/virtual-keys/components/KeyInfoView";
import useTeams from "@/app/(dashboard)/virtual-keys/hooks/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import KeyInfoView from "@/app/(dashboard)/virtual-keys/components/KeyInfoView";
interface AllKeysTableProps {
keys: KeyResponse[];

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { fetchTeams } from "@/app/dashboard/virtual-keys/networking";
import { fetchTeams } from "@/app/(dashboard)/virtual-keys/networking";
import { Team } from "@/components/key_team_helpers/key_list";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
const useTeams = () => {
const [teams, setTeams] = useState<Team[]>([]);

View File

@@ -1,15 +1,15 @@
"use client";
import VirtualKeysTable from "@/app/dashboard/virtual-keys/components/VirtualKeysTable/VirtualKeysTable";
import VirtualKeysTable from "@/app/(dashboard)/virtual-keys/components/VirtualKeysTable/VirtualKeysTable";
import { useState } from "react";
import useKeyList from "@/components/key_team_helpers/key_list";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Col, Grid } from "@tremor/react";
import CreateKey from "@/app/dashboard/virtual-keys/components/CreateKey";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized"
import CreateKey from "@/app/(dashboard)/virtual-keys/components/CreateKey";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
const VirtualKeysPage = () => {
const {accessToken, userRole} = useAuthorized();
const { accessToken, userRole } = useAuthorized();
const [createClicked, setCreateClicked] = useState<boolean>(false);
const queryClient = new QueryClient();

View File

@@ -1,36 +0,0 @@
"use client";
import Sidebar2 from "@/app/dashboard/components/Sidebar2";
import Sidebar from "@/components/leftnav";
import React from "react";
import useFeatureFlags from "@/hooks/useFeatureFlags";
import useAuthorized from "@/app/dashboard/hooks/useAuthorized";
interface SidebarProviderProps {
defaultSelectedKey: string;
sidebarCollapsed: boolean;
setPage: (page: string) => void;
}
const SidebarProvider = ({defaultSelectedKey, sidebarCollapsed, setPage}: SidebarProviderProps) => {
const {accessToken, userRole} = useAuthorized();
const { refactoredUIFlag, setRefactoredUIFlag } = useFeatureFlags();
return (
refactoredUIFlag ? (
<Sidebar2 accessToken={accessToken} userRole={userRole} />
) : (
<Sidebar
accessToken={accessToken}
setPage={setPage}
userRole={userRole}
defaultSelectedKey={defaultSelectedKey}
collapsed={sidebarCollapsed}
/>
)
);
}
export default SidebarProvider;

View File

@@ -40,8 +40,8 @@ import UIThemeSettings from "@/components/ui_theme_settings";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { cx } from "@/lib/cva.config";
import useFeatureFlags, { FeatureFlagsProvider } from "@/hooks/useFeatureFlags";
import Sidebar2 from "@/app/dashboard/components/Sidebar2";
import SidebarProvider from "@/app/dashboard/components/SidebarProvider";
import Sidebar2 from "@/app/(dashboard)/components/Sidebar2";
import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider";
function getCookie(name: string) {
const cookieValue = document.cookie.split("; ").find((row) => row.startsWith(name + "="));
@@ -126,6 +126,10 @@ export default function CreateKeyPage() {
return searchParams.get("page") || "api-keys";
});
useEffect(() => {
setPage(searchParams.get("page") || "api-keys");
}, [searchParams]);
// Custom setPage function that updates URL
const updatePage = (newPage: string) => {
// Update URL without full page reload

View File

@@ -56,6 +56,17 @@ interface MenuItem {
icon?: React.ReactNode;
}
/** ---------- Base URL helpers ---------- */
/**
* Normalizes NEXT_PUBLIC_BASE_URL to either "/" or "/ui/" (always with a trailing slash).
* Supported env values: "" or "ui/".
*/
const getBasePath = () => {
const raw = process.env.NEXT_PUBLIC_BASE_URL ?? "";
const trimmed = raw.replace(/^\/+|\/+$/g, ""); // strip leading/trailing slashes
return trimmed ? `/${trimmed}/` : "/"; // ensure trailing slash
};
const Sidebar: React.FC<SidebarProps> = ({ accessToken, setPage, userRole, defaultSelectedKey, collapsed = false }) => {
// Note: If a menu item does not have a role, it is visible to all roles.
const menuItems: MenuItem[] = [
@@ -214,6 +225,7 @@ const Sidebar: React.FC<SidebarProps> = ({ accessToken, setPage, userRole, defau
],
},
];
// Find the menu item that matches the default page, including in submenus
const findMenuItemKey = (page: string): string => {
// Check top-level items
@@ -248,6 +260,15 @@ const Sidebar: React.FC<SidebarProps> = ({ accessToken, setPage, userRole, defau
return true;
});
// Centralized navigation that prefixes the base path ("/" or "/ui/")
const goTo = (page: string) => {
const base = getBasePath(); // "/" or "/ui/"
const newSearchParams = new URLSearchParams(window.location.search);
newSearchParams.set("page", page);
window.history.pushState(null, "", `${base}?${newSearchParams.toString()}`); // e.g. "/ui/?page=..."
setPage(page);
};
return (
<Layout style={{ minHeight: "100vh" }}>
<Sider
@@ -291,21 +312,9 @@ const Sidebar: React.FC<SidebarProps> = ({ accessToken, setPage, userRole, defau
key: child.key,
icon: child.icon,
label: child.label,
onClick: () => {
const newSearchParams = new URLSearchParams(window.location.search);
newSearchParams.set("page", child.page);
window.history.pushState(null, "", `?${newSearchParams.toString()}`);
setPage(child.page);
},
onClick: () => goTo(child.page),
})),
onClick: !item.children
? () => {
const newSearchParams = new URLSearchParams(window.location.search);
newSearchParams.set("page", item.page);
window.history.pushState(null, "", `?${newSearchParams.toString()}`);
setPage(item.page);
}
: undefined,
onClick: !item.children ? () => goTo(item.page) : undefined,
}))}
/>
</ConfigProvider>

View File

@@ -131,11 +131,10 @@ export const fetchUserModels = async (
}
};
/**
* ─────────────────────────────────────────────────────────────────────────
* @deprecated
* This component is being DEPRECATED in favor of src/app/dashboard/virtual-keys/components/CreateKey.tsx
* This component is being DEPRECATED in favor of src/app/(dashboard)/virtual-keys/components/CreateKey.tsx
* Please contribute to the new refactor.
* ─────────────────────────────────────────────────────────────────────────
*/

View File

@@ -50,7 +50,7 @@ interface KeyInfoViewProps {
/**
* ─────────────────────────────────────────────────────────────────────────
* @deprecated
* This component is being DEPRECATED in favor of src/app/dashboard/virtual-keys/components/KeyInfoView.tsx
* This component is being DEPRECATED in favor of src/app/(dashboard)/virtual-keys/components/KeyInfoView.tsx
* Please contribute to the new refactor.
* ─────────────────────────────────────────────────────────────────────────
*/

View File

@@ -138,7 +138,7 @@ interface CombinedLimits {
/**
* ─────────────────────────────────────────────────────────────────────────
* @deprecated
* This component is being DEPRECATED in favor of src/app/dashboard/virtual-keys/components/VirtualKeysTable/
* This component is being DEPRECATED in favor of src/app/(dashboard)/virtual-keys/components/VirtualKeysTable/
* Please contribute to the new refactor.
* ─────────────────────────────────────────────────────────────────────────
*/

View File

@@ -1,26 +1,87 @@
'use client';
import React, {createContext, useContext, useState} from 'react';
"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
type Flags = {
refactoredUIFlag: boolean;
setRefactoredUIFlag: (v: boolean) => void;
};
const STORAGE_KEY = "feature.refactoredUIFlag";
const FeatureFlagsCtx = createContext<Flags | null>(null);
export const FeatureFlagsProvider = ({ children }: { children: React.ReactNode }) => {
const [refactoredUIFlag, setRefactoredUIFlag] = useState(false);
return (
<FeatureFlagsCtx.Provider value={{ refactoredUIFlag, setRefactoredUIFlag }}>
{children}
</FeatureFlagsCtx.Provider>
);
/** Safely read the flag from localStorage. If anything goes wrong, reset to false. */
function readFlagSafely(): boolean {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw === null) {
localStorage.setItem(STORAGE_KEY, "false");
return false;
}
const v = raw.trim().toLowerCase();
if (v === "true" || v === "1") return true;
if (v === "false" || v === "0") return false;
// Last chance: try JSON.parse in case something odd was stored.
const parsed = JSON.parse(raw);
if (typeof parsed === "boolean") return parsed;
// Malformed → reset to false
localStorage.setItem(STORAGE_KEY, "false");
return false;
} catch {
// If even accessing localStorage throws, best effort reset then default to false
try {
localStorage.setItem(STORAGE_KEY, "false");
} catch {}
return false;
}
}
function writeFlagSafely(v: boolean) {
try {
localStorage.setItem(STORAGE_KEY, String(v));
} catch {
// Ignore write errors; state will still reflect the intended value.
}
}
export const FeatureFlagsProvider = ({ children }: { children: React.ReactNode }) => {
// Lazy init reads from localStorage only on the client
const [refactoredUIFlag, setRefactoredUIFlagState] = useState<boolean>(() => readFlagSafely());
const setRefactoredUIFlag = (v: boolean) => {
setRefactoredUIFlagState(v);
writeFlagSafely(v);
};
// Keep this flag in sync across tabs/windows.
useEffect(() => {
const onStorage = (e: StorageEvent) => {
if (e.key === STORAGE_KEY && e.newValue != null) {
const next = e.newValue.trim().toLowerCase();
setRefactoredUIFlagState(next === "true" || next === "1");
}
// If the key was cleared elsewhere, self-heal to false.
if (e.key === STORAGE_KEY && e.newValue === null) {
writeFlagSafely(false);
setRefactoredUIFlagState(false);
}
};
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}, []);
return (
<FeatureFlagsCtx.Provider value={{ refactoredUIFlag, setRefactoredUIFlag }}>{children}</FeatureFlagsCtx.Provider>
);
};
const useFeatureFlags = () => {
const ctx = useContext(FeatureFlagsCtx);
if (!ctx) throw new Error('useFeatureFlags must be used within FeatureFlagsProvider');
if (!ctx) throw new Error("useFeatureFlags must be used within FeatureFlagsProvider");
return ctx;
}
};
export default useFeatureFlags;