Skip to content

Commit

Permalink
Merge pull request #4 from blockworks-foundation/max/styling
Browse files Browse the repository at this point in the history
Layout for proposal list
  • Loading branch information
mschneider authored Aug 25, 2021
2 parents 2a9a6a6 + eb19282 commit 52a322f
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 72 deletions.
9 changes: 9 additions & 0 deletions components/PageBodyContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const PageBodyContainer = ({ children }) => (
<div className="min-h-screen grid grid-cols-12 gap-4 py-10">
<div className="col-span-12 px-4 xl:col-start-2 xl:col-span-10">
{children}
</div>
</div>
)

export default PageBodyContainer
77 changes: 77 additions & 0 deletions components/ProposalCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ChevronRightIcon } from '@heroicons/react/solid'
import { ClockIcon } from '@heroicons/react/outline'
import StatusBadge from './StatusBadge'
import moment from 'moment'
import { Proposal, ProposalState } from '../models/accounts'
import BN from 'bn.js'
import Link from 'next/link'
import { MintInfo } from '@solana/spl-token'

export const ProposalStateLabels = {
0: 'Draft',
1: 'Draft',
2: 'Active',
3: 'Approved',
4: 'Approved',
5: 'Approved',
6: 'Cancelled',
7: 'Denied',
8: 'Error',
}

type ProposalCardProps = {
id: string
proposal: Proposal
mint: MintInfo
}

const votePrecision = 10000
const calculatePct = (c: BN, total: BN) =>
c.mul(new BN(votePrecision)).div(total).toNumber() * (100 / votePrecision)

const fmtUnixTime = (d: BN) => moment.unix(d.toNumber()).fromNow()

const ProposalCard = ({ id, proposal, mint }: ProposalCardProps) => {
const yesVotePct = calculatePct(proposal.yesVotesCount, mint.supply)
const noVotePct = calculatePct(proposal.noVotesCount, mint.supply)

return (
<div>
<Link href={`/proposal/${id}`}>
<a>
<div className="bg-bkg-2 p-6 rounded-md">
<div className="flex items-center justify-between">
<h3 className="text-fgd-1">{proposal.name}</h3>
<div className="flex items-center">
<StatusBadge status={ProposalStateLabels[proposal.state]} />
<ChevronRightIcon className="h-6 ml-2 text-primary-light w-6" />
</div>
</div>
<div className="flex items-center text-fgd-3 text-sm">
<span className="flex items-center">
<ClockIcon className="h-4 mr-1.5 w-4" />
{proposal.votingCompletedAt
? `${ProposalState[proposal.state]} ${fmtUnixTime(
proposal.votingCompletedAt
)}`
: proposal.votingAt
? `Proposed ${fmtUnixTime(proposal.votingAt)}`
: `Drafted ${fmtUnixTime(proposal.draftAt)}`}
</span>
</div>
{proposal.descriptionLink ? (
<p className="mt-3">{proposal.descriptionLink}</p>
) : null}

<div className="flex space-x-4 mt-6">
<div className="w-1/4">Yes: {yesVotePct}%</div>
<div className="w-1/4">No: {noVotePct}%</div>
</div>
</div>
</a>
</Link>
</div>
)
}

export default ProposalCard
78 changes: 78 additions & 0 deletions components/ProposalFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState, useReducer } from 'react'
import { ChevronDownIcon } from '@heroicons/react/solid'
import { Disclosure } from '@headlessui/react'
import Switch from './Switch'

const initialFilterSettings = {
Active: true,
Approved: true,
Denied: true,
}

const ProposalFilter = () => {
const [filters, setFilters] = useState([])

const [filterSettings, setFilterSettings] = useReducer(
(state, newState) => ({ ...state, ...newState }),
initialFilterSettings
)

const handleFilters = (name, checked) => {
setFilterSettings({ [name]: checked })
if (!checked) {
filters.push(name)
} else {
setFilters(filters.filter((n) => n !== name))
}
}

return (
<Disclosure as="div" className="relative">
{({ open }) => (
<>
<Disclosure.Button
className={`border border-fgd-4 default-transition font-normal pl-3 pr-2 py-2.5 rounded-md text-fgd-1 text-sm hover:bg-bkg-3 focus:outline-none`}
>
<div className="flex items-center justify-between">
Filter
<ChevronDownIcon
className={`default-transition h-5 w-5 ml-1 ${
open ? 'transform rotate-180' : 'transform rotate-360'
}`}
/>
</div>
</Disclosure.Button>
<Disclosure.Panel
className={`bg-bkg-3 mt-2 p-4 absolute right-0 w-56 z-20 rounded-md text-sm`}
>
<div>
<div className="flex items-center justify-between pb-2">
Active
<Switch
checked={filterSettings.Active}
onChange={(checked) => handleFilters('Active', checked)}
/>
</div>
<div className="flex items-center justify-between pb-2">
Approved
<Switch
checked={filterSettings.Approved}
onChange={(checked) => handleFilters('Approved', checked)}
/>
</div>
<div className="flex items-center justify-between">
Denied
<Switch
checked={filterSettings.Denied}
onChange={(checked) => handleFilters('Denied', checked)}
/>
</div>
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
}

export default ProposalFilter
9 changes: 9 additions & 0 deletions components/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const StatusBadge = ({ status }) => {
return (
<div className="bg-bkg-3 flex items-center px-2 py-1 rounded-full">
<span className="text-fgd-1 text-xs">{status}</span>
</div>
)
}

export default StatusBadge
48 changes: 48 additions & 0 deletions components/Switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FunctionComponent } from 'react'

interface SwitchProps {
checked: boolean
className?: string
onChange: (x) => void
}

const Switch: FunctionComponent<SwitchProps> = ({
checked = false,
className = '',
children,
onChange,
}) => {
const handleClick = () => {
onChange(!checked)
}

return (
<div className={`flex items-center ${className}`}>
<span className="mr-1">
<span className="">{children}</span>
</span>
<button
type="button"
className={`${
checked ? 'bg-primary-light' : 'bg-bkg-4'
} relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent
rounded-full cursor-pointer transition-colors ease-in-out duration-200
focus:outline-none`}
role="switch"
aria-checked={checked}
onClick={handleClick}
>
<span className="sr-only">{children}</span>
<span
aria-hidden="true"
className={`${
checked ? 'translate-x-5' : 'translate-x-0'
} pointer-events-none inline-block h-5 w-5 rounded-full bg-white
shadow transform ring-0 transition ease-in-out duration-200`}
></span>
</button>
</div>
)
}

export default Switch
5 changes: 4 additions & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../styles/index.css'
import useWallet from '../hooks/useWallet'
import Notifications from '../components/Notification'
import NavBar from '../components/NavBar'
import PageBodyContainer from '../components/PageBodyContainer'

function App({ Component, pageProps }) {
useWallet()
Expand Down Expand Up @@ -44,7 +45,9 @@ function App({ Component, pageProps }) {
<div className="w-screen h-2 bg-gradient-to-r from-mango-red via-mango-yellow to-mango-green"></div>
<NavBar />
<Notifications />
<Component {...pageProps} />
<PageBodyContainer>
<Component {...pageProps} />
</PageBodyContainer>
<div className="w-screen h-2 bg-gradient-to-r from-mango-red via-mango-yellow to-mango-green"></div>
</div>
</ThemeProvider>
Expand Down
123 changes: 52 additions & 71 deletions pages/dao/[symbol].tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useRouter } from 'next/router'
import Link from 'next/link'
import useWalletStore from '../../stores/useWalletStore'
import moment from 'moment'
import useRealm from '../../hooks/useRealm'
import BN from 'bn.js'
import React from 'react'
import ProposalFilter from '../../components/ProposalFilter'
import ProposalCard from '../../components/ProposalCard'
import Button from '../../components/Button'

export const ProposalStateLabels = {
0: 'Draft',
Expand All @@ -22,20 +23,9 @@ const DAO = () => {
const { symbol } = router.query

const wallet = useWalletStore((s) => s.current)
const {
mint,
governances,
proposals,
realmTokenAccount,
ownTokenRecord,
} = useRealm(symbol as string)

const votePrecision = 10000
const formatVoteCount = (c: BN) =>
`${
c.mul(new BN(votePrecision)).div(mint.supply).toNumber() *
(100 / votePrecision)
}%`
const { mint, proposals, realmTokenAccount, ownTokenRecord } = useRealm(
symbol as string
)

// DEBUG print remove
console.log(
Expand All @@ -53,64 +43,55 @@ const DAO = () => {
wallet?.connected && ownTokenRecord
)

const displayedProposal = Object.entries(proposals)
.filter(([_k, v]) => v.info.votingAt)
.sort(
(a, b) => b[1].info.votingAt.toNumber() - a[1].info.votingAt.toNumber()
)

return (
<>
<div className="m-10">
<h1>{symbol}</h1>
<p>
in Wallet:{' '}
{realmTokenAccount
? realmTokenAccount.account.amount.toString()
: 'N/A'}
</p>
<p>
in Governance:{' '}
{ownTokenRecord
? ownTokenRecord.info.governingTokenDepositAmount.toNumber()
: 'N/A'}
</p>
<p>Proposals:</p>
{Object.entries(proposals)
.filter(([_k, v]) => v.info.votingAt)
.sort(
(a, b) =>
b[1].info.votingAt.toNumber() - a[1].info.votingAt.toNumber()
)
.map(([k, v]) => (
<div className="m-10 p-4 border" key={k}>
<Link href={`/proposal/${k}`}>
<a>
<h3>{v.info.name}</h3>
<p>{v.info.descriptionLink}</p>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-8 space-y-3">
<div className="flex items-center justify-between">
<h2>{`${displayedProposal.length} proposals`}</h2>
<ProposalFilter />
</div>
{displayedProposal.map(([k, v]) => (
<ProposalCard key={k} id={k} mint={mint} proposal={v['info']} />
))}
</div>
<div className="col-span-4">
<div className="bg-bkg-2 col-span-4 p-6 rounded-md space-y-6">
<h3 className="mb-4">MNGO balance</h3>

{v.info.votingCompletedAt ? (
<p>
Ended{' '}
{moment
.unix(v.info.votingCompletedAt.toNumber())
.fromNow()}
</p>
) : (
<p>
Created{' '}
{moment.unix(v.info.votingAt.toNumber()).fromNow()}
</p>
)}
<p>{ProposalStateLabels[v.info.state]}</p>
<p>
Votes Yes: {formatVoteCount(v.info.yesVotesCount)} No:{' '}
{formatVoteCount(v.info.noVotesCount)}
</p>
<p>
{`Yes Threshold: ${
governances[v.info.governance.toBase58()]?.info.config
.voteThresholdPercentage.value
}%`}
</p>
</a>
</Link>
<div className="flex space-x-4 items-center">
<div>Deposited</div>
<div className="col-span-3 bg-bkg-3 p-4 rounded">
<div className="text-xl">
{ownTokenRecord
? ownTokenRecord.info.governingTokenDepositAmount.toNumber()
: 'N/A'}
</div>
</div>
</div>
))}
<div className="flex space-x-4 items-center">
<div>In Wallet</div>
<div className="col-span-3 bg-bkg-3 p-4 rounded">
<div className="text-xl">
{realmTokenAccount
? realmTokenAccount.account.amount.toString()
: 'N/A'}
</div>
</div>
</div>

<div className="flex space-x-4">
<Button className="w-1/2">Deposit</Button>
<Button className="w-1/2">Withdraw</Button>
</div>
</div>
</div>
</div>
</>
)
Expand Down

0 comments on commit 52a322f

Please sign in to comment.