From 05d67a66b587b8539ec380751d92dc67acf900e5 Mon Sep 17 00:00:00 2001 From: neokry Date: Wed, 19 Apr 2023 14:02:05 +0800 Subject: [PATCH 1/5] Add proposal votes --- .../modules/dao/components/SectionHandler.tsx | 30 +---- .../ProposalDescription.tsx | 81 ++++++------ .../ProposalVotes/ProposalVotes.tsx | 26 ++++ .../components/ProposalVotes/VotePlacard.tsx | 116 ++++++++++++++++++ .../components/ProposalVotes/index.ts | 1 + apps/web/src/pages/dao/[token]/[tokenId].tsx | 7 +- apps/web/src/pages/dao/[token]/index.tsx | 7 +- apps/web/src/pages/dao/[token]/vote/[id].tsx | 32 ++++- 8 files changed, 227 insertions(+), 73 deletions(-) create mode 100644 apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx create mode 100644 apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx create mode 100644 apps/web/src/modules/proposal/components/ProposalVotes/index.ts 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..01657411 --- /dev/null +++ b/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx @@ -0,0 +1,26 @@ +import { Flex } 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]) + + return ( + + {proposal.votes?.map((vote) => ( + + ))} + + ) +} 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..aea7f0c8 --- /dev/null +++ b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx @@ -0,0 +1,116 @@ +import { Flex, Grid, Text, atoms } from '@zoralabs/zord' +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' + +export interface VotePlacardProps { + vote: ProposalVoteFragment + totalVotes: number +} + +export const VotePlacard: React.FC = ({ vote, totalVotes }) => { + const { ensName, ensAvatar } = useEnsData(vote.voter) + const [open, setOpen] = useState(false) + const { isMobile } = useLayoutStore() + + const supportStyle = useMemo(() => { + const base = atoms({ + borderStyle: 'solid', + borderRadius: 'phat', + 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="transparent" + cursor={vote.reason ? 'pointer' : 'auto'} + align={'center'} + mb="x2" + px={isMobile ? 'x2' : 'x6'} + py="x6" + borderStyle="solid" + borderColor="border" + borderRadius="curved" + w="100%" + > + + {vote.support} + + + + + {ensName || walletSnippet(vote.voter)} + + + + + + {vote.weight} {vote.weight === 1 ? 'Vote' : 'Votes'} + + {!isMobile && ( + + {votePercentage}% + + )} + + + {(isMobile || 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..20684b60 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 && } - - + + + + ) } From d41fae985410839dd1de306bdd0a3274874a7a89 Mon Sep 17 00:00:00 2001 From: neokry Date: Wed, 19 Apr 2023 14:58:32 +0800 Subject: [PATCH 2/5] Update styles --- .../proposal/components/ProposalVotes/VotePlacard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx index aea7f0c8..9515c111 100644 --- a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx +++ b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx @@ -14,7 +14,7 @@ export interface VotePlacardProps { export const VotePlacard: React.FC = ({ vote, totalVotes }) => { const { ensName, ensAvatar } = useEnsData(vote.voter) - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(true) const { isMobile } = useLayoutStore() const supportStyle = useMemo(() => { @@ -63,8 +63,8 @@ export const VotePlacard: React.FC = ({ vote, totalVotes }) => {vote.support} - - + + {ensName || walletSnippet(vote.voter)} From 21676725f349db67248c2c36f117d89f51ff31d3 Mon Sep 17 00:00:00 2001 From: neokry Date: Wed, 19 Apr 2023 15:21:12 +0800 Subject: [PATCH 3/5] Hide reasons on touch for mobile --- .../modules/proposal/components/ProposalVotes/VotePlacard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx index 9515c111..233df456 100644 --- a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx +++ b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx @@ -92,7 +92,7 @@ export const VotePlacard: React.FC = ({ vote, totalVotes }) => )} - {(isMobile || open) && vote.reason && ( + {open && vote.reason && ( Date: Wed, 19 Apr 2023 16:00:41 +0800 Subject: [PATCH 4/5] Fix mobile styles and add animation --- .../components/ProposalVotes/VotePlacard.tsx | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx index 233df456..4dd5f6f8 100644 --- a/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx +++ b/apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx @@ -1,4 +1,5 @@ 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' @@ -7,6 +8,22 @@ 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 @@ -21,6 +38,7 @@ export const VotePlacard: React.FC = ({ vote, totalVotes }) => const base = atoms({ borderStyle: 'solid', borderRadius: 'phat', + borderWidth: 'thin', py: 'x1', px: 'x3', mr: 'x2', @@ -44,12 +62,13 @@ export const VotePlacard: React.FC = ({ vote, totalVotes }) => onClick={() => (vote.reason ? setOpen((x) => !x) : null)} columns={7} gap={isMobile ? 'x1' : 'x0'} - backgroundColor="transparent" + 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" @@ -92,25 +111,36 @@ export const VotePlacard: React.FC = ({ vote, totalVotes }) => )} - {open && vote.reason && ( - - {vote.reason} - - )} + + {open && vote.reason && ( + + + {vote.reason} + + + )} + ) } From e4db0e8ec74cb670d6479537ac32c1befe7d43cd Mon Sep 17 00:00:00 2001 From: neokry Date: Fri, 21 Apr 2023 09:35:10 +0800 Subject: [PATCH 5/5] Add helper text when no votes are found --- .../components/ProposalVotes/ProposalVotes.tsx | 14 ++++++++++---- apps/web/src/pages/dao/[token]/[tokenId].tsx | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx b/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx index 01657411..f15b4490 100644 --- a/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx +++ b/apps/web/src/modules/proposal/components/ProposalVotes/ProposalVotes.tsx @@ -1,4 +1,4 @@ -import { Flex } from '@zoralabs/zord' +import { Flex, Text } from '@zoralabs/zord' import { useMemo } from 'react' import { Proposal } from 'src/data/graphql/requests/proposalQuery' @@ -16,11 +16,17 @@ export const ProposalVotes: React.FC = ({ proposal }) => { return proposal.votes.reduce((acc, vote) => acc + vote.weight, 0) }, [proposal.votes]) + const hasVotes = proposal.votes?.length || 0 > 0 + return ( - {proposal.votes?.map((vote) => ( - - ))} + {hasVotes ? ( + proposal.votes?.map((vote) => ) + ) : ( + + No votes yet for this proposal. + + )} ) } diff --git a/apps/web/src/pages/dao/[token]/[tokenId].tsx b/apps/web/src/pages/dao/[token]/[tokenId].tsx index 20684b60..4eb0130c 100644 --- a/apps/web/src/pages/dao/[token]/[tokenId].tsx +++ b/apps/web/src/pages/dao/[token]/[tokenId].tsx @@ -112,7 +112,7 @@ const TokenPage: NextPageWithLayout = ({