diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 11a99c710..6b8dc7623 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -1,6 +1,5 @@ -import { Box, Button, Flex, Icon, Text } from '@chakra-ui/react'; +import { Box, Button, Flex, Text } from '@chakra-ui/react'; import { abis } from '@fractal-framework/fractal-contracts'; -import { ArrowUpRight } from '@phosphor-icons/react'; import { format } from 'date-fns'; import { formatInTimeZone } from 'date-fns-tz'; import { useEffect, useMemo, useState } from 'react'; @@ -193,7 +192,7 @@ export function AzoriusProposalSummary({ proposal }: { proposal: AzoriusProposal alignItems="center" justifyContent="space-between" > - {format(eventDate, DEFAULT_DATE_TIME_FORMAT)} + {format(eventDate, DEFAULT_DATE_TIME_FORMAT)} diff --git a/src/components/Roles/forms/RoleFormPaymentStreams.tsx b/src/components/Roles/forms/RoleFormPaymentStreams.tsx index 397e9da7f..ea9a1b568 100644 --- a/src/components/Roles/forms/RoleFormPaymentStreams.tsx +++ b/src/components/Roles/forms/RoleFormPaymentStreams.tsx @@ -77,6 +77,25 @@ export function RoleFormPaymentStreams() { {({ push: pushPayment }: { push: (streamFormValue: SablierPaymentFormValues) => void }) => ( + {sortedPayments.length === 0 && ( )} - {!!sortedPayments.length && } diff --git a/src/components/ui/cards/DAONodeInfoCard.tsx b/src/components/ui/cards/DAONodeInfoCard.tsx index 508add2b9..52e3a1abe 100644 --- a/src/components/ui/cards/DAONodeInfoCard.tsx +++ b/src/components/ui/cards/DAONodeInfoCard.tsx @@ -96,7 +96,6 @@ export function DAONodeInfoCard(props: { > {/* Convert multisig badge casing here since it's already been cached to avoid another migration */} {type === 'MULTISIG' ? `${type[0]}${type.slice(1).toLocaleLowerCase()}` : type} - {type} ))} diff --git a/src/components/ui/links/DisplayAddress.tsx b/src/components/ui/links/DisplayAddress.tsx index bf67d84ad..152b318f8 100644 --- a/src/components/ui/links/DisplayAddress.tsx +++ b/src/components/ui/links/DisplayAddress.tsx @@ -1,5 +1,4 @@ -import { Flex, Text, Icon, LinkProps } from '@chakra-ui/react'; -import { ArrowUpRight } from '@phosphor-icons/react'; +import { Flex, Text, LinkProps } from '@chakra-ui/react'; import { ReactNode } from 'react'; import { Address } from 'viem'; import { useGetAccountName } from '../../../hooks/utils/useGetAccountName'; @@ -36,13 +35,6 @@ export function DisplayAddress({ > {children || displayAddress.displayName} - {!isTextLink && ( - - )} ); diff --git a/src/components/ui/links/DisplayTransaction.tsx b/src/components/ui/links/DisplayTransaction.tsx index f0c1ecb1c..27af60140 100644 --- a/src/components/ui/links/DisplayTransaction.tsx +++ b/src/components/ui/links/DisplayTransaction.tsx @@ -1,31 +1,18 @@ -import { Flex, Text, Icon } from '@chakra-ui/react'; -import { ArrowUpRight } from '@phosphor-icons/react'; +import { Flex, Text } from '@chakra-ui/react'; import { createAccountSubstring } from '../../../hooks/utils/useGetAccountName'; import EtherscanLink from './EtherscanLink'; -export default function DisplayTransaction({ - txHash, - isTextLink, -}: { - txHash: string; - isTextLink?: boolean; -}) { +export default function DisplayTransaction({ txHash }: { txHash: string }) { const displayName = createAccountSubstring(txHash); return ( {displayName} - {!isTextLink && ( - - )} ); diff --git a/src/components/ui/menus/CreateProposalMenu/index.tsx b/src/components/ui/menus/CreateProposalMenu/index.tsx index d13f7b2dc..4652fea29 100644 --- a/src/components/ui/menus/CreateProposalMenu/index.tsx +++ b/src/components/ui/menus/CreateProposalMenu/index.tsx @@ -1,24 +1,11 @@ -import { - Box, - Button, - Divider, - Flex, - Icon, - Menu, - MenuButton, - MenuItem, - MenuList, - Text, -} from '@chakra-ui/react'; +import { Button, Flex, Icon, Text } from '@chakra-ui/react'; import { CaretDown } from '@phosphor-icons/react'; -import { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { Address } from 'viem'; -import { NEUTRAL_2_82_TRANSPARENT } from '../../../../constants/common'; import { DAO_ROUTES } from '../../../../constants/routes'; import { useNetworkConfigStore } from '../../../../providers/NetworkConfig/useNetworkConfigStore'; -import { EaseOutComponent } from '../../utils/EaseOutComponent'; +import { OptionMenu } from '../OptionMenu'; export function CreateProposalMenu({ safeAddress }: { safeAddress: Address }) { const { t } = useTranslation('proposal'); @@ -28,102 +15,42 @@ export function CreateProposalMenu({ safeAddress }: { safeAddress: Address }) { const navigate = useNavigate(); return ( - - - {({ isOpen }) => ( - - - - {t('createProposal')} - - - - {isOpen && ( - - - - - - navigate(DAO_ROUTES.proposalNew.relative(addressPrefix, safeAddress)) - } - noOfLines={1} - display="flex" - alignItems="center" - justifyContent="flex-start" - rounded="0.75rem" - gap={2} - > - - {t('createFromScratch')} - - - - - - - navigate( - DAO_ROUTES.proposalTemplates.relative(addressPrefix, safeAddress), - ) - } - noOfLines={1} - display="flex" - alignItems="center" - justifyContent="flex-start" - rounded="0.75rem" - gap={2} - > - - {t('browseTemplates')} - - - - - - - )} - - )} - - + + {t('createProposal')} + + + } + options={[ + { + optionKey: t('createFromScratch'), + onClick: () => navigate(DAO_ROUTES.proposalNew.relative(addressPrefix, safeAddress)), + }, + { + optionKey: t('browseTemplates'), + onClick: () => + navigate(DAO_ROUTES.proposalTemplates.relative(addressPrefix, safeAddress)), + }, + ]} + namespace="proposal" + buttonAs={Button} + buttonProps={{ + variant: 'tertiary', + paddingX: '0.5rem', + paddingY: '0.25rem', + _hover: { bg: 'neutral-2' }, + _active: { + color: 'lilac-0', + bg: 'neutral-2', + }, + }} + /> ); } diff --git a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx index 440222d94..72a807406 100644 --- a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx +++ b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx @@ -148,9 +148,9 @@ export function ManageDAOMenu() { guard.userHasVotes ) { if (type === GovernanceType.MULTISIG) { - return [freezeOption, modifyGovernanceOption, settingsOption]; + return [settingsOption, freezeOption, modifyGovernanceOption]; } else { - return [freezeOption, settingsOption]; + return [settingsOption, freezeOption]; } } else if ( guard.freezeProposalCreatedTime !== null && @@ -163,20 +163,17 @@ export function ManageDAOMenu() { module => module.moduleType === FractalModuleType.FRACTAL, ); if (fractalModule) { - return [clawBackOption, settingsOption]; + return [settingsOption, clawBackOption]; } else { return [settingsOption]; } } else { - const optionsArr = []; - if (canUserCreateProposal) { - if (type === GovernanceType.MULTISIG) { - optionsArr.push(modifyGovernanceOption); - } - } - - optionsArr.push(settingsOption); - return optionsArr; + return [ + settingsOption, + ...(canUserCreateProposal && type === GovernanceType.MULTISIG + ? [modifyGovernanceOption] + : []), + ]; } }, [ guard, @@ -190,7 +187,26 @@ export function ManageDAOMenu() { canUserCreateProposal, ]); - return ( + return options.length === 1 ? ( + + } + onClick={options[0].onClick} + variant="tertiary" + p="0.25rem" + h="fit-content" + sx={{ + span: { + h: '1.25rem', + }, + }} + /> + ) : ( } - titleKey={canUserCreateProposal ? 'titleManageDAO' : 'titleViewDAODetails'} options={options} namespace="menu" buttonAs={IconButton} diff --git a/src/components/ui/menus/OptionMenu/OptionsList.tsx b/src/components/ui/menus/OptionMenu/OptionsList.tsx index 0c8112d8e..44017ae9c 100644 --- a/src/components/ui/menus/OptionMenu/OptionsList.tsx +++ b/src/components/ui/menus/OptionMenu/OptionsList.tsx @@ -12,7 +12,7 @@ export function OptionsList({ namespace, titleKey, }: IOptionsList) { - const { t } = useTranslation(namespace); + const { t } = useTranslation(namespace || 'menu'); const createHandleItemClick = (option: IOption) => (e: MouseEvent | ChangeEvent) => { e.stopPropagation(); @@ -32,41 +32,47 @@ export function OptionsList({ )} {options.map((option, i) => { const clickListener = createHandleItemClick(option); + return ( - - {showOptionSelected ? ( - - - {t(option.optionKey)} - - ) : ( - t(option.optionKey) - )} - {showOptionCount && {option.count}} - + {option.renderer ? ( + option.renderer() + ) : ( + + {showOptionSelected ? ( + + + {t(option.optionKey)} + + ) : ( + t(option.optionKey) + )} + {showOptionCount && {option.count}} + + )} + {i !== options.length - 1 && } ); diff --git a/src/components/ui/menus/OptionMenu/index.tsx b/src/components/ui/menus/OptionMenu/index.tsx index 7a0f78af9..66e5ad871 100644 --- a/src/components/ui/menus/OptionMenu/index.tsx +++ b/src/components/ui/menus/OptionMenu/index.tsx @@ -3,7 +3,6 @@ import { MouseEvent, ReactNode, RefObject } from 'react'; import { useTranslation } from 'react-i18next'; import { NEUTRAL_2_82_TRANSPARENT } from '../../../../constants/common'; import { DecentTooltip } from '../../DecentTooltip'; -import { EaseOutComponent } from '../../utils/EaseOutComponent'; import { OptionsList } from './OptionsList'; import { IOption, IOptionsList } from './types'; @@ -50,17 +49,15 @@ export function OptionMenu({ backdropFilter="auto" backdropBlur="10px" > - - {children} - - + {children} + ); diff --git a/src/components/ui/menus/OptionMenu/types.tsx b/src/components/ui/menus/OptionMenu/types.tsx index 629966bca..ca2581715 100644 --- a/src/components/ui/menus/OptionMenu/types.tsx +++ b/src/components/ui/menus/OptionMenu/types.tsx @@ -3,6 +3,7 @@ export interface IOption { count?: number; onClick: () => void; isSelected?: boolean; + renderer?: () => JSX.Element; } export interface IOptionsList { @@ -10,6 +11,6 @@ export interface IOptionsList { closeOnSelect?: boolean; showOptionCount?: boolean; showOptionSelected?: boolean; - namespace: string; + namespace?: string; titleKey?: string; } diff --git a/src/components/ui/menus/SafesMenu/SafeMenuItem.tsx b/src/components/ui/menus/SafesMenu/SafeMenuItem.tsx index 2b3f9c124..16e43f0c5 100644 --- a/src/components/ui/menus/SafesMenu/SafeMenuItem.tsx +++ b/src/components/ui/menus/SafesMenu/SafeMenuItem.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Flex, Image, MenuItem, Spacer, Text } from '@chakra-ui/react'; +import { Button, Flex, Image, MenuItem, Spacer, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { Address } from 'viem'; @@ -28,38 +28,36 @@ export function SafeMenuItem({ address, network, name }: SafeMenuItemProps) { }; return ( - - - - - - {name || t('loadingFavorite')} - - - - - - {/* Network Icon */} - - - + + + + + {name || t('loadingFavorite')} + + + + + + {/* Network Icon */} + + ); } diff --git a/src/components/ui/menus/SafesMenu/index.tsx b/src/components/ui/menus/SafesMenu/index.tsx index dc5f5d769..150abf884 100644 --- a/src/components/ui/menus/SafesMenu/index.tsx +++ b/src/components/ui/menus/SafesMenu/index.tsx @@ -1,22 +1,10 @@ -import { - Box, - Button, - Flex, - Hide, - Icon, - IconButton, - Menu, - MenuButton, - Show, - Text, - useDisclosure, -} from '@chakra-ui/react'; +import { Box, Button, Hide, Icon, IconButton, Show, Text, useDisclosure } from '@chakra-ui/react'; import { CaretDown, Star } from '@phosphor-icons/react'; -import { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; +import { useAccountFavorites } from '../../../../hooks/DAO/loaders/useFavorites'; import { AllSafesDrawer } from '../../../../pages/home/AllSafesDrawer'; -import { EaseOutComponent } from '../../utils/EaseOutComponent'; -import { SafesList } from './SafesList'; +import { OptionMenu } from '../OptionMenu'; +import { SafeMenuItem } from './SafeMenuItem'; export function SafesMenu() { const { t } = useTranslation('home'); @@ -26,6 +14,8 @@ export function SafesMenu() { onClose: onSafesDrawerClose, } = useDisclosure(); + const { favoritesList } = useAccountFavorites(); + return ( @@ -46,47 +36,48 @@ export function SafesMenu() { - - {({ isOpen }) => ( - - - - {t('mySafes')} - - - - {isOpen && ( - - - - )} - - )} - + + {t('mySafes')} + + + } + options={favoritesList.map(favorite => ({ + optionKey: `${favorite.networkPrefix}:${favorite.address}`, + onClick: () => {}, + renderer: () => ( + + ), + }))} + buttonAs={Button} + buttonProps={{ + variant: 'tertiary', + color: 'white-0', + _hover: { color: 'white-0', bg: 'neutral-3' }, + _active: { + color: 'white-0', + bg: 'neutral-3', + }, + paddingX: '0.75rem', + paddingY: '0.25rem', + }} + closeOnSelect={false} + showOptionSelected + showOptionCount + /> void }) { - const { governance, governanceContracts } = useFractal(); - const azoriusGovernance = governance as AzoriusGovernance; - const { data: walletClient } = useWalletClient(); - const publicClient = usePublicClient(); - const { address: account } = useAccount(); - const [userBalance, setUserBalance] = useState({ - value: '', - bigintValue: 0n, - }); - - const { loadERC20TokenAccountData } = useERC20LinearToken({ onMount: false }); - const [contractCall, pending] = useTransaction(); - const { - approved, - approveTransaction, - pending: approvalPending, - } = useApproval( - governanceContracts.underlyingTokenAddress, - azoriusGovernance.votesToken?.address, - userBalance.bigintValue, - ); - - const { t } = useTranslation(['modals', 'treasury']); - const { restrictChars } = useFormHelpers(); - - const getUserUnderlyingTokenBalance = useCallback(async () => { - if ( - !azoriusGovernance.votesToken?.decimals || - !azoriusGovernance.votesToken.underlyingTokenData || - !publicClient || - !account - ) - return; - const baseTokenContract = getContract({ - address: azoriusGovernance.votesToken.underlyingTokenData.address, - abi: erc20Abi, - client: publicClient, - }); - try { - const [balance, decimals] = await Promise.all([ - baseTokenContract.read.balanceOf([account]), - baseTokenContract.read.decimals(), - ]); - setUserBalance({ - value: formatCoin( - balance, - false, - decimals, - azoriusGovernance.votesToken?.underlyingTokenData?.symbol, - ), - bigintValue: balance, - }); - } catch (e) { - logError(e); - return; - } - }, [account, azoriusGovernance.votesToken, publicClient]); - - useEffect(() => { - getUserUnderlyingTokenBalance(); - }, [getUserUnderlyingTokenBalance]); - - const handleFormSubmit = useCallback( - (amount: BigIntValuePair) => { - const { votesTokenAddress } = governanceContracts; - if (!votesTokenAddress || !account || !walletClient) return; - - const wrapperTokenContract = getContract({ - abi: abis.VotesERC20Wrapper, - address: votesTokenAddress, - client: walletClient, - }); - - contractCall({ - contractFn: () => wrapperTokenContract.write.depositFor([account, amount.bigintValue!]), - pendingMessage: t('wrapTokenPendingMessage'), - failedMessage: t('wrapTokenFailedMessage'), - successMessage: t('wrapTokenSuccessMessage'), - successCallback: async () => { - await loadERC20TokenAccountData(); - }, - completedCallback: () => { - close(); - }, - }); - }, - [account, contractCall, governanceContracts, close, t, loadERC20TokenAccountData, walletClient], - ); - - // @dev next couple of lines are written like this, to keep typing equivalent during the conversion from BN to bigint - const userBalanceBigIntValue = userBalance.bigintValue; - const userBalanceBigIntValueIsZero = userBalanceBigIntValue - ? userBalanceBigIntValue === 0n - : undefined; - - if ( - !azoriusGovernance.votesToken?.decimals || - !azoriusGovernance.votesToken.underlyingTokenData || - userBalanceBigIntValueIsZero - ) { - return null; - } - - return ( - { - const { amount } = values; - handleFormSubmit(amount); - }} - validationSchema={Yup.object().shape({ - amount: Yup.object({ - value: Yup.string().required(), - bigintValue: Yup.mixed().required(), - }).test({ - name: 'Wrap Token Validation', - message: t('wrapTokenError'), - test: amount => { - const amountBN = amount.bigintValue as bigint; - if (!amount) return false; - - if (amountBN === 0n) return false; - if (userBalance.bigintValue! === 0n) return false; - if (amountBN > userBalance.bigintValue!) return false; - return true; - }, - }), - })} - > - {({ handleSubmit, values, setFieldValue, errors }: FormikProps) => { - return ( -
- - - - - - - setFieldValue('amount', valuePair)} - data-testid="wrapToken-amount" - onKeyDown={restrictChars} - maxValue={userBalance.bigintValue} - /> - - - {approved ? ( - - ) : ( - - )} - -
- ); - }} -
- ); -} diff --git a/src/components/ui/proposal/InfoRow.tsx b/src/components/ui/proposal/InfoRow.tsx index 2d459fd78..ae938674b 100644 --- a/src/components/ui/proposal/InfoRow.tsx +++ b/src/components/ui/proposal/InfoRow.tsx @@ -23,23 +23,13 @@ export default function InfoRow({ {tooltip === undefined ? ( txHash ? ( - + ) : ( {value} ) ) : ( - {txHash ? ( - - ) : ( - {value} - )} + {txHash ? : {value}} )}
diff --git a/src/hooks/DAO/loaders/useHatsTree.ts b/src/hooks/DAO/loaders/useHatsTree.ts index b87155a61..8e4ed5ff6 100644 --- a/src/hooks/DAO/loaders/useHatsTree.ts +++ b/src/hooks/DAO/loaders/useHatsTree.ts @@ -10,27 +10,23 @@ import { SablierV2LockupLinearAbi } from '../../../assets/abi/SablierV2LockupLin import { useFractal } from '../../../providers/App/AppProvider'; import useIPFSClient from '../../../providers/App/hooks/useIPFSClient'; import { useNetworkConfigStore } from '../../../providers/NetworkConfig/useNetworkConfigStore'; -import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore'; import { DecentHatsError } from '../../../store/roles/rolesStoreUtils'; import { useRolesStore } from '../../../store/roles/useRolesStore'; import { SablierPayment } from '../../../types/roles'; import { convertStreamIdToBigInt } from '../../streams/useCreateSablierStream'; import { CacheExpiry, CacheKeys } from '../../utils/cache/cacheDefaults'; import { getValue, setValue } from '../../utils/cache/useLocalStorage'; -import { useParseSafeAddress } from '../useParseSafeAddress'; const hatsSubgraphClient = new HatsSubgraphClient({}); const useHatsTree = () => { const { t } = useTranslation('roles'); - const { safeAddress } = useParseSafeAddress(); const { governanceContracts: { linearVotingErc20WithHatsWhitelistingAddress, linearVotingErc721WithHatsWhitelistingAddress, }, } = useFractal(); - const { safe } = useDaoInfoStore(); const { hatsTreeId, contextChainId, @@ -297,10 +293,10 @@ const useHatsTree = () => { }, [hatsTree, updateRolesWithStreams, getPaymentStreams, streamsFetched]); useEffect(() => { - if (safeAddress && safe?.address && safeAddress !== safe.address && hatsTree) { + if (!hatsTreeId && !!hatsTree) { resetHatsStore(); } - }, [resetHatsStore, safeAddress, safe?.address, hatsTree]); + }, [resetHatsStore, hatsTree, hatsTreeId]); }; export { useHatsTree }; diff --git a/src/hooks/DAO/useKeyValuePairs.ts b/src/hooks/DAO/useKeyValuePairs.ts index dd313a38a..87f7e93c4 100644 --- a/src/hooks/DAO/useKeyValuePairs.ts +++ b/src/hooks/DAO/useKeyValuePairs.ts @@ -1,7 +1,6 @@ import { abis } from '@fractal-framework/fractal-contracts'; import { hatIdToTreeId } from '@hatsprotocol/sdk-v1-core'; import { useEffect } from 'react'; -import { useSearchParams } from 'react-router-dom'; import { Address, GetContractEventsReturnType, getContract } from 'viem'; import { usePublicClient } from 'wagmi'; import { logError } from '../../helpers/errorLogging'; @@ -99,14 +98,11 @@ const useKeyValuePairs = () => { contracts: { keyValuePairs, sablierV2LockupLinear }, } = useNetworkConfigStore(); const { setHatKeyValuePairData } = useRolesStore(); - const [searchParams] = useSearchParams(); const safeAddress = node.safe?.address; useEffect(() => { - const safeParam = searchParams.get('dao'); - - if (!publicClient || !safeAddress || safeAddress !== safeParam?.split(':')[1]) { + if (!safeAddress || !publicClient) { return; } @@ -160,7 +156,6 @@ const useKeyValuePairs = () => { keyValuePairs, safeAddress, publicClient, - searchParams, setHatKeyValuePairData, sablierV2LockupLinear, ]); diff --git a/src/hooks/utils/useCreateRoles.ts b/src/hooks/utils/useCreateRoles.ts index 6678f0398..6490f8349 100644 --- a/src/hooks/utils/useCreateRoles.ts +++ b/src/hooks/utils/useCreateRoles.ts @@ -915,6 +915,9 @@ export default function useCreateRoles() { throw new Error('Cannot prepare transactions for edited role without smart address'); } const newPredictedHatSmartAccount = await predictSmartAccount(BigInt(formHat.id)); + if (!newPredictedHatSmartAccount) { + throw new Error('Cannot predict smart account'); + } const newStreamTxData = createBatchLinearStreamCreationTx( newStreamsOnHat.map(stream => ({ ...stream, recipient: newPredictedHatSmartAccount })), ); diff --git a/src/i18n/locales/en/menu.json b/src/i18n/locales/en/menu.json index c7dfaa113..253f61f7e 100644 --- a/src/i18n/locales/en/menu.json +++ b/src/i18n/locales/en/menu.json @@ -3,8 +3,6 @@ "connectWallet": "Connect Wallet", "disconnect": "Disconnect", "wallet": "Wallet", - "titleManageDAO": "Manage Safe", - "titleViewDAODetails": "View Safe Details", "titleManageProposalTemplate": "Manage Template", "optionCreateSubDAO": "Create SubDAO", "optionInitiateFreeze": "Initiate a Freeze", diff --git a/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx b/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx index 718c4d2e4..5841d80f4 100644 --- a/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx +++ b/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx @@ -44,7 +44,12 @@ export function SafeGovernanceSettingsPage() { > {(isERC20Governance || isERC721Governance) && ( - {t('daoSettingsGovernance')} + + {t('daoSettingsGovernance')} + )} diff --git a/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx b/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx index cc4ad181d..bb331a6a2 100644 --- a/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx +++ b/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx @@ -11,6 +11,7 @@ import { BarLoader } from '../../../../components/ui/loaders/BarLoader'; import { ModalType } from '../../../../components/ui/modals/ModalProvider'; import { useDecentModal } from '../../../../components/ui/modals/useDecentModal'; import NestedPageHeader from '../../../../components/ui/page/Header/NestedPageHeader'; +import Divider from '../../../../components/ui/utils/Divider'; import { NEUTRAL_2_84 } from '../../../../constants/common'; import { DAO_ROUTES } from '../../../../constants/routes'; import { useCanUserCreateProposal } from '../../../../hooks/utils/useCanUserSubmitProposal'; @@ -88,17 +89,6 @@ export function SafePermissionsSettingsPage() { display="flex" bg={{ base: 'transparent', md: NEUTRAL_2_84 }} > - {canUserCreateProposal && ( - - )} {!isLoaded ? ( )} + + {canUserCreateProposal && ( + + + + + )} diff --git a/src/store/roles/rolesStoreUtils.ts b/src/store/roles/rolesStoreUtils.ts index 5db97f48a..166bec4b6 100644 --- a/src/store/roles/rolesStoreUtils.ts +++ b/src/store/roles/rolesStoreUtils.ts @@ -134,7 +134,7 @@ export const predictAccountAddress = async (params: { tokenId, ]); if (!(await publicClient.getBytecode({ address: predictedAddress }))) { - throw new DecentHatsError('Predicted address is not a contract'); + return; } return predictedAddress; }; @@ -288,6 +288,10 @@ export const sanitize = async ( publicClient, }); + if (!topHatSmartAddress) { + throw new DecentHatsError('Top Hat smart address is not valid'); + } + const whitelistingVotingContract = whitelistingVotingStrategy ? getContract({ abi: abis.LinearERC20VotingWithHatsProposalCreation, @@ -320,6 +324,9 @@ export const sanitize = async ( tokenId: BigInt(rawAdminHat.id), publicClient, }); + if (!adminHatSmartAddress) { + throw new DecentHatsError('Admin Hat smart address is not valid'); + } const adminHat: DecentAdminHat = { id: rawAdminHat.id,