Skip to content

Commit

Permalink
feat: feature flags
Browse files Browse the repository at this point in the history
  • Loading branch information
solidovic committed Mar 3, 2024
1 parent c601d7f commit feb28e4
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ WALLETCONNECT_PROJECT_ID=

# ETH Stake Widget API for IPFS mode
WIDGET_API_BASE_PATH_FOR_IPFS=

FEATURE_FLAGS_PAGE_IS_ENABLED=false
28 changes: 28 additions & 0 deletions config/feature-flags/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useContext } from 'react';
import invariant from 'tiny-invariant';

import { FeatureFlagsContext, FeatureFlagsContextType } from './provider';
import { FeatureFlagsType } from './types';

type UseFeatureFlagReturnType = {
[key in keyof FeatureFlagsType]: boolean;
} & {
setFeatureFlag: (featureFlag: keyof FeatureFlagsType, value: boolean) => void;
};

export const useFeatureFlag = (
flag: keyof FeatureFlagsType,
): UseFeatureFlagReturnType => {
const context = useContext(FeatureFlagsContext);
invariant(context, 'Attempt to use `feature flag` outside of provider');
return {
[flag]: context[flag],
setFeatureFlag: context.setFeatureFlag,
};
};

export const useFeatureFlags = (): FeatureFlagsContextType => {
const context = useContext(FeatureFlagsContext);
invariant(context, 'Attempt to use `feature flag` outside of provider');
return context;
};
4 changes: 4 additions & 0 deletions config/feature-flags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './hooks';
export * from './provider';
export * from './types';
export * from './utils';
59 changes: 59 additions & 0 deletions config/feature-flags/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
PropsWithChildren,
useMemo,
useState,
useCallback,
createContext,
} from 'react';
import { useLocalStorage } from '@lido-sdk/react';

import { getFeatureFlagsDefault } from './utils';
import { FeatureFlagsType } from './types';

const STORAGE_FEATURE_FLAGS = 'lido-feature-flags';

const FEATURE_FLAGS_DEFAULT = getFeatureFlagsDefault();

export type FeatureFlagsContextType = FeatureFlagsType & {
setFeatureFlag: (featureFlag: keyof FeatureFlagsType, value: boolean) => void;
};

export const FeatureFlagsContext =
createContext<FeatureFlagsContextType | null>(null);

export const FeatureFlagsProvider = ({ children }: PropsWithChildren) => {
const [featureFlagsLocalStorage, setFeatureFlagsLocalStorage] =
useLocalStorage(STORAGE_FEATURE_FLAGS, FEATURE_FLAGS_DEFAULT);

const [featureFlagsState, setFeatureFlagsState] = useState<FeatureFlagsType>(
featureFlagsLocalStorage,
);

const setFeatureFlag = useCallback(
(featureFlag: keyof FeatureFlagsType, value: boolean) => {
setFeatureFlagsLocalStorage({
...featureFlagsState,
[featureFlag]: value,
});
setFeatureFlagsState({
...featureFlagsState,
[featureFlag]: value,
});
},
[featureFlagsState, setFeatureFlagsLocalStorage],
);

const contextValue = useMemo(() => {
return {
...FEATURE_FLAGS_DEFAULT,
...featureFlagsState,
setFeatureFlag: setFeatureFlag,
};
}, [featureFlagsState, setFeatureFlag]);

return (
<FeatureFlagsContext.Provider value={contextValue}>
{children}
</FeatureFlagsContext.Provider>
);
};
9 changes: 9 additions & 0 deletions config/feature-flags/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// TODO: maybe do we wants use 'feature flags' for this?
// export const FEATURE_FLAGS_PAGE_IS_ENABLED = 'featureFlagsPageIsEnabled';
export const RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED =
'rpcSettingsPageOnInfraIsEnabled';

export type FeatureFlagsType = {
// [FEATURE_FLAGS_PAGE_IS_ENABLED]: boolean;
[RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED]: boolean;
};
9 changes: 9 additions & 0 deletions config/feature-flags/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FeatureFlagsType } from './types';

export const getFeatureFlagsDefault = (): FeatureFlagsType => {
return {
// TODO: maybe do we wants use 'feature flags' for this?
// featureFlagsPageIsEnabled: true,
rpcSettingsPageOnInfraIsEnabled: false,
};
};
3 changes: 3 additions & 0 deletions env-dynamics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ export const prefillUnsafeElRpcUrls17000 = process.env.PREFILL_UNSAFE_EL_RPC_URL

/** @type string */
export const widgetApiBasePathForIpfs = process.env.WIDGET_API_BASE_PATH_FOR_IPFS;

/** @type boolean */
export const featureFlagsPageIsEnabled = toBoolean(process.env.FEATURE_FLAGS_PAGE_IS_ENABLED);
45 changes: 45 additions & 0 deletions pages/feature-flags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FC } from 'react';
import { GetStaticProps } from 'next';
import { Block, Checkbox } from '@lidofinance/lido-ui';

import { config } from 'config';
import {
useFeatureFlag,
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
} from 'config/feature-flags';
import { Layout } from 'shared/components';
import NoSSRWrapper from 'shared/components/no-ssr-wrapper';

const FeatureFlags: FC = () => {
const { rpcSettingsPageOnInfraIsEnabled, setFeatureFlag } = useFeatureFlag(
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
);

return (
<Layout title="Feature Flags">
<NoSSRWrapper>
<br />
<Block>
<Checkbox
checked={rpcSettingsPageOnInfraIsEnabled}
onChange={() =>
setFeatureFlag(
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
!rpcSettingsPageOnInfraIsEnabled,
)
}
label="RPC settings page on infra"
/>
</Block>
</NoSSRWrapper>
</Layout>
);
};

export default FeatureFlags;

export const getStaticProps: GetStaticProps = async () => {
if (!config.featureFlagsPageIsEnabled) return { notFound: true };

return { props: {} };
};
29 changes: 20 additions & 9 deletions pages/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import { FC } from 'react';
import { GetStaticProps } from 'next';
// import { GetStaticProps } from 'next';

import { getConfig } from 'config';
// import { config } from 'config';
import {
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
useFeatureFlag,
} from 'config/feature-flags';
import { Layout } from 'shared/components';
import { SettingsForm } from 'features/settings/settings-form';
import NoSSRWrapper from 'shared/components/no-ssr-wrapper';

const Settings: FC = () => {
const { rpcSettingsPageOnInfraIsEnabled } = useFeatureFlag(
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
);

return (
<Layout title="Settings">
<SettingsForm />
<NoSSRWrapper>
{rpcSettingsPageOnInfraIsEnabled && <SettingsForm />}
{!rpcSettingsPageOnInfraIsEnabled && <>Settings Not Available!</>}
</NoSSRWrapper>
</Layout>
);
};

export default Settings;

export const getStaticProps: GetStaticProps = async () => {
const { ipfsMode } = getConfig();
if (!ipfsMode) return { notFound: true };

return { props: {} };
};
// export const getStaticProps: GetStaticProps = async () => {
// if (!config.ipfsMode) return { notFound: true };
//
// return { props: {} };
// };
27 changes: 15 additions & 12 deletions providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CookieThemeProvider } from '@lidofinance/lido-ui';
import { GlobalStyle } from 'styles';
import { ConfigProvider } from 'config';
import { UserConfigProvider } from 'config/user-config';
import { FeatureFlagsProvider } from 'config/feature-flags';

import { AppFlagProvider } from './app-flag';
import { IPFSInfoBoxStatusesProvider } from './ipfs-info-box-statuses';
Expand All @@ -16,18 +17,20 @@ export { MODAL, ModalContext } from './modals';
export const Providers: FC<PropsWithChildren> = ({ children }) => (
<ConfigProvider>
<UserConfigProvider>
<AppFlagProvider>
<CookieThemeProvider>
<GlobalStyle />
<Web3Provider>
<IPFSInfoBoxStatusesProvider>
<InpageNavigationProvider>
<ModalProvider>{children}</ModalProvider>
</InpageNavigationProvider>
</IPFSInfoBoxStatusesProvider>
</Web3Provider>
</CookieThemeProvider>
</AppFlagProvider>
<FeatureFlagsProvider>
<AppFlagProvider>
<CookieThemeProvider>
<GlobalStyle />
<Web3Provider>
<IPFSInfoBoxStatusesProvider>
<InpageNavigationProvider>
<ModalProvider>{children}</ModalProvider>
</InpageNavigationProvider>
</IPFSInfoBoxStatusesProvider>
</Web3Provider>
</CookieThemeProvider>
</AppFlagProvider>
</FeatureFlagsProvider>
</UserConfigProvider>
</ConfigProvider>
);
11 changes: 10 additions & 1 deletion shared/components/layout/header/components/header-wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { ThemeToggler } from '@lidofinance/lido-ui';
import NoSSRWrapper from '../../../no-ssr-wrapper';

import { getConfig } from 'config';
import {
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
useFeatureFlag,
} from 'config/feature-flags';
const { ipfsMode } = getConfig();

import { IPFSInfoBox } from 'features/ipfs/ipfs-info-box';
Expand All @@ -23,6 +27,9 @@ const HeaderWallet: FC = () => {
const router = useRouter();
const { active } = useWeb3();
const { chainId } = useSDK();
const { rpcSettingsPageOnInfraIsEnabled } = useFeatureFlag(
RPC_SETTINGS_PAGE_ON_INFRA_IS_ENABLED,
);

const chainName = CHAINS[chainId];
const testNet = chainId !== CHAINS.Mainnet;
Expand All @@ -44,7 +51,9 @@ const HeaderWallet: FC = () => {
) : (
<Connect size="sm" />
)}
{ipfsMode && <HeaderSettingsButton />}
{(ipfsMode || rpcSettingsPageOnInfraIsEnabled) && (
<HeaderSettingsButton />
)}
{!queryTheme && <ThemeToggler data-testid="themeToggler" />}
{ipfsMode && (
<IPFSInfoBoxOnlyDesktopWrapper>
Expand Down

0 comments on commit feb28e4

Please sign in to comment.