diff --git a/apps/web/src/modules/dao/components/SectionHandler.tsx b/apps/web/src/modules/dao/components/SectionHandler.tsx index 3c688133..1dc12f21 100644 --- a/apps/web/src/modules/dao/components/SectionHandler.tsx +++ b/apps/web/src/modules/dao/components/SectionHandler.tsx @@ -1,7 +1,6 @@ import { Box, Flex, Text } from '@zoralabs/zord' import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' -import { useRouter } from 'next/router' import React, { ReactElement } from 'react' import { @@ -18,10 +17,8 @@ interface SectionHandlerProps { title: string component: ReactElement[] }[] - collectionAddress: string - tokenId?: string - activeTab?: string - preAuction?: boolean + activeTab: string + basePath: string } interface activeSectionProps { @@ -31,18 +28,12 @@ interface activeSectionProps { export const SectionHandler: React.FC = ({ sections, - collectionAddress, - tokenId, activeTab, - preAuction = false, + basePath, }) => { - const router = useRouter() - /* handle active session if: - - no tab query param is defined (pre auction start) - - no tab query param defined (post auction start) - query tab is defined - unknown query tab is set @@ -55,15 +46,8 @@ export const SectionHandler: React.FC = ({ ) const activeSection: activeSectionProps | undefined = React.useMemo(() => { - const activity = tab('Activity') - const about = tab('About') - - if (!activeTab) { - return preAuction ? activity : about - } - - return tab(unslugify(activeTab)) ?? activity - }, [preAuction, activeTab, tab]) + return tab(unslugify(activeTab)) + }, [activeTab, tab]) return ( <> @@ -80,9 +64,7 @@ export const SectionHandler: React.FC = ({ return ( = ({ ) return ( - -
- - - {description} - - -
+ + +
+ + + {description} + + +
-
- - - {!!tokenImage && !error && ( - proposer - )} - +
+ + + {!!tokenImage && !error && ( + proposer + )} + - {displayName} - -
+ {displayName} +
+
-
- -
+
+ +
+
) } diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx new file mode 100644 index 00000000..f15b4490 --- /dev/null +++ b/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx @@ -0,0 +1,32 @@ +import { Flex, Text } from '@zoralabs/zord' +import { useMemo } from 'react' + +import { Proposal } from 'src/data/graphql/requests/proposalQuery' +import { propPageWrapper } from 'src/styles/Proposals.css' + +import { VotePlacard } from './VotePlacard' + +export type ProposalVotesProps = { + proposal: Proposal +} + +export const ProposalVotes: React.FC = ({ proposal }) => { + const totalVotes = useMemo(() => { + if (!proposal.votes) return 0 + return proposal.votes.reduce((acc, vote) => acc + vote.weight, 0) + }, [proposal.votes]) + + const hasVotes = proposal.votes?.length || 0 > 0 + + return ( + + {hasVotes ? ( + proposal.votes?.map((vote) => ) + ) : ( + + No votes yet for this proposal. + + )} + + ) +} diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx new file mode 100644 index 00000000..4dd5f6f8 --- /dev/null +++ b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx @@ -0,0 +1,146 @@ +import { Flex, Grid, Text, atoms } from '@zoralabs/zord' +import { AnimatePresence, motion } from 'framer-motion' +import { useMemo, useState } from 'react' + +import { Avatar } from 'src/components/Avatar' +import { ProposalVoteFragment, Support } from 'src/data/graphql/sdk.generated' +import { useEnsData } from 'src/hooks' +import { useLayoutStore } from 'src/stores' +import { walletSnippet } from 'src/utils/helpers' + +const variants = { + inital: { + height: 0, + overflow: 'hidden', + transition: { + animate: 'easeInOut', + }, + }, + animate: { + height: 'auto', + transition: { + animate: 'easeInOut', + }, + }, +} + +export interface VotePlacardProps { + vote: ProposalVoteFragment + totalVotes: number +} + +export const VotePlacard: React.FC = ({ vote, totalVotes }) => { + const { ensName, ensAvatar } = useEnsData(vote.voter) + const [open, setOpen] = useState(true) + const { isMobile } = useLayoutStore() + + const supportStyle = useMemo(() => { + const base = atoms({ + borderStyle: 'solid', + borderRadius: 'phat', + borderWidth: 'thin', + py: 'x1', + px: 'x3', + mr: 'x2', + }) + + switch (vote.support) { + case Support.For: + return [base, atoms({ color: 'positive', borderColor: 'positiveDisabled' })] + case Support.Against: + return [base, atoms({ color: 'negative', borderColor: 'negativeDisabled' })] + case Support.Abstain: + return [base, atoms({ color: 'text3', borderColor: 'border' })] + } + }, [vote.support]) + + const votePercentage = ((100 * vote.weight) / totalVotes).toFixed(2) + + return ( + (vote.reason ? setOpen((x) => !x) : null)} + columns={7} + gap={isMobile ? 'x1' : 'x0'} + backgroundColor="background1" + cursor={vote.reason ? 'pointer' : 'auto'} + align={'center'} + mb="x2" + px={isMobile ? 'x2' : 'x6'} + py="x6" + color="text1" + borderStyle="solid" + borderColor="border" + borderRadius="curved" + w="100%" + > + + {vote.support} + + + + + {ensName || walletSnippet(vote.voter)} + + + + + + {vote.weight} {vote.weight === 1 ? 'Vote' : 'Votes'} + + {!isMobile && ( + + {votePercentage}% + + )} + + + + {open && vote.reason && ( + + + {vote.reason} + + + )} + + + ) +} diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/index.ts b/apps/web/src/modules/proposal/components/ProposalVotes/index.ts new file mode 100644 index 00000000..0f798e5a --- /dev/null +++ b/apps/web/src/modules/proposal/components/ProposalVotes/index.ts @@ -0,0 +1 @@ +export * from './ProposalVotes' diff --git a/apps/web/src/pages/dao/[token]/[tokenId].tsx b/apps/web/src/pages/dao/[token]/[tokenId].tsx index e635f069..4eb0130c 100644 --- a/apps/web/src/pages/dao/[token]/[tokenId].tsx +++ b/apps/web/src/pages/dao/[token]/[tokenId].tsx @@ -97,6 +97,8 @@ const TokenPage: NextPageWithLayout = ({ const ogDescription = description.length > 111 ? `${description.slice(0, 111)}...` : description + const activeTab = query?.tab ? (query.tab as string) : 'About' + return ( = ({ = ({ collectionAddress }) => { ) } + const activeTab = query?.tab ? (query.tab as string) : 'Activity' + return ( @@ -84,9 +86,8 @@ const DaoPage: NextPageWithLayout = ({ collectionAddress }) => { ) diff --git a/apps/web/src/pages/dao/[token]/vote/[id].tsx b/apps/web/src/pages/dao/[token]/vote/[id].tsx index 72e4a2c4..eb5291d8 100644 --- a/apps/web/src/pages/dao/[token]/vote/[id].tsx +++ b/apps/web/src/pages/dao/[token]/vote/[id].tsx @@ -1,5 +1,5 @@ import { readContracts } from '@wagmi/core' -import { Flex } from '@zoralabs/zord' +import { Box, Flex } from '@zoralabs/zord' import { ethers } from 'ethers' import { isAddress } from 'ethers/lib/utils.js' import { GetServerSideProps } from 'next' @@ -15,6 +15,7 @@ import getDAOAddresses from 'src/data/contract/requests/getDAOAddresses' import getToken, { TokenWithWinner } from 'src/data/contract/requests/getToken' import { getProposal } from 'src/data/graphql/requests/proposalQuery' import { getDaoLayout } from 'src/layouts/DaoLayout' +import { SectionHandler } from 'src/modules/dao' import { ProposalActions, ProposalDescription, @@ -22,6 +23,7 @@ import { ProposalHeader, isProposalOpen, } from 'src/modules/proposal' +import { ProposalVotes } from 'src/modules/proposal/components/ProposalVotes' import { NextPageWithLayout } from 'src/pages/_app' import { ProposalOgMetadata } from 'src/pages/api/og/proposal' import { propPageWrapper } from 'src/styles/Proposals.css' @@ -45,6 +47,22 @@ const VotePage: NextPageWithLayout = ({ getProposal(id) ) + const sections = React.useMemo(() => { + if (!proposal) return [] + return [ + { + title: 'Details', + component: [ + , + ], + }, + { + title: 'Votes', + component: [], + }, + ] + }, [proposal]) + if (!proposal) { return null } @@ -60,17 +78,23 @@ const VotePage: NextPageWithLayout = ({ description={`View this proposal from ${daoName}`} /> - + {displayActions && } - - + + + + ) }