From cc1897c491c394862740c4df56cc7a59eb3fe9ec Mon Sep 17 00:00:00 2001 From: kassad Date: Mon, 12 Feb 2024 06:01:47 -0800 Subject: [PATCH] Migrated env feature flags to openfeature --- package.json | 1 + packages/commonwealth/client/scripts/App.tsx | 9 +- .../client/scripts/helpers/feature-flags.ts | 29 +- .../hooks/openFeature/OpenFeatureProvider.tsx | 61 ++++ .../hooks/openFeature/useFeatureFlag.ts | 318 ++++++++++++++++++ .../client/scripts/hooks/useFlag.ts | 8 + .../client/scripts/hooks/useWallets.tsx | 7 +- .../scripts/navigation/CommonDomainRoutes.tsx | 14 +- .../scripts/navigation/CustomDomainRoutes.tsx | 11 +- .../client/scripts/navigation/Router.tsx | 35 +- .../client/scripts/views/SublayoutHeader.tsx | 5 +- .../AdminOnboardingSlider.tsx | 5 +- .../CommunityStake/useCommunityStake.ts | 7 +- .../Header/UserDropdown/UserDropdown.tsx | 5 +- .../components/Header/useJoinCommunity.tsx | 5 +- .../ReactionButton/CommentReactionButton.tsx | 5 +- .../sidebar/AdminSection/AdminSection.tsx | 5 +- .../CommunitySection/CommunitySection.tsx | 15 +- .../components/sidebar/old_admin_section.tsx | 5 +- .../CommunityProfileForm.tsx | 5 +- .../pages/CreateCommunity/CreateCommunity.tsx | 8 +- .../CommunityTypeStep/CommunityTypeStep.tsx | 5 +- .../CreateCommunity/useCreateCommunity.ts | 3 + .../views/pages/CreateCommunity/utils.ts | 8 +- .../HeaderWithFilters/HeaderWithFilters.tsx | 5 +- .../ReactionButton/ReactionButton.tsx | 5 +- .../views/pages/discussions_redirect.tsx | 5 +- .../scripts/views/pages/landing/header.tsx | 5 +- .../community_metadata_rows.tsx | 8 +- .../manage_community/discord-callback.tsx | 5 +- .../pages/manage_community/manage_roles.tsx | 9 +- .../manage_community/upgrade_roles_form.tsx | 5 +- .../pages/view_thread/ViewThreadPage.tsx | 7 +- .../client/scripts/views/pages/web3login.tsx | 5 +- packages/commonwealth/package.json | 2 + yarn.lock | 15 + 36 files changed, 570 insertions(+), 85 deletions(-) create mode 100644 packages/commonwealth/client/scripts/hooks/openFeature/OpenFeatureProvider.tsx create mode 100644 packages/commonwealth/client/scripts/hooks/openFeature/useFeatureFlag.ts create mode 100644 packages/commonwealth/client/scripts/hooks/useFlag.ts diff --git a/package.json b/package.json index 9f5f1207859..42fb8eacf00 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@babel/register": "^7.4.0", "@canvas-js/core": "0.5.0-alpha4", "@istanbuljs/nyc-config-typescript": "^0.1.3", + "@openfeature/web-sdk": "^0.4.12", "@openzeppelin/contracts": "^2.4.0", "@openzeppelin/contracts-governance": "npm:@openzeppelin/contracts@^4.3.2", "@osmonauts/lcd": "^0.10.0", diff --git a/packages/commonwealth/client/scripts/App.tsx b/packages/commonwealth/client/scripts/App.tsx index a3999172c0f..4c9fb0aebb6 100644 --- a/packages/commonwealth/client/scripts/App.tsx +++ b/packages/commonwealth/client/scripts/App.tsx @@ -1,3 +1,4 @@ +import { InMemoryProvider, OpenFeature } from '@openfeature/web-sdk'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import useInitApp from 'hooks/useInitApp'; @@ -6,6 +7,8 @@ import React, { StrictMode } from 'react'; import { RouterProvider } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import { queryClient } from 'state/api/config'; +import { featureFlags } from './helpers/feature-flags'; +import { OpenFeatureProvider } from './hooks/openFeature/OpenFeatureProvider'; import { CWIcon } from './views/components/component_kit/cw_icons/cw_icon'; const Splash = () => { @@ -17,6 +20,8 @@ const Splash = () => { ); }; +OpenFeature.setProvider(new InMemoryProvider(featureFlags)); + const App = () => { const { customDomain, isLoading } = useInitApp(); @@ -26,7 +31,9 @@ const App = () => { {isLoading ? ( ) : ( - + + + )} diff --git a/packages/commonwealth/client/scripts/helpers/feature-flags.ts b/packages/commonwealth/client/scripts/helpers/feature-flags.ts index aef64deb040..c65cfdf0ddf 100644 --- a/packages/commonwealth/client/scripts/helpers/feature-flags.ts +++ b/packages/commonwealth/client/scripts/helpers/feature-flags.ts @@ -1,12 +1,25 @@ -// As of 240205, we are moving away from the use of env vars for feature flags, -// and towards the use of Unleash for all flag management. +// This is our in memory provider setup. It does not automatically ensure your +// feature flag is set on our Unleash instance (May not be available on prod). // -// See knowledge_base/Feature-Flags.md for info. +// See knowledge_base/Feature-Flags.md for more info. + +const buildFlag = (env: string) => { + return { + variants: { + on: true, + off: false, + }, + disabled: false, + defaultVariant: env === 'true' ? 'on' : 'off', + }; +}; export const featureFlags = { - proposalTemplates: process.env.FLAG_PROPOSAL_TEMPLATES === 'true', - communityHomepage: process.env.FLAG_COMMUNITY_HOMEPAGE === 'true', - newAdminOnboardingEnabled: process.env.FLAG_NEW_ADMIN_ONBOARDING === 'true', - communityStake: process.env.FLAG_COMMUNITY_STAKE === 'true', - newSignInModal: process.env.FLAG_NEW_SIGN_IN_MODAL === 'true', + proposalTemplates: buildFlag(process.env.FLAG_PROPOSAL_TEMPLATES), + communityHomepage: buildFlag(process.env.FLAG_COMMUNITY_HOMEPAGE), + newAdminOnboarding: buildFlag(process.env.FLAG_NEW_ADMIN_ONBOARDING), + communityStake: buildFlag(process.env.FLAG_COMMUNITY_STAKE), + newSignInModal: buildFlag(process.env.FLAG_NEW_SIGN_IN_MODAL), }; + +export type AvailableFeatureFlag = keyof typeof featureFlags; diff --git a/packages/commonwealth/client/scripts/hooks/openFeature/OpenFeatureProvider.tsx b/packages/commonwealth/client/scripts/hooks/openFeature/OpenFeatureProvider.tsx new file mode 100644 index 00000000000..5d1bdc35826 --- /dev/null +++ b/packages/commonwealth/client/scripts/hooks/openFeature/OpenFeatureProvider.tsx @@ -0,0 +1,61 @@ +/** + * This file was grabbed from https://github.com/open-feature. There was an issue with the react-sdk npm deploy, + * so I had copied the code + */ + +import { Client, OpenFeature } from '@openfeature/web-sdk'; +import * as React from 'react'; + +type ClientOrClientName = + | { + /** + * The name of the client. + * @see OpenFeature.setProvider() and overloads. + */ + clientName: string; + /** + * OpenFeature client to use. + */ + client?: never; + } + | { + /** + * OpenFeature client to use. + */ + client: Client; + /** + * The name of the client. + * @see OpenFeature.setProvider() and overloads. + */ + clientName?: never; + }; + +type ProviderProps = { + children?: any; +} & ClientOrClientName; + +const Context = React.createContext(undefined); + +export const OpenFeatureProvider = ({ + client, + clientName, + children, +}: ProviderProps) => { + if (!client) { + client = OpenFeature.getClient(clientName); + } + + return {children}; +}; + +export const useOpenFeatureClient = () => { + const client = React.useContext(Context); + + if (!client) { + throw new Error( + 'No OpenFeature client available - components using OpenFeature must be wrapped with an ', + ); + } + + return client; +}; diff --git a/packages/commonwealth/client/scripts/hooks/openFeature/useFeatureFlag.ts b/packages/commonwealth/client/scripts/hooks/openFeature/useFeatureFlag.ts new file mode 100644 index 00000000000..037fe497fc6 --- /dev/null +++ b/packages/commonwealth/client/scripts/hooks/openFeature/useFeatureFlag.ts @@ -0,0 +1,318 @@ +/** + * This file was grabbed from https://github.com/open-feature. There was an issue with the react-sdk npm deploy, + * so I had copied the code + */ +import { + Client, + EvaluationDetails, + FlagEvaluationOptions, + FlagValue, + JsonObject, + JsonValue, + ProviderEvents, + ProviderStatus, +} from '@openfeature/web-sdk'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { useOpenFeatureClient } from './OpenFeatureProvider'; + +type ReactFlagEvaluationOptions = { + /** + * Suspend flag evaluations while the provider is not ready. + * Set to false if you don't want to use React Suspense API. + * Defaults to true. + */ + suspend?: boolean; + /** + * Update the component if the provider emits a ConfigurationChanged event. + * Set to false to prevent components from re-rendering when flag value changes + * are received by the associated provider. + * Defaults to true. + */ + updateOnConfigurationChanged?: boolean; + /** + * Update the component when the OpenFeature context changes. + * Set to false to prevent components from re-rendering when attributes which + * may be factors in flag evaluation change. + * Defaults to true. + */ + updateOnContextChanged?: boolean; +} & FlagEvaluationOptions; + +const DEFAULT_OPTIONS: ReactFlagEvaluationOptions = { + updateOnContextChanged: true, + updateOnConfigurationChanged: true, + suspend: true, +}; + +enum SuspendState { + Pending, + Success, + Error, +} + +/** + * Evaluates a feature flag, returning a boolean. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @param {boolean} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { boolean} a EvaluationDetails object for this evaluation + */ +export function useBooleanFlagValue( + flagKey: string, + defaultValue: boolean, + options?: ReactFlagEvaluationOptions, +): boolean { + return useBooleanFlagDetails(flagKey, defaultValue, options).value; +} + +/** + * Evaluates a feature flag, returning evaluation details. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @param {boolean} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { EvaluationDetails} a EvaluationDetails object for this evaluation + */ +export function useBooleanFlagDetails( + flagKey: string, + defaultValue: boolean, + options?: ReactFlagEvaluationOptions, +): EvaluationDetails { + return useAttachHandlersAndResolve( + flagKey, + defaultValue, + (client) => { + return client.getBooleanDetails; + }, + options, + ); +} + +/** + * Evaluates a feature flag, returning a string. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @template {string} [T=string] A optional generic argument constraining the string + * @param {T} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { boolean} a EvaluationDetails object for this evaluation + */ +export function useStringFlagValue( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): T { + return useStringFlagDetails(flagKey, defaultValue, options).value; +} + +/** + * Evaluates a feature flag, returning evaluation details. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @template {string} [T=string] A optional generic argument constraining the string + * @param {T} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { EvaluationDetails} a EvaluationDetails object for this evaluation + */ +export function useStringFlagDetails( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): EvaluationDetails { + return useAttachHandlersAndResolve( + flagKey, + defaultValue, + (client) => { + return client.getStringDetails; + }, + options, + ); +} + +/** + * Evaluates a feature flag, returning a number. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @template {number} [T=number] A optional generic argument constraining the number + * @param {T} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { boolean} a EvaluationDetails object for this evaluation + */ +export function useNumberFlagValue( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): T { + return useNumberFlagDetails(flagKey, defaultValue, options).value; +} + +/** + * Evaluates a feature flag, returning evaluation details. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @template {number} [T=number] A optional generic argument constraining the number + * @param {T} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { EvaluationDetails} a EvaluationDetails object for this evaluation + */ +export function useNumberFlagDetails( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): EvaluationDetails { + return useAttachHandlersAndResolve( + flagKey, + defaultValue, + (client) => { + return client.getNumberDetails; + }, + options, + ); +} + +/** + * Evaluates a feature flag, returning an object. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure + * @param {T} defaultValue the default value + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { boolean} a EvaluationDetails object for this evaluation + */ +export function useObjectFlagValue( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): boolean | string | number | JsonObject | JsonValue[] { + return useObjectFlagDetails(flagKey, defaultValue, options).value; +} + +/** + * Evaluates a feature flag, returning evaluation details. + * By default, components will re-render when the flag value changes. + * @param {string} flagKey the flag identifier + * @param {T} defaultValue the default value + * @template {JsonValue} [T=JsonValue] A optional generic argument describing the structure + * @param {ReactFlagEvaluationOptions} options options for this evaluation + * @returns { EvaluationDetails} a EvaluationDetails object for this evaluation + */ +export function useObjectFlagDetails( + flagKey: string, + defaultValue: T, + options?: ReactFlagEvaluationOptions, +): EvaluationDetails { + return useAttachHandlersAndResolve( + flagKey, + defaultValue, + (client) => { + return client.getObjectDetails; + }, + options, + ); +} + +function useAttachHandlersAndResolve( + flagKey: string, + defaultValue: T, + resolver: ( + client: Client, + ) => (flagKey: string, defaultValue: T) => EvaluationDetails, + options?: ReactFlagEvaluationOptions, +): EvaluationDetails { + const [, updateState] = useState(); + const defaultedOptions: any = { ...DEFAULT_OPTIONS, ...options }; + const forceUpdate = () => { + updateState({}); + }; + const client = useOpenFeatureClient(); + + useEffect(() => { + if (client.providerStatus !== ProviderStatus.READY) { + // update when the provider is ready + client.addHandler(ProviderEvents.Ready, forceUpdate); + if (defaultedOptions.suspend) { + suspend(client, updateState); + } + } + + if (defaultedOptions.updateOnContextChanged) { + // update when the context changes + client.addHandler(ProviderEvents.ContextChanged, forceUpdate); + } + + if (defaultedOptions.updateOnConfigurationChanged) { + // update when the provider configuration changes + client.addHandler(ProviderEvents.ConfigurationChanged, forceUpdate); + } + return () => { + // cleanup the handlers (we can do this unconditionally with no impact) + client.removeHandler(ProviderEvents.Ready, forceUpdate); + client.removeHandler(ProviderEvents.ContextChanged, forceUpdate); + client.removeHandler(ProviderEvents.ConfigurationChanged, forceUpdate); + }; + }, [client]); + + return resolver(client).call(client, flagKey, defaultValue); +} + +/** + * Suspend function. If this runs, components using the calling hook will be suspended. + * @param {Client} client the OpenFeature client + * @param {Function} updateState the state update function + */ +function suspend( + client: Client, + updateState: Dispatch>, +) { + let suspendResolver: () => void; + let suspendRejecter: () => void; + const suspendPromise = new Promise((resolve) => { + suspendResolver = () => { + resolve(); + client.removeHandler(ProviderEvents.Ready, suspendResolver); // remove handler once it's run + }; + suspendRejecter = () => { + resolve(); // we still resolve here, since we don't want to throw errors + client.removeHandler(ProviderEvents.Error, suspendRejecter); // remove handler once it's run + }; + client.addHandler(ProviderEvents.Ready, suspendResolver); + client.addHandler(ProviderEvents.Error, suspendRejecter); + }); + updateState(suspenseWrapper(suspendPromise)); +} + +/** + * Promise wrapper that throws unresolved promises to support React suspense. + * @param {Promise} promise to wrap + * @template T flag type + * @returns {Function} suspense-compliant lambda + */ +function suspenseWrapper(promise: Promise) { + let status: SuspendState = SuspendState.Pending; + let result: T; + + const suspended = promise.then( + (value) => { + status = SuspendState.Success; + result = value; + }, + (error) => { + status = SuspendState.Error; + result = error; + }, + ); + + return () => { + switch (status) { + case SuspendState.Pending: + throw suspended; + case SuspendState.Success: + return result; + case SuspendState.Error: + throw result; + default: + throw new Error('Suspending promise is in an unknown state.'); + } + }; +} diff --git a/packages/commonwealth/client/scripts/hooks/useFlag.ts b/packages/commonwealth/client/scripts/hooks/useFlag.ts new file mode 100644 index 00000000000..beaea08b4ef --- /dev/null +++ b/packages/commonwealth/client/scripts/hooks/useFlag.ts @@ -0,0 +1,8 @@ +import { AvailableFeatureFlag } from '../helpers/feature-flags'; +import { useBooleanFlagValue } from './openFeature/useFeatureFlag'; + +export const useFlag = (name: AvailableFeatureFlag) => { + return useBooleanFlagValue(name, false, { + updateOnConfigurationChanged: false, + }); +}; diff --git a/packages/commonwealth/client/scripts/hooks/useWallets.tsx b/packages/commonwealth/client/scripts/hooks/useWallets.tsx index dc27d84b53f..6f72b238f3f 100644 --- a/packages/commonwealth/client/scripts/hooks/useWallets.tsx +++ b/packages/commonwealth/client/scripts/hooks/useWallets.tsx @@ -30,7 +30,6 @@ import { } from '../../../shared/analytics/types'; import NewProfilesController from '../controllers/server/newProfiles'; import { setDarkMode } from '../helpers/darkMode'; -import { featureFlags } from '../helpers/feature-flags'; import { getAddressFromWallet, loginToAxie, @@ -50,6 +49,7 @@ import type { } from '../views/pages/login/types'; import { useBrowserAnalyticsTrack } from './useBrowserAnalyticsTrack'; import useBrowserWindow from './useBrowserWindow'; +import { useFlag } from './useFlag'; type IuseWalletProps = { initialBody?: LoginActiveStep; @@ -62,13 +62,14 @@ type IuseWalletProps = { }; const useWallets = (walletProps: IuseWalletProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [avatarUrl, setAvatarUrl] = useState(); const [address, setAddress] = useState(); const [activeStep, setActiveStep] = useState(); const [profiles, setProfiles] = useState>(); const [sidebarType, setSidebarType] = useState(); const [username, setUsername] = useState( - featureFlags.newSignInModal ? 'Anonymous' : '', + newSignInModalEnabled ? 'Anonymous' : '', ); const [email, setEmail] = useState(); const [wallets, setWallets] = useState>>(); @@ -382,7 +383,7 @@ const useWallets = (walletProps: IuseWalletProps) => { setSidebarType('newOrReturning'); setActiveStep('selectAccountType'); - if (featureFlags.newSignInModal) { + if (newSignInModalEnabled) { // Create the account with default values await onCreateNewAccount( walletToUse, diff --git a/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx b/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx index 5d50e2a30db..ef768f57fe6 100644 --- a/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx +++ b/packages/commonwealth/client/scripts/navigation/CommonDomainRoutes.tsx @@ -1,8 +1,8 @@ -import { featureFlags } from 'helpers/feature-flags'; import { Navigate } from 'navigation/helpers'; import React, { lazy } from 'react'; import { Route } from 'react-router-dom'; import { withLayout } from 'views/Layout'; +import { RouteFeatureFlags } from './Router'; const LandingPage = lazy(() => import('views/pages/landing')); const WhyCommonwealthPage = lazy(() => import('views/pages/why_commonwealth')); @@ -102,7 +102,11 @@ const NewProfilePage = lazy(() => import('views/pages/new_profile')); const EditNewProfilePage = lazy(() => import('views/pages/edit_new_profile')); const ProfilePageRedirect = lazy(() => import('views/pages/profile_redirect')); -const CommonDomainRoutes = () => [ +const CommonDomainRoutes = ({ + proposalTemplatesEnabled, + newAdminOnboardingEnabled, + communityHomepageEnabled, +}: RouteFeatureFlags) => [ [ scoped: true, })} />, - ...(featureFlags.communityHomepage + ...(communityHomepageEnabled ? [ [ // DISCUSSIONS END // CONTRACTS - ...(featureFlags.proposalTemplates + ...(proposalTemplatesEnabled ? [ [ />, // ADMIN - ...(featureFlags.newAdminOnboardingEnabled + ...(newAdminOnboardingEnabled ? [ import('views/pages/search')); @@ -94,7 +94,10 @@ const NewProfilePage = lazy(() => import('views/pages/new_profile')); const EditNewProfilePage = lazy(() => import('views/pages/edit_new_profile')); const ProfilePageRedirect = lazy(() => import('views/pages/profile_redirect')); -const CustomDomainRoutes = () => { +const CustomDomainRoutes = ({ + proposalTemplatesEnabled, + newAdminOnboardingEnabled, +}: RouteFeatureFlags) => { return [ { // DISCUSSIONS END // CONTRACTS - ...(featureFlags.proposalTemplates + ...(proposalTemplatesEnabled ? [ { // CONTRACTS END // ADMIN - ...(featureFlags.newAdminOnboardingEnabled + ...(newAdminOnboardingEnabled ? [ - createBrowserRouter( +export type RouteFeatureFlags = { + proposalTemplatesEnabled: boolean; + newAdminOnboardingEnabled: boolean; + communityHomepageEnabled: boolean; +}; + +const Router = (customDomain: string) => { + const client = OpenFeature.getClient(); + const proposalTemplatesEnabled = client.getBooleanValue( + 'proposalTemplates', + false, + ); + const newAdminOnboardingEnabled = client.getBooleanValue( + 'newAdminOnboarding', + false, + ); + const communityHomepageEnabled = client.getBooleanValue( + 'communityHomepageEnabled', + false, + ); + const flags = { + proposalTemplatesEnabled, + newAdminOnboardingEnabled, + communityHomepageEnabled, + }; + + return createBrowserRouter( createRoutesFromElements([ ...GeneralRoutes(), - ...(customDomain ? CustomDomainRoutes() : CommonDomainRoutes()), + ...(customDomain ? CustomDomainRoutes(flags) : CommonDomainRoutes(flags)), , - ]) + ]), ); +}; export default Router; diff --git a/packages/commonwealth/client/scripts/views/SublayoutHeader.tsx b/packages/commonwealth/client/scripts/views/SublayoutHeader.tsx index e88f84dc9b0..32d5796363d 100644 --- a/packages/commonwealth/client/scripts/views/SublayoutHeader.tsx +++ b/packages/commonwealth/client/scripts/views/SublayoutHeader.tsx @@ -11,7 +11,7 @@ import { HelpMenuPopover } from 'views/menus/help_menu'; import { AuthModal } from 'views/modals/AuthModal'; import { FeedbackModal } from 'views/modals/feedback_modal'; import { LoginModal } from 'views/modals/login_modal'; -import { featureFlags } from '../helpers/feature-flags'; +import { useFlag } from '../hooks/useFlag'; import app from '../state'; import { CWDivider } from './components/component_kit/cw_divider'; import { CWIconButton } from './components/component_kit/cw_icon_button'; @@ -30,6 +30,7 @@ type SublayoutHeaderProps = { }; export const SublayoutHeader = ({ onMobile }: SublayoutHeaderProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [isFeedbackModalOpen, setIsFeedbackModalOpen] = useState(false); const navigate = useCommonNavigate(); const { @@ -149,7 +150,7 @@ export const SublayoutHeader = ({ onMobile }: SublayoutHeaderProps) => { onClose={() => setIsFeedbackModalOpen(false)} open={isFeedbackModalOpen} /> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/client/scripts/views/components/AdminOnboardingSlider/AdminOnboardingSlider.tsx b/packages/commonwealth/client/scripts/views/components/AdminOnboardingSlider/AdminOnboardingSlider.tsx index 670bd1eac4b..baa2219bbdf 100644 --- a/packages/commonwealth/client/scripts/views/components/AdminOnboardingSlider/AdminOnboardingSlider.tsx +++ b/packages/commonwealth/client/scripts/views/components/AdminOnboardingSlider/AdminOnboardingSlider.tsx @@ -1,4 +1,3 @@ -import { featureFlags } from 'helpers/feature-flags'; import useUserActiveAccount from 'hooks/useUserActiveAccount'; import { useCommonNavigate } from 'navigation/helpers'; import React, { useState } from 'react'; @@ -8,6 +7,7 @@ import { useFetchThreadsQuery } from 'state/api/threads'; import { useFetchTopicsQuery } from 'state/api/topics'; import useAdminOnboardingSliderMutationStore from 'state/ui/adminOnboardingCards'; import Permissions from 'utils/Permissions'; +import { useFlag } from '../../../hooks/useFlag'; import { CWText } from '../component_kit/cw_text'; import { CWModal } from '../component_kit/new_designs/CWModal'; import { CWButton } from '../component_kit/new_designs/cw_button'; @@ -16,6 +16,7 @@ import './AdminOnboardingSlider.scss'; import { DismissModal } from './DismissModal'; export const AdminOnboardingSlider = () => { + const newAdminOnboardingEnabled = useFlag('newAdminOnboarding'); useUserActiveAccount(); const community = app.config.chains.getById(app.activeChainId()); @@ -78,7 +79,7 @@ export const AdminOnboardingSlider = () => { threads.length > 0 && hasAnyIntegration) || !(Permissions.isSiteAdmin() || Permissions.isCommunityAdmin()) || - !featureFlags.newAdminOnboardingEnabled || + !newAdminOnboardingEnabled || [ ...shouldHideAdminCardsTemporary, ...shouldHideAdminCardsPermanently, diff --git a/packages/commonwealth/client/scripts/views/components/CommunityStake/useCommunityStake.ts b/packages/commonwealth/client/scripts/views/components/CommunityStake/useCommunityStake.ts index 5a17e122647..2ac473868dc 100644 --- a/packages/commonwealth/client/scripts/views/components/CommunityStake/useCommunityStake.ts +++ b/packages/commonwealth/client/scripts/views/components/CommunityStake/useCommunityStake.ts @@ -1,5 +1,4 @@ import { commonProtocol } from '@hicommonwealth/core'; -import { featureFlags } from 'helpers/feature-flags'; import useUserLoggedIn from 'hooks/useUserLoggedIn'; import app from 'state'; import { @@ -7,6 +6,7 @@ import { useGetBuyPriceQuery, useGetUserStakeBalanceQuery, } from 'state/api/communityStake'; +import { useFlag } from '../../../hooks/useFlag'; interface UseCommunityStakeProps { communityId?: string; @@ -15,6 +15,7 @@ interface UseCommunityStakeProps { } const useCommunityStake = (props: UseCommunityStakeProps = {}) => { + const communityStakeEnabled = useFlag('communityStake'); const { communityId, stakeId = commonProtocol.STAKE_ID, @@ -31,13 +32,13 @@ const useCommunityStake = (props: UseCommunityStakeProps = {}) => { useFetchCommunityStakeQuery({ communityId: communityId || activeCommunityId, stakeId, - apiEnabled: featureFlags.communityStake && !!activeCommunityId, + apiEnabled: communityStakeEnabled && !!activeCommunityId, }); const stakeData = stakeResponse?.data?.result; const stakeEnabled = stakeData?.stake_enabled; const apiEnabled = Boolean( - featureFlags.communityStake && + communityStakeEnabled && stakeEnabled && (walletAddress || activeAccountAddress) && !!activeCommunityNamespace && diff --git a/packages/commonwealth/client/scripts/views/components/Header/UserDropdown/UserDropdown.tsx b/packages/commonwealth/client/scripts/views/components/Header/UserDropdown/UserDropdown.tsx index 750b06a1e80..eb89e282d94 100644 --- a/packages/commonwealth/client/scripts/views/components/Header/UserDropdown/UserDropdown.tsx +++ b/packages/commonwealth/client/scripts/views/components/Header/UserDropdown/UserDropdown.tsx @@ -19,6 +19,7 @@ import { import { isWindowMediumSmallInclusive } from 'views/components/component_kit/helpers'; import SessionRevalidationModal from 'views/modals/SessionRevalidationModal'; import { LoginModal } from 'views/modals/login_modal'; +import { useFlag } from '../../../../hooks/useFlag'; import { CWModal } from '../../component_kit/new_designs/CWModal'; import './UserDropdown.scss'; import { UserDropdownItem } from './UserDropdownItem'; @@ -29,7 +30,6 @@ import axios from 'axios'; import { notifyError, notifySuccess } from 'controllers/app/notifications'; import WebWalletController from 'controllers/app/web_wallets'; import { setDarkMode } from 'helpers/darkMode'; -import { featureFlags } from 'helpers/feature-flags'; import useAdminOnboardingSliderMutationStore from 'state/ui/adminOnboardingCards'; import useGroupMutationBannerStore from 'state/ui/group'; import { AuthModal } from 'views/modals/AuthModal'; @@ -58,6 +58,7 @@ const handleLogout = async () => { }; const UserDropdown = () => { + const newSignInModalEnabled = useFlag('newSignInModal'); const navigate = useCommonNavigate(); const [isOpen, setIsOpen] = useState(false); const [isDarkModeOn, setIsDarkModeOn] = useState( @@ -194,7 +195,7 @@ const UserDropdown = () => { )} /> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/client/scripts/views/components/Header/useJoinCommunity.tsx b/packages/commonwealth/client/scripts/views/components/Header/useJoinCommunity.tsx index 38798bd408c..fe4c3c7944c 100644 --- a/packages/commonwealth/client/scripts/views/components/Header/useJoinCommunity.tsx +++ b/packages/commonwealth/client/scripts/views/components/Header/useJoinCommunity.tsx @@ -4,7 +4,6 @@ import { setActiveAccount, } from 'controllers/app/login'; import { isSameAccount } from 'helpers'; -import { featureFlags } from 'helpers/feature-flags'; import AddressInfo from 'models/AddressInfo'; import React, { useState } from 'react'; import app from 'state'; @@ -13,12 +12,14 @@ import { TOSModal } from 'views/components/Header/TOSModal'; import { AccountSelector } from 'views/components/component_kit/cw_wallets_list'; import { isWindowMediumSmallInclusive } from 'views/components/component_kit/helpers'; import { LoginModal } from 'views/modals/login_modal'; +import { useFlag } from '../../../hooks/useFlag'; import { AuthModal } from '../../modals/AuthModal'; import { CWModal } from '../component_kit/new_designs/CWModal'; const NON_INTEROP_NETWORKS = [ChainNetwork.AxieInfinity]; const useJoinCommunity = () => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [isAccountSelectorModalOpen, setIsAccountSelectorModalOpen] = useState(false); const [isTOSModalOpen, setIsTOSModalOpen] = useState(false); @@ -232,7 +233,7 @@ const useJoinCommunity = () => { const LoginModalWrapper = ( <> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/client/scripts/views/components/ReactionButton/CommentReactionButton.tsx b/packages/commonwealth/client/scripts/views/components/ReactionButton/CommentReactionButton.tsx index 4aa4980b27a..961a0dd1627 100644 --- a/packages/commonwealth/client/scripts/views/components/ReactionButton/CommentReactionButton.tsx +++ b/packages/commonwealth/client/scripts/views/components/ReactionButton/CommentReactionButton.tsx @@ -1,11 +1,11 @@ import { notifyError } from 'controllers/app/notifications'; import { SessionKeyError } from 'controllers/server/sessions'; -import { featureFlags } from 'helpers/feature-flags'; import useUserActiveAccount from 'hooks/useUserActiveAccount'; import React, { useState } from 'react'; import app from 'state'; import CWUpvoteSmall from 'views/components/component_kit/new_designs/CWUpvoteSmall'; import { useSessionRevalidationModal } from 'views/modals/SessionRevalidationModal'; +import { useFlag } from '../../../hooks/useFlag'; import type Comment from '../../../models/Comment'; import { useCreateCommentReactionMutation, @@ -30,6 +30,7 @@ export const CommentReactionButton = ({ tooltipText = '', onReaction, }: CommentReactionButtonProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); const { activeAccount: hasJoinedCommunity } = useUserActiveAccount(); @@ -112,7 +113,7 @@ export const CommentReactionButton = ({ return ( <> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/client/scripts/views/components/sidebar/AdminSection/AdminSection.tsx b/packages/commonwealth/client/scripts/views/components/sidebar/AdminSection/AdminSection.tsx index 1bde6dea284..2644cd42045 100644 --- a/packages/commonwealth/client/scripts/views/components/sidebar/AdminSection/AdminSection.tsx +++ b/packages/commonwealth/client/scripts/views/components/sidebar/AdminSection/AdminSection.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { handleRedirectClicks } from 'helpers'; -import { featureFlags } from 'helpers/feature-flags'; import { useCommonNavigate } from 'navigation/helpers'; import { matchRoutes, useLocation } from 'react-router-dom'; import app from 'state'; +import { useFlag } from '../../../../hooks/useFlag'; import { SidebarSectionGroup } from '../sidebar_section'; import type { SectionGroupAttrs, SidebarSectionAttrs } from '../types'; import { useSidebarTreeToggle } from '../useSidebarTreeToggle'; const AdminSection = () => { + const proposalTemplatesEnabled = useFlag('proposalTemplates'); const navigate = useCommonNavigate(); const location = useLocation(); const { resetSidebarState, setToggleTree, toggledTreeState } = @@ -176,7 +177,7 @@ const AdminSection = () => { ); }, }, - ...(featureFlags.proposalTemplates + ...(proposalTemplatesEnabled ? [ { title: 'Contract Templates', diff --git a/packages/commonwealth/client/scripts/views/components/sidebar/CommunitySection/CommunitySection.tsx b/packages/commonwealth/client/scripts/views/components/sidebar/CommunitySection/CommunitySection.tsx index c47084c2158..e505648a2cf 100644 --- a/packages/commonwealth/client/scripts/views/components/sidebar/CommunitySection/CommunitySection.tsx +++ b/packages/commonwealth/client/scripts/views/components/sidebar/CommunitySection/CommunitySection.tsx @@ -1,5 +1,4 @@ import 'components/sidebar/CommunitySection/CommunitySection.scss'; -import { featureFlags } from 'helpers/feature-flags'; import useUserActiveAccount from 'hooks/useUserActiveAccount'; import useUserLoggedIn from 'hooks/useUserLoggedIn'; import { useCommonNavigate } from 'navigation/helpers'; @@ -14,6 +13,7 @@ import { CWDivider } from 'views/components/component_kit/cw_divider'; import { CWModal } from 'views/components/component_kit/new_designs/CWModal'; import { SubscriptionButton } from 'views/components/subscription_button'; import ManageCommunityStakeModal from 'views/modals/ManageCommunityStakeModal/ManageCommunityStakeModal'; +import { useFlag } from '../../../../hooks/useFlag'; import useManageCommunityStakeModalStore from '../../../../state/ui/modals/manageCommunityStakeModal'; import Permissions from '../../../../utils/Permissions'; import { CWIcon } from '../../component_kit/cw_icons/cw_icon'; @@ -33,6 +33,9 @@ interface CommunitySectionProps { } export const CommunitySection = ({ showSkeleton }: CommunitySectionProps) => { + const newAdminOnboardingEnabled = useFlag('newAdminOnboarding'); + const communityHomepageEnabled = useFlag('communityHomepage'); + const communityStakeEnabled = useFlag('communityStake'); const navigate = useCommonNavigate(); const { pathname } = useLocation(); const { isLoggedIn } = useUserLoggedIn(); @@ -66,7 +69,7 @@ export const CommunitySection = ({ showSkeleton }: CommunitySectionProps) => { address={activeAccount?.address} /> - {featureFlags.communityStake && stakeEnabled && ( + {communityStakeEnabled && stakeEnabled && ( { {showAdmin && ( <> - {featureFlags.newAdminOnboardingEnabled ? ( - - ) : ( - - )} + {newAdminOnboardingEnabled ? : } )} - {featureFlags.communityHomepage && app.chain?.meta.hasHomepage && ( + {communityHomepageEnabled && app.chain?.meta.hasHomepage && (
navigate('/feed')} diff --git a/packages/commonwealth/client/scripts/views/components/sidebar/old_admin_section.tsx b/packages/commonwealth/client/scripts/views/components/sidebar/old_admin_section.tsx index d647e23209c..b38ad478678 100644 --- a/packages/commonwealth/client/scripts/views/components/sidebar/old_admin_section.tsx +++ b/packages/commonwealth/client/scripts/views/components/sidebar/old_admin_section.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { handleRedirectClicks } from 'helpers'; -import { featureFlags } from 'helpers/feature-flags'; import { useCommonNavigate } from 'navigation/helpers'; import { matchRoutes, useLocation } from 'react-router-dom'; import app from 'state'; import { useNewTopicModalStore } from 'state/ui/modals'; import { sidebarStore } from 'state/ui/sidebar'; +import { useFlag } from '../../../hooks/useFlag'; import { NewTopicModal } from '../../modals/new_topic_modal'; import { OrderTopicsModal } from '../../modals/order_topics_modal'; import { isWindowSmallInclusive } from '../component_kit/helpers'; @@ -51,6 +51,7 @@ const setAdminToggleTree = (path: string, toggle: boolean) => { }; const AdminSectionComponent = () => { + const proposalTemplatesEnabled = useFlag('proposalTemplates'); const navigate = useCommonNavigate(); const location = useLocation(); @@ -117,7 +118,7 @@ const AdminSectionComponent = () => { ); }, }, - ...(featureFlags.proposalTemplates + ...(proposalTemplatesEnabled ? [ { title: 'Contract action templates', diff --git a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/CommunityProfile/CommunityProfileForm/CommunityProfileForm.tsx b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/CommunityProfile/CommunityProfileForm/CommunityProfileForm.tsx index efd8103dee3..4861a7cef16 100644 --- a/packages/commonwealth/client/scripts/views/pages/CommunityManagement/CommunityProfile/CommunityProfileForm/CommunityProfileForm.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CommunityManagement/CommunityProfile/CommunityProfileForm/CommunityProfileForm.tsx @@ -1,6 +1,5 @@ import { DefaultPage } from '@hicommonwealth/core'; import { notifyError, notifySuccess } from 'controllers/app/notifications'; -import { featureFlags } from 'helpers/feature-flags'; import getLinkType from 'helpers/linkType'; import React, { useState } from 'react'; import { slugifyPreserveDashes } from 'shared/utils'; @@ -23,6 +22,7 @@ import { CWTextInput } from 'views/components/component_kit/new_designs/CWTextIn import { CWButton } from 'views/components/component_kit/new_designs/cw_button'; import { CWRadioButton } from 'views/components/component_kit/new_designs/cw_radio_button'; import { CWToggle } from 'views/components/component_kit/new_designs/cw_toggle'; +import { useFlag } from '../../../../../hooks/useFlag'; import { getCommunityTags } from '../../../manage_community/helpers'; import './CommunityProfileForm.scss'; import { CommunityTags, FormSubmitValues } from './types'; @@ -32,6 +32,7 @@ import { } from './validation'; const CommunityProfileForm = () => { + const communityStakeEnabled = useFlag('communityStake'); const communityTagOptions: CommunityTags[] = ['DeFi', 'DAO']; const community = app.config.chains.getById(app.activeChainId()); @@ -206,7 +207,7 @@ const CommunityProfileForm = () => { placeholder="Community URL" value={`${window.location.origin}/${communityId}`} /> - {featureFlags.communityStake && ( + {communityStakeEnabled && ( <> { + const communityStakeEnabled = useFlag('communityStake'); const { createCommunityStep, selectedCommunity, @@ -76,7 +78,11 @@ const CreateCommunity = () => {
{!isSuccessStep && ( )} diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx index 2ba4bc7cea5..f306614e2a8 100644 --- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx +++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/steps/CommunityTypeStep/CommunityTypeStep.tsx @@ -18,10 +18,10 @@ import { MixpanelLoginPayload, } from '../../../../../../../shared/analytics/types'; import { useBrowserAnalyticsTrack } from '../../../../../hooks/useBrowserAnalyticsTrack'; +import { useFlag } from '../../../../../hooks/useFlag'; import { communityTypeOptions } from './helpers'; import { ChainBase } from '@hicommonwealth/core'; -import { featureFlags } from 'helpers/feature-flags'; import { AuthModal } from 'views/modals/AuthModal'; import './CommunityTypeStep.scss'; @@ -38,6 +38,7 @@ const CommunityTypeStep = ({ setSelectedAddress, handleContinue, }: CommunityTypeStepProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [isNewCommunityAdminModalOpen, setIsNewCommunityAdminModalOpen] = useState(false); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); @@ -142,7 +143,7 @@ const CommunityTypeStep = ({ onClose={() => setIsNewCommunityAdminModalOpen(false)} open={isNewCommunityAdminModalOpen} /> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( { + const communityStakeEnabled = useFlag('communityStake'); const [createCommunityStep, setCreateCommunityStep] = useState(CreateCommunityStep.CommunityTypeSelection); const [selectedCommunity, setSelectedCommunity] = useState( @@ -21,6 +23,7 @@ const useCreateCommunity = () => { createCommunityStep, setCreateCommunityStep, showCommunityStakeStep, + communityStakeEnabled, ); }; diff --git a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/utils.ts b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/utils.ts index b11a49b4a0a..a7651536ff6 100644 --- a/packages/commonwealth/client/scripts/views/pages/CreateCommunity/utils.ts +++ b/packages/commonwealth/client/scripts/views/pages/CreateCommunity/utils.ts @@ -1,6 +1,4 @@ import React from 'react'; - -import { featureFlags } from 'helpers/feature-flags'; import { CWFormStepsProps } from 'views/components/component_kit/new_designs/CWFormSteps/CWFormSteps'; export enum CreateCommunityStep { @@ -13,6 +11,7 @@ export enum CreateCommunityStep { export const getFormSteps = ( createCommunityStep: CreateCommunityStep, showCommunityStakeStep: boolean, + communityStakeEnabled: boolean, ): CWFormStepsProps['steps'] => { return [ { @@ -31,7 +30,7 @@ export const getFormSteps = ( ? 'active' : 'completed', }, - ...((featureFlags.communityStake && showCommunityStakeStep + ...((communityStakeEnabled && showCommunityStakeStep ? [ { label: 'Community Stake', @@ -54,6 +53,7 @@ export const handleChangeStep = ( React.SetStateAction >, showCommunityStakeStep: boolean, + communityStakeEnabled: boolean, ) => { switch (createCommunityStep) { case CreateCommunityStep.CommunityTypeSelection: @@ -62,7 +62,7 @@ export const handleChangeStep = ( case CreateCommunityStep.BasicInformation: setCreateCommunityStep( forward - ? featureFlags.communityStake && showCommunityStakeStep + ? communityStakeEnabled && showCommunityStakeStep ? CreateCommunityStep.CommunityStake : CreateCommunityStep.Success : CreateCommunityStep.CommunityTypeSelection, diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/HeaderWithFilters/HeaderWithFilters.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/HeaderWithFilters/HeaderWithFilters.tsx index e860cd812ce..cfdfb463cc1 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/HeaderWithFilters/HeaderWithFilters.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/HeaderWithFilters/HeaderWithFilters.tsx @@ -1,5 +1,4 @@ import { parseCustomStages, threadStageToLabel } from 'helpers'; -import { featureFlags } from 'helpers/feature-flags'; import { isUndefined } from 'helpers/typeGuards'; import useBrowserWindow from 'hooks/useBrowserWindow'; import useForceRerender from 'hooks/useForceRerender'; @@ -21,6 +20,7 @@ import { CWText } from 'views/components/component_kit/cw_text'; import { CWModal } from 'views/components/component_kit/new_designs/CWModal'; import { CWButton } from 'views/components/component_kit/new_designs/cw_button'; import { EditTopicModal } from 'views/modals/edit_topic_modal'; +import { useFlag } from '../../../../hooks/useFlag'; import type Topic from '../../../../models/Topic'; import { ThreadFeaturedFilterTypes, @@ -56,6 +56,7 @@ export const HeaderWithFilters = ({ onIncludeArchivedThreads, isOnArchivePage, }: HeaderWithFiltersProps) => { + const communityStakeEnabled = useFlag('communityStake'); const navigate = useCommonNavigate(); const [topicSelectedToEdit, setTopicSelectedToEdit] = useState(null); const [isDismissStakeBannerModalOpen, setIsDismissStakeBannerModalOpen] = @@ -182,7 +183,7 @@ export const HeaderWithFilters = ({ }; const stakeBannerEnabled = - featureFlags.communityStake && + communityStakeEnabled && stakeEnabled && isBannerVisible(app.activeChainId()); diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ReactionButton/ReactionButton.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ReactionButton/ReactionButton.tsx index ca7d8d5470a..4e160b6433d 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ReactionButton/ReactionButton.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ReactionButton/ReactionButton.tsx @@ -1,5 +1,4 @@ import { SessionKeyError } from 'controllers/server/sessions'; -import { featureFlags } from 'helpers/feature-flags'; import type Thread from 'models/Thread'; import React, { useState } from 'react'; import app from 'state'; @@ -18,6 +17,7 @@ import { TooltipWrapper } from 'views/components/component_kit/new_designs/cw_th import { CWUpvote } from 'views/components/component_kit/new_designs/cw_upvote'; import { AuthModal } from 'views/modals/AuthModal'; import { useSessionRevalidationModal } from 'views/modals/SessionRevalidationModal'; +import { useFlag } from '../../../../../../hooks/useFlag'; import { LoginModal } from '../../../../../modals/login_modal'; import { ReactionButtonSkeleton } from './ReactionButtonSkeleton'; @@ -36,6 +36,7 @@ export const ReactionButton = ({ showSkeleton, tooltipText, }: ReactionButtonProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); const reactors = thread?.associatedReactions?.map((t) => t.address); const activeAddress = app.user.activeAccount?.address; @@ -160,7 +161,7 @@ export const ReactionButton = ({ )}
)} - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/client/scripts/views/pages/discussions_redirect.tsx b/packages/commonwealth/client/scripts/views/pages/discussions_redirect.tsx index 0c8434ea6e8..e8c7072d555 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions_redirect.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions_redirect.tsx @@ -3,12 +3,13 @@ import React, { useEffect } from 'react'; import { NavigateOptions } from 'react-router-dom'; import { DefaultPage } from '@hicommonwealth/core'; -import { featureFlags } from 'helpers/feature-flags'; import { useCommonNavigate } from 'navigation/helpers'; import app from 'state'; +import { useFlag } from '../../hooks/useFlag'; import { PageLoading } from './loading'; export default function DiscussionsRedirect() { + const communityHomepageEnabled = useFlag('communityHomepage'); const navigate = useCommonNavigate(); useEffect(() => { @@ -17,7 +18,7 @@ export default function DiscussionsRedirect() { const { defaultPage, defaultOverview, hasHomepage } = app.chain.meta; let view; - if (featureFlags.communityHomepage && hasHomepage) { + if (communityHomepageEnabled && hasHomepage) { view = defaultPage; } else { view = defaultOverview ? DefaultPage.Overview : DefaultPage.Discussions; diff --git a/packages/commonwealth/client/scripts/views/pages/landing/header.tsx b/packages/commonwealth/client/scripts/views/pages/landing/header.tsx index 4804a41d77c..40be0a1bddc 100644 --- a/packages/commonwealth/client/scripts/views/pages/landing/header.tsx +++ b/packages/commonwealth/client/scripts/views/pages/landing/header.tsx @@ -1,9 +1,9 @@ -import { featureFlags } from 'helpers/feature-flags'; import useBrowserWindow from 'hooks/useBrowserWindow'; import { useCommonNavigate } from 'navigation/helpers'; import 'pages/landing/header.scss'; import React, { useState } from 'react'; import { LoginModal } from 'views/modals/login_modal'; +import { useFlag } from '../../../hooks/useFlag'; import { CWIconButton } from '../../components/component_kit/cw_icon_button'; import { CWText } from '../../components/component_kit/cw_text'; import { CWModal } from '../../components/component_kit/new_designs/CWModal'; @@ -15,6 +15,7 @@ type HeaderProps = { }; export const Header = ({ onLogin }: HeaderProps) => { + const newSignInModalEnabled = useFlag('newSignInModal'); const navigate = useCommonNavigate(); const [isAuthModalOpen, setIsAuthModalOpen] = useState(false); @@ -47,7 +48,7 @@ export const Header = ({ onLogin }: HeaderProps) => {
)} - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( { + const communityHomepageEnabled = useFlag('communityHomepage'); + const params = new URLSearchParams(window.location.search); const returningFromDiscordCallback = params.get( 'returningFromDiscordCallback', @@ -339,7 +341,7 @@ export const CommunityMetadataRows = ({ } /> - {featureFlags.communityHomepage && ( + {communityHomepageEnabled && ( )} - {featureFlags.communityHomepage && hasHomepage ? ( + {communityHomepageEnabled && hasHomepage ? ( { + const newAdminOnboardingEnabled = useFlag('newAdminOnboarding'); const navigate = useCommonNavigate(); const [failed, setFailed] = useState(false); const [failureMessage, setFailureMessage] = useState(''); - const redirectPath = featureFlags.newAdminOnboardingEnabled + const redirectPath = newAdminOnboardingEnabled ? 'manage/integrations' : 'manage'; diff --git a/packages/commonwealth/client/scripts/views/pages/manage_community/manage_roles.tsx b/packages/commonwealth/client/scripts/views/pages/manage_community/manage_roles.tsx index 7177c9056c5..10c0cb23f51 100644 --- a/packages/commonwealth/client/scripts/views/pages/manage_community/manage_roles.tsx +++ b/packages/commonwealth/client/scripts/views/pages/manage_community/manage_roles.tsx @@ -1,12 +1,12 @@ import axios from 'axios'; import { notifyError } from 'controllers/app/notifications'; -import { featureFlags } from 'helpers/feature-flags'; import { useCommonNavigate } from 'navigation/helpers'; import 'pages/manage_community/manage_roles.scss'; import React from 'react'; import app from 'state'; import { User } from 'views/components/user/user'; import { openConfirmation } from 'views/modals/confirmation_modal'; +import { useFlag } from '../../../hooks/useFlag'; import RoleInfo from '../../../models/RoleInfo'; import { CWIcon } from '../../components/component_kit/cw_icons/cw_icon'; import { CWLabel } from '../../components/component_kit/cw_label'; @@ -22,6 +22,7 @@ export const ManageRoles = ({ onRoleUpdate, roledata, }: ManageRoleRowProps) => { + const newAdminOnboardingEnabled = useFlag('newAdminOnboarding'); const navigate = useCommonNavigate(); const communityObj = { chain: app.activeChainId() }; @@ -110,11 +111,7 @@ export const ManageRoles = ({ onClick: async () => { await removeRole(role); if (isLosingAdminPermissions) { - navigate( - featureFlags.newAdminOnboardingEnabled - ? '/manage/moderators' - : '/', - ); + navigate(newAdminOnboardingEnabled ? '/manage/moderators' : '/'); } }, }, diff --git a/packages/commonwealth/client/scripts/views/pages/manage_community/upgrade_roles_form.tsx b/packages/commonwealth/client/scripts/views/pages/manage_community/upgrade_roles_form.tsx index fca7fcc8783..fcf573ce954 100644 --- a/packages/commonwealth/client/scripts/views/pages/manage_community/upgrade_roles_form.tsx +++ b/packages/commonwealth/client/scripts/views/pages/manage_community/upgrade_roles_form.tsx @@ -1,11 +1,11 @@ import { AccessLevel } from '@hicommonwealth/core'; import { notifyError, notifySuccess } from 'controllers/app/notifications'; import { formatAddressShort } from 'helpers'; -import { featureFlags } from 'helpers/feature-flags'; import $ from 'jquery'; import 'pages/manage_community/upgrade_roles_form.scss'; import React, { useMemo, useState } from 'react'; import app from 'state'; +import { useFlag } from '../../../hooks/useFlag'; import type RoleInfo from '../../../models/RoleInfo'; import { CWRadioGroup } from '../../components/component_kit/cw_radio_group'; import { CWButton } from '../../components/component_kit/new_designs/cw_button'; @@ -24,6 +24,7 @@ export const UpgradeRolesForm = ({ searchTerm, setSearchTerm, }: UpgradeRolesFormProps) => { + const newAdminOnboardingEnabled = useFlag('newAdminOnboarding'); const [role, setRole] = useState(''); const [user, setUser] = useState(''); const [radioButtons, setRadioButtons] = useState([ @@ -91,7 +92,7 @@ export const UpgradeRolesForm = ({ />
- {featureFlags.newAdminOnboardingEnabled ? ( + {newAdminOnboardingEnabled ? ( <> {newAdminOnboardingEnabledOptions.map((o, i) => { return ( diff --git a/packages/commonwealth/client/scripts/views/pages/view_thread/ViewThreadPage.tsx b/packages/commonwealth/client/scripts/views/pages/view_thread/ViewThreadPage.tsx index 1b2b68e0f0d..93465f14bc1 100644 --- a/packages/commonwealth/client/scripts/views/pages/view_thread/ViewThreadPage.tsx +++ b/packages/commonwealth/client/scripts/views/pages/view_thread/ViewThreadPage.tsx @@ -3,7 +3,6 @@ import axios from 'axios'; import { notifyError } from 'controllers/app/notifications'; import { extractDomain, isDefaultStage } from 'helpers'; import { commentsByDate } from 'helpers/dates'; -import { featureFlags } from 'helpers/feature-flags'; import { filterLinks, getThreadActionTooltipText } from 'helpers/threads'; import { useBrowserAnalyticsTrack } from 'hooks/useBrowserAnalyticsTrack'; import useBrowserWindow from 'hooks/useBrowserWindow'; @@ -32,6 +31,7 @@ import useJoinCommunity from 'views/components/Header/useJoinCommunity'; import JoinCommunityBanner from 'views/components/JoinCommunityBanner'; import { PageNotFound } from 'views/pages/404'; import { MixpanelPageViewEvent } from '../../../../../shared/analytics/types'; +import { useFlag } from '../../../hooks/useFlag'; import useManageDocumentTitle from '../../../hooks/useManageDocumentTitle'; import Poll from '../../../models/Poll'; import { Link, LinkDisplay, LinkSource } from '../../../models/Thread'; @@ -80,6 +80,7 @@ type ViewThreadPageProps = { }; const ViewThreadPage = ({ identifier }: ViewThreadPageProps) => { + const proposalTemplatesEnabled = useFlag('proposalTemplates'); const threadId = identifier.split('-')[0]; const navigate = useCommonNavigate(); @@ -310,9 +311,9 @@ const ViewThreadPage = ({ identifier }: ViewThreadPageProps) => { linkedThreads.length > 0 || isAuthor || isAdminOrMod; const showTemplateOptions = - featureFlags.proposalTemplates && (isAuthor || isAdminOrMod); + proposalTemplatesEnabled && (isAuthor || isAdminOrMod); const showLinkedTemplateOptions = - featureFlags.proposalTemplates && linkedTemplates.length > 0; + proposalTemplatesEnabled && linkedTemplates.length > 0; const hasSnapshotProposal = thread.links.find((x) => x.source === 'snapshot'); diff --git a/packages/commonwealth/client/scripts/views/pages/web3login.tsx b/packages/commonwealth/client/scripts/views/pages/web3login.tsx index fc823a8fc7e..7f834c58164 100644 --- a/packages/commonwealth/client/scripts/views/pages/web3login.tsx +++ b/packages/commonwealth/client/scripts/views/pages/web3login.tsx @@ -1,10 +1,10 @@ -import { featureFlags } from 'helpers/feature-flags'; import { isNonEmptyString } from 'helpers/typeGuards'; import $ from 'jquery'; import 'pages/web3login.scss'; import React from 'react'; import { Link, useSearchParams } from 'react-router-dom'; import app from 'state'; +import { useFlag } from '../../hooks/useFlag'; import { CWButton } from '../components/component_kit/cw_button'; import { CWText } from '../components/component_kit/cw_text'; import { isWindowMediumSmallInclusive } from '../components/component_kit/helpers'; @@ -15,6 +15,7 @@ import { PageNotFound } from './404'; import { PageLoading } from './loading'; const Web3LoginPage = () => { + const newSignInModalEnabled = useFlag('newSignInModal'); const [searchParams] = useSearchParams(); const [errorMsg, setErrorMsg] = React.useState(''); const [isLoading, setIsLoading] = React.useState(false); @@ -64,7 +65,7 @@ const Web3LoginPage = () => { return ( <> - {!featureFlags.newSignInModal ? ( + {!newSignInModalEnabled ? ( setIsAuthModalOpen(false)} /> diff --git a/packages/commonwealth/package.json b/packages/commonwealth/package.json index 20d89e1c628..4c8dfbffb86 100644 --- a/packages/commonwealth/package.json +++ b/packages/commonwealth/package.json @@ -86,6 +86,8 @@ "@metamask/detect-provider": "^2.0.0", "@metamask/eth-sig-util": "^4.0.0", "@mui/base": "5.0.0-beta.5", + "@openfeature/core": "^0.0.25", + "@openfeature/server-sdk": "^1.11.0", "@phosphor-icons/react": "^2.0.8", "@polkadot/api": "6.0.5", "@polkadot/api-derive": "6.0.5", diff --git a/yarn.lock b/yarn.lock index 7ad1e404a1a..f92e146165e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4323,6 +4323,21 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz#b41053e360c31a32c2640c9a45ee981a7e603fe0" integrity sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg== +"@openfeature/core@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@openfeature/core/-/core-0.0.25.tgz#8e40b6988692f0038057480ec3ae8be9c72ea3b9" + integrity sha512-/NrqVXY39WTOZ3EUtqnFOXFlvcJNuXFm+gi3Zs7wz8SzNdFhvrFtsL7ylQtIgYK5WBOP1HxrtAwrpEicaO1emw== + +"@openfeature/server-sdk@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@openfeature/server-sdk/-/server-sdk-1.11.0.tgz#c81827274a452603e36fcb94ded53c19a18f297b" + integrity sha512-r4J2J3NYb/g9wpV2pqVXyk3nkQuO4NTXdZfPbIvK9gNx2oOV9+tTsp3t3w7yBxFfbjzssZ0nyhfCd1IayOG0LQ== + +"@openfeature/web-sdk@^0.4.12": + version "0.4.12" + resolved "https://registry.yarnpkg.com/@openfeature/web-sdk/-/web-sdk-0.4.12.tgz#11851ba8a45e7d000965cd0b50a282bdd4203fd6" + integrity sha512-DcJ1VNS8L8DAvi9P5tJcI7cjeNCWDlOAcLkbDkuODEaW/15abJh82IH591cMgecmAgA6mE+8vEUWE4GEWakG+Q== + "@openzeppelin/contracts-governance@npm:@openzeppelin/contracts@^4.3.2": version "4.3.2" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.2.tgz#ff80affd6d352dbe1bbc5b4e1833c41afd6283b6"