Skip to content

Commit

Permalink
Replace "feature flags" with "query param modes"
Browse files Browse the repository at this point in the history
  • Loading branch information
adamgall committed Jan 14, 2025
1 parent bc8c422 commit 49e2afb
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 56 deletions.
4 changes: 0 additions & 4 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,5 @@ VITE_APP_SITE_URL="https://app.dev.decentdao.org"
# WalletConnect Cloud Project ID
VITE_APP_WALLET_CONNECT_PROJECT_ID=""

# FEATURE FLAGS (Must equal "ON")
VITE_APP_FLAG_DEVELOPMENT_MODE=""
VITE_APP_FLAG_DEMO_MODE=""

# Use legacy Netlify balances backend
VITE_APP_USE_LEGACY_BACKEND=""
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { WarningCircle } from '@phosphor-icons/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { isDevMode } from '../../../constants/common';
import { getModeStatus } from '../../../hooks/utils/modes';
import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore';
import { FractalModuleType, ICreationStepProps, VotingStrategyType } from '../../../types';
import { BigIntInput } from '../../ui/forms/BigIntInput';
Expand Down Expand Up @@ -78,8 +78,9 @@ export function AzoriusGovernance(props: ICreationStepProps) {

useStepRedirect({ values });

const [votingPeriodDays, setVotingPeriodDays] = useState(isDevMode() ? 0.0021 : 7);
const [timelockPeriodDays, setTimelockPeriodDays] = useState(isDevMode() ? 0 : 1);
const isDevMode = getModeStatus('DEV');
const [votingPeriodDays, setVotingPeriodDays] = useState(isDevMode ? 0.0021 : 7);
const [timelockPeriodDays, setTimelockPeriodDays] = useState(isDevMode ? 0 : 1);
const [executionPeriodDays, setExecutionPeriodDays] = useState(2);

useEffect(() => {
Expand Down
9 changes: 6 additions & 3 deletions src/components/Roles/RolePaymentDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Address, getAddress, Hex } from 'viem';
import { useAccount, usePublicClient } from 'wagmi';
import { DETAILS_BOX_SHADOW, isDemoMode } from '../../constants/common';
import { DETAILS_BOX_SHADOW } from '../../constants/common';
import { DAO_ROUTES } from '../../constants/routes';
import { getModeStatus } from '../../hooks/utils/modes';
import { useNetworkConfigStore } from '../../providers/NetworkConfig/useNetworkConfigStore';
import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore';
import { useRolesStore } from '../../store/roles/useRolesStore';
Expand Down Expand Up @@ -145,6 +146,8 @@ export function RolePaymentDetails({
const { refreshWithdrawableAmount } = useRolesStore();
const navigate = useNavigate();
const publicClient = usePublicClient();
const isDemoMode = getModeStatus('DEMO');

const canWithdraw = useMemo(() => {
if (
connectedAccount &&
Expand All @@ -153,8 +156,8 @@ export function RolePaymentDetails({
) {
return true;
}
return isDemoMode();
}, [connectedAccount, payment.recipient, showWithdraw, roleHatWearerAddress]);
return isDemoMode;
}, [connectedAccount, payment.recipient, showWithdraw, roleHatWearerAddress, isDemoMode]);

const assignedTerm = useMemo(() => {
return roleTerms.find(term => term.termEndDate.getTime() === payment.endDate.getTime());
Expand Down
25 changes: 2 additions & 23 deletions src/components/Roles/forms/RoleFormPaymentStream.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Alert, Box, Button, Flex, FormControl, Icon, Show, Text } from '@chakra-ui/react';
import { ArrowRight, Info, Trash } from '@phosphor-icons/react';
import { addDays, addMinutes } from 'date-fns';
import { addDays } from 'date-fns';
import { Field, FieldProps, FormikErrors, useFormikContext } from 'formik';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DETAILS_BOX_SHADOW, isDevMode } from '../../../constants/common';
import { DETAILS_BOX_SHADOW } from '../../../constants/common';
import { useRolesStore } from '../../../store/roles/useRolesStore';
import { RoleFormValues, RoleHatFormValue } from '../../../types/roles';
import { DatePicker } from '../../ui/forms/DatePicker';
Expand Down Expand Up @@ -215,27 +215,6 @@ export function RoleFormPaymentStream({ formIndex }: { formIndex: number }) {
{t('save')}
</Button>
)}
{isDevMode() && !canBeCancelled && (
<Button
onClick={() => {
if (payment === undefined) {
return;
}
const nowDate = new Date();
setFieldValue(`roleEditing.payments.${formIndex}`, {
...payment,
amount: {
value: '100',
bigintValue: 100000000000000000000n,
},
startDate: addMinutes(nowDate, 1),
endDate: addMinutes(nowDate, 10),
});
}}
>
Ze stream ends in 10!
</Button>
)}
{canBeCancelled && (
<Show above="md">
<Button
Expand Down
4 changes: 3 additions & 1 deletion src/components/ui/page/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, ComponentWithAs, Flex, Icon as ChakraIcon, IconProps } from '@chakra-ui/react';
import { Box, Icon as ChakraIcon, ComponentWithAs, Flex, IconProps } from '@chakra-ui/react';
import { DiscordLogo, Icon, TelegramLogo, XLogo } from '@phosphor-icons/react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
Expand All @@ -10,6 +10,7 @@ import {
URL_TWITTER,
URL_WARPCAST,
} from '../../../constants/url';
import { ModeButtons } from '../../../hooks/utils/modes';
import ExternalLink from '../links/ExternalLink';

function NavigationIconLink(props: {
Expand Down Expand Up @@ -94,6 +95,7 @@ export function Footer() {
DisplayIcon={DiscordLogo}
/>
</Flex>
<ModeButtons />
</Flex>
);
}
4 changes: 4 additions & 0 deletions src/components/ui/page/Global/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FavoritesCacheValue,
} from '../../../../hooks/utils/cache/cacheDefaults';
import { setValue } from '../../../../hooks/utils/cache/useLocalStorage';
import { useInitializeModes } from '../../../../hooks/utils/modes';
import { getSafeName } from '../../../../hooks/utils/useGetSafeName';
import {
getNetworkConfig,
Expand Down Expand Up @@ -91,6 +92,9 @@ const useUpdateFavoritesCache = (onFavoritesUpdated: () => void) => {
export function Global() {
useUserTracking();

// Initialize all modes from URL parameters
useInitializeModes();

// Trigger a re-render when favorite names are updated
const [favoritesUpdatedTrigger, setFavoritesUpdatedTrigger] = useState(0);
useUpdateFavoritesCache(() => setFavoritesUpdatedTrigger(prev => prev + 1));
Expand Down
19 changes: 0 additions & 19 deletions src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,6 @@ export const SIDEBAR_WIDTH = '4.25rem';

export const MAX_CONTENT_WIDTH = '80rem';

const features = {
developmentMode: 'DEVELOPMENT_MODE',
demoMode: 'DEMO_MODE',
} as const;

type FeatureFlag = (typeof features)[keyof typeof features];

export const isFeatureEnabled = (feature: FeatureFlag) => {
const featureStatus = import.meta.env[`VITE_APP_FLAG_${feature}`];
if (featureStatus === 'ON') {
return true;
} else {
return false;
}
};

export const isDevMode = () => isFeatureEnabled(features.developmentMode);
export const isDemoMode = () => isFeatureEnabled(features.demoMode);

/**
* @dev DO NOT CHANGE THE SALT
* @note This SALT is used to generate the account address for the Hats Smart Account
Expand Down
111 changes: 111 additions & 0 deletions src/hooks/utils/modes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Button, Icon as ChakraIcon } from '@chakra-ui/react';
import { X } from '@phosphor-icons/react';
import { useCallback, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';

export const MODE_QUERY_PARAMS = {
DEMO: 'demo_mode',
DEV: 'dev_mode',
} as const;

export const MODE_VALUES = {
ON: 'on',
OFF: 'off',
} as const;

function getModeStorageKey(modeType: keyof typeof MODE_QUERY_PARAMS) {
return `decent_${MODE_QUERY_PARAMS[modeType]}`;
}

export function useInitializeModes() {
const [searchParams] = useSearchParams();

useEffect(() => {
(Object.keys(MODE_QUERY_PARAMS) as Array<keyof typeof MODE_QUERY_PARAMS>).forEach(modeType => {
const MODE_QUERY_PARAM = MODE_QUERY_PARAMS[modeType];
const MODE_STORAGE_KEY = getModeStorageKey(modeType);
const rawValue = searchParams.get(MODE_QUERY_PARAM)?.toLowerCase();

if (rawValue === MODE_VALUES.ON) {
localStorage.setItem(MODE_STORAGE_KEY, 'true');
} else if (rawValue === MODE_VALUES.OFF) {
localStorage.setItem(MODE_STORAGE_KEY, 'false');
}
});
}, [searchParams]);
}

export function getModeStatus(modeType: keyof typeof MODE_QUERY_PARAMS): boolean {
try {
const MODE_STORAGE_KEY = getModeStorageKey(modeType);
return localStorage.getItem(MODE_STORAGE_KEY) === 'true';
} catch {
return false;
}
}

function useToggleMode() {
const [searchParams] = useSearchParams();

return useCallback(
(
modeType: keyof typeof MODE_QUERY_PARAMS,
modeValue: (typeof MODE_VALUES)[keyof typeof MODE_VALUES],
) => {
const newParams = new URLSearchParams(searchParams);
newParams.set(MODE_QUERY_PARAMS[modeType], modeValue);
window.location.href = `${window.location.pathname}?${newParams.toString()}`;
},
[searchParams],
);
}

interface ModeToggleButtonProps {
modeType: keyof typeof MODE_QUERY_PARAMS;
isActive: boolean;
onToggle: (
modeType: keyof typeof MODE_QUERY_PARAMS,
modeValue: (typeof MODE_VALUES)[keyof typeof MODE_VALUES],
) => void;
}

function ModeToggleButton({ modeType, isActive, onToggle }: ModeToggleButtonProps) {
if (!isActive) {
return null;
}

return (
<Button
size="sm"
variant="secondary"
onClick={() => onToggle(modeType, MODE_VALUES.OFF)}
pr="0.3rem"
>
{modeType.charAt(0) + modeType.slice(1).toLowerCase()}{' '}
<ChakraIcon
as={X}
boxSize="16px"
position="relative"
top="1px"
/>
</Button>
);
}

export function ModeButtons() {
const handleToggleMode = useToggleMode();
const modes = Object.keys(MODE_QUERY_PARAMS) as Array<keyof typeof MODE_QUERY_PARAMS>;

return (
<>
{modes.map(modeType => (
<ModeToggleButton
key={modeType}
modeType={modeType}
isActive={getModeStatus(modeType)}
onToggle={handleToggleMode}
/>
))}
</>
);
}
8 changes: 5 additions & 3 deletions src/hooks/utils/useCanUserSubmitProposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { abis } from '@fractal-framework/fractal-contracts';
import { useCallback, useEffect, useState } from 'react';
import { Address, getContract } from 'viem';
import { useAccount, usePublicClient } from 'wagmi';
import { isDemoMode } from '../../constants/common';
import { useFractal } from '../../providers/App/AppProvider';
import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI';
import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore';
import { GovernanceType } from '../../types';
import { getModeStatus } from './modes';
import useVotingStrategiesAddresses from './useVotingStrategiesAddresses';

export function useCanUserCreateProposal() {
Expand All @@ -27,6 +27,8 @@ export function useCanUserCreateProposal() {

const { getVotingStrategies } = useVotingStrategiesAddresses();

const isDemoMode = getModeStatus('DEMO');

/**
* Performs a check whether user has access rights to create proposal for DAO
* @param {string} safeAddress - parameter to verify that user can create proposal for this specific DAO.
Expand Down Expand Up @@ -117,13 +119,13 @@ export function useCanUserCreateProposal() {

useEffect(() => {
const loadCanUserCreateProposal = async () => {
const newCanCreateProposal = isDemoMode() || (await getCanUserCreateProposal());
const newCanCreateProposal = isDemoMode || (await getCanUserCreateProposal());
if (newCanCreateProposal !== canUserCreateProposal) {
setCanUserCreateProposal(newCanCreateProposal);
}
};
loadCanUserCreateProposal();
}, [getCanUserCreateProposal, canUserCreateProposal]);
}, [getCanUserCreateProposal, canUserCreateProposal, isDemoMode]);

return { canUserCreateProposal, getCanUserCreateProposal };
}

0 comments on commit 49e2afb

Please sign in to comment.