Skip to content
This repository was archived by the owner on Nov 10, 2023. It is now read-only.
43 changes: 27 additions & 16 deletions src/components/AppLayout/Sidebar/useSidebarItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,44 @@ import { useRouteMatch } from 'react-router-dom'
import { ListItemType } from 'src/components/List'
import ListIcon from 'src/components/List/ListIcon'
import { SAFELIST_ADDRESS } from 'src/routes/routes'
import { FEATURES } from 'src/config/networks/network.d'
import { useSelector } from 'react-redux'
import { safeFeaturesEnabledSelector } from 'src/logic/safe/store/selectors'

const useSidebarItems = (): ListItemType[] => {
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS))
const matchSafe = useRouteMatch({ path: `${SAFELIST_ADDRESS}`, strict: false })
const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` })
const matchSafeWithAction = useRouteMatch({ path: `${SAFELIST_ADDRESS}/:safeAddress/:safeAction` }) as {
url: string
params: Record<string, string>
}

const sidebarItems = useMemo((): ListItemType[] => {
return useMemo((): ListItemType[] => {
if (!matchSafe || !matchSafeWithAddress) {
return []
}

const settingsItem = {
label: 'Settings',
icon: <ListIcon type="settings" />,
selected: matchSafeWithAction?.params.safeAction === 'settings',
href: `${matchSafeWithAddress?.url}/settings`,
}

const safeSidebar = safeAppsEnabled
? [
{
label: 'Apps',
icon: <ListIcon type="apps" />,
selected: matchSafeWithAction?.params.safeAction === 'apps',
href: `${matchSafeWithAddress?.url}/apps`,
},
settingsItem,
]
: [settingsItem]

return [
{
label: 'ASSETS',
Expand All @@ -37,22 +61,9 @@ const useSidebarItems = (): ListItemType[] => {
selected: matchSafeWithAction?.params.safeAction === 'address-book',
href: `${matchSafeWithAddress?.url}/address-book`,
},
{
label: 'Apps',
icon: <ListIcon type="apps" />,
selected: matchSafeWithAction?.params.safeAction === 'apps',
href: `${matchSafeWithAddress?.url}/apps`,
},
{
label: 'Settings',
icon: <ListIcon type="settings" />,
selected: matchSafeWithAction?.params.safeAction === 'settings',
href: `${matchSafeWithAddress?.url}/settings`,
},
...safeSidebar,
]
}, [matchSafe, matchSafeWithAction, matchSafeWithAddress])

return sidebarItems
}, [matchSafe, matchSafeWithAction, matchSafeWithAddress, safeAppsEnabled])
}

export { useSidebarItems }
8 changes: 4 additions & 4 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const getCurrentEnvironment = (): string => {

type NetworkSpecificConfiguration = EnvironmentSettings & {
network: NetworkSettings,
features?: SafeFeatures,
disabledFeatures?: SafeFeatures,
}

const configuration = (): NetworkSpecificConfiguration => {
Expand All @@ -37,7 +37,7 @@ const configuration = (): NetworkSpecificConfiguration => {
return {
...configFile.environment.production,
network: configFile.network,
features: configFile.features,
disabledFeatures: configFile.disabledFeatures,
}
}

Expand All @@ -49,7 +49,7 @@ const configuration = (): NetworkSpecificConfiguration => {
return {
...networkBaseConfig,
network: configFile.network,
features: configFile.features,
disabledFeatures: configFile.disabledFeatures,
}
}

Expand Down Expand Up @@ -77,7 +77,7 @@ export const getNetworkExplorerInfo = (): { name: string; url: string; apiUrl: s
apiUrl: getConfig()?.networkExplorerApiUrl,
})

export const getNetworkConfigFeatures = (): SafeFeatures | undefined => getConfig()?.features
export const getNetworkConfigDisabledFeatures = (): SafeFeatures => getConfig()?.disabledFeatures || []

export const getNetworkInfo = (): NetworkSettings => getConfig()?.network

Expand Down
16 changes: 10 additions & 6 deletions src/config/networks/network.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
// matches src/logic/tokens/store/model/token.ts `TokenProps` type

export enum FEATURES {
ERC721 = 'ERC721',
ERC1155 = 'ERC1155',
SAFE_APPS = 'SAFE_APPS',
CONTRACT_INTERACTION = 'CONTRACT_INTERACTION'
}

type Token = {
address: string
name: string
Expand Down Expand Up @@ -34,11 +42,7 @@ export type NetworkSettings = {
// something around this to display or not some critical sections in the app, depending on the network support
// I listed the ones that may conflict with the network.
// If non is present, all the sections are available.
export type SafeFeatures = {
safeApps?: boolean,
collectibles?: boolean,
contractInteraction?: boolean
}
export type SafeFeatures = FEATURES[]

type GasPrice = {
gasPrice: number
Expand Down Expand Up @@ -68,6 +72,6 @@ type SafeEnvironments = {

export interface NetworkConfig {
network: NetworkSettings
features?: SafeFeatures
disabledFeatures?: SafeFeatures
environment: SafeEnvironments
}
3 changes: 3 additions & 0 deletions src/logic/safe/store/actions/fetchSafe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch
modules: buildModulesLinkedList(modules?.array, modules?.next),
nonce: Number(remoteNonce),
threshold: Number(remoteThreshold),
featuresEnabled: localSafe?.currentVersion
? enabledFeatures(localSafe?.currentVersion)
: localSafe?.featuresEnabled,
}),
)

Expand Down
3 changes: 2 additions & 1 deletion src/logic/safe/store/models/safe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { List, Map, Record, RecordOf, Set } from 'immutable'
import { FEATURES } from 'src/config/networks/network.d'

export type SafeOwner = {
name: string
Expand All @@ -24,7 +25,7 @@ export type SafeRecordProps = {
recurringUser?: boolean
currentVersion: string
needsUpdate: boolean
featuresEnabled: Array<string>
featuresEnabled: Array<FEATURES>
}

const makeSafe = Record<SafeRecordProps>({
Expand Down
29 changes: 22 additions & 7 deletions src/logic/safe/utils/safeVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d'

import { getGnosisSafeInstanceAt, getSafeMasterContract } from 'src/logic/contracts/safeContracts'
import { LATEST_SAFE_VERSION } from 'src/utils/constants'
import { getNetworkConfigDisabledFeatures } from 'src/config'
import { FEATURES } from 'src/config/networks/network.d'

export const FEATURES = [
{ name: 'ERC721', validVersion: '>=1.1.1' },
{ name: 'ERC1155', validVersion: '>=1.1.1' },
type FeatureConfigByVersion = {
name: FEATURES
validVersion?: string
}

const FEATURES_BY_VERSION: FeatureConfigByVersion[] = [
{ name: FEATURES.ERC721, validVersion: '>=1.1.1' },
{ name: FEATURES.ERC1155, validVersion: '>=1.1.1' },
{ name: FEATURES.SAFE_APPS },
{ name: FEATURES.CONTRACT_INTERACTION },
]

type Feature = typeof FEATURES[number]
type Feature = typeof FEATURES_BY_VERSION[number]

export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string): boolean => {
if (!currentVersion || !latestVersion) {
Expand All @@ -27,13 +36,19 @@ export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string)
export const getCurrentSafeVersion = (gnosisSafeInstance: GnosisSafe): Promise<string> =>
gnosisSafeInstance.methods.VERSION().call()

export const enabledFeatures = (version: string): string[] =>
FEATURES.reduce((acc: string[], feature: Feature) => {
if (semverSatisfies(version, feature.validVersion)) {
const checkFeatureEnabledByVersion = (featureConfig: FeatureConfigByVersion, version: string) => {
return featureConfig.validVersion ? semverSatisfies(version, featureConfig.validVersion) : true
}

export const enabledFeatures = (version: string): FEATURES[] => {
const disabledFeatures = getNetworkConfigDisabledFeatures()
return FEATURES_BY_VERSION.reduce((acc: FEATURES[], feature: Feature) => {
if (!disabledFeatures.includes(feature.name) && checkFeatureEnabledByVersion(feature, version)) {
acc.push(feature.name)
}
return acc
}, [])
}

interface SafeVersionInfo {
current: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ContractInteractionIcon from 'src/routes/safe/components/Transactions/Txs

import Collectible from '../assets/collectibles.svg'
import Token from '../assets/token.svg'
import { FEATURES } from 'src/config/networks/network.d'

type ActiveScreen = 'sendFunds' | 'sendCollectible' | 'contractInteraction'

Expand All @@ -29,7 +30,8 @@ interface ChooseTxTypeProps {
const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTypeProps): React.ReactElement => {
const classes = useStyles()
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
const erc721Enabled = featuresEnabled?.includes('ERC721')
const erc721Enabled = featuresEnabled?.includes(FEATURES.ERC721)
const contractInteractionEnabled = featuresEnabled?.includes(FEATURES.CONTRACT_INTERACTION)
const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress)

React.useEffect(() => {
Expand Down Expand Up @@ -99,22 +101,24 @@ const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTy
Send collectible
</Button>
)}
<Button
color="primary"
disabled={disableContractInteraction}
minHeight={52}
minWidth={260}
onClick={() => setActiveScreen('contractInteraction')}
variant="outlined"
testId="modal-contract-interaction-btn"
>
<Img
alt="Contract Interaction"
className={classNames(classes.leftIcon, classes.iconSmall)}
src={ContractInteractionIcon}
/>
Contract Interaction
</Button>
{contractInteractionEnabled && (
<Button
color="primary"
disabled={disableContractInteraction}
minHeight={52}
minWidth={260}
onClick={() => setActiveScreen('contractInteraction')}
variant="outlined"
testId="modal-contract-interaction-btn"
>
<Img
alt="Contract Interaction"
className={classNames(classes.leftIcon, classes.iconSmall)}
src={ContractInteractionIcon}
/>
Contract Interaction
</Button>
)}
</Col>
</Row>
</>
Expand Down
7 changes: 4 additions & 3 deletions src/routes/safe/components/Balances/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import SendModal from 'src/routes/safe/components/Balances/SendModal'
import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown'
import {
safeFeaturesEnabledSelector,
safeParamAddressFromStateSelector,
safeNameSelector,
safeParamAddressFromStateSelector,
} from 'src/logic/safe/store/selectors'

import { wrapInSuspense } from 'src/utils/wrapInSuspense'
import { useFetchTokens } from 'src/logic/safe/hooks/useFetchTokens'
import { Route, Switch, NavLink, Redirect } from 'react-router-dom'
import { NavLink, Redirect, Route, Switch } from 'react-router-dom'
import { FEATURES } from 'src/config/networks/network.d'

const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles'))
const Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins'))
Expand Down Expand Up @@ -58,7 +59,7 @@ const Balances = (): React.ReactElement => {
useFetchTokens(address as string)

useEffect(() => {
const erc721Enabled = Boolean(featuresEnabled?.includes('ERC721'))
const erc721Enabled = Boolean(featuresEnabled?.includes(FEATURES.ERC721))

setState((prevState) => ({
...prevState,
Expand Down
17 changes: 15 additions & 2 deletions src/routes/safe/container/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { GenericModal } from '@gnosis.pm/safe-react-components'

import NoSafe from 'src/components/NoSafe'
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { wrapInSuspense } from 'src/utils/wrapInSuspense'
import { SAFELIST_ADDRESS } from 'src/routes/routes'
import { FEATURES } from 'src/config/networks/network.d'

export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn'
export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
Expand All @@ -34,7 +35,9 @@ const Container = (): React.ReactElement => {

const safeAddress = useSelector(safeParamAddressFromStateSelector)
const provider = useSelector(providerNameSelector)
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` })
const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS))

if (!safeAddress) {
return <NoSafe provider={provider} text="Safe not found" />
Expand Down Expand Up @@ -67,7 +70,17 @@ const Container = (): React.ReactElement => {
path={`${matchSafeWithAddress?.path}/transactions`}
render={() => wrapInSuspense(<TxsTable />, null)}
/>
<Route exact path={`${matchSafeWithAddress?.path}/apps`} render={() => wrapInSuspense(<Apps />, null)} />
<Route
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove the <Route in case safe-apps are disabled.
{safeAppsEnabled && <Route...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nevermind, I understand why you did this!

exact
path={`${matchSafeWithAddress?.path}/apps`}
render={({ history }) => {
if (!safeAppsEnabled) {
history.push(`${matchSafeWithAddress?.url}/balances`)
}
return wrapInSuspense(<Apps />, null)
}}
/>

<Route
exact
path={`${matchSafeWithAddress?.path}/settings`}
Expand Down