feat: add feature flag modal in local/staging environments (#4291)
* initial * add feature flag modal * updates * help * working now! * SIMPLIFY * useref delete * naming * move modal out * rename * make button * rename * work on vercel * styling Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
This commit is contained in:
parent
25ea7f9caf
commit
51aa02d878
110
src/components/FeatureFlagModal/FeatureFlagModal.tsx
Normal file
110
src/components/FeatureFlagModal/FeatureFlagModal.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { FeatureFlag, useUpdateFlag } from 'featureFlags'
|
||||
import { Phase0Variant, usePhase0Flag } from 'featureFlags/flags/phase0'
|
||||
import { ReactNode } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledModal = styled.div`
|
||||
position: fixed;
|
||||
display: flex;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
height: fit-content;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 18px;
|
||||
padding: 20px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
z-index: 100;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
||||
function Modal({ open, children }: { open: boolean; children: ReactNode }) {
|
||||
return open ? <StyledModal>{children}</StyledModal> : null
|
||||
}
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0px;
|
||||
`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: ${({ theme }) => theme.none};
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const Header = styled(Row)`
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
function Variant({ option }: { option: string }) {
|
||||
return <option value={option}>{option}</option>
|
||||
}
|
||||
|
||||
function FeatureFlagOption({
|
||||
variants,
|
||||
featureFlag,
|
||||
value,
|
||||
label,
|
||||
}: {
|
||||
variants: string[]
|
||||
featureFlag: FeatureFlag
|
||||
value: string
|
||||
label: string
|
||||
}) {
|
||||
const updateFlag = useUpdateFlag()
|
||||
return (
|
||||
<Row key={featureFlag}>
|
||||
{featureFlag}: {label}
|
||||
<select
|
||||
id={featureFlag}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
updateFlag(featureFlag, e.target.value)
|
||||
window.location.reload()
|
||||
}}
|
||||
>
|
||||
{variants.map((variant) => (
|
||||
<Variant key={variant} option={variant} />
|
||||
))}
|
||||
</select>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FeatureFlagModal() {
|
||||
const open = useModalIsOpen(ApplicationModal.FEATURE_FLAGS)
|
||||
const toggle = useToggleFeatureFlags()
|
||||
|
||||
return (
|
||||
<Modal open={open}>
|
||||
<Header>
|
||||
Feature Flag Settings
|
||||
<CloseButton onClick={toggle}>
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(Phase0Variant)}
|
||||
value={usePhase0Flag()}
|
||||
featureFlag={FeatureFlag.phase0}
|
||||
label="All Phase 0 changes (redesign, explore, header)."
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
|
||||
@ -214,6 +215,7 @@ export default function Menu() {
|
||||
const toggleMenu = useToggleModal(ApplicationModal.MENU)
|
||||
useOnClickOutside(node, open ? toggleMenu : undefined)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
|
||||
@ -299,12 +301,16 @@ export default function Menu() {
|
||||
<Trans>Claim UNI</Trans>
|
||||
</UNIbutton>
|
||||
)}
|
||||
{['development', 'staging'].includes(process.env.NODE_ENV) && (
|
||||
<ToggleMenuItem onClick={openFeatureFlagsModal}>Feature Flags</ToggleMenuItem>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
</StyledMenu>
|
||||
<PrivacyPolicyModal />
|
||||
<FeatureFlagModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { createContext, ReactNode, useContext } from 'react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { createContext, ReactNode, useCallback, useContext } from 'react'
|
||||
|
||||
interface FeatureFlagsContextType {
|
||||
isLoaded: boolean
|
||||
@ -16,16 +18,28 @@ export function useFeatureFlagsContext(): FeatureFlagsContextType {
|
||||
}
|
||||
}
|
||||
|
||||
/* update and save feature flag settings */
|
||||
export const featureFlagSettings = atomWithStorage<Record<string, string>>('featureFlags', {})
|
||||
|
||||
export function useUpdateFlag() {
|
||||
const [featureFlags, setFeatureFlags] = useAtom(featureFlagSettings)
|
||||
|
||||
return useCallback(
|
||||
(featureFlag: string, option: string) => {
|
||||
featureFlags[featureFlag] = option
|
||||
setFeatureFlags(featureFlags)
|
||||
},
|
||||
[featureFlags, setFeatureFlags]
|
||||
)
|
||||
}
|
||||
|
||||
export function FeatureFlagsProvider({ children }: { children: ReactNode }) {
|
||||
// TODO(vm): `isLoaded` to `true` so `App.tsx` will render. Later, this will be dependent on
|
||||
// flags loading from Amplitude, with a timeout.
|
||||
const variant = process.env.NODE_ENV === 'development' ? 'enabled' : 'control'
|
||||
const featureFlags = useAtomValue(featureFlagSettings)
|
||||
const value = {
|
||||
isLoaded: true,
|
||||
flags: {
|
||||
phase0: variant,
|
||||
phase1: variant,
|
||||
},
|
||||
flags: featureFlags,
|
||||
}
|
||||
return <FeatureFlagContext.Provider value={value}>{children}</FeatureFlagContext.Provider>
|
||||
}
|
||||
@ -35,8 +49,12 @@ export function useFeatureFlagsIsLoaded(): boolean {
|
||||
}
|
||||
|
||||
export enum BaseVariant {
|
||||
Control = 'Control',
|
||||
Enabled = 'Enabled',
|
||||
Control = 'control',
|
||||
Enabled = 'enabled',
|
||||
}
|
||||
|
||||
export enum FeatureFlag {
|
||||
phase0 = 'phase0',
|
||||
}
|
||||
|
||||
export function useBaseFlag(flag: string): BaseVariant {
|
||||
|
@ -70,6 +70,10 @@ export function useToggleTimeSelector(): () => void {
|
||||
return useToggleModal(ApplicationModal.TIME_SELECTOR)
|
||||
}
|
||||
|
||||
export function useToggleFeatureFlags(): () => void {
|
||||
return useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
}
|
||||
|
||||
// returns a function that allows adding a popup
|
||||
export function useAddPopup(): (content: PopupContent, key?: string, removeAfterMs?: number) => void {
|
||||
const dispatch = useAppDispatch()
|
||||
|
@ -31,6 +31,7 @@ export enum ApplicationModal {
|
||||
TIME_SELECTOR,
|
||||
SHARE,
|
||||
NETWORK_FILTER,
|
||||
FEATURE_FLAGS,
|
||||
}
|
||||
|
||||
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
|
||||
|
Loading…
Reference in New Issue
Block a user