RepresentativesInfoPanel.tsx
1 import { Representative, Rpresented } from '@aave/contract-helpers'; 2 import { PlusIcon } from '@heroicons/react/outline'; 3 import { ExternalLinkIcon } from '@heroicons/react/solid'; 4 import { Trans } from '@lingui/macro'; 5 import { Box, Button, IconButton, Paper, Stack, SvgIcon, Typography } from '@mui/material'; 6 import { CompactableTypography, CompactMode } from 'src/components/CompactableTypography'; 7 import { Link } from 'src/components/primitives/Link'; 8 import { useRepresentatives } from 'src/hooks/governance/useRepresentatives'; 9 // import { useIsContractAddress } from 'src/hooks/useIsContractAddress'; 10 import { useModalContext } from 'src/hooks/useModal'; 11 import { useRootStore } from 'src/store/root'; 12 import { networkConfigs } from 'src/ui-config/networksConfig'; 13 14 import { ZERO_ADDRESS } from './utils/formatProposal'; 15 16 // Setting up a representative is only useful for smart contract wallets. 17 // If connected account is an EOA, this section is hidden. 18 19 // If an account has representatives, then we assume that there is no need to set up a representative, 20 // and it will show the addresses that have selected the current account to represent them. 21 export const RepresentativesInfoPanel = () => { 22 const { openGovRepresentatives } = useModalContext(); 23 const account = useRootStore((state) => state.account); 24 25 const { data } = useRepresentatives(account); 26 27 { 28 // const { data: isContractAddress, isFetching: fetchingIsContractAddress } = 29 // useIsContractAddress(account); 30 } 31 32 const isAddressSelectedAsRepresentative = data?.Represented.some( 33 (r) => r.votersRepresented.length > 0 34 ); 35 36 // if (!isContractAddress) { 37 // return null; 38 // } 39 40 return ( 41 <Paper sx={{ mt: 2 }}> 42 <Box sx={{ px: 6, pb: 6, pt: 4 }}> 43 <Stack direction="row" alignItems="center" justifyContent="space-between"> 44 <Typography typography="h3"> 45 <Trans>Linked addresses</Trans> 46 </Typography> 47 {isAddressSelectedAsRepresentative ? null : ( 48 <Button onClick={() => openGovRepresentatives(data?.Representatives || [])}> 49 <Typography typography="subheader1"> 50 <Trans>Edit</Trans> 51 </Typography> 52 </Button> 53 )} 54 </Stack> 55 <Stack gap={8} sx={{ mt: 2 }}> 56 <Stack direction="column"> 57 <Typography variant="description" color="text.secondary"> 58 {isAddressSelectedAsRepresentative ? ( 59 <Trans> 60 Representing smart contract wallet (ie. Safe) addresses on other chains. 61 </Trans> 62 ) : ( 63 <Trans> 64 Representative smart contract wallet (ie. Safe) addresses on other chains. 65 </Trans> 66 )} 67 </Typography> 68 </Stack> 69 <Stack alignItems="start" gap={6}> 70 {isAddressSelectedAsRepresentative ? ( 71 <Representing representing={data?.Represented || []} /> 72 ) : ( 73 <Representatives 74 representatives={data?.Representatives || []} 75 onOpenRepresentatives={() => openGovRepresentatives(data?.Representatives || [])} 76 /> 77 )} 78 </Stack> 79 </Stack> 80 </Box> 81 </Paper> 82 ); 83 }; 84 85 const Representatives = ({ 86 representatives, 87 onOpenRepresentatives, 88 }: { 89 representatives: Representative[]; 90 onOpenRepresentatives: () => void; 91 }) => { 92 return ( 93 <> 94 {representatives.map((representative, i) => ( 95 <Stack gap={4} key={i} direction="column" alignItems="self-start"> 96 <Network 97 networkLogoPath={networkConfigs[representative.chainId].networkLogoPath} 98 networkName={networkConfigs[representative.chainId].name} 99 /> 100 {representative.representative === ZERO_ADDRESS ? ( 101 <Stack direction="row" gap={1} alignItems="center"> 102 <IconButton 103 sx={(theme) => ({ 104 height: '24px', 105 width: '24px', 106 background: theme.palette.background.disabled, 107 })} 108 onClick={onOpenRepresentatives} 109 > 110 <SvgIcon sx={{ p: 1 }}> 111 <PlusIcon /> 112 </SvgIcon> 113 </IconButton> 114 <Typography variant="subheader1" color="text.muted"> 115 <Trans>Connect</Trans> 116 </Typography> 117 </Stack> 118 ) : ( 119 <AddressLink 120 explorerLink={networkConfigs[representative.chainId].explorerLink} 121 address={representative.representative} 122 /> 123 )} 124 </Stack> 125 ))} 126 </> 127 ); 128 }; 129 130 const Representing = ({ representing }: { representing: Rpresented[] }) => { 131 return ( 132 <> 133 {representing.map((representing, i) => ( 134 <Stack gap={4} key={i} direction="column" alignItems="self-start"> 135 <Network 136 networkLogoPath={networkConfigs[representing.chainId].networkLogoPath} 137 networkName={networkConfigs[representing.chainId].name} 138 /> 139 {representing.votersRepresented.length === 0 ? ( 140 <Typography sx={{ ml: 4 }} color="text.secondary"> 141 <Trans>None</Trans> 142 </Typography> 143 ) : ( 144 representing.votersRepresented.map((voter, i) => ( 145 <AddressLink 146 key={i} 147 explorerLink={networkConfigs[representing.chainId].explorerLink} 148 address={voter} 149 /> 150 )) 151 )} 152 </Stack> 153 ))} 154 </> 155 ); 156 }; 157 158 const Network = ({ 159 networkLogoPath, 160 networkName, 161 }: { 162 networkLogoPath: string; 163 networkName: string; 164 }) => { 165 return ( 166 <Stack direction="row" alignItems="center" gap={2}> 167 <img src={networkLogoPath} height="16px" width="16px" alt="network logo" /> 168 <Typography variant="subheader1">{networkName}</Typography> 169 </Stack> 170 ); 171 }; 172 173 const AddressLink = ({ explorerLink, address }: { explorerLink: string; address: string }) => { 174 return ( 175 <Link href={`${explorerLink}/address/${address}`}> 176 <Stack direction="row" alignItems="center" gap={1}> 177 <CompactableTypography 178 variant="subheader1" 179 compactMode={CompactMode.MD} 180 compact 181 sx={{ ml: 4 }} 182 > 183 {address} 184 </CompactableTypography> 185 <SvgIcon 186 sx={(theme) => ({ 187 width: 14, 188 height: 14, 189 ml: 0.5, 190 color: theme.palette.text.muted, 191 })} 192 > 193 <ExternalLinkIcon /> 194 </SvgIcon> 195 </Stack> 196 </Link> 197 ); 198 };