diff --git a/src/components/DAOTreasury/components/Transactions.tsx b/src/components/DAOTreasury/components/Transactions.tsx index 5d70c4c21..5f57a8c19 100644 --- a/src/components/DAOTreasury/components/Transactions.tsx +++ b/src/components/DAOTreasury/components/Transactions.tsx @@ -188,9 +188,8 @@ export function PaginationCount({ shownTransactions }: { shownTransactions: numb type="address" value={safe.address} p={0} + isTextLink textStyle="labels-large" - outline="unset" - outlineOffset="unset" borderWidth={0} > {t('transactionsTotalCount', { count: totalTransfers })} diff --git a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx index 37f6509dd..361ef3fdc 100644 --- a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx @@ -36,11 +36,11 @@ function DayStepperInput({ - {t('days', { ns: 'common' })} onInputChange(Number(val))} /> diff --git a/src/components/DaoCreator/formComponents/Multisig.tsx b/src/components/DaoCreator/formComponents/Multisig.tsx index c06f88ce8..668cf19e4 100644 --- a/src/components/DaoCreator/formComponents/Multisig.tsx +++ b/src/components/DaoCreator/formComponents/Multisig.tsx @@ -142,11 +142,12 @@ export function Multisig(props: ICreationStepProps) { } isRequired > - {/* @todo replace with stepper input */} - validateNumber(value, 'multisig.signatureThreshold')} - value={values.multisig.signatureThreshold} - /> + + validateNumber(value, 'multisig.signatureThreshold')} + value={values.multisig.signatureThreshold} + /> + diff --git a/src/components/Roles/RolePaymentDetails.tsx b/src/components/Roles/RolePaymentDetails.tsx index 923a85f1b..7c17c37ee 100644 --- a/src/components/Roles/RolePaymentDetails.tsx +++ b/src/components/Roles/RolePaymentDetails.tsx @@ -319,8 +319,12 @@ export function RolePaymentDetails({ mx={4} > - + diff --git a/src/components/SafeSettings/ERC20TokenContainer.tsx b/src/components/SafeSettings/ERC20TokenContainer.tsx index aab5f5ad5..4cb1d153e 100644 --- a/src/components/SafeSettings/ERC20TokenContainer.tsx +++ b/src/components/SafeSettings/ERC20TokenContainer.tsx @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next'; import { useFractal } from '../../providers/App/AppProvider'; import { AzoriusGovernance } from '../../types'; import { formatCoin } from '../../utils'; -import { StyledBox } from '../ui/containers/StyledBox'; import { DisplayAddress } from '../ui/links/DisplayAddress'; import { BarLoader } from '../ui/loaders/BarLoader'; @@ -15,13 +14,17 @@ export function ERC20TokenContainer() { const { votesToken } = azoriusGovernance; return ( - + {t('governanceTokenTitle')} {votesToken ? ( {/* TOKEN NAME */} @@ -76,6 +79,6 @@ export function ERC20TokenContainer() { )} - + ); } diff --git a/src/components/SafeSettings/ERC721TokensContainer.tsx b/src/components/SafeSettings/ERC721TokensContainer.tsx index 0ea09dad3..289f99df5 100644 --- a/src/components/SafeSettings/ERC721TokensContainer.tsx +++ b/src/components/SafeSettings/ERC721TokensContainer.tsx @@ -1,8 +1,7 @@ -import { Flex, Grid, GridItem, Text } from '@chakra-ui/react'; +import { Box, Flex, Grid, GridItem, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useFractal } from '../../providers/App/AppProvider'; import { AzoriusGovernance } from '../../types'; -import { StyledBox } from '../ui/containers/StyledBox'; import { DisplayAddress } from '../ui/links/DisplayAddress'; import { BarLoader } from '../ui/loaders/BarLoader'; @@ -14,7 +13,13 @@ export function ERC721TokensContainer() { const { erc721Tokens } = azoriusGovernance; return ( - + {t('governanceERC721TokenTitle')} {erc721Tokens ? ( @@ -97,6 +102,6 @@ export function ERC721TokensContainer() { )} - + ); } diff --git a/src/components/SafeSettings/SettingsContentBox.tsx b/src/components/SafeSettings/SettingsContentBox.tsx index 7ccb6c09b..d6e58f2f7 100644 --- a/src/components/SafeSettings/SettingsContentBox.tsx +++ b/src/components/SafeSettings/SettingsContentBox.tsx @@ -1,6 +1,5 @@ import { BoxProps } from '@chakra-ui/react'; import { PropsWithChildren } from 'react'; -import { NEUTRAL_2_84 } from '../../constants/common'; import { StyledBox } from '../ui/containers/StyledBox'; export function SettingsContentBox({ children, ...props }: PropsWithChildren) { @@ -9,7 +8,6 @@ export function SettingsContentBox({ children, ...props }: PropsWithChildren {children} diff --git a/src/components/SafeSettings/Signers/SignersContainer.tsx b/src/components/SafeSettings/Signers/SignersContainer.tsx index f6cca9046..460556a7f 100644 --- a/src/components/SafeSettings/Signers/SignersContainer.tsx +++ b/src/components/SafeSettings/Signers/SignersContainer.tsx @@ -1,11 +1,10 @@ -import { Button, Flex, Hide, HStack, Icon, Show, Text } from '@chakra-ui/react'; +import { Box, Button, Flex, Hide, HStack, Icon, Show, Text } from '@chakra-ui/react'; import { MinusCircle, PlusCircle } from '@phosphor-icons/react'; import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Address, getAddress } from 'viem'; import { useAccount } from 'wagmi'; import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore'; -import { StyledBox } from '../../ui/containers/StyledBox'; import { DisplayAddress } from '../../ui/links/DisplayAddress'; import { ModalType } from '../../ui/modals/ModalProvider'; import { useDecentModal } from '../../ui/modals/useDecentModal'; @@ -102,7 +101,7 @@ export function SignersContainer() { }, [account, signers]); return ( - + {t('signers', { ns: 'common' })} {userIsSigner && ( @@ -131,6 +130,6 @@ export function SignersContainer() { threshold={safe?.threshold} /> ))} - + ); } diff --git a/src/components/ui/forms/NumberStepperInput.tsx b/src/components/ui/forms/NumberStepperInput.tsx index 169f767ae..c9b2b5bbd 100644 --- a/src/components/ui/forms/NumberStepperInput.tsx +++ b/src/components/ui/forms/NumberStepperInput.tsx @@ -1,6 +1,8 @@ import { Button, HStack, + InputGroup, + InputRightElement, NumberDecrementStepper, NumberIncrementStepper, NumberInput, @@ -11,9 +13,11 @@ import { Plus, Minus } from '@phosphor-icons/react'; export function NumberStepperInput({ value, onChange, + unitHint, }: { value?: string | number; onChange: (val: string) => void; + unitHint?: string; }) { const stepperButton = (direction: 'inc' | 'dec') => ( } - options={favoritesList.map(favorite => ({ - optionKey: `${favorite.networkPrefix}:${favorite.address}`, - onClick: () => {}, - renderer: () => ( - - ), - }))} + options={ + !favoritesList.length + ? [ + { + optionKey: 'empty-favorites', + onClick: () => {}, + renderer: () => ( + {t('emptyFavorites', { ns: 'dashboard' })} + ), + }, + ] + : favoritesList.map(favorite => ({ + optionKey: `${favorite.networkPrefix}:${favorite.address}`, + onClick: () => {}, + renderer: () => ( + + ), + })) + } buttonAs={Button} buttonProps={{ variant: 'tertiary', diff --git a/src/hooks/DAO/loaders/useDecentTreasury.ts b/src/hooks/DAO/loaders/useDecentTreasury.ts index 5fe543476..2c59c919e 100644 --- a/src/hooks/DAO/loaders/useDecentTreasury.ts +++ b/src/hooks/DAO/loaders/useDecentTreasury.ts @@ -22,6 +22,8 @@ import { } from '../../../types'; import { formatCoin } from '../../../utils'; import { MOCK_MORALIS_ETH_ADDRESS } from '../../../utils/address'; +import { CacheExpiry, CacheKeys } from '../../utils/cache/cacheDefaults'; +import { setValue } from '../../utils/cache/useLocalStorage'; export const useDecentTreasury = () => { // tracks the current valid DAO address / chain; helps prevent unnecessary calls @@ -168,12 +170,14 @@ export const useDecentTreasury = () => { // make unique .filter((value, index, self) => self.indexOf(value) === index) // turn them into Address type - .map(address => getAddress(address)); + .map(getAddress); const transfersTokenInfo = await Promise.all( tokenAddressesOfTransfers.map(async address => { + let tokenInfo: TokenInfoResponse; + try { - return await safeAPI.getToken(address); + tokenInfo = await safeAPI.getToken(address); } catch (e) { const fallbackTokenData = tokenBalances?.find( tokenBalanceData => getAddress(tokenBalanceData.tokenAddress) === address, @@ -198,7 +202,7 @@ export const useDecentTreasury = () => { }; } - return { + tokenInfo = { address, name: fallbackTokenData.name, symbol: fallbackTokenData.symbol, @@ -206,6 +210,13 @@ export const useDecentTreasury = () => { logoUri: fallbackTokenData.logo, }; } + + setValue( + { cacheName: CacheKeys.TOKEN_INFO, tokenAddress: address }, + tokenInfo, + CacheExpiry.NEVER, + ); + return tokenInfo; }), ); diff --git a/src/hooks/DAO/loaders/useHatsTree.ts b/src/hooks/DAO/loaders/useHatsTree.ts index 0dbe4d0e2..4e81b64c2 100644 --- a/src/hooks/DAO/loaders/useHatsTree.ts +++ b/src/hooks/DAO/loaders/useHatsTree.ts @@ -204,6 +204,12 @@ const useHatsTree = () => { ? secondsTimestampToDate(lockupLinearStream.cliffTime) : undefined; + const logo = + getValue({ + cacheName: CacheKeys.TOKEN_INFO, + tokenAddress: getAddress(lockupLinearStream.asset.address), + })?.logoUri || ''; + return { streamId: lockupLinearStream.id, contractAddress: lockupLinearStream.contract.address, @@ -213,7 +219,7 @@ const useHatsTree = () => { name: lockupLinearStream.asset.name, symbol: lockupLinearStream.asset.symbol, decimals: lockupLinearStream.asset.decimals, - logo: '', // @todo - how do we get logo? + logo, }, amount: { bigintValue: BigInt(lockupLinearStream.depositAmount), diff --git a/src/hooks/utils/cache/cacheDefaults.ts b/src/hooks/utils/cache/cacheDefaults.ts index fb98a8cb3..13b98cee9 100644 --- a/src/hooks/utils/cache/cacheDefaults.ts +++ b/src/hooks/utils/cache/cacheDefaults.ts @@ -1,3 +1,4 @@ +import { TokenInfoResponse } from '@safe-global/api-kit'; import { Address } from 'viem'; import { AzoriusProposal, DaoHierarchyInfo } from '../../../types'; @@ -34,52 +35,60 @@ export enum CacheKeys { MIGRATION = 'Migration', IPFS_HASH = 'IPFS Hash', HIERARCHY_DAO_INFO = 'Hierarchy DAO Info', + TOKEN_INFO = 'Token Info', // indexDB keys DECODED_TRANSACTION_PREFIX = 'decode_trans_', MULTISIG_METADATA_PREFIX = 'm_m_', } -export type CacheKey = { +type CacheKey = { cacheName: CacheKeys; version: number; }; -export interface FavoritesCacheKey extends CacheKey { +interface FavoritesCacheKey extends CacheKey { cacheName: CacheKeys.FAVORITES; } -export interface MasterCacheKey extends CacheKey { +interface MasterCacheKey extends CacheKey { cacheName: CacheKeys.MASTER_COPY; chainId: number; proxyAddress: Address; moduleProxyFactoryAddress: Address; } -export interface ProposalCacheKey extends CacheKey { +interface ProposalCacheKey extends CacheKey { cacheName: CacheKeys.PROPOSAL_CACHE; proposalId: string; contractAddress: Address; } -export interface AverageBlockTimeCacheKey extends CacheKey { +interface AverageBlockTimeCacheKey extends CacheKey { cacheName: CacheKeys.AVERAGE_BLOCK_TIME; chainId: number; } -export interface IPFSHashCacheKey extends CacheKey { +interface IPFSHashCacheKey extends CacheKey { cacheName: CacheKeys.IPFS_HASH; hash: string; chainId: number; } -export interface HierarchyDAOInfoCacheKey extends CacheKey { +interface HierarchyDAOInfoCacheKey extends CacheKey { cacheName: CacheKeys.HIERARCHY_DAO_INFO; chainId: number; daoAddress: Address; } +interface TokenInfoCacheKey extends CacheKey { + cacheName: CacheKeys.TOKEN_INFO; + chainId: number; + tokenAddress: Address; +} + export type CacheKeyType = | FavoritesCacheKey | HierarchyDAOInfoCacheKey + | TokenInfoCacheKey | MasterCacheKey | ProposalCacheKey | AverageBlockTimeCacheKey @@ -105,6 +114,7 @@ type CacheKeyToValueMap = { [CacheKeys.MIGRATION]: number; [CacheKeys.IPFS_HASH]: string; [CacheKeys.HIERARCHY_DAO_INFO]: DaoHierarchyInfo; + [CacheKeys.TOKEN_INFO]: TokenInfoResponse; }; export type CacheValueType = T extends { cacheName: infer U } @@ -123,6 +133,7 @@ export const CACHE_VERSIONS: { [key: string]: number } = Object.freeze({ [CacheKeys.PROPOSAL_CACHE]: 1, [CacheKeys.AVERAGE_BLOCK_TIME]: 1, [CacheKeys.HIERARCHY_DAO_INFO]: 1, + [CacheKeys.TOKEN_INFO]: 1, }); /** diff --git a/src/i18n/locales/en/daoCreate.json b/src/i18n/locales/en/daoCreate.json index 372560c0f..7752f85fe 100644 --- a/src/i18n/locales/en/daoCreate.json +++ b/src/i18n/locales/en/daoCreate.json @@ -1,10 +1,10 @@ { "addNFTButton": "Import another token", "errorMinSigners": "Number of owners must be greater than 0.", - "errorLowSignerThreshold": "Threshold must be greater than 0", + "errorLowSignerThreshold": "Threshold must be greater than 0.", "errorHighSignerThreshold": "Threshold must be less than number of owners.", "labelSigThreshold": "Threshold", - "helperSigThreshold": "The number of owners required to approve a transaction on this multisig", + "helperSigThreshold": "The number of owners required to approve a transaction on this multisig.", "titleSignerAddresses": "Owners", "subTitleSignerAddresses": "These addresses will be able to approve or reject transactions on this multisig.", "titleGetStarted": "Get Started", @@ -19,11 +19,11 @@ "nftDetailsToken": "Token", "nftDetailsWeight": "Weight", "n/a": "n/a", - "labelConnectWallet": "To deploy a new Decent Safe", - "titleFundingOptions": "Go to parent Safe", + "labelConnectWallet": "To deploy a new DAO", + "titleFundingOptions": "Go to parent DAO", "labelSelectNFT": "Select NFT", "labelNFTAddress": "NFT Address", - "helperNFTAddress": "Import an existing ERC-721 token", + "helperNFTAddress": "Import an existing ERC-721 token.", "labelNFTWeight": "Weight", "helperNFTWeight": "How many votes is this token worth?", "labelVotingPeriod": "Voting Period", @@ -40,19 +40,19 @@ "labelTimelockPeriod": "Timelock Period", "helperTimelockPeriod": "The length of time required between passing a proposal and it being executable onchain.", "labelFreezeVotesThreshold": "Freeze Votes Threshold", - "helperFreezeVotesThreshold": "Total votes required by the parent (out of {{totalVotes}}) to freeze this child Safe entirely", + "helperFreezeVotesThreshold": "Total votes required by the parent (out of {{totalVotes}}) to freeze this child DAO entirely.", "labelFreezeProposalPeriod": "Freeze Proposal Period", - "helperFreezeProposalPeriod": "The length of time (in minutes) for a Freeze Vote's starting and ending point", + "helperFreezeProposalPeriod": "The length of time (in minutes) for a Freeze Vote's starting and ending point.", "exampleFreezeProposalPeriod": "10,080 minutes = 1 week", "labelFreezePeriod": "Freeze Period", - "helperFreezePeriod": "The length of time (in minutes) a successful Freeze Vote will freeze this child Safe", + "helperFreezePeriod": "The length of time (in minutes) a successful Freeze Vote will freeze this child DAO.", "exampleFreezePeriod": "10,080 minutes = 1 week", - "freezeGuardDescription": "These configuration values may be changed later on by passing a proposal to do so on this child Safe", + "freezeGuardDescription": "This configuration may be changed later via a proposal on this child DAO.", "errorDuplicateAddress": "Duplicate Address", "errorNoAllocation": "Enter a token allocation", "errorAllocation": "Invalid token allocation", "labelParentAllocation": "Parent token holder claiming", - "helperParentAllocation": "The total number of tokens claimable by all parent token holders", + "helperParentAllocation": "The total number of tokens claimable by all parent token holders.", "labelAddAllocation": "Add Allocation", "labelAddProposer": "", "labelTokenName": "Name", @@ -64,13 +64,13 @@ "labelMultisigGov": "Multisig (m of n)", "descMultisigGov": "Best for small groups and/or frequent onchain activity.", "labelAzoriusErc20Gov": "ERC-20 Token Voting", - "labelAzoriusErc20HatsWhitelistingGov": "Token Voting + Whitelisted Proposers Safe", + "labelAzoriusErc20HatsWhitelistingGov": "Token Voting + Whitelisted Proposers DAO", "descAzoriusErc20Gov": "Best for distributing power through liquid fungible tokens.", - "descAzoriusErc20HatsWhitelistingGov": "Proposals can be created only by whitelisted Roles members", + "descAzoriusErc20HatsWhitelistingGov": "Proposals can be created only by whitelisted Roles members.", "labelAzoriusErc721Gov": "ERC-721 Token Voting", - "labelAzoriusErc721HatsWhitelistingGov": "NFT Voting + Whitelisted Proposers Safe", + "labelAzoriusErc721HatsWhitelistingGov": "NFT Voting + Whitelisted Proposers DAO", "descAzoriusErc721Gov": "Best for adding governance capabilities to an NFT collection.", - "descAzoriusErc721HatsWhitelistingGov": "Proposals can be created only by whitelisted Roles members", + "descAzoriusErc721HatsWhitelistingGov": "Proposals can be created only by whitelisted Roles members.", "labelDAOName": "Name", "daoNamePlaceholder": "Name", "helperDAOName": "What is your DAO called?", @@ -82,7 +82,7 @@ "titleTokenContract": "Contract", "titleAddress": "Address", "titleAmount": "Amount", - "createSubDAOPendingToastMessage": "Creating your child Safe proposal", + "createSubDAOPendingToastMessage": "Creating your child DAO proposal", "createSubDAOSuccessToastMessage": "Deployment successful", "createSubDAOFailureToastMessage": "Deployment failed", "helperAllocations": "Any unallocated tokens will be placed in the newly deployed DAO treasury.", @@ -101,6 +101,6 @@ "tooltipNftVoting": "NFT Voting allows a group of ERC-721 (NFT) holders to propose and vote on transactions. Multiple NFT addresses can be used. <1>Learn more", "errorUnsupportedCreateOption": "Previously selected Governance option is not supported on this network.", "attachFractalModuleLabel": "Enable Clawback", - "attachFractalModuleDescription": "This setting controls whether Parent Safe will be able to execute arbitrary transactions on Child Safe bypassing voting process on Child Safe.", - "fractalModuleAttachedDescription": "This setting can not be modified as Fractal Module already attached to the Safe." + "attachFractalModuleDescription": "This setting controls whether Parent DAO will be able to execute arbitrary transactions on Child DAO bypassing voting process on Child DAO.", + "fractalModuleAttachedDescription": "This setting can not be modified as Fractal Module already attached to the DAO." } diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index c33e6f921..3536ecd62 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -15,7 +15,7 @@ "modulesAndGuardsTitle": "Modules and Guards", "permissionsTitle": "Permissions", "daoSettingsGeneral": "General", - "daoSettingsGovernance": "Governance Parameters", + "daoSettingsGovernance": "Governance", "daoMetadataName": "DAO Name", "daoMetadataSnapshot": "Snapshot Space", "daoMetadataConnectSnapshot": "Connect to Snapshot Space", diff --git a/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx b/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx index b9dab6539..8e4bf8594 100644 --- a/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx +++ b/src/pages/dao/settings/governance/SafeGovernanceSettingsPage.tsx @@ -1,4 +1,4 @@ -import { Show, Text } from '@chakra-ui/react'; +import { Box, Show, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { zeroAddress } from 'viem'; import { InfoGovernance } from '../../../../components/DaoDashboard/Info/InfoGovernance'; @@ -6,7 +6,6 @@ import { ERC20TokenContainer } from '../../../../components/SafeSettings/ERC20To import { ERC721TokensContainer } from '../../../../components/SafeSettings/ERC721TokensContainer'; import { SettingsContentBox } from '../../../../components/SafeSettings/SettingsContentBox'; import { SignersContainer } from '../../../../components/SafeSettings/Signers/SignersContainer'; -import { StyledBox } from '../../../../components/ui/containers/StyledBox'; import NestedPageHeader from '../../../../components/ui/page/Header/NestedPageHeader'; import { DAO_ROUTES } from '../../../../constants/routes'; import { useFractal } from '../../../../providers/App/AppProvider'; @@ -40,18 +39,26 @@ export function SafeGovernanceSettingsPage() { {(isERC20Governance || isERC721Governance) && ( - + {t('daoSettingsGovernance')} - - + + + + )} {isERC20Governance ? ( diff --git a/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx b/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx index 96e010618..0a0e82864 100644 --- a/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx +++ b/src/pages/dao/settings/permissions/SafePermissionsSettingsPage.tsx @@ -152,15 +152,17 @@ export function SafePermissionsSettingsPage() { - } - aria-label={t('edit')} - opacity={0} - color="neutral-6" - border="none" - /> + {canUserCreateProposal && ( + } + aria-label={t('edit')} + opacity={0} + color="neutral-6" + border="none" + /> + )} )} diff --git a/src/store/roles/useRolesStore.ts b/src/store/roles/useRolesStore.ts index 3f6da74f1..40c4cc86a 100644 --- a/src/store/roles/useRolesStore.ts +++ b/src/store/roles/useRolesStore.ts @@ -117,12 +117,11 @@ const useRolesStore = create()((set, get) => ({ const filteredStreamIds = streamIdsToHatIdsMap .filter(ids => ids.hatId === BigInt(roleHat.id)) .map(ids => ids.streamId); + return { ...roleHat, payments: roleHat.isTermed - ? roleHat.payments?.filter(payment => { - return filteredStreamIds.includes(payment.streamId); - }) + ? roleHat.payments?.filter(payment => filteredStreamIds.includes(payment.streamId)) : roleHat.payments, }; }),