Skip to content

Commit

Permalink
Merge pull request #216 from ourzora/add-reasons-to-proposal
Browse files Browse the repository at this point in the history
Add proposal votes
  • Loading branch information
neokry authored Apr 21, 2023
2 parents 4f0ad6d + b7edfdf commit 0a71d81
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 73 deletions.
30 changes: 6 additions & 24 deletions apps/web/src/modules/dao/components/SectionHandler.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -18,10 +17,8 @@ interface SectionHandlerProps {
title: string
component: ReactElement[]
}[]
collectionAddress: string
tokenId?: string
activeTab?: string
preAuction?: boolean
activeTab: string
basePath: string
}

interface activeSectionProps {
Expand All @@ -31,18 +28,12 @@ interface activeSectionProps {

export const SectionHandler: React.FC<SectionHandlerProps> = ({
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
Expand All @@ -55,15 +46,8 @@ export const SectionHandler: React.FC<SectionHandlerProps> = ({
)

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 (
<>
Expand All @@ -80,9 +64,7 @@ export const SectionHandler: React.FC<SectionHandlerProps> = ({
return (
<Link
href={{
pathname: tokenId
? `/dao/${collectionAddress}/${tokenId}`
: `/dao/${collectionAddress}`,
pathname: basePath,
query: {
tab: slugify(section.title),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sdk } from 'src/data/graphql/client'
import { Proposal } from 'src/data/graphql/requests/proposalQuery'
import { SortDirection, TokenSortKey } from 'src/data/graphql/sdk.generated'
import { useEnsData } from 'src/hooks/useEnsData'
import { propPageWrapper } from 'src/styles/Proposals.css'

import { DecodedTransactions } from './DecodedTransactions'
import { proposalDescription } from './ProposalDescription.css'
Expand Down Expand Up @@ -53,48 +54,50 @@ export const ProposalDescription: React.FC<ProposalDescriptionProps> = ({
)

return (
<Flex direction={'column'} mt={{ '@initial': 'x6', '@768': 'x13' }}>
<Section title="Description">
<Paragraph overflow={'auto'}>
<ReactMarkdown
className={proposalDescription}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
remarkPlugins={[remarkGfm]}
>
{description}
</ReactMarkdown>
</Paragraph>
</Section>
<Flex className={propPageWrapper}>
<Flex direction={'column'} mt={{ '@initial': 'x6', '@768': 'x13' }}>
<Section title="Description">
<Paragraph overflow={'auto'}>
<ReactMarkdown
className={proposalDescription}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
remarkPlugins={[remarkGfm]}
>
{description}
</ReactMarkdown>
</Paragraph>
</Section>

<Section title="Proposer">
<Flex direction={'row'} placeItems={'center'}>
<Box
backgroundColor="background2"
width={'x8'}
height={'x8'}
mr={'x2'}
borderRadius={'small'}
position="relative"
>
{!!tokenImage && !error && (
<Image
alt="proposer"
src={tokenImage}
quality={50}
width={128}
height={128}
className={atoms({ borderRadius: 'small' })}
/>
)}
</Box>
<Section title="Proposer">
<Flex direction={'row'} placeItems={'center'}>
<Box
backgroundColor="background2"
width={'x8'}
height={'x8'}
mr={'x2'}
borderRadius={'small'}
position="relative"
>
{!!tokenImage && !error && (
<Image
alt="proposer"
src={tokenImage}
quality={50}
width={128}
height={128}
className={atoms({ borderRadius: 'small' })}
/>
)}
</Box>

<Box>{displayName}</Box>
</Flex>
</Section>
<Box>{displayName}</Box>
</Flex>
</Section>

<Section title="Proposed Transactions">
<DecodedTransactions targets={targets} calldatas={calldatas} values={values} />
</Section>
<Section title="Proposed Transactions">
<DecodedTransactions targets={targets} calldatas={calldatas} values={values} />
</Section>
</Flex>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -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<ProposalVotesProps> = ({ 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 (
<Flex className={propPageWrapper}>
{hasVotes ? (
proposal.votes?.map((vote) => <VotePlacard vote={vote} totalVotes={totalVotes} />)
) : (
<Text textAlign={'center'} color="text3" mt="x4">
No votes yet for this proposal.
</Text>
)}
</Flex>
)
}
146 changes: 146 additions & 0 deletions apps/web/src/modules/proposal/components/ProposalVotes/VotePlacard.tsx
Original file line number Diff line number Diff line change
@@ -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<VotePlacardProps> = ({ 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 (
<Grid
as="button"
onClick={() => (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%"
>
<Text
fontSize={isMobile ? 12 : 14}
style={{ width: 'max-content' }}
className={supportStyle}
>
{vote.support}
</Text>
<Flex align={'center'} style={{ gridColumn: 'span 4 / span 4' }}>
<Avatar address={vote.voter} src={ensAvatar} size={isMobile ? '24' : '32'} />
<Text variant={isMobile ? 'label-sm' : 'label-md'} ml="x2">
{ensName || walletSnippet(vote.voter)}
</Text>
</Flex>

<Flex
align={'center'}
style={{ gridColumn: 'span 2 / span 2', justifyContent: 'end' }}
>
<Text variant={isMobile ? 'label-sm' : 'label-md'} mr={isMobile ? 'x2' : 'x6'}>
{vote.weight} {vote.weight === 1 ? 'Vote' : 'Votes'}
</Text>
{!isMobile && (
<Text
style={{ fontWeight: 500 }}
fontSize={12}
borderStyle="solid"
borderColor="border"
color="text4"
borderRadius="phat"
py="x1"
px="x2"
>
{votePercentage}%
</Text>
)}
</Flex>

<AnimatePresence initial={false}>
{open && vote.reason && (
<motion.div
variants={variants}
initial={'inital'}
animate={'animate'}
exit={'inital'}
style={{
gridColumn: isMobile ? 'span 7 / span 7' : '2 / span 6',
}}
>
<Text
variant={isMobile ? 'paragraph-sm' : 'paragraph-md'}
borderRadius="curved"
mt="x4"
p="x6"
textAlign={'left'}
style={{
fontWeight: 400,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
minWidth: '80%',
backgroundColor: '#F9F9F9',
}}
>
{vote.reason}
</Text>
</motion.div>
)}
</AnimatePresence>
</Grid>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ProposalVotes'
7 changes: 4 additions & 3 deletions apps/web/src/pages/dao/[token]/[tokenId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ const TokenPage: NextPageWithLayout<TokenPageProps> = ({
const ogDescription =
description.length > 111 ? `${description.slice(0, 111)}...` : description

const activeTab = query?.tab ? (query.tab as string) : 'About'

return (
<Flex direction="column" pb="x30">
<Meta
Expand All @@ -109,9 +111,8 @@ const TokenPage: NextPageWithLayout<TokenPageProps> = ({
<Auction auctionAddress={addresses.auction} collection={collection} token={token} />
<SectionHandler
sections={sections}
activeTab={query?.tab ? (query.tab as string) : undefined}
collectionAddress={collection}
tokenId={tokenId}
activeTab={activeTab}
basePath={`/dao/${collection}/${tokenId}`}
/>

<AnimatedModal
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/pages/dao/[token]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const DaoPage: NextPageWithLayout<DaoPageProps> = ({ collectionAddress }) => {
)
}

const activeTab = query?.tab ? (query.tab as string) : 'Activity'

return (
<Flex direction="column" pb="x30">
<Meta title={'dao page'} slug={'/'} />
Expand All @@ -84,9 +86,8 @@ const DaoPage: NextPageWithLayout<DaoPageProps> = ({ collectionAddress }) => {

<SectionHandler
sections={sections}
collectionAddress={collectionAddress}
activeTab={query?.tab ? (query.tab as string) : undefined}
preAuction={true}
activeTab={activeTab}
basePath={`/dao/${collectionAddress}`}
/>
</Flex>
)
Expand Down
Loading

2 comments on commit 0a71d81

@vercel
Copy link

@vercel vercel bot commented on 0a71d81 Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 0a71d81 Apr 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

testnet-nouns-builder – ./apps/web

testnet-nouns-builder-git-main-ourzora.vercel.app
testnet-nouns-builder-ourzora.vercel.app
testnet.nouns.build

Please sign in to comment.