feat: create Frontend feature flags (#3029)
* Add UI feature flag config * [autofix.ci] apply automated fixes * hide general settings if there is nothing to show * make sure to handle !autoLogin case * [autofix.ci] apply automated fixes * missed commit * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: anovazzi1 <otavio2204@gmail.com>
This commit is contained in:
parent
818a17d0db
commit
72ff6d3a58
7 changed files with 127 additions and 54 deletions
6
src/frontend/feature-config.json
Normal file
6
src/frontend/feature-config.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"ENABLE_DARK_MODE": true,
|
||||||
|
"ENABLE_API": true,
|
||||||
|
"ENABLE_LANGFLOW_STORE": true,
|
||||||
|
"ENABLE_PROFILE_ICONS": true
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
|
@ -140,6 +141,8 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
<div>
|
<div>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
</div>
|
</div>
|
||||||
|
{FeatureFlags.ENABLE_API && (
|
||||||
|
<>
|
||||||
<div className="flex cursor-pointer items-center gap-2">
|
<div className="flex cursor-pointer items-center gap-2">
|
||||||
{currentFlow && currentFlow.data && (
|
{currentFlow && currentFlow.data && (
|
||||||
<ApiModal
|
<ApiModal
|
||||||
|
|
@ -164,6 +167,8 @@ export default function FlowToolbar(): JSX.Element {
|
||||||
<div>
|
<div>
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`side-bar-button ${
|
className={`side-bar-button ${
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { useLogout } from "@/controllers/API/queries/auth";
|
import { useLogout } from "@/controllers/API/queries/auth";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
|
|
@ -182,6 +183,7 @@ export default function Header(): JSX.Element {
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
|
{FeatureFlags.ENABLE_DARK_MODE && (
|
||||||
<button
|
<button
|
||||||
className="extra-side-bar-save-disable"
|
className="extra-side-bar-save-disable"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -189,11 +191,18 @@ export default function Header(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{dark ? (
|
{dark ? (
|
||||||
<IconComponent name="SunIcon" className="side-bar-button-size" />
|
<IconComponent
|
||||||
|
name="SunIcon"
|
||||||
|
className="side-bar-button-size"
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<IconComponent name="MoonIcon" className="side-bar-button-size" />
|
<IconComponent
|
||||||
|
name="MoonIcon"
|
||||||
|
className="side-bar-button-size"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<AlertDropdown>
|
<AlertDropdown>
|
||||||
<div className="extra-side-bar-save-disable relative">
|
<div className="extra-side-bar-save-disable relative">
|
||||||
{notificationCenter && (
|
{notificationCenter && (
|
||||||
|
|
@ -216,10 +225,17 @@ export default function Header(): JSX.Element {
|
||||||
data-testid="user-profile-settings"
|
data-testid="user-profile-settings"
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
|
{FeatureFlags.ENABLE_PROFILE_ICONS ? (
|
||||||
<img
|
<img
|
||||||
src={profileImageUrl}
|
src={profileImageUrl}
|
||||||
className="h-7 w-7 shrink-0 focus-visible:outline-0"
|
className="h-7 w-7 shrink-0 focus-visible:outline-0"
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<IconComponent
|
||||||
|
name="Settings"
|
||||||
|
className="side-bar-button-size"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="mr-1 mt-1 min-w-40">
|
<DropdownMenuContent className="mr-1 mt-1 min-w-40">
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
|
import useAuthStore from "@/stores/authStore";
|
||||||
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
import ForwardedIconComponent from "../../components/genericIconComponent";
|
import ForwardedIconComponent from "../../components/genericIconComponent";
|
||||||
|
|
@ -10,12 +13,26 @@ export default function SettingsPage(): JSX.Element {
|
||||||
const setCurrentFlowId = useFlowsManagerStore(
|
const setCurrentFlowId = useFlowsManagerStore(
|
||||||
(state) => state.setCurrentFlowId,
|
(state) => state.setCurrentFlowId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||||
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
|
|
||||||
|
// Hides the General settings if there is nothing to show
|
||||||
|
const showGeneralSettings =
|
||||||
|
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentFlowId("");
|
setCurrentFlowId("");
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
const sidebarNavItems = [
|
const sidebarNavItems: {
|
||||||
{
|
href?: string;
|
||||||
|
title: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
if (showGeneralSettings) {
|
||||||
|
sidebarNavItems.push({
|
||||||
title: "General",
|
title: "General",
|
||||||
href: "/settings/general",
|
href: "/settings/general",
|
||||||
icon: (
|
icon: (
|
||||||
|
|
@ -24,7 +41,10 @@ export default function SettingsPage(): JSX.Element {
|
||||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebarNavItems.push(
|
||||||
{
|
{
|
||||||
title: "Global Variables",
|
title: "Global Variables",
|
||||||
href: "/settings/global-variables",
|
href: "/settings/global-variables",
|
||||||
|
|
@ -65,7 +85,7 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
);
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
title="Settings"
|
title="Settings"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
||||||
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
|
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
|
||||||
import useAuthStore from "@/stores/authStore";
|
import useAuthStore from "@/stores/authStore";
|
||||||
|
|
@ -101,6 +102,7 @@ export const GeneralPage = () => {
|
||||||
<GeneralPageHeaderComponent />
|
<GeneralPageHeaderComponent />
|
||||||
|
|
||||||
<div className="grid gap-6">
|
<div className="grid gap-6">
|
||||||
|
{FeatureFlags.ENABLE_PROFILE_ICONS && (
|
||||||
<ProfilePictureFormComponent
|
<ProfilePictureFormComponent
|
||||||
profilePicture={profilePicture}
|
profilePicture={profilePicture}
|
||||||
handleInput={handleInput}
|
handleInput={handleInput}
|
||||||
|
|
@ -108,6 +110,7 @@ export const GeneralPage = () => {
|
||||||
handleGetProfilePictures={handleGetProfilePictures}
|
handleGetProfilePictures={handleGetProfilePictures}
|
||||||
userData={userData}
|
userData={userData}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!autoLogin && (
|
{!autoLogin && (
|
||||||
<PasswordFormComponent
|
<PasswordFormComponent
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
|
import useAuthStore from "@/stores/authStore";
|
||||||
|
import { useStoreStore } from "@/stores/storeStore";
|
||||||
import { Suspense, lazy } from "react";
|
import { Suspense, lazy } from "react";
|
||||||
import { Navigate, Route, Routes } from "react-router-dom";
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
||||||
|
|
@ -36,6 +39,13 @@ const StorePage = lazy(() => import("./pages/StorePage"));
|
||||||
const ViewPage = lazy(() => import("./pages/ViewPage"));
|
const ViewPage = lazy(() => import("./pages/ViewPage"));
|
||||||
|
|
||||||
const Router = () => {
|
const Router = () => {
|
||||||
|
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||||
|
const hasStore = useStoreStore((state) => state.hasStore);
|
||||||
|
|
||||||
|
// Hides the General settings if there is nothing to show
|
||||||
|
const showGeneralSettings =
|
||||||
|
FeatureFlags.ENABLE_PROFILE_ICONS || hasStore || !autoLogin;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
|
|
@ -77,10 +87,20 @@ const Router = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route index element={<Navigate replace to={"general"} />} />
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Navigate
|
||||||
|
replace
|
||||||
|
to={showGeneralSettings ? "general" : "global-variables"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
||||||
<Route path="api-keys" element={<ApiKeysPage />} />
|
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||||
|
{showGeneralSettings && (
|
||||||
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
||||||
|
)}
|
||||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||||
<Route path="messages" element={<MessagesPage />} />
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import FeatureFlags from "@/../feature-config.json";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { checkHasApiKey, checkHasStore } from "../controllers/API";
|
import { checkHasApiKey, checkHasStore } from "../controllers/API";
|
||||||
import { StoreStoreType } from "../types/zustand/store";
|
import { StoreStoreType } from "../types/zustand/store";
|
||||||
|
|
@ -9,7 +10,9 @@ export const useStoreStore = create<StoreStoreType>((set) => ({
|
||||||
loadingApiKey: true,
|
loadingApiKey: true,
|
||||||
checkHasStore: () => {
|
checkHasStore: () => {
|
||||||
checkHasStore().then((res) => {
|
checkHasStore().then((res) => {
|
||||||
set({ hasStore: res?.enabled ?? false });
|
set({
|
||||||
|
hasStore: FeatureFlags.ENABLE_LANGFLOW_STORE && (res?.enabled ?? false),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),
|
updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue