├── .nvmrc ├── .husky ├── .gitignore └── pre-commit ├── src ├── ui │ ├── helpModals │ │ ├── assets.ts │ │ ├── getRepresentationsData.ts │ │ ├── HelpModalText.tsx │ │ ├── getDelegateData.ts │ │ ├── HelpModalTextButton.tsx │ │ ├── getTestTx.ts │ │ ├── HelpModalWrapper.tsx │ │ ├── getTestTransactions.ts │ │ ├── HelpModalHomeButton.tsx │ │ ├── HelpModalContainer.tsx │ │ ├── HelpRepresentationsTx.tsx │ │ ├── HelpModalCaption.tsx │ │ └── HelpDelegateTx.tsx │ ├── primitives │ │ ├── IconBox.tsx │ │ ├── Image.tsx │ │ ├── Divider.tsx │ │ ├── NoSSR.tsx │ │ ├── Container.tsx │ │ └── Input.tsx │ ├── utils │ │ ├── getChainName.ts │ │ ├── text-center-ellipsis.ts │ │ ├── useDebounce.ts │ │ ├── relativePath.ts │ │ ├── routes.ts │ │ ├── disablePageLoader.ts │ │ ├── useClickOutside.tsx │ │ ├── metaTexts.ts │ │ ├── inputValidation.ts │ │ ├── useMediaQuery.ts │ │ ├── useScrollDirection.ts │ │ └── ThemeRegistry.tsx │ ├── store │ │ └── uiSelectors.tsx │ ├── components │ │ ├── AppLoading.tsx │ │ ├── NoDataWrapper.tsx │ │ ├── ChainNameWithIcon.tsx │ │ ├── TopPanelContainer.tsx │ │ ├── TableHeaderTitle.tsx │ │ ├── CheckBox.tsx │ │ ├── CopyToClipboard.tsx │ │ ├── Web3Icons │ │ │ ├── WalletIcon.tsx │ │ │ ├── ChainIcon.tsx │ │ │ └── AssetIcon.tsx │ │ ├── TableContainer.tsx │ │ ├── CustomSkeleton.tsx │ │ ├── NetworkIcon.tsx │ │ ├── Tooltip.tsx │ │ ├── Link.tsx │ │ └── InputWithAnimation.tsx │ ├── layouts │ │ ├── AppGlobalStyles.tsx │ │ └── AppLayout.tsx │ ├── index.ts │ └── pages │ │ └── NotFoundPage.tsx ├── proposals │ ├── components │ │ ├── actionModals │ │ │ ├── types.ts │ │ │ ├── ActionModalContentWrapper.tsx │ │ │ ├── ActivateVotingModal.tsx │ │ │ ├── ExecuteProposalModal.tsx │ │ │ ├── CloseVotingModal.tsx │ │ │ ├── ActivateVotingOnVotingMachineModal.tsx │ │ │ ├── CreatePayloadModal.tsx │ │ │ ├── ExecutePayloadModal.tsx │ │ │ ├── CreateProposalModal.tsx │ │ │ └── SendProofsModal.tsx │ │ ├── timeline │ │ │ ├── static.ts │ │ │ ├── types.ts │ │ │ ├── TimelineLine.tsx │ │ │ ├── TimelineItemStateWrapper.tsx │ │ │ └── TimelineCanceledItem.tsx │ │ ├── ProposalsPagination.tsx │ │ ├── CreateFieldsArrayTitle.tsx │ │ ├── proposalHistory │ │ │ └── helpers.ts │ │ ├── proposal │ │ │ ├── LeftPanelWrapper.tsx │ │ │ ├── RightPanelWrapper.tsx │ │ │ ├── ProposalPageWrapperWithCache.tsx │ │ │ └── PayloadCreator.tsx │ │ ├── CreateFormWrapper.tsx │ │ ├── VotedState.tsx │ │ ├── RepresentationIcon.tsx │ │ ├── ProposalStatus.tsx │ │ ├── DetailsModalWrapper.tsx │ │ ├── proposalList │ │ │ ├── VotingPowerLoading.tsx │ │ │ └── ProposalListItemWrapper.tsx │ │ ├── VoteLine.tsx │ │ ├── BlockWrapper.tsx │ │ ├── ProposalStatusWithDate.tsx │ │ └── CreateFieldArrayWrapper.tsx │ ├── utils │ │ ├── formatVoterAddress.ts │ │ └── statuses.ts │ └── store │ │ └── proposalsListCacheSlice.ts ├── delegate │ ├── store │ │ └── delegationSelectors.ts │ ├── types.ts │ ├── utils │ │ └── getFormDelegateData.ts │ └── components │ │ ├── DelegateModal.tsx │ │ └── DelegateTableWrapper.tsx ├── web3 │ ├── store │ │ ├── creationFeesSelectors.ts │ │ └── web3Selectors.ts │ ├── components │ │ ├── ChainsIcons.tsx │ │ ├── wallet │ │ │ └── WalletItem.tsx │ │ ├── representation │ │ │ └── RepresentingButtonChainsIcon.tsx │ │ └── powers │ │ │ └── PowersInfoModal.tsx │ ├── providers │ │ ├── Web3HelperProvider.tsx │ │ └── WagmiProvider.tsx │ └── utils │ │ ├── ensHelpers.tsx │ │ └── assetsBalanceSlots.ts ├── proposalCreateOverview │ ├── types.ts │ └── components │ │ └── SeatBeltReportModal.tsx ├── utils │ ├── initialClients.ts │ ├── configs.ts │ ├── getScanLink.ts │ ├── getAssetName.ts │ ├── cacheGithubLinks.ts │ ├── appConfig.ts │ ├── githubCacheRequests.ts │ └── getProposalMetadata.ts ├── representations │ ├── store │ │ └── representationsSelectors.ts │ ├── utils │ │ └── getFormRepresentationsData.ts │ └── components │ │ ├── RepresentationsModal.tsx │ │ └── RepresentationsTableWrapper.tsx ├── rpcSwitcher │ ├── store │ │ └── rpcSwitcherSelectors.ts │ └── components │ │ └── RpcSwitcherTableWrapper.tsx ├── payloadsExplorer │ └── store │ │ └── payloadsExplorerSelectors.ts ├── transactions │ ├── hooks │ │ └── useLastTxLocalStatus.tsx │ └── components │ │ ├── TransactionsModal.tsx │ │ ├── CopyErrorButton.tsx │ │ ├── BasicActionModal.tsx │ │ └── TransactionsModalContent.tsx └── store │ └── ZustandStoreProvider.tsx ├── public ├── favicon.ico ├── metaLogo.jpg └── images │ ├── icons │ ├── check.svg │ ├── arrowToBottom.svg │ ├── arrowToLeft.svg │ ├── arrowToRight.svg │ ├── arrowToTop.svg │ ├── cross.svg │ ├── copy.svg │ ├── rowIcon.svg │ ├── arrowRight.svg │ ├── download.svg │ ├── link.svg │ ├── darkTheme.svg │ ├── columnsIcon.svg │ ├── claimFee.svg │ ├── toTopArrow.svg │ ├── linkIcon.svg │ ├── arrowToRightThin.svg │ ├── backArrow.svg │ ├── delegationIcon.svg │ ├── twitterX.svg │ ├── searchIcon.svg │ ├── warningIcon.svg │ ├── settings.svg │ ├── pencil.svg │ ├── reload.svg │ ├── replacedIcon.svg │ ├── web.svg │ ├── info.svg │ ├── settingsBorders.svg │ └── gelato.svg │ └── representation │ ├── multichain.svg │ └── representationVotingPower.svg ├── .prettierrc.json ├── pages ├── delegate.tsx ├── index.tsx ├── 404.tsx ├── rpc-switcher.tsx ├── proposal.tsx ├── create.tsx ├── representations.tsx ├── payloads-explorer.tsx ├── _app.tsx └── proposal-create-overview.tsx ├── .env.example ├── next-env.d.ts ├── svgo.config.js ├── app ├── manifest.json ├── create │ └── page.page.tsx ├── delegate │ └── page.page.tsx ├── rpc-switcher │ └── page.page.tsx ├── payloads-explorer │ └── page.page.tsx ├── representations │ └── page.page.tsx ├── layout.page.tsx └── proposal-create-overview │ └── page.page.tsx ├── .github └── workflows │ ├── release-please.yml │ └── ipfs_deploy.yml ├── .gitignore ├── vercel.json ├── tsconfig.json ├── next.config.js ├── README.md └── eslint.config.mjs /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /src/ui/helpModals/assets.ts: -------------------------------------------------------------------------------- 1 | export const assets = 'AAVE + aAAVE + stkAAVE'; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgd-labs/aave-governance-v3-interface/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/metaLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgd-labs/aave-governance-v3-interface/HEAD/public/metaLogo.jpg -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "jsxBracketSameLine": true 6 | } -------------------------------------------------------------------------------- /pages/delegate.tsx: -------------------------------------------------------------------------------- 1 | import { DelegatePage } from '../src/delegate/components/DelegatePage'; 2 | 3 | export default function Delegate() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_DEPLOY_FOR_IPFS=false 2 | NEXT_PUBLIC_TERMS_AND_CONDITIONS_VISIBLE=false 3 | WC_PROJECT_ID=your_wc_project_id#https://docs.walletconnect.com/2.0/cloud/relay -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { ProposalPage } from '../src/proposals/components/proposalList/ProposalPage'; 2 | 3 | export default function Home() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/ui/primitives/IconBox.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const IconBox = styled('div')({ 4 | position: 'relative', 5 | zIndex: 0, 6 | lineHeight: 0, 7 | }); 8 | -------------------------------------------------------------------------------- /src/ui/primitives/Image.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const Image = styled('img')({ 4 | position: 'relative', 5 | zIndex: 0, 6 | lineHeight: 0, 7 | }); 8 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { NotFoundPage } from '../src/ui/pages/NotFoundPage'; 4 | 5 | export default function NotFound() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /pages/rpc-switcher.tsx: -------------------------------------------------------------------------------- 1 | import { RpcSwitcherPage } from '../src/rpcSwitcher/components/RpcSwitcherPage'; 2 | 3 | export default function Representations() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /pages/proposal.tsx: -------------------------------------------------------------------------------- 1 | import { ProposalClientPage } from '../src/proposals/components/proposal/ProposalClientPage'; 2 | 3 | export default function Proposal() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /public/images/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/ui/primitives/Divider.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const Divider = styled('div')(({ theme }) => ({ 4 | borderBottom: `1px solid ${theme.palette.$secondaryBorder}`, 5 | })); 6 | -------------------------------------------------------------------------------- /pages/create.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { CreateProposalPage } from '../src/ui/pages/CreateProposalPage'; 4 | 5 | export default function Create() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/types.ts: -------------------------------------------------------------------------------- 1 | export interface ActionModalBasicTypes { 2 | isOpen: boolean; 3 | setIsOpen: (value: boolean) => void; 4 | proposalId: number; 5 | fromList?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /pages/representations.tsx: -------------------------------------------------------------------------------- 1 | import { RepresentationsPage } from '../src/representations/components/RepresentationsPage'; 2 | 3 | export default function Representations() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /pages/payloads-explorer.tsx: -------------------------------------------------------------------------------- 1 | import { PayloadsExplorerPage } from '../src/payloadsExplorer/components/PayloadsExplorerPage'; 2 | 3 | export default function PayloadsExplorer() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /public/images/icons/arrowToBottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/icons/arrowToLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/icons/arrowToRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/icons/arrowToTop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/ui/primitives/NoSSR.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import { ReactNode } from 'react'; 3 | 4 | const NoSSR = ({ children }: { children: ReactNode }) => <>{children}; 5 | 6 | export default dynamic(() => Promise.resolve(NoSSR), { ssr: false }); 7 | -------------------------------------------------------------------------------- /public/images/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/icons/rowIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/icons/arrowRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /src/ui/utils/getChainName.ts: -------------------------------------------------------------------------------- 1 | import { appConfig } from '../../utils/appConfig'; 2 | import { chainInfoHelper } from '../../utils/configs'; 3 | 4 | export function getChainName(chainId: number) { 5 | return chainInfoHelper.getChainParameters(chainId || appConfig.govCoreChainId) 6 | .name; 7 | } 8 | -------------------------------------------------------------------------------- /src/ui/utils/text-center-ellipsis.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/prefer-default-export 2 | export function textCenterEllipsis(str: string, from: number, to: number) { 3 | return `${(str || '').substr(0, from)}...${(str || '').substr( 4 | (str || '').length - to, 5 | (str || '').length, 6 | )}`; 7 | } 8 | -------------------------------------------------------------------------------- /public/images/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/delegate/store/delegationSelectors.ts: -------------------------------------------------------------------------------- 1 | import { Address, zeroAddress } from 'viem'; 2 | 3 | export function getToAddress(activeAddress: Address, toAddress: Address) { 4 | if (toAddress === activeAddress || toAddress === zeroAddress) { 5 | return ''; 6 | } else if (toAddress) { 7 | return toAddress; 8 | } 9 | return ''; 10 | } 11 | -------------------------------------------------------------------------------- /public/images/representation/multichain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/web3/store/creationFeesSelectors.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | import { ICreationFeesSlice } from './creationFeesSlice'; 4 | 5 | export const selectCreationFeesDataByCreator = ( 6 | store: ICreationFeesSlice, 7 | creator: Address, 8 | ) => { 9 | const creationFeesData = store.creationFeesData; 10 | return creationFeesData[creator] || {}; 11 | }; 12 | -------------------------------------------------------------------------------- /public/images/icons/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/proposalCreateOverview/types.ts: -------------------------------------------------------------------------------- 1 | import { Address, Hex } from 'viem'; 2 | 3 | export type PayloadParams = { 4 | chainId: number; 5 | accessLevel: number; 6 | payloadsController: Address; 7 | payloadId: number; 8 | }; 9 | 10 | export interface InitialParams { 11 | proposalId?: number; 12 | ipfsHash?: Hex; 13 | votingPortal?: Address; 14 | payloads: PayloadParams[]; 15 | } 16 | -------------------------------------------------------------------------------- /public/images/icons/darkTheme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /svgo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | multipass: true, 3 | js2svg: { 4 | indent: 2, 5 | pretty: true, 6 | }, 7 | plugins: [ 8 | { 9 | name: 'preset-default', 10 | params: { 11 | overrides: { 12 | removeViewBox: false, 13 | cleanupIds: false, 14 | collapseGroups: false, 15 | }, 16 | }, 17 | } 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /src/proposals/components/timeline/static.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const TimelineLineWrapper = styled('div')({ 4 | position: 'absolute', 5 | left: 0, 6 | height: 6, 7 | }); 8 | 9 | export const TimelineCanceledItemWrapper = styled('div')({ 10 | display: 'flex', 11 | position: 'absolute', 12 | alignItems: 'center', 13 | justifyContent: 'center', 14 | top: 2, 15 | }); 16 | -------------------------------------------------------------------------------- /src/ui/helpModals/getRepresentationsData.ts: -------------------------------------------------------------------------------- 1 | import { mainnet } from 'viem/chains'; 2 | 3 | import { RepresentationDataItem } from '../../representations/store/representationsSlice'; 4 | 5 | export function getRepresentationsData() { 6 | return { 7 | [mainnet.id]: { 8 | representative: '0x2Ae626304D770eed47E5C80bF64E44A2352FF53b', 9 | represented: [], 10 | }, 11 | } as Record; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/initialClients.ts: -------------------------------------------------------------------------------- 1 | import { ClientsRecord } from '@bgd-labs/frontend-web3-utils'; 2 | 3 | import { appUsedNetworks } from './appConfig'; 4 | import { CHAINS, createViemClient } from './chains'; 5 | 6 | export const initialClients: ClientsRecord = {}; 7 | appUsedNetworks.forEach((chain) => { 8 | initialClients[chain] = createViemClient( 9 | CHAINS[chain], 10 | CHAINS[chain].rpcUrls.default.http[0], 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Aave governance v3", 3 | "name": "Aave governance v3", 4 | "description": "Non-custodial liquidity protocol", 5 | "iconPath": "aave.svg", 6 | "icons": [ 7 | { 8 | "src": "aave.svg", 9 | "sizes": "32x32", 10 | "type": "image/svg" 11 | } 12 | ], 13 | "start_url": "/", 14 | "display": "standalone", 15 | "theme_color": "#000000", 16 | "background_color": "#ffffff" 17 | } -------------------------------------------------------------------------------- /src/ui/store/uiSelectors.tsx: -------------------------------------------------------------------------------- 1 | import { IUISlice } from './uiSlice'; 2 | 3 | export const selectAllTestTransactions = (state: IUISlice) => { 4 | return Object.values(state.testTransactionsPool).sort( 5 | (a, b) => b.localTimestamp - a.localTimestamp, 6 | ); 7 | }; 8 | 9 | export const closeHelpModal = (state: IUISlice) => { 10 | state.setIsHelpModalClosed(true); 11 | setTimeout(() => state.setIsHelpModalClosed(false), 1000); 12 | }; 13 | -------------------------------------------------------------------------------- /src/ui/utils/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useDebounce(value: T, delay?: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | useEffect(() => { 6 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500); 7 | return () => { 8 | clearTimeout(timer); 9 | }; 10 | }, [value, delay]); 11 | return debouncedValue; 12 | } 13 | -------------------------------------------------------------------------------- /public/images/icons/columnsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/images/representation/representationVotingPower.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/representations/store/representationsSelectors.ts: -------------------------------------------------------------------------------- 1 | import { RepresentativeAddress } from './representationsSlice'; 2 | 3 | export const checkIsVotingAvailable = ( 4 | representative: RepresentativeAddress, 5 | votingChainId: number, 6 | ) => { 7 | return representative.address !== '' 8 | ? !!representative.chainsIds.length && 9 | !!representative.chainsIds.find((chainId) => chainId === votingChainId) 10 | : true; 11 | }; 12 | -------------------------------------------------------------------------------- /src/ui/utils/relativePath.ts: -------------------------------------------------------------------------------- 1 | import { isForIPFS } from '../../utils/appConfig'; 2 | 3 | export const setRelativePath = (path: string) => { 4 | if (typeof window !== 'undefined') { 5 | const { pathname } = window.location; 6 | const ipfsMatch = /.*\/Qm\w{44}\//.exec(pathname); 7 | const basePath = ipfsMatch ? ipfsMatch[0] : ''; 8 | return isForIPFS ? `${basePath}${path}` : path; 9 | } else { 10 | return path; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/configs.ts: -------------------------------------------------------------------------------- 1 | import { initChainInformationConfig } from '@bgd-labs/frontend-web3-utils'; 2 | 3 | import { CHAINS } from './chains'; 4 | 5 | // ipfs gateway to get proposals metadata 6 | export const ipfsGateway = 'https://dweb.link/ipfs'; 7 | export const fallbackGateways = [ 8 | 'https://ipfs.eth.aragon.network/ipfs', 9 | 'https://ipfs.runfission.com/ipfs', 10 | ]; 11 | 12 | export const chainInfoHelper = initChainInformationConfig(CHAINS); 13 | -------------------------------------------------------------------------------- /src/ui/helpModals/HelpModalText.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | interface HelpModalTextProps { 5 | children: ReactNode; 6 | mb?: number; 7 | } 8 | 9 | export function HelpModalText({ children, mb }: HelpModalTextProps) { 10 | return ( 11 | 17 | {children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/ui/helpModals/getDelegateData.ts: -------------------------------------------------------------------------------- 1 | import { DelegateItem } from '../../delegate/types'; 2 | import { appConfig } from '../../utils/appConfig'; 3 | 4 | export function getDelegateData() { 5 | return [ 6 | { 7 | underlyingAsset: appConfig.additional.aaveAddress, 8 | symbol: 'AAVE', 9 | amount: 100, 10 | votingToAddress: '', 11 | propositionToAddress: '0x86E284421664840Cb65C5b918Da59c01ED8fA666', 12 | }, 13 | ] as DelegateItem[]; 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/primitives/Container.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const Container = styled('div')(({ theme }) => ({ 4 | margin: '0 auto', 5 | position: 'relative', 6 | zIndex: 2, 7 | paddingLeft: 8, 8 | paddingRight: 8, 9 | maxWidth: 988, 10 | [theme.breakpoints.up('sm')]: { 11 | paddingLeft: 24, 12 | paddingRight: 24, 13 | maxWidth: 1022, 14 | }, 15 | [theme.breakpoints.up('lg')]: { 16 | maxWidth: 1228, 17 | }, 18 | })); 19 | -------------------------------------------------------------------------------- /public/images/icons/claimFee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: google-github-actions/release-please-action@v3 17 | with: 18 | release-type: node 19 | package-name: aave-governance-v3-interface 20 | pull-request-header: 'I have created a release' 21 | -------------------------------------------------------------------------------- /public/images/icons/toTopArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/getScanLink.ts: -------------------------------------------------------------------------------- 1 | import { appConfig } from './appConfig'; 2 | import { CHAINS } from './chains'; 3 | 4 | export function getScanLink({ 5 | chainId = appConfig.govCoreChainId, 6 | address, 7 | type = 'address', 8 | }: { 9 | chainId?: number; 10 | address: string; 11 | type?: 'address' | 'tx'; 12 | }) { 13 | const baseUrl = CHAINS[chainId]?.blockExplorers?.default.url || ''; 14 | const url = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; 15 | return `${url}${type}/${address}`; 16 | } 17 | -------------------------------------------------------------------------------- /public/images/icons/linkIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/icons/arrowToRightThin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/proposals/utils/formatVoterAddress.ts: -------------------------------------------------------------------------------- 1 | import { VotersData } from '@bgd-labs/aave-governance-ui-helpers'; 2 | 3 | import { textCenterEllipsis } from '../../ui/utils/text-center-ellipsis'; 4 | 5 | export function formatVoterAddress(vote: VotersData, isBig?: boolean) { 6 | return vote.ensName 7 | ? vote.ensName.length > (isBig ? 18 : 10) 8 | ? textCenterEllipsis(vote.ensName, isBig ? 12 : 5, isBig ? 6 : 5) 9 | : vote.ensName 10 | : textCenterEllipsis(vote.address, isBig ? 8 : 5, isBig ? 8 : 5); 11 | } 12 | -------------------------------------------------------------------------------- /public/images/icons/backArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/ui/utils/routes.ts: -------------------------------------------------------------------------------- 1 | export const ROUTES = { 2 | main: '/', 3 | delegate: '/delegate', 4 | representations: '/representations', 5 | proposal: (proposalId: number, ipfsHash: string) => 6 | `/proposal?proposalId=${proposalId}&ipfsHash=${ipfsHash}`, 7 | proposalWithoutIpfs: (proposalId: number) => 8 | `/proposal?proposalId=${proposalId}`, 9 | rpcSwitcher: '/rpc-switcher', 10 | proposalCreateOverview: '/proposal-create-overview', 11 | payloadsExplorer: '/payloads-explorer', 12 | adi: 'https://adi.onaave.com/', 13 | }; 14 | -------------------------------------------------------------------------------- /src/ui/utils/disablePageLoader.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress'; 2 | 3 | import { isForIPFS } from '../../utils/appConfig'; 4 | 5 | export function disablePageLoader() { 6 | if (typeof document !== 'undefined' && !isForIPFS) { 7 | setTimeout(() => { 8 | NProgress.start(); 9 | NProgress.done(); 10 | document.documentElement.classList.remove('nprogress-busy'); 11 | const nprogressElement = document.getElementById('nprogress'); 12 | if (nprogressElement) { 13 | nprogressElement.remove(); 14 | } 15 | }, 1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .env 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .idea 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | # storybooks 38 | build-storybook.log* 39 | -------------------------------------------------------------------------------- /public/images/icons/delegationIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/rpcSwitcher/store/rpcSwitcherSelectors.ts: -------------------------------------------------------------------------------- 1 | import { Client } from 'viem'; 2 | 3 | import { IRpcSwitcherSlice } from './rpcSwitcherSlice'; 4 | 5 | export const selectAppClients = (store: IRpcSwitcherSlice) => { 6 | return Object.entries(store.appClients).reduce( 7 | (acc, [key, value]) => { 8 | acc[key] = value.instance; 9 | return acc; 10 | }, 11 | {} as Record, 12 | ); 13 | }; 14 | 15 | export const selectIsRpcAppHasErrors = (store: IRpcSwitcherSlice) => { 16 | return Object.values(store.rpcAppErrors).some((error) => error.error); 17 | }; 18 | -------------------------------------------------------------------------------- /public/images/icons/twitterX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/web3/store/web3Selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from '../../store'; 2 | 3 | export const selectCurrentPowers = (store: RootState) => { 4 | if (store.representative.address) { 5 | return store.currentPowers[store.representative.address]; 6 | } else if (store.activeWallet?.address) { 7 | return store.currentPowers[store.activeWallet?.address]; 8 | } else { 9 | return; 10 | } 11 | }; 12 | 13 | export const selectCurrentPowersForActiveWallet = (store: RootState) => { 14 | if (store.activeWallet?.address) { 15 | return store.currentPowers[store.activeWallet?.address]; 16 | } else { 17 | return; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "headers": [ 3 | { 4 | "source": "/(.*)", 5 | "headers": [ 6 | { 7 | "key": "Referrer-Policy", 8 | "value": "no-referrer-when-downgrade" 9 | }, 10 | { 11 | "key": "X-Content-Type-Options", 12 | "value": "nosniff" 13 | }, 14 | { 15 | "key": "Content-Security-Policy", 16 | "value": "frame-ancestors 'self' https://app.safe.global https://metissafe.tech" 17 | }, 18 | { 19 | "key": "X-XSS-Protection", 20 | "value": "1; mode=block" 21 | } 22 | ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /src/proposals/components/ProposalsPagination.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '../../store/ZustandStoreProvider'; 2 | import { Pagination } from '../../ui'; 3 | import { selectProposalsPages } from '../store/proposalsSelectors'; 4 | 5 | export const ProposalsPagination = () => { 6 | const pages = useStore((state) => selectProposalsPages(state)); 7 | const setActivePage = useStore((state) => state.setActivePage); 8 | const activePage = useStore((state) => state.activePage); 9 | 10 | return ( 11 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/proposals/components/CreateFieldsArrayTitle.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | interface CreateFieldsArrayTitleProps { 5 | title: string; 6 | isTitleVisible: boolean; 7 | children: ReactNode; 8 | } 9 | 10 | export function CreateFieldsArrayTitle({ 11 | title, 12 | isTitleVisible, 13 | children, 14 | }: CreateFieldsArrayTitleProps) { 15 | return ( 16 | 17 | {isTitleVisible && ( 18 | 19 | {title} 20 | 21 | )} 22 | 23 | {children} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/create/page.page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import React from 'react'; 3 | 4 | import { CreateProposalPage } from '../../src/ui/pages/CreateProposalPage'; 5 | import { metaTexts } from '../../src/ui/utils/metaTexts'; 6 | 7 | export const metadata: Metadata = { 8 | title: `${metaTexts.main}${metaTexts.createPageMetaTitle}`, 9 | description: metaTexts.createPageMetaDescription, 10 | openGraph: { 11 | images: ['/metaLogo.jpg'], 12 | title: `${metaTexts.main}${metaTexts.createPageMetaTitle}`, 13 | description: metaTexts.createPageMetaDescription, 14 | }, 15 | }; 16 | 17 | export default function Page() { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /app/delegate/page.page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import React from 'react'; 3 | 4 | import { DelegatePage } from '../../src/delegate/components/DelegatePage'; 5 | import { metaTexts } from '../../src/ui/utils/metaTexts'; 6 | 7 | export const metadata: Metadata = { 8 | title: `${metaTexts.main}${metaTexts.delegatePageMetaTitle}`, 9 | description: metaTexts.delegatePageMetaDescription, 10 | openGraph: { 11 | images: ['/metaLogo.jpg'], 12 | title: `${metaTexts.main}${metaTexts.delegatePageMetaTitle}`, 13 | description: metaTexts.delegatePageMetaDescription, 14 | }, 15 | }; 16 | 17 | export default function Page() { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/proposals/components/proposalHistory/helpers.ts: -------------------------------------------------------------------------------- 1 | import { HistoryItemType } from '@bgd-labs/aave-governance-ui-helpers'; 2 | 3 | export function getHistoryId({ 4 | proposalId, 5 | type, 6 | id, 7 | chainId, 8 | }: { 9 | proposalId: number; 10 | type: HistoryItemType; 11 | id: number; 12 | chainId: number; 13 | }) { 14 | if ( 15 | type === HistoryItemType.PAYLOADS_CREATED || 16 | type === HistoryItemType.PAYLOADS_QUEUED || 17 | type === HistoryItemType.PAYLOADS_EXECUTED || 18 | type === HistoryItemType.PAYLOADS_EXPIRED 19 | ) { 20 | return `${proposalId}_${type}_${id}_${chainId}`; 21 | } else { 22 | return `${proposalId}_${type}`; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/payloadsExplorer/store/payloadsExplorerSelectors.ts: -------------------------------------------------------------------------------- 1 | import { Hex } from 'viem'; 2 | 3 | import { RootState } from '../../store'; 4 | 5 | export const selectPayloadExploreById = ( 6 | store: RootState, 7 | chainId: number, 8 | address: Hex, 9 | payloadId: number, 10 | ) => { 11 | if (!store.payloadsExploreData[chainId]) { 12 | return; 13 | } else if (!store.payloadsExploreData[chainId][address]) { 14 | return; 15 | } else if ( 16 | !store.payloadsExploreData[chainId][address][`${address}_${payloadId}`] 17 | ) { 18 | return; 19 | } else { 20 | return store.payloadsExploreData[chainId][address][ 21 | `${address}_${payloadId}` 22 | ]; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/ui/components/AppLoading.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | 3 | import { Container } from '../primitives/Container'; 4 | import NoSSR from '../primitives/NoSSR'; 5 | import { texts } from '../utils/texts'; 6 | import { NoDataWrapper } from './NoDataWrapper'; 7 | import { RocketLoader } from './RocketLoader'; 8 | 9 | export function AppLoading() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | {texts.other.appLoading} 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/rpc-switcher/page.page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import React from 'react'; 3 | 4 | import { RpcSwitcherPage } from '../../src/rpcSwitcher/components/RpcSwitcherPage'; 5 | import { metaTexts } from '../../src/ui/utils/metaTexts'; 6 | 7 | export const metadata: Metadata = { 8 | title: `${metaTexts.main}${metaTexts.rpcSwitcherPageMetaTitle}`, 9 | description: metaTexts.rpcSwitcherPageMetaDescription, 10 | openGraph: { 11 | images: ['/metaLogo.jpg'], 12 | title: `${metaTexts.main}${metaTexts.rpcSwitcherPageMetaTitle}`, 13 | description: metaTexts.rpcSwitcherPageMetaDescription, 14 | }, 15 | }; 16 | 17 | export default function Page() { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /app/payloads-explorer/page.page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import React, { Suspense } from 'react'; 3 | 4 | import { PayloadsExplorerPage } from '../../src/payloadsExplorer/components/PayloadsExplorerPage'; 5 | import { metaTexts } from '../../src/ui/utils/metaTexts'; 6 | 7 | export const metadata: Metadata = { 8 | title: metaTexts.ipfsTitle, 9 | description: metaTexts.ipfsDescription, 10 | openGraph: { 11 | images: ['/metaLogo.jpg'], 12 | title: metaTexts.ipfsTitle, 13 | description: metaTexts.ipfsDescription, 14 | }, 15 | }; 16 | 17 | export default function PayloadsExplorer() { 18 | return ( 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/delegate/types.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers'; 2 | import { Address } from 'viem'; 3 | 4 | export type DelegateItem = { 5 | underlyingAsset: Address; 6 | symbol: Asset; 7 | amount: number; 8 | votingToAddress: Address | string; 9 | propositionToAddress: Address | string; 10 | }; 11 | 12 | export type DelegateData = { 13 | underlyingAsset: Address; 14 | votingToAddress: Address | string; 15 | propositionToAddress: Address | string; 16 | }; 17 | 18 | export type TxDelegateData = { 19 | symbol: Asset; 20 | underlyingAsset: Address; 21 | bothAddresses?: Address | string; 22 | votingToAddress?: Address | string; 23 | propositionToAddress?: Address | string; 24 | }; 25 | -------------------------------------------------------------------------------- /src/ui/components/NoDataWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | import { BoxWith3D } from './BoxWith3D'; 4 | 5 | interface NoDataWrapperProps { 6 | children: ReactNode; 7 | } 8 | export function NoDataWrapper({ children }: NoDataWrapperProps) { 9 | return ( 10 | 24 | {children} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/getAssetName.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers'; 2 | 3 | import { appConfig } from './appConfig'; 4 | 5 | export function getAssetName(assetAddress: string) { 6 | switch (assetAddress.toLowerCase()) { 7 | case appConfig.additional.aaveAddress.toLowerCase(): 8 | return Asset.AAVE; 9 | case appConfig.additional.stkAAVEAddress.toLowerCase(): 10 | return Asset.STKAAVE; 11 | case appConfig.additional.aAaveAddress.toLowerCase(): 12 | return Asset.AAAVE; 13 | case appConfig.govCoreConfig.contractAddress.toLowerCase(): 14 | return Asset.GOVCORE; 15 | 16 | default: 17 | console.error('Token address incorrect'); 18 | return ''; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/representations/page.page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | import React from 'react'; 3 | 4 | import { RepresentationsPage } from '../../src/representations/components/RepresentationsPage'; 5 | import { metaTexts } from '../../src/ui/utils/metaTexts'; 6 | 7 | export const metadata: Metadata = { 8 | title: `${metaTexts.main}${metaTexts.representationsPageMetaTitle}`, 9 | description: metaTexts.representationsPageMetaDescription, 10 | openGraph: { 11 | images: ['/metaLogo.jpg'], 12 | title: `${metaTexts.main}${metaTexts.representationsPageMetaTitle}`, 13 | description: metaTexts.representationsPageMetaDescription, 14 | }, 15 | }; 16 | 17 | export default function Page() { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/proposals/components/timeline/types.ts: -------------------------------------------------------------------------------- 1 | import { ProposalStateWithName } from '@bgd-labs/aave-governance-ui-helpers'; 2 | 3 | export interface TimelineItemType { 4 | title: string; 5 | position?: number; 6 | finished: boolean; 7 | type: TimelineItemTypeType; 8 | timestamp: number; 9 | timestampForEstimatedState?: number; 10 | visibility?: boolean; 11 | state?: ProposalStateWithName; 12 | color?: 13 | | 'achieved' 14 | | 'regular' 15 | | 'bigRegular' 16 | | 'bigSuccess' 17 | | 'bigError' 18 | | 'bigCanceled'; 19 | rocketVisible?: boolean; 20 | } 21 | 22 | export enum TimelineItemTypeType { 23 | created = 'created', 24 | openToVote = 'openToVote', 25 | votingClosed = 'votingClosed', 26 | finished = 'Finished', 27 | } 28 | -------------------------------------------------------------------------------- /public/images/icons/searchIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/transactions/hooks/useLastTxLocalStatus.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useLastTxLocalStatus as baseUseTxLocalStatus } from '@bgd-labs/frontend-web3-utils'; 4 | import { zeroAddress } from 'viem'; 5 | 6 | import { useStore } from '../../store/ZustandStoreProvider'; 7 | import { TransactionUnion } from '../store/transactionsSlice'; 8 | 9 | export function useLastTxLocalStatus({ 10 | type, 11 | payload, 12 | }: Pick) { 13 | const transactionsPool = useStore((store) => store.transactionsPool); 14 | const activeWallet = useStore((store) => store.activeWallet); 15 | 16 | return baseUseTxLocalStatus({ 17 | transactionsPool, 18 | activeAddress: activeWallet?.address || zeroAddress, 19 | type, 20 | payload, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/representations/utils/getFormRepresentationsData.ts: -------------------------------------------------------------------------------- 1 | import { RepresentationFormData } from '../store/representationsSlice'; 2 | 3 | interface GetFormRepresentationsDataParams { 4 | chainId: number; 5 | representativeAddress?: string; 6 | formData?: RepresentationFormData[]; 7 | } 8 | 9 | export function getFormRepresentationsData({ 10 | chainId, 11 | representativeAddress, 12 | formData, 13 | }: GetFormRepresentationsDataParams) { 14 | const formRepresentationsDataItem = 15 | !!formData && 16 | !!formData.length && 17 | formData.find((item) => item.chainId === chainId); 18 | 19 | return typeof formRepresentationsDataItem !== 'boolean' && 20 | typeof formRepresentationsDataItem !== 'undefined' 21 | ? formRepresentationsDataItem.representative 22 | : representativeAddress; 23 | } 24 | -------------------------------------------------------------------------------- /public/images/icons/warningIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/proposals/components/proposal/LeftPanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | interface LeftPanelWrapperProps { 5 | children: ReactNode; 6 | } 7 | 8 | export function LeftPanelWrapper({ children }: LeftPanelWrapperProps) { 9 | return ( 10 | ({ 12 | width: '100%', 13 | [theme.breakpoints.up('sm')]: { 14 | width: 290, 15 | mr: 18, 16 | position: 'sticky', 17 | transition: 'all 0.5s ease', 18 | top: 50, 19 | }, 20 | [theme.breakpoints.up('lg')]: { 21 | mr: 24, 22 | width: 340, 23 | }, 24 | '@media only screen and (max-height: 550px)': { 25 | position: 'static', 26 | }, 27 | })}> 28 | {children} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/ui/utils/useClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | type UseClickOutsideType = { 4 | ref: React.RefObject; 5 | outsideClickFunc: () => void; 6 | additionalCondition?: boolean; 7 | }; 8 | 9 | export function useClickOutside({ 10 | ref, 11 | outsideClickFunc, 12 | additionalCondition, 13 | }: UseClickOutsideType) { 14 | useEffect(() => { 15 | function handleClickOutside(event: any) { 16 | if ( 17 | ref.current && 18 | !ref.current.contains(event.target) && 19 | additionalCondition 20 | ) { 21 | outsideClickFunc(); 22 | } 23 | } 24 | document.addEventListener('mousedown', handleClickOutside); 25 | return () => { 26 | document.removeEventListener('mousedown', handleClickOutside); 27 | }; 28 | }, [ref, additionalCondition]); 29 | } 30 | -------------------------------------------------------------------------------- /src/ui/components/ChainNameWithIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/system'; 2 | 3 | import { getChainName } from '../utils/getChainName'; 4 | import { NetworkIcon } from './NetworkIcon'; 5 | 6 | interface ChainNameWithIconProps { 7 | chainId: number; 8 | onlyIcon?: boolean; 9 | iconSize?: number; 10 | css?: SxProps; 11 | textCss?: SxProps; 12 | } 13 | 14 | export function ChainNameWithIcon({ 15 | chainId, 16 | onlyIcon, 17 | iconSize, 18 | css, 19 | textCss, 20 | }: ChainNameWithIconProps) { 21 | return ( 22 | 23 | 24 | {!onlyIcon && ( 25 | 26 | {getChainName(chainId)} 27 | 28 | )} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "ESNext", 16 | "moduleResolution": "bundler", 17 | "allowSyntheticDefaultImports": true, 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "preserve", 21 | "incremental": true, 22 | "plugins": [ 23 | { 24 | "name": "next" 25 | } 26 | ] 27 | }, 28 | "include": [ 29 | "next-env.d.ts", 30 | "typings/env.d.ts", 31 | "**/*.ts", 32 | "**/*.tsx", 33 | ".next/types/**/*.ts" 34 | ], 35 | "exclude": [ 36 | "node_modules" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/helpModals/HelpModalTextButton.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | interface HelpModalTextButtonProps { 5 | onClick: () => void; 6 | children: ReactNode; 7 | white?: boolean; 8 | } 9 | 10 | export function HelpModalTextButton({ 11 | onClick, 12 | children, 13 | white, 14 | }: HelpModalTextButtonProps) { 15 | return ( 16 | ({ 21 | display: 'inline', 22 | cursor: 'pointer', 23 | color: white ? '$textWhite' : '$textSecondary', 24 | textDecoration: 'underline', 25 | transition: 'all 0.2s ease', 26 | hover: { 27 | color: white ? theme.palette.$textSecondary : theme.palette.$text, 28 | }, 29 | })}> 30 | {children} 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/ui/primitives/Input.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/system'; 2 | 3 | export const Input = styled('input')(({ theme }) => ({ 4 | position: 'relative', 5 | width: '100%', 6 | lineHeight: '1', 7 | fontWeight: '400', 8 | transition: 'all 0.2s ease', 9 | 10 | fontSize: 11, 11 | padding: '7px 23px 7px 5px', 12 | border: `1px solid ${theme.palette.$main}`, 13 | color: theme.palette.$text, 14 | backgroundColor: theme.palette.$mainLight, 15 | [theme.breakpoints.up('xsm')]: { 16 | padding: '7px 23px 7px 5px', 17 | fontSize: 12, 18 | }, 19 | [theme.breakpoints.up('lg')]: { 20 | padding: '8px 25px 8px 10px', 21 | fontSize: 13, 22 | }, 23 | '&:active, &:focus': { 24 | backgroundColor: theme.palette.$light, 25 | }, 26 | '&::placeholder': { 27 | color: theme.palette.$textDisabled, 28 | }, 29 | '&:hover': { 30 | backgroundColor: theme.palette.$light, 31 | }, 32 | })); 33 | -------------------------------------------------------------------------------- /src/proposals/components/CreateFormWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | import { BoxWith3D } from '../../ui'; 5 | 6 | interface CreateFormWrapperProps { 7 | title: string; 8 | children: ReactNode; 9 | } 10 | 11 | export function CreateFormWrapper({ title, children }: CreateFormWrapperProps) { 12 | const theme = useTheme(); 13 | 14 | return ( 15 | 25 | 26 | {title} 27 | 28 | {children} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/cacheGithubLinks.ts: -------------------------------------------------------------------------------- 1 | import { coreName } from './appConfig'; 2 | 3 | export const githubInitialUrl = 4 | 'https://raw.githubusercontent.com/bgd-labs/aave-governance-ui-helpers/main/cache'; 5 | export const githubStartUrl = `${githubInitialUrl}/ui/${coreName}`; 6 | 7 | export const listViewPath = '/list_view_proposals.json'; 8 | export const cachedProposalsIdsPath = '/cached_proposals_ids.json'; 9 | export const cachedCreationFeesPath = '/creation_fees.json'; 10 | export const cachedProposalsPayloadsPath = '/proposals_payloads.json'; 11 | 12 | export const cachedDetailsPath = (id: number) => 13 | `/proposals/proposal_${id}.json`; 14 | 15 | export const cachedVotesPath = (id: number) => 16 | `/votes/vote_for_proposal_${id}.json`; 17 | 18 | export const cachedEventsPath = (id: number) => 19 | `/events/proposal_${id}_events.json`; 20 | 21 | export const cachedIPFSDataPath = (ipfsHash: string) => 22 | `/ipfs/${ipfsHash}.json`; 23 | -------------------------------------------------------------------------------- /src/ui/helpModals/getTestTx.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TxAdapter, 3 | TxLocalStatusTxParams, 4 | WalletType, 5 | } from '@bgd-labs/frontend-web3-utils'; 6 | import dayjs from 'dayjs'; 7 | import { zeroAddress, zeroHash } from 'viem'; 8 | 9 | import { TransactionUnion } from '../../transactions/store/transactionsSlice'; 10 | import { appConfig } from '../../utils/appConfig'; 11 | 12 | export function getTestTx({ 13 | txPending, 14 | txSuccess, 15 | }: { 16 | txPending: boolean; 17 | txSuccess: boolean; 18 | }) { 19 | return { 20 | adapter: TxAdapter.Ethereum, 21 | chainId: appConfig.govCoreChainId, 22 | type: 'test', 23 | from: zeroAddress, 24 | localTimestamp: dayjs().unix(), 25 | txKey: zeroHash, 26 | hash: zeroHash, 27 | walletType: WalletType.Injected, 28 | pending: txPending, 29 | isError: false, 30 | isSuccess: txSuccess, 31 | isReplaced: false, 32 | } as TxLocalStatusTxParams; 33 | } 34 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/ActionModalContentWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | interface ActionModalWrapperProps { 5 | children: ReactNode; 6 | } 7 | 8 | export function ActionModalTitle({ title }: { title: string }) { 9 | return ( 10 | 11 | 12 | {title} 13 | 14 | 15 | ); 16 | } 17 | 18 | export function ActionModalContentWrapper({ 19 | children, 20 | }: ActionModalWrapperProps) { 21 | return ( 22 | 33 | {children} 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/proposals/components/timeline/TimelineLine.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps, useTheme } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | export function TimelineLine({ 5 | textColor = 'light', 6 | isFull, 7 | sx, 8 | }: { 9 | textColor: 'light' | 'secondary'; 10 | isFull?: boolean; 11 | sx: SxProps; 12 | }) { 13 | const theme = useTheme(); 14 | 15 | return ( 16 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/proposals/store/proposalsListCacheSlice.ts: -------------------------------------------------------------------------------- 1 | import { FinishedProposalForList } from '@bgd-labs/aave-governance-ui-helpers'; 2 | import { StoreSlice } from '@bgd-labs/frontend-web3-utils'; 3 | 4 | export interface IProposalsListCacheSlice { 5 | loadingListCache: boolean; 6 | setLoadingListCache: (value: boolean) => void; 7 | 8 | cachedProposalsIds: number[]; 9 | setCachedProposalsIds: (value: number[]) => void; 10 | cachedProposals: FinishedProposalForList[]; 11 | setCachedProposals: (value: FinishedProposalForList[]) => void; 12 | } 13 | 14 | export const createProposalsListCacheSlice: StoreSlice< 15 | IProposalsListCacheSlice 16 | > = (set) => ({ 17 | loadingListCache: true, 18 | setLoadingListCache: (value) => set({ loadingListCache: value }), 19 | 20 | cachedProposalsIds: [], 21 | setCachedProposalsIds: (value) => set({ cachedProposalsIds: value }), 22 | cachedProposals: [], 23 | setCachedProposals: (value) => set({ cachedProposals: value }), 24 | }); 25 | -------------------------------------------------------------------------------- /public/images/icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/proposals/components/VotedState.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/system'; 2 | 3 | import { texts } from '../../ui/utils/texts'; 4 | 5 | interface VotedStateProps { 6 | support: boolean; 7 | inProcess?: boolean; 8 | isBig?: boolean; 9 | css?: SxProps; 10 | } 11 | 12 | export function VotedState({ 13 | support, 14 | isBig, 15 | inProcess, 16 | css, 17 | }: VotedStateProps) { 18 | return ( 19 | 22 | {inProcess ? texts.proposals.voting : texts.proposals.voted}{' '} 23 | 34 | {support ? texts.other.toggleFor : texts.other.toggleAgainst} 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/components/TopPanelContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | import { Container } from '../index'; 5 | 6 | interface TopPanelContainerProps { 7 | children: ReactNode; 8 | withoutContainer?: boolean; 9 | } 10 | 11 | export function TopPanelContainer({ 12 | children, 13 | withoutContainer, 14 | }: TopPanelContainerProps) { 15 | return ( 16 | <> 17 | {!withoutContainer ? ( 18 | 19 | ({ 21 | mb: 18, 22 | [theme.breakpoints.up('sm')]: { 23 | mb: 24, 24 | }, 25 | })}> 26 | {children} 27 | 28 | 29 | ) : ( 30 | ({ 32 | mb: 18, 33 | [theme.breakpoints.up('sm')]: { 34 | mb: 24, 35 | }, 36 | })}> 37 | {children} 38 | 39 | )} 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/proposals/components/timeline/TimelineItemStateWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | export function TimelineItemStateWrapper({ 5 | children, 6 | color, 7 | }: { 8 | children: ReactNode; 9 | color: 'success' | 'error' | 'expired' | 'secondary'; 10 | }) { 11 | return ( 12 | ({ 15 | position: 'absolute', 16 | top: 'calc(100% + 36px)', 17 | color: 18 | color === 'success' 19 | ? theme.palette.$mainFor 20 | : color === 'error' 21 | ? theme.palette.$mainAgainst 22 | : color === 'expired' 23 | ? theme.palette.$textDisabled 24 | : color === 'secondary' 25 | ? theme.palette.$text 26 | : theme.palette.$text, 27 | [theme.breakpoints.up('lg')]: { 28 | top: 'calc(100% + 40px)', 29 | }, 30 | })}> 31 | {children} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/components/TableHeaderTitle.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | interface TableHeaderTitleProps { 5 | title?: string; 6 | right?: boolean; 7 | center?: boolean; 8 | css?: SxProps; 9 | } 10 | 11 | export function TableHeaderTitle({ 12 | title, 13 | center, 14 | right, 15 | css, 16 | }: TableHeaderTitleProps) { 17 | return ( 18 | 19 | ({ 22 | typography: 'h1', 23 | display: 'inline-flex', 24 | width: '100%', 25 | alignItems: 'center', 26 | justifyContent: center ? 'center' : right ? 'flex-end' : 'flex-start', 27 | [theme.breakpoints.up('sm')]: { 28 | typography: 'h1', 29 | height: 47, 30 | }, 31 | [theme.breakpoints.up('md')]: { 32 | typography: 'h1', 33 | height: 62, 34 | }, 35 | })}> 36 | {title} 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/web3/components/ChainsIcons.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | 3 | import { NetworkIcon } from '../../ui/components/NetworkIcon'; 4 | 5 | interface ChainsIconsProps { 6 | chains: number[]; 7 | alwaysVisible?: boolean; 8 | } 9 | 10 | export function ChainsIcons({ chains, alwaysVisible }: ChainsIconsProps) { 11 | const theme = useTheme(); 12 | 13 | if (chains.length <= 1 && !alwaysVisible) return null; 14 | 15 | return ( 16 | 18 | {chains.map((chainId, index) => ( 19 | 35 | ))} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/utils/metaTexts.ts: -------------------------------------------------------------------------------- 1 | export const metaTexts = { 2 | main: 'Aave Governance - ', 3 | keywords: 4 | 'governance, voting, decentralized Finance, defi, aave, ethereum, polygon, avalanche, optimism, arbitrum, base, metis, assets, erc-20, smart contracts, open finance, trustless', 5 | proposalListMetaDescription: 'List of governance proposals', 6 | createPageMetaTitle: 'Create', 7 | createPageMetaDescription: 'Create a new proposal (dev)', 8 | notFoundPageMetaTitle: 'Page not found', 9 | notFoundPageMetaDescription: '404, page not found', 10 | delegatePageMetaTitle: 'Delegation', 11 | delegatePageMetaDescription: 12 | 'Management of voting/proposition power delegations', 13 | representationsPageMetaTitle: 'Representations', 14 | representationsPageMetaDescription: 15 | 'Management of representations and representatives', 16 | rpcSwitcherPageMetaTitle: 'RPC Switcher', 17 | rpcSwitcherPageMetaDescription: 'Management of RPC URLs', 18 | proposalId: (id: string) => `Proposal ${id}`, 19 | ipfsTitle: 'Aave Governance', 20 | ipfsDescription: 'Vote on the Aave ecosystem', 21 | }; 22 | -------------------------------------------------------------------------------- /src/proposals/utils/statuses.ts: -------------------------------------------------------------------------------- 1 | import { ProposalStateWithName } from '@bgd-labs/aave-governance-ui-helpers'; 2 | 3 | export type ProposalStateForFilter = { 4 | value: number | null; 5 | title: ProposalStateWithName | 'All'; 6 | }; 7 | 8 | export const proposalStatuses = [ 9 | { 10 | value: 0, 11 | title: ProposalStateWithName.Created, 12 | }, 13 | { 14 | value: 1, 15 | title: ProposalStateWithName.Active, 16 | }, 17 | { 18 | value: 2, 19 | title: ProposalStateWithName.Succeed, 20 | }, 21 | { 22 | value: 3, 23 | title: ProposalStateWithName.Failed, 24 | }, 25 | { 26 | value: 4, 27 | title: ProposalStateWithName.Executed, 28 | }, 29 | { 30 | value: 5, 31 | title: ProposalStateWithName.Expired, 32 | }, 33 | { 34 | value: 6, 35 | title: ProposalStateWithName.Canceled, 36 | }, 37 | { 38 | value: 7, 39 | title: ProposalStateWithName.ActiveAll, 40 | }, 41 | ]; 42 | 43 | export const proposalStatusesForFilter: ProposalStateForFilter[] = [ 44 | ...proposalStatuses, 45 | { 46 | value: null, 47 | title: 'All', 48 | }, 49 | ]; 50 | -------------------------------------------------------------------------------- /src/ui/components/CheckBox.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | 3 | interface CheckBoxProps { 4 | value: boolean; 5 | } 6 | 7 | export function CheckBox({ value }: CheckBoxProps) { 8 | const theme = useTheme(); 9 | 10 | return ( 11 | 30 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/proposals/components/RepresentationIcon.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import RepresentationVotingPowerIcon from '/public/images/representation/representationVotingPower.svg'; 5 | 6 | import { IconBox } from '../../ui/primitives/IconBox'; 7 | 8 | interface RepresentationIconProps { 9 | address: string; 10 | disabled: boolean; 11 | } 12 | 13 | export function RepresentationIcon({ 14 | address, 15 | disabled, 16 | }: RepresentationIconProps) { 17 | const theme = useTheme(); 18 | 19 | if (!address) return null; 20 | 21 | const color = disabled ? theme.palette.$textDisabled : theme.palette.$text; 22 | 23 | return ( 24 | svg': { 30 | width: 14, 31 | height: 14, 32 | path: { stroke: color }, 33 | '.borders': { 34 | stroke: color, 35 | }, 36 | '.filled': { 37 | fill: color, 38 | stroke: color, 39 | }, 40 | }, 41 | }}> 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/ui/utils/inputValidation.ts: -------------------------------------------------------------------------------- 1 | import { isAddress } from 'viem'; 2 | 3 | import { texts } from './texts'; 4 | 5 | export const required = (value: any) => 6 | value || value === 0 ? undefined : texts.other.requiredValidation; 7 | 8 | export const addressValidator = (value: any) => 9 | isAddress(value) || value === '' || value === undefined 10 | ? undefined 11 | : texts.other.addressValidation; 12 | 13 | export const ensNameOrAddressValidator = (value: string) => 14 | (value && (value.endsWith('.eth') || isAddress(value))) || 15 | value === '' || 16 | value === undefined 17 | ? undefined 18 | : texts.other.ensNameValidation; 19 | 20 | export const composeValidators = 21 | (...validators: any) => 22 | (value: any) => 23 | validators.reduce( 24 | (error: any, validator: any) => error || validator(value), 25 | undefined, 26 | ); 27 | 28 | export const rpcUrlValidator = (value: string) => { 29 | if (value && value.startsWith('https://')) { 30 | try { 31 | new URL(value); 32 | return undefined; 33 | } catch { 34 | return texts.other.rpcUrlValidation; 35 | } 36 | } 37 | return texts.other.rpcUrlValidation; 38 | }; 39 | -------------------------------------------------------------------------------- /public/images/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ui/layouts/AppGlobalStyles.tsx: -------------------------------------------------------------------------------- 1 | import { EmotionCache } from '@emotion/cache'; 2 | import { CacheProvider } from '@emotion/react'; 3 | import { ThemeProvider as PreferredThemeProvider } from 'next-themes'; 4 | import React, { ReactNode, useEffect } from 'react'; 5 | 6 | import { useStore } from '../../store/ZustandStoreProvider'; 7 | import { createEmotionCache } from '../utils/themeMUI'; 8 | import { MUIThemeProvider } from './MUIThemeProvider'; 9 | 10 | // Client-side cache, shared for the whole session of the user in the browser. 11 | const clientSideEmotionCache = createEmotionCache(); 12 | 13 | export function AppGlobalStyles({ 14 | children, 15 | emotionCache = clientSideEmotionCache, 16 | }: { 17 | children: ReactNode; 18 | emotionCache?: EmotionCache; 19 | }) { 20 | const setIsRendered = useStore((store) => store.setIsRendered); 21 | useEffect(() => setIsRendered(), []); 22 | 23 | return ( 24 | 27 | 28 | {children} 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/ui/components/CopyToClipboard.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { ReactNode, useState } from 'react'; 3 | 4 | import { texts } from '../utils/texts'; 5 | import { Tooltip } from './Tooltip'; 6 | 7 | interface CopyToClipboardProps { 8 | copyText: string; 9 | copyTooltipText?: string; 10 | children: ReactNode; 11 | } 12 | 13 | export function CopyToClipboard({ 14 | copyText, 15 | copyTooltipText, 16 | children, 17 | }: CopyToClipboardProps) { 18 | const [copied, setCopied] = useState(false); 19 | 20 | const handleCopy = async (event: React.MouseEvent) => { 21 | event.preventDefault(); 22 | await navigator.clipboard.writeText(copyText); 23 | setCopied(true); 24 | setTimeout(() => setCopied(false), 1000); 25 | }; 26 | 27 | return ( 28 | 29 | 33 | {copied ? texts.other.copied : copyTooltipText || texts.other.copy} 34 | 35 | }> 36 | {children} 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/ui/components/Web3Icons/WalletIcon.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Web3Icon } from '@bgd-labs/react-web3-icons'; 4 | import { Box, SxProps } from '@mui/system'; 5 | 6 | import { CustomSkeleton } from '../CustomSkeleton'; 7 | 8 | interface WalletIconProps { 9 | walletName: string; 10 | size?: number; 11 | css?: SxProps; 12 | } 13 | 14 | /** 15 | * Renders a wallet icon specified by walletName. 16 | */ 17 | const WalletIcon = ({ walletName, size, css, ...props }: WalletIconProps) => { 18 | return ( 19 | 26 | 38 | 39 | 40 | } 41 | {...props} 42 | /> 43 | 44 | ); 45 | }; 46 | 47 | export default WalletIcon; 48 | -------------------------------------------------------------------------------- /src/proposals/components/ProposalStatus.tsx: -------------------------------------------------------------------------------- 1 | import { CombineProposalState } from '@bgd-labs/aave-governance-ui-helpers'; 2 | import { Box } from '@mui/system'; 3 | 4 | import { proposalStatuses } from '../utils/statuses'; 5 | 6 | export interface ProposalStatusProps { 7 | status: CombineProposalState; 8 | isSecondary?: boolean; 9 | isFinished?: boolean; 10 | } 11 | 12 | export function ProposalStatus({ 13 | status, 14 | isFinished, 15 | isSecondary, 16 | }: ProposalStatusProps) { 17 | const statusTitle = proposalStatuses.find((s) => s.value === status)?.title; 18 | 19 | return ( 20 | 38 | {statusTitle} 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/proposals/components/DetailsModalWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | import { BasicModal, Divider } from '../../ui'; 5 | import { texts } from '../../ui/utils/texts'; 6 | 7 | interface DetailsModalWrapperProps { 8 | isOpen: boolean; 9 | setIsOpen: (value: boolean) => void; 10 | proposalId: number; 11 | children: ReactNode; 12 | } 13 | 14 | export function DetailsModalWrapper({ 15 | proposalId, 16 | isOpen, 17 | setIsOpen, 18 | children, 19 | }: DetailsModalWrapperProps) { 20 | return ( 21 | 26 | ({ [theme.breakpoints.up('sm')]: { pt: 20 } })}> 27 | 28 | {texts.proposals.detailsModalTitle(proposalId)} 29 | 30 | 31 | ({ 33 | mt: 18, 34 | mb: 24, 35 | [theme.breakpoints.up('lg')]: { mt: 24, mb: 30 }, 36 | })} 37 | /> 38 | 39 | {children} 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/delegate/utils/getFormDelegateData.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | import { DelegateData } from '../types'; 4 | 5 | interface GetFormDelegateDataParams { 6 | underlyingAsset?: Address; 7 | votingToAddress?: Address | string; 8 | propositionToAddress?: Address | string; 9 | formDelegateData?: DelegateData[]; 10 | } 11 | 12 | export function getFormDelegateData({ 13 | underlyingAsset, 14 | votingToAddress, 15 | propositionToAddress, 16 | formDelegateData, 17 | }: GetFormDelegateDataParams) { 18 | const formDelegatedDataItem = 19 | !!formDelegateData && 20 | !!formDelegateData.length && 21 | formDelegateData.find((item) => item.underlyingAsset === underlyingAsset); 22 | 23 | const formVotingToAddress = 24 | typeof formDelegatedDataItem !== 'boolean' && 25 | typeof formDelegatedDataItem !== 'undefined' 26 | ? formDelegatedDataItem.votingToAddress 27 | : votingToAddress; 28 | 29 | const formPropositionToAddress = 30 | typeof formDelegatedDataItem !== 'boolean' && 31 | typeof formDelegatedDataItem !== 'undefined' 32 | ? formDelegatedDataItem.propositionToAddress 33 | : propositionToAddress; 34 | 35 | return { 36 | formVotingToAddress, 37 | formPropositionToAddress, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/ui/utils/useMediaQuery.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useMediaQuery(query: string): boolean { 4 | const getMatches = (query: string): boolean => { 5 | // Prevents SSR issues 6 | if (typeof window !== 'undefined') { 7 | return window.matchMedia(query).matches; 8 | } 9 | return false; 10 | }; 11 | 12 | const [matches, setMatches] = useState(getMatches(query)); 13 | 14 | function handleChange() { 15 | setMatches(getMatches(query)); 16 | } 17 | 18 | useEffect(() => { 19 | const matchMedia = window.matchMedia(query); 20 | 21 | // Triggered at the first client-side load and if query changes 22 | handleChange(); 23 | 24 | // Listen matchMedia 25 | if (matchMedia.addListener) { 26 | matchMedia.addListener(handleChange); 27 | } else { 28 | matchMedia.addEventListener('change', handleChange); 29 | } 30 | 31 | return () => { 32 | if (matchMedia.removeListener) { 33 | matchMedia.removeListener(handleChange); 34 | } else { 35 | matchMedia.removeEventListener('change', handleChange); 36 | } 37 | }; 38 | // eslint-disable-next-line react-hooks/exhaustive-deps 39 | }, [query]); 40 | 41 | return matches; 42 | } 43 | -------------------------------------------------------------------------------- /src/proposals/components/proposalList/VotingPowerLoading.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { CustomSkeleton } from '../../../ui/components/CustomSkeleton'; 5 | 6 | export function VotingPowerLoading() { 7 | const theme = useTheme(); 8 | return ( 9 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/ui/components/TableContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | import { BoxWith3D } from './BoxWith3D'; 5 | 6 | export const TableContainerChildren = ({ 7 | children, 8 | forHelp, 9 | }: { 10 | children: ReactNode; 11 | forHelp?: boolean; 12 | }) => { 13 | return ( 14 | 20 | {children} 21 | 22 | ); 23 | }; 24 | 25 | interface TableContainerProps { 26 | forHelp?: boolean; 27 | children: ReactNode; 28 | } 29 | 30 | export function TableContainer({ forHelp, children }: TableContainerProps) { 31 | const theme = useTheme(); 32 | 33 | return ( 34 | 49 | {children} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/appConfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | appConfigInit, 3 | CoreNetworkName, 4 | payloadsControllerChainIds, 5 | votingMachineChainIds, 6 | } from '@bgd-labs/aave-governance-ui-helpers'; 7 | import { avalanche, goerli, polygon, sepolia } from 'viem/chains'; 8 | 9 | export const isForIPFS = process.env.NEXT_PUBLIC_DEPLOY_FOR_IPFS === 'true'; 10 | export const isTermsAndConditionsVisible = 11 | process.env.NEXT_PUBLIC_TERMS_AND_CONDITIONS_VISIBLE === 'true'; 12 | 13 | export const coreName: CoreNetworkName = 'mainnet'; 14 | export const WC_PROJECT_ID = 15 | process.env.WC_PROJECT_ID || 'e6ed0c48443e54cc875462bbaec6e3a7'; // https://docs.walletconnect.com/2.0/cloud/relay 16 | 17 | export const appUsedNetworks: number[] = [ 18 | ...votingMachineChainIds[coreName], 19 | ...payloadsControllerChainIds[coreName], 20 | ].filter((value, index, self) => self.indexOf(value) === index); 21 | 22 | export const gelatoApiKeys: Record = { 23 | [polygon.id]: 'eyUjscMpge_d3qScFe2ueftb95FDZ1eChyDJGqPx2uQ_', 24 | [avalanche.id]: 'iGCOci5z6zZYDDQ1p916xsKmr2pfP5o1EAQcRhy1_fI_', 25 | // testnets 26 | [goerli.id]: 'MgZBKc6a7GHzxlrkdHCWIsazai_Niqbps42wvPlE7xE_', 27 | [sepolia.id]: 'MgZBKc6a7GHzxlrkdHCWIsazai_Niqbps42wvPlE7xE_', 28 | }; 29 | 30 | export const appConfig = appConfigInit(coreName); 31 | -------------------------------------------------------------------------------- /src/store/ZustandStoreProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { createContext, type ReactNode, useContext, useRef } from 'react'; 4 | import { create, type StoreApi, useStore as useZustandStore } from 'zustand'; 5 | 6 | import { createRootSlice, RootState } from './index'; 7 | 8 | // provider with zustand store https://docs.pmnd.rs/zustand/guides/nextjs 9 | export const ZustandStoreContext = createContext | null>( 10 | null, 11 | ); 12 | 13 | export interface ZustandStoreProviderProps { 14 | children: ReactNode; 15 | } 16 | 17 | export const ZustandStoreProvider = ({ 18 | children, 19 | }: ZustandStoreProviderProps) => { 20 | const storeRef = useRef>(); 21 | 22 | if (!storeRef.current) { 23 | storeRef.current = create(createRootSlice); 24 | } 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export const useStore = (selector: (store: RootState) => T): T => { 34 | const zustandStoreContext = useContext(ZustandStoreContext); 35 | 36 | if (!zustandStoreContext) { 37 | throw new Error(`useStore must be use within ZustandStoreProvider`); 38 | } 39 | 40 | return useZustandStore(zustandStoreContext, selector); 41 | }; 42 | -------------------------------------------------------------------------------- /src/ui/components/Web3Icons/ChainIcon.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Web3Icon } from '@bgd-labs/react-web3-icons'; 4 | import { chainsIconsPack } from '@bgd-labs/react-web3-icons/dist/iconsPacks/chainsIconsPack'; 5 | import { Box, SxProps } from '@mui/system'; 6 | 7 | import { CustomSkeleton } from '../CustomSkeleton'; 8 | 9 | interface ChainIconProps { 10 | chainId: number; 11 | size?: number; 12 | css?: SxProps; 13 | } 14 | /** 15 | * Renders a chain icon specified by chainId. 16 | */ 17 | const ChainIcon = ({ chainId, size, css, ...props }: ChainIconProps) => { 18 | return ( 19 | 27 | 40 | 41 | 42 | } 43 | {...props} 44 | /> 45 | 46 | ); 47 | }; 48 | 49 | export default ChainIcon; 50 | -------------------------------------------------------------------------------- /src/ui/utils/useScrollDirection.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { media } from './themeMUI'; 4 | import { useMediaQuery } from './useMediaQuery'; 5 | 6 | export function useScrollDirection() { 7 | const sm = useMediaQuery(media.sm); 8 | 9 | const [scrollDirection, setScrollDirection] = useState<'down' | 'up' | null>( 10 | null, 11 | ); 12 | 13 | let lastScrollY = typeof window !== 'undefined' ? window.pageYOffset : 0; 14 | 15 | const updateScrollDirection = () => { 16 | if (typeof window !== 'undefined') { 17 | const scrollY = window.pageYOffset; 18 | const direction = scrollY > lastScrollY ? 'down' : 'up'; 19 | if ( 20 | direction !== scrollDirection && 21 | (scrollY - lastScrollY > 0 || scrollY - lastScrollY < 0) 22 | ) { 23 | setScrollDirection(direction); 24 | } 25 | lastScrollY = scrollY > 0 ? scrollY : 0; 26 | } 27 | }; 28 | 29 | useEffect(() => { 30 | if (typeof window !== 'undefined' && !sm) { 31 | window.addEventListener('scroll', updateScrollDirection); // add event listener 32 | } 33 | return () => { 34 | if (typeof window !== 'undefined') { 35 | window.removeEventListener('scroll', updateScrollDirection); // clean up 36 | } 37 | }; 38 | }, [scrollDirection]); 39 | 40 | return { scrollDirection }; 41 | } 42 | -------------------------------------------------------------------------------- /src/proposals/components/VoteLine.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | 3 | export interface VoteLineProps { 4 | percent: number; 5 | color?: 'for' | 'against'; 6 | width?: string | number; 7 | } 8 | 9 | export function VoteLine({ 10 | percent, 11 | color = 'for', 12 | width = 164, 13 | }: VoteLineProps) { 14 | return ( 15 | ({ 18 | display: 'inline-flex', 19 | alignItems: 'center', 20 | position: 'relative', 21 | height: 8, 22 | width: width, 23 | backgroundColor: '$mainLight', 24 | borderColor: `${theme.palette.$mainElements} !important`, 25 | borderStyle: 'solid', 26 | borderWidth: '1px', 27 | borderTopWidth: 2, 28 | borderRightWidth: 2, 29 | [theme.breakpoints.up('lg')]: { 30 | height: 10, 31 | borderTopWidth: 3, 32 | borderRightWidth: 3, 33 | }, 34 | })}> 35 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/proposals/components/BlockWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps, useTheme } from '@mui/system'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | import { BoxWith3D } from '../../ui'; 5 | 6 | interface BlockWrapperProps { 7 | children: ReactNode; 8 | contentColor?: string; 9 | leftBorderColor?: string; 10 | bottomBorderColor?: string; 11 | css?: SxProps; 12 | toBottom?: boolean; 13 | } 14 | 15 | export function BlockWrapper({ 16 | children, 17 | contentColor, 18 | leftBorderColor, 19 | bottomBorderColor, 20 | css, 21 | toBottom, 22 | }: BlockWrapperProps) { 23 | const theme = useTheme(); 24 | 25 | return ( 26 | 42 | 48 | {children} 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /app/layout.page.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from 'next/font/google'; 2 | import NextTopLoader from 'nextjs-toploader'; 3 | import React from 'react'; 4 | 5 | import { ZustandStoreProvider } from '../src/store/ZustandStoreProvider'; 6 | import AppLayout from '../src/ui/layouts/AppLayout'; 7 | import ThemeRegistry from '../src/ui/utils/ThemeRegistry'; 8 | import WagmiProvider from '../src/web3/providers/WagmiProvider'; 9 | 10 | export const interNextFont = Inter({ 11 | weight: ['300', '400', '600', '700', '800'], 12 | style: ['normal'], 13 | subsets: ['latin'], 14 | display: 'swap', 15 | }); 16 | 17 | export const metadata = { 18 | metadataBase: process.env.VERCEL_URL 19 | ? `https://${process.env.VERCEL_URL}` 20 | : `http://localhost:${process.env.PORT || 3000}`, 21 | manifest: '/manifest.json', 22 | icons: { 23 | icon: '/favicon.ico', 24 | }, 25 | }; 26 | 27 | export default function RootLayout({ 28 | children, 29 | }: { 30 | children: React.ReactNode; 31 | }) { 32 | return ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {children} 41 | 42 | 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/proposalCreateOverview/components/SeatBeltReportModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React, { useRef } from 'react'; 3 | 4 | import { BasicModal } from '../../ui'; 5 | import { CopyAndExternalIconsSet } from '../../ui/components/CopyAndExternalIconsSet'; 6 | import { MarkdownContainer } from '../../ui/components/MarkdownContainer'; 7 | 8 | interface SeatBeltReportModalProps { 9 | isOpen: boolean; 10 | setIsOpen: (value: boolean) => void; 11 | link: string; 12 | report?: string; 13 | } 14 | 15 | export function SeatBeltReportModal({ 16 | isOpen, 17 | setIsOpen, 18 | link, 19 | report, 20 | }: SeatBeltReportModalProps) { 21 | const initialFocusRef = useRef(null); 22 | 23 | if (!report) return null; 24 | 25 | return ( 26 | 32 | 38 | Seatbelt Report 39 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/ui/components/CustomSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | import Skeleton, { SkeletonProps } from 'react-loading-skeleton'; 4 | 5 | import { useStore } from '../../store/ZustandStoreProvider'; 6 | 7 | interface CustomSkeletonProps extends SkeletonProps { 8 | variant?: 'default' | 'dark'; 9 | } 10 | 11 | export function CustomSkeleton({ 12 | variant = 'default', 13 | ...rest 14 | }: CustomSkeletonProps) { 15 | const isRendered = useStore((store) => store.isRendered); 16 | 17 | return ( 18 | ({ 20 | '.react-loading-skeleton': { 21 | backgroundColor: isRendered 22 | ? `${theme.palette.$light} !important` 23 | : theme.palette.$light, 24 | 25 | '&:after': { 26 | backgroundImage: `linear-gradient(90deg, ${ 27 | isRendered 28 | ? `${theme.palette.$light} !important` 29 | : theme.palette.$light 30 | }, ${ 31 | isRendered 32 | ? `${theme.palette.$middleLight} !important` 33 | : theme.palette.$middleLight 34 | }, ${ 35 | isRendered 36 | ? `${theme.palette.$light} !important` 37 | : theme.palette.$light 38 | })`, 39 | }, 40 | }, 41 | })}> 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/ui/components/Web3Icons/AssetIcon.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers'; 4 | import { Web3Icon } from '@bgd-labs/react-web3-icons'; 5 | import { Box, SxProps } from '@mui/system'; 6 | 7 | import { CustomSkeleton } from '../CustomSkeleton'; 8 | 9 | /** 10 | * Renders an asset icon specified by symbol. 11 | */ 12 | const AssetIcon = ({ 13 | symbol, 14 | size, 15 | css, 16 | ...props 17 | }: { 18 | symbol: string; 19 | size?: number; 20 | css?: SxProps; 21 | }) => { 22 | return ( 23 | 34 | 47 | 48 | 49 | } 50 | {...props} 51 | /> 52 | 53 | ); 54 | }; 55 | 56 | export default AssetIcon; 57 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/ActivateVotingModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { useStore } from '../../../store/ZustandStoreProvider'; 5 | import { ActionModal } from '../../../transactions/components/ActionModal'; 6 | import { TxType } from '../../../transactions/store/transactionsSlice'; 7 | import { texts } from '../../../ui/utils/texts'; 8 | import { 9 | ActionModalContentWrapper, 10 | ActionModalTitle, 11 | } from './ActionModalContentWrapper'; 12 | import { ActionModalBasicTypes } from './types'; 13 | 14 | export function ActivateVotingModal({ 15 | isOpen, 16 | setIsOpen, 17 | proposalId, 18 | }: ActionModalBasicTypes) { 19 | const activateVoting = useStore((state) => state.activateVoting); 20 | 21 | return ( 22 | await activateVoting(proposalId)} 26 | isOpen={isOpen} 27 | setIsOpen={setIsOpen} 28 | actionButtonTitle={texts.other.confirm} 29 | topBlock={ 30 | 31 | } 32 | withCancelButton> 33 | 34 | 35 | {texts.proposalActions.activateVotingDescription} 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/ExecuteProposalModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { useStore } from '../../../store/ZustandStoreProvider'; 5 | import { ActionModal } from '../../../transactions/components/ActionModal'; 6 | import { TxType } from '../../../transactions/store/transactionsSlice'; 7 | import { texts } from '../../../ui/utils/texts'; 8 | import { 9 | ActionModalContentWrapper, 10 | ActionModalTitle, 11 | } from './ActionModalContentWrapper'; 12 | import { ActionModalBasicTypes } from './types'; 13 | 14 | export function ExecuteProposalModal({ 15 | isOpen, 16 | setIsOpen, 17 | proposalId, 18 | }: ActionModalBasicTypes) { 19 | const executeProposal = useStore((state) => state.executeProposal); 20 | 21 | return ( 22 | await executeProposal(proposalId)} 26 | isOpen={isOpen} 27 | setIsOpen={setIsOpen} 28 | actionButtonTitle={texts.other.confirm} 29 | topBlock={ 30 | 31 | } 32 | withCancelButton> 33 | 34 | 35 | {texts.proposalActions.executeProposalDescription} 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/web3/providers/Web3HelperProvider.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import { useStore } from '../../store/ZustandStoreProvider'; 4 | import { appUsedNetworks } from '../../utils/appConfig'; 5 | import { chainInfoHelper } from '../../utils/configs'; 6 | 7 | function Child() { 8 | const activeWallet = useStore((state) => state.activeWallet); 9 | const resetL1Balances = useStore((state) => state.resetL1Balances); 10 | const fullClearSupportObject = useStore( 11 | (state) => state.fullClearSupportObject, 12 | ); 13 | const getRepresentingAddress = useStore( 14 | (state) => state.getRepresentingAddress, 15 | ); 16 | const representationData = useStore((state) => state.representationData); 17 | const getRepresentationData = useStore( 18 | (state) => state.getRepresentationData, 19 | ); 20 | const initEns = useStore((state) => state.initEns); 21 | const initClients = useStore((state) => state.initClients); 22 | 23 | useEffect(() => { 24 | initEns(); 25 | initClients(chainInfoHelper, appUsedNetworks); 26 | }, []); 27 | 28 | useEffect(() => { 29 | resetL1Balances(); 30 | fullClearSupportObject(); 31 | setTimeout(() => getRepresentationData(), 1); 32 | }, [activeWallet?.address]); 33 | 34 | useEffect(() => { 35 | getRepresentingAddress(); 36 | }, [activeWallet?.address, representationData]); 37 | 38 | return null; 39 | } 40 | 41 | export default function Web3HelperProvider() { 42 | return ; 43 | } 44 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'react-loading-skeleton/dist/skeleton.css'; 2 | 3 | import { EmotionCache } from '@emotion/cache'; 4 | import { NextPage } from 'next'; 5 | import type { AppProps } from 'next/app'; 6 | import { useRouter } from 'next/router'; 7 | import React, { useMemo } from 'react'; 8 | 9 | import { ZustandStoreProvider } from '../src/store/ZustandStoreProvider'; 10 | import { AppGlobalStyles } from '../src/ui'; 11 | import AppLayout from '../src/ui/layouts/AppLayout'; 12 | import WagmiProvider from '../src/web3/providers/WagmiProvider'; 13 | 14 | type NextPageWithLayout = NextPage & { 15 | getLayout?: (page: React.ReactElement) => React.ReactNode; 16 | }; 17 | 18 | interface GovernanceAppProps extends AppProps { 19 | Component: NextPageWithLayout; 20 | emotionCache?: EmotionCache; 21 | } 22 | 23 | function GovernanceApp({ 24 | Component, 25 | pageProps, 26 | emotionCache, 27 | }: GovernanceAppProps) { 28 | const router = useRouter(); 29 | 30 | const getLayout = Component.getLayout ?? ((page: React.ReactNode) => page); 31 | 32 | useMemo(() => { 33 | router.prefetch = async () => {}; 34 | }, [router]); 35 | 36 | return ( 37 | <> 38 | 39 | 40 | 41 | 42 | {getLayout()} 43 | 44 | 45 | 46 | ); 47 | } 48 | 49 | export default GovernanceApp; 50 | -------------------------------------------------------------------------------- /src/web3/utils/ensHelpers.tsx: -------------------------------------------------------------------------------- 1 | import makeBlockie from 'ethereum-blockies-base64'; 2 | import { Address, Hex, isAddress } from 'viem'; 3 | import { mainnet } from 'viem/chains'; 4 | import { getEnsAddress, getEnsAvatar, getEnsName, normalize } from 'viem/ens'; 5 | 6 | import { chainInfoHelper } from '../../utils/configs'; 7 | 8 | const client = chainInfoHelper.clientInstances[mainnet.id].instance; 9 | 10 | export const getName = async (address: Hex) => { 11 | try { 12 | const name = await getEnsName(client, { address }); 13 | return name ? name : undefined; 14 | } catch (error) { 15 | console.error('ENS name lookup error', error); 16 | } 17 | }; 18 | 19 | export const getAvatar = async (name: string, address: string) => { 20 | try { 21 | const background_image = await getEnsAvatar(client, { name }); 22 | return background_image ? background_image : makeBlockie(address); 23 | } catch (error) { 24 | console.error('ENS avatar lookup error', error); 25 | } 26 | }; 27 | 28 | export const getAddress = async (name: string) => { 29 | try { 30 | const address = await getEnsAddress(client, { 31 | name: normalize(name), 32 | }); 33 | return (address ? address.toLocaleLowerCase() : undefined) as 34 | | Address 35 | | undefined; 36 | } catch (error) { 37 | console.error('ENS address lookup error', error); 38 | } 39 | }; 40 | 41 | export const isEnsName = (address: string) => !isAddress(address); 42 | 43 | export const ENS_TTL = 60 * 60 * 24; // 1 day 44 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/CloseVotingModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { useStore } from '../../../store/ZustandStoreProvider'; 5 | import { ActionModal } from '../../../transactions/components/ActionModal'; 6 | import { TxType } from '../../../transactions/store/transactionsSlice'; 7 | import { texts } from '../../../ui/utils/texts'; 8 | import { 9 | ActionModalContentWrapper, 10 | ActionModalTitle, 11 | } from './ActionModalContentWrapper'; 12 | import { ActionModalBasicTypes } from './types'; 13 | 14 | export function CloseVotingModal({ 15 | votingChainId, 16 | isOpen, 17 | setIsOpen, 18 | proposalId, 19 | }: { votingChainId: number } & ActionModalBasicTypes) { 20 | const closeAndSendVote = useStore((state) => state.closeAndSendVote); 21 | 22 | return ( 23 | 27 | await closeAndSendVote(votingChainId, proposalId) 28 | } 29 | isOpen={isOpen} 30 | setIsOpen={setIsOpen} 31 | actionButtonTitle={texts.other.confirm} 32 | topBlock={} 33 | withCancelButton> 34 | 35 | 36 | {texts.proposalActions.closeVotingDescription} 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/ui/components/NetworkIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/system'; 2 | import { useEffect, useState } from 'react'; 3 | import { toHex } from 'viem'; 4 | 5 | import { chainInfoHelper } from '../../utils/configs'; 6 | import { Tooltip } from './Tooltip'; 7 | import ChainIcon from './Web3Icons/ChainIcon'; 8 | 9 | interface NetworkIconProps { 10 | chainId: number; 11 | size?: number; 12 | css?: SxProps; 13 | withTooltip?: boolean; 14 | } 15 | 16 | export function NetworkIcon({ 17 | chainId, 18 | size, 19 | css, 20 | withTooltip, 21 | }: NetworkIconProps) { 22 | const [chain, setChain] = useState( 23 | chainInfoHelper.getChainParameters(chainId), 24 | ); 25 | 26 | useEffect(() => { 27 | if (chainId) { 28 | setChain(chainInfoHelper.getChainParameters(chainId)); 29 | } 30 | }, [chainId]); 31 | 32 | return ( 33 | <> 34 | {withTooltip ? ( 35 | 45 | {chain.name}: {chain.id}
({toHex(chain.id)}) 46 | 47 | }> 48 | 49 |
50 | ) : ( 51 | 52 | )} 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /public/images/icons/reload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/utils/githubCacheRequests.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CachedDetails, 3 | FinishedProposalForList, 4 | ProposalHistoryItem, 5 | VotersData, 6 | } from '@bgd-labs/aave-governance-ui-helpers'; 7 | import { createAlova } from 'alova'; 8 | import GlobalFetch from 'alova/GlobalFetch'; 9 | import ReactHook from 'alova/react'; 10 | 11 | import { 12 | cachedDetailsPath, 13 | cachedEventsPath, 14 | cachedProposalsIdsPath, 15 | cachedVotesPath, 16 | githubStartUrl, 17 | listViewPath, 18 | } from './cacheGithubLinks'; 19 | 20 | const alovaInstance = createAlova({ 21 | baseURL: githubStartUrl, 22 | statesHook: ReactHook, 23 | requestAdapter: GlobalFetch(), 24 | responded: { 25 | onSuccess: async (response) => { 26 | const data = await response.json(); 27 | return data ? data : undefined; 28 | }, 29 | }, 30 | }); 31 | 32 | export const getProposalListCacheFromGithub = alovaInstance.Get<{ 33 | totalProposalCount: number; 34 | proposals: FinishedProposalForList[]; 35 | }>(listViewPath); 36 | 37 | export const getCachedProposalsIdsFromGithub = alovaInstance.Get<{ 38 | cachedProposalsIds: number[]; 39 | }>(cachedProposalsIdsPath); 40 | 41 | export const getProposalDetailsCache = (id: number) => 42 | alovaInstance.Get(cachedDetailsPath(id)); 43 | 44 | export const getProposalVotesCache = (id: number) => 45 | alovaInstance.Get<{ votes: VotersData[] }>(cachedVotesPath(id)); 46 | 47 | export const getProposalEventsCache = (id: number) => 48 | alovaInstance.Get>(cachedEventsPath(id)); 49 | -------------------------------------------------------------------------------- /public/images/icons/replacedIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ui/index.ts: -------------------------------------------------------------------------------- 1 | import { BackButton3D } from './components/BackButton3D'; 2 | import { BasicModal } from './components/BasicModal'; 3 | import { BigButton } from './components/BigButton'; 4 | import { BoxWith3D } from './components/BoxWith3D'; 5 | import { CopyToClipboard } from './components/CopyToClipboard'; 6 | import { FilterDropdown } from './components/FilterDropdown'; 7 | import { Link } from './components/Link'; 8 | import { Pagination } from './components/Pagination'; 9 | import { SmallButton } from './components/SmallButton'; 10 | import { Spinner } from './components/Spinner'; 11 | import { ThemeSwitcher } from './components/ThemeSwitcher'; 12 | import { Timer } from './components/Timer'; 13 | import { ToggleButton } from './components/ToggleButton'; 14 | import { Tooltip } from './components/Tooltip'; 15 | import { AppGlobalStyles } from './layouts/AppGlobalStyles'; 16 | import { MainLayout } from './layouts/MainLayout'; 17 | import { Container } from './primitives/Container'; 18 | import { Divider } from './primitives/Divider'; 19 | import { Image } from './primitives/Image'; 20 | import { Input } from './primitives/Input'; 21 | import NoSSR from './primitives/NoSSR'; 22 | 23 | export { 24 | AppGlobalStyles, 25 | BackButton3D, 26 | BasicModal, 27 | BigButton, 28 | BoxWith3D, 29 | Container, 30 | CopyToClipboard, 31 | Divider, 32 | FilterDropdown, 33 | Image, 34 | Input, 35 | Link, 36 | MainLayout, 37 | NoSSR, 38 | Pagination, 39 | SmallButton, 40 | Spinner, 41 | ThemeSwitcher, 42 | Timer, 43 | ToggleButton, 44 | Tooltip, 45 | }; 46 | -------------------------------------------------------------------------------- /src/ui/helpModals/HelpModalWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | import { useStore } from '../../store/ZustandStoreProvider'; 5 | import { BasicModal } from '../components/BasicModal'; 6 | 7 | interface HelpModalWrapperProps { 8 | image: ReactNode; 9 | children: ReactNode; 10 | isOpen?: boolean; 11 | setIsOpen?: (value: boolean) => void; 12 | onBackButtonClick?: () => void; 13 | } 14 | 15 | export function HelpModalWrapper({ 16 | image, 17 | children, 18 | isOpen, 19 | setIsOpen, 20 | onBackButtonClick, 21 | }: HelpModalWrapperProps) { 22 | const isHelpModalOpen = useStore((store) => store.isHelpModalOpen); 23 | const setIsHelpModalOpen = useStore((store) => store.setIsHelpModalOpen); 24 | 25 | return ( 26 | 34 | 41 | {image} 42 | 47 | {children} 48 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/proposals/components/ProposalStatusWithDate.tsx: -------------------------------------------------------------------------------- 1 | import { ProposalWaitForState } from '@bgd-labs/aave-governance-ui-helpers'; 2 | import { Box, SxProps } from '@mui/system'; 3 | import dayjs from 'dayjs'; 4 | 5 | import { ProposalStatus, ProposalStatusProps } from './ProposalStatus'; 6 | 7 | export interface ProposalStatusWithDateProps extends ProposalStatusProps { 8 | timestamp: number; 9 | waitForState?: ProposalWaitForState; 10 | isFinished: boolean; 11 | css?: SxProps; 12 | } 13 | 14 | export function ProposalStatusWithDate({ 15 | status, 16 | timestamp, 17 | isSecondary, 18 | waitForState, 19 | isFinished, 20 | css, 21 | }: ProposalStatusWithDateProps) { 22 | return ( 23 | 26 | {waitForState && !isFinished ? ( 27 | 34 | {waitForState} 35 | 36 | ) : ( 37 | <> 38 | 43 | 46 | {dayjs.unix(timestamp).format('MMM D, YYYY')} 47 | 48 | 49 | )} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/ui/helpModals/getTestTransactions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionStatus, 3 | TxAdapter, 4 | WalletType, 5 | } from '@bgd-labs/frontend-web3-utils'; 6 | import { zeroAddress, zeroHash } from 'viem'; 7 | 8 | import { TransactionUnion } from '../../transactions/store/transactionsSlice'; 9 | 10 | export type TransactionItem = TransactionUnion & { 11 | status?: number | undefined; 12 | pending: boolean; 13 | walletType: WalletType; 14 | }; 15 | 16 | export const generateStatus = () => { 17 | if (Math.round(Math.random()) > 0) { 18 | return TransactionStatus.Success; 19 | } else { 20 | return TransactionStatus.Reverted; 21 | } 22 | }; 23 | 24 | export const makeTestTransaction = ( 25 | timestamp: number, 26 | pending: boolean, 27 | status?: TransactionStatus, 28 | ) => { 29 | return { 30 | adapter: TxAdapter.Ethereum, 31 | txKey: zeroHash, 32 | type: 'test', 33 | chainId: 1, 34 | from: zeroAddress, 35 | hash: zeroHash, 36 | nonce: timestamp, 37 | pending: pending, 38 | to: zeroAddress, 39 | timestamp: timestamp, 40 | localTimestamp: timestamp, 41 | walletType: WalletType.Injected, 42 | status, 43 | } as TransactionItem; 44 | }; 45 | 46 | export function getTestTransactionsPool() { 47 | const transactions = [...Array(5)].map((item, index) => { 48 | return makeTestTransaction(index, false, generateStatus()); 49 | }); 50 | 51 | const transactionsPool: Record = {}; 52 | transactions.forEach((tx) => { 53 | transactionsPool[tx.localTimestamp] = tx; 54 | }); 55 | 56 | return transactionsPool; 57 | } 58 | -------------------------------------------------------------------------------- /src/ui/helpModals/HelpModalHomeButton.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { BoxWith3D } from '../components/BoxWith3D'; 5 | import { setRelativePath } from '../utils/relativePath'; 6 | import { texts } from '../utils/texts'; 7 | 8 | export function HelpModalHomeButton() { 9 | const theme = useTheme(); 10 | 11 | return ( 12 | 26 | 44 | 51 | {texts.faq.other.mainMenu} 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/ui/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SxProps } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | interface TooltipProps { 5 | children: ReactNode; 6 | tooltipContent: ReactNode; 7 | color?: 'light' | 'dark'; 8 | position?: 'top'; 9 | tooltipCss?: SxProps; 10 | } 11 | 12 | export function Tooltip({ 13 | children, 14 | tooltipContent, 15 | color = 'light', 16 | position, 17 | tooltipCss, 18 | }: TooltipProps) { 19 | return ( 20 | 31 | {children} 32 | 33 | 52 | {tooltipContent} 53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/proposals/components/proposal/RightPanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import { ReactNode } from 'react'; 3 | 4 | import { BoxWith3D } from '../../../ui'; 5 | 6 | interface RightPanelWrapperProps { 7 | children: ReactNode; 8 | onlyChildren?: boolean; 9 | } 10 | 11 | export function RightPanelWrapper({ 12 | children, 13 | onlyChildren, 14 | }: RightPanelWrapperProps) { 15 | const theme = useTheme(); 16 | 17 | return ( 18 | <> 19 | {onlyChildren ? ( 20 | 34 | {children} 35 | 36 | ) : ( 37 | 55 | {children} 56 | 57 | )} 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/transactions/components/TransactionsModal.tsx: -------------------------------------------------------------------------------- 1 | import { selectAllTransactionsByWallet } from '@bgd-labs/frontend-web3-utils'; 2 | import React from 'react'; 3 | 4 | import { useStore } from '../../store/ZustandStoreProvider'; 5 | import { BasicModal } from '../../ui'; 6 | import { TransactionUnion, TxType } from '../store/transactionsSlice'; 7 | import { TransactionsModalContent } from './TransactionsModalContent'; 8 | 9 | interface TransactionsModalProps { 10 | isOpen: boolean; 11 | setIsOpen: (value: boolean) => void; 12 | } 13 | 14 | export function TransactionsModal({ 15 | isOpen, 16 | setIsOpen, 17 | }: TransactionsModalProps) { 18 | const setAccountInfoModalOpen = useStore( 19 | (store) => store.setAccountInfoModalOpen, 20 | ); 21 | const activeWallet = useStore((store) => store.activeWallet); 22 | const transactionsPool = useStore((store) => store.transactionsPool); 23 | 24 | const allTransactions = activeWallet 25 | ? selectAllTransactionsByWallet( 26 | transactionsPool, 27 | activeWallet.address, 28 | ) 29 | : []; 30 | 31 | return ( 32 | 37 | 41 | !!Object.keys(TxType).find((key) => key === tx.type)?.length, 42 | ) 43 | .sort((a, b) => b.localTimestamp - a.localTimestamp)} 44 | onBackButtonClick={() => { 45 | setIsOpen(false); 46 | setAccountInfoModalOpen(true); 47 | }} 48 | /> 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/transactions/components/CopyErrorButton.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import CopyIcon from '/public/images/icons/copy.svg'; 5 | 6 | import { CopyToClipboard } from '../../ui'; 7 | import { IconBox } from '../../ui/primitives/IconBox'; 8 | import { texts } from '../../ui/utils/texts'; 9 | 10 | interface CopyErrorButtonProps { 11 | errorMessage: Error | string; 12 | } 13 | 14 | export function CopyErrorButton({ errorMessage }: CopyErrorButtonProps) { 15 | const theme = useTheme(); 16 | 17 | return ( 18 | 19 | 36 | {texts.other.copyError} 37 | svg': { 43 | width: 9, 44 | height: 9, 45 | }, 46 | ml: 4, 47 | path: { 48 | transition: 'all 0.2s ease', 49 | stroke: theme.palette.$textSecondary, 50 | }, 51 | }}> 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /pages/proposal-create-overview.tsx: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from 'next/navigation'; 2 | // eslint-disable-next-line import/default 3 | import queryString from 'query-string'; 4 | import React from 'react'; 5 | import { Hex } from 'viem'; 6 | 7 | import { ProposalCreateOverviewPage } from '../src/proposalCreateOverview/components/ProposalCreateOverviewPage'; 8 | import { 9 | InitialParams, 10 | PayloadParams, 11 | } from '../src/proposalCreateOverview/types'; 12 | 13 | export default function ProposalCreateOverview() { 14 | const searchParams = useSearchParams(); 15 | 16 | if (!searchParams) return null; 17 | 18 | // params 19 | const proposalId = searchParams.get('proposalId') 20 | ? (Number(searchParams.get('proposalId')) as number) 21 | : undefined; 22 | const ipfsHash = searchParams.get('ipfsHash') 23 | ? (String(searchParams.get('ipfsHash')) as Hex) 24 | : undefined; 25 | const votingPortal = searchParams.get('votingPortal') 26 | ? (String(searchParams.get('votingPortal')) as Hex) 27 | : undefined; 28 | 29 | const payloads: Record = {}; 30 | Object.entries(queryString.parse(searchParams.toString())) 31 | .filter((value) => value[0].startsWith('payload[')) 32 | .forEach((value) => { 33 | const indexKey = Number(value[0].split('[')[1].split(']')[0]); 34 | const paramKey = value[0].split('.')[1]; 35 | 36 | payloads[indexKey] = { 37 | ...payloads[indexKey], 38 | [paramKey]: value[1], 39 | }; 40 | }); 41 | 42 | const initialParams: InitialParams = { 43 | proposalId, 44 | ipfsHash, 45 | votingPortal, 46 | payloads: Object.values(payloads), 47 | }; 48 | 49 | return ; 50 | } 51 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/ActivateVotingOnVotingMachineModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { useStore } from '../../../store/ZustandStoreProvider'; 5 | import { ActionModal } from '../../../transactions/components/ActionModal'; 6 | import { TxType } from '../../../transactions/store/transactionsSlice'; 7 | import { getChainName } from '../../../ui/utils/getChainName'; 8 | import { texts } from '../../../ui/utils/texts'; 9 | import { 10 | ActionModalContentWrapper, 11 | ActionModalTitle, 12 | } from './ActionModalContentWrapper'; 13 | import { ActionModalBasicTypes } from './types'; 14 | 15 | export function ActivateVotingOnVotingMachineModal({ 16 | votingChainId, 17 | isOpen, 18 | setIsOpen, 19 | proposalId, 20 | }: { votingChainId: number } & ActionModalBasicTypes) { 21 | const activateVotingOnVotingMachine = useStore( 22 | (state) => state.activateVotingOnVotingMachine, 23 | ); 24 | 25 | return ( 26 | 30 | await activateVotingOnVotingMachine(votingChainId, proposalId) 31 | } 32 | isOpen={isOpen} 33 | setIsOpen={setIsOpen} 34 | actionButtonTitle={texts.other.confirm} 35 | topBlock={ 36 | 37 | } 38 | withCancelButton> 39 | 40 | 41 | {texts.proposalActions.activateVotingDescription}{' '} 42 | {getChainName(votingChainId)} chain 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/getProposalMetadata.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getProposalMetadata as baseGetProposalMetadata, 3 | ProposalMetadata, 4 | } from '@bgd-labs/aave-governance-ui-helpers'; 5 | import matter from 'gray-matter'; 6 | 7 | import { texts } from '../ui/utils/texts'; 8 | import { cachedIPFSDataPath, githubStartUrl } from './cacheGithubLinks'; 9 | import { fallbackGateways, ipfsGateway } from './configs'; 10 | 11 | export async function getProposalMetadata({ 12 | hash, 13 | setIpfsError, 14 | }: { 15 | hash: string; 16 | setIpfsError?: (ipfsHash: string, text?: string, remove?: boolean) => void; 17 | }): Promise { 18 | try { 19 | const request = await fetch(`${githubStartUrl}${cachedIPFSDataPath(hash)}`); 20 | if (request.ok) { 21 | const response = await request.json(); 22 | const { content, data } = matter(response.description); 23 | return { 24 | ...response, 25 | ipfsHash: hash, 26 | description: content, 27 | ...data, 28 | }; 29 | } else { 30 | console.error( 31 | "Can't fetch cached ipfs data. Try to fetch from IPFS gateway", 32 | ); 33 | return await baseGetProposalMetadata({ 34 | hash, 35 | gateway: ipfsGateway, 36 | setIpfsError, 37 | errorText: texts.other.fetchFromIpfsError, 38 | fallbackGateways, 39 | }); 40 | } 41 | } catch (e) { 42 | console.error( 43 | 'An error occurred while fetching proposal metadata from IPFS, trying to request one more time.', 44 | ); 45 | return await baseGetProposalMetadata({ 46 | hash, 47 | gateway: ipfsGateway, 48 | setIpfsError, 49 | errorText: texts.other.fetchFromIpfsError, 50 | fallbackGateways, 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/transactions/components/BasicActionModal.tsx: -------------------------------------------------------------------------------- 1 | import { SxProps } from '@mui/system'; 2 | import React, { useEffect } from 'react'; 3 | 4 | import { BasicModal } from '../../ui'; 5 | import { 6 | ActionModalContent, 7 | ActionModalContentProps, 8 | } from './ActionModalContent'; 9 | 10 | export interface BasicActionModalProps extends ActionModalContentProps { 11 | isOpen: boolean; 12 | setFullTxErrorMessage: (value: string) => void; 13 | withMinHeight?: boolean; 14 | minHeight?: number; 15 | contentCss?: SxProps; 16 | } 17 | 18 | export function BasicActionModal({ 19 | isOpen, 20 | setIsOpen, 21 | topBlock, 22 | contentMinHeight, 23 | children, 24 | isTxStart, 25 | setIsTxStart, 26 | error, 27 | setError, 28 | successElement, 29 | withoutTryAgainWhenError, 30 | fullTxErrorMessage, 31 | setFullTxErrorMessage, 32 | tx, 33 | withMinHeight, 34 | minHeight, 35 | contentCss, 36 | }: BasicActionModalProps) { 37 | useEffect(() => { 38 | setIsTxStart(false); 39 | setError(''); 40 | setFullTxErrorMessage(''); 41 | }, [isOpen]); 42 | 43 | return ( 44 | 51 | 63 | {children} 64 | 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/CreatePayloadModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { 5 | BasicActionModal, 6 | BasicActionModalProps, 7 | } from '../../../transactions/components/BasicActionModal'; 8 | import { texts } from '../../../ui/utils/texts'; 9 | import { 10 | ActionModalContentWrapper, 11 | ActionModalTitle, 12 | } from './ActionModalContentWrapper'; 13 | 14 | export function CreatePayloadModal({ 15 | isOpen, 16 | setIsOpen, 17 | isTxStart, 18 | tx, 19 | setIsTxStart, 20 | setError, 21 | error, 22 | payloadId, 23 | fullTxErrorMessage, 24 | setFullTxErrorMessage, 25 | }: Pick< 26 | BasicActionModalProps, 27 | | 'isOpen' 28 | | 'setIsOpen' 29 | | 'isTxStart' 30 | | 'setIsTxStart' 31 | | 'setError' 32 | | 'error' 33 | | 'fullTxErrorMessage' 34 | | 'setFullTxErrorMessage' 35 | | 'tx' 36 | > & { payloadId: number }) { 37 | return ( 38 | 52 | {texts.proposalActions.createPayloadSuccess(payloadId)} 53 | 54 | } 55 | topBlock={ 56 | 57 | }> 58 | 59 | 60 | {texts.proposalActions.createPayloadDescription(payloadId)} 61 | 62 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/ExecutePayloadModal.tsx: -------------------------------------------------------------------------------- 1 | import { InitialPayload } from '@bgd-labs/aave-governance-ui-helpers'; 2 | import { Box } from '@mui/system'; 3 | import React from 'react'; 4 | 5 | import { useStore } from '../../../store/ZustandStoreProvider'; 6 | import { ActionModal } from '../../../transactions/components/ActionModal'; 7 | import { TxType } from '../../../transactions/store/transactionsSlice'; 8 | import { texts } from '../../../ui/utils/texts'; 9 | import { 10 | ActionModalContentWrapper, 11 | ActionModalTitle, 12 | } from './ActionModalContentWrapper'; 13 | import { ActionModalBasicTypes } from './types'; 14 | 15 | export function ExecutePayloadModal({ 16 | isOpen, 17 | setIsOpen, 18 | proposalId, 19 | payload, 20 | withController, 21 | }: ActionModalBasicTypes & { 22 | payload: InitialPayload; 23 | withController?: boolean; 24 | }) { 25 | const executePayload = useStore((state) => state.executePayload); 26 | 27 | return ( 28 | 39 | await executePayload(proposalId, payload, withController) 40 | } 41 | isOpen={isOpen} 42 | setIsOpen={setIsOpen} 43 | actionButtonTitle={texts.other.confirm} 44 | topBlock={ 45 | 46 | } 47 | withCancelButton> 48 | 49 | 50 | {texts.proposalActions.executePayloadDescription} 51 | 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/web3/components/wallet/WalletItem.tsx: -------------------------------------------------------------------------------- 1 | import { WalletType } from '@bgd-labs/frontend-web3-utils'; 2 | import { Box } from '@mui/system'; 3 | import React from 'react'; 4 | 5 | import { useStore } from '../../../store/ZustandStoreProvider'; 6 | import { BoxWith3D } from '../../../ui'; 7 | import WalletIcon from '../../../ui/components/Web3Icons/WalletIcon'; 8 | 9 | export type Wallet = { 10 | walletType: WalletType; 11 | walletName: string; 12 | onClick?: () => void; 13 | isVisible?: boolean; 14 | setOpenImpersonatedForm?: (value: boolean) => void; 15 | }; 16 | 17 | export function WalletItem({ 18 | walletType, 19 | walletName, 20 | onClick, 21 | setOpenImpersonatedForm, 22 | }: Wallet) { 23 | const connectWallet = useStore((state) => state.connectWallet); 24 | 25 | const iconSize = 28; 26 | 27 | const handleWalletClick = async () => { 28 | if (walletType === WalletType.Impersonated && setOpenImpersonatedForm) { 29 | setOpenImpersonatedForm(true); 30 | } else { 31 | await connectWallet(walletType); 32 | } 33 | }; 34 | 35 | return ( 36 | 41 | 56 | 57 | {walletName} 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/proposals/components/actionModals/CreateProposalModal.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/system'; 2 | import React from 'react'; 3 | 4 | import { 5 | BasicActionModal, 6 | BasicActionModalProps, 7 | } from '../../../transactions/components/BasicActionModal'; 8 | import { texts } from '../../../ui/utils/texts'; 9 | import { 10 | ActionModalContentWrapper, 11 | ActionModalTitle, 12 | } from './ActionModalContentWrapper'; 13 | 14 | export function CreateProposalModal({ 15 | isOpen, 16 | setIsOpen, 17 | isTxStart, 18 | setIsTxStart, 19 | setError, 20 | error, 21 | proposalId, 22 | fullTxErrorMessage, 23 | setFullTxErrorMessage, 24 | tx, 25 | }: Pick< 26 | BasicActionModalProps, 27 | | 'isOpen' 28 | | 'setIsOpen' 29 | | 'isTxStart' 30 | | 'setIsTxStart' 31 | | 'setError' 32 | | 'error' 33 | | 'fullTxErrorMessage' 34 | | 'setFullTxErrorMessage' 35 | | 'tx' 36 | > & { proposalId: number }) { 37 | return ( 38 | 52 | {texts.proposalActions.createProposalSuccess(proposalId)} 53 | 54 | } 55 | topBlock={ 56 | 57 | }> 58 | 59 | 60 | {texts.proposalActions.createProposalDescription(proposalId)} 61 | 62 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/ui/utils/ThemeRegistry.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | // eslint-disable-next-line import/default 4 | import createCache from '@emotion/cache'; 5 | import { useServerInsertedHTML } from 'next/navigation'; 6 | import React from 'react'; 7 | 8 | import { AppGlobalStyles } from '../layouts/AppGlobalStyles'; 9 | import { GlobalStyles } from './GlobalStyles'; 10 | 11 | // This implementation is from emotion-js 12 | // https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 13 | export default function ThemeRegistry(props: any) { 14 | const { options, children } = props; 15 | 16 | const [{ cache, flush }] = React.useState(() => { 17 | const cache = createCache(options); 18 | cache.compat = true; 19 | const prevInsert = cache.insert; 20 | let inserted: string[] = []; 21 | cache.insert = (...args) => { 22 | const serialized = args[1]; 23 | if (cache.inserted[serialized.name] === undefined) { 24 | inserted.push(serialized.name); 25 | } 26 | return prevInsert(...args); 27 | }; 28 | const flush = () => { 29 | const prevInserted = inserted; 30 | inserted = []; 31 | return prevInserted; 32 | }; 33 | return { cache, flush }; 34 | }); 35 | 36 | useServerInsertedHTML(() => { 37 | const names = flush(); 38 | if (names.length === 0) { 39 | return null; 40 | } 41 | let styles = ''; 42 | for (const name of names) { 43 | styles += cache.inserted[name]; 44 | } 45 | return ( 46 | <> 47 | 48 |