Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout #4

Merged
merged 2 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Copy link
Collaborator

Choose a reason for hiding this comment

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

These labels won't work for all scenarios because you also have to consider states and time. For example executingAt will be set when first instruction is executed but you might still have more instructions to execute so it can't be Executed. check getStateTimestamp(). For time related status see getStateLabel. After voting time expires we should show Voting Ended
If you don't want to go into the details then leave it with me

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wasn't aware of those functions: unfortunately some of the states are not formulated in past participle, making it hard to fit them into the schema: ${state} ${x} days ago so we might need to setup custom logic.

Screen Shot 2021-08-26 at 12 25 32 AM

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated with something that is hopefully not incorrect for now, but we should have a chat about this at the earliest convenience :)

</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