-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from jmrossy/tx-forms
Register & lock transaction forms
- Loading branch information
Showing
68 changed files
with
1,365 additions
and
641 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,72 @@ | ||
'use client'; | ||
|
||
import Image from 'next/image'; | ||
import { SolidButton } from 'src/components/buttons/SolidButton'; | ||
import { Section } from 'src/components/layout/Section'; | ||
import { Amount } from 'src/components/numbers/Amount'; | ||
import { useBalance, useLockedBalance } from 'src/features/account/hooks'; | ||
import { LockActionType } from 'src/features/locking/types'; | ||
import { useStakingRewards } from 'src/features/staking/rewards/useStakingRewards'; | ||
import { useStakingBalances } from 'src/features/staking/useStakingBalances'; | ||
import { useTransactionModal } from 'src/features/transactions/TransactionModal'; | ||
import { TxModalType } from 'src/features/transactions/types'; | ||
import Lock from 'src/images/icons/lock.svg'; | ||
import Unlock from 'src/images/icons/unlock.svg'; | ||
import Withdraw from 'src/images/icons/withdraw.svg'; | ||
import { usePageInvariant } from 'src/utils/navigation'; | ||
import { useAccount } from 'wagmi'; | ||
|
||
export default function Page() { | ||
const account = useAccount(); | ||
const address = account?.address; | ||
|
||
usePageInvariant(!!address, '/', 'No account connected'); | ||
|
||
return <Section containerClassName="space-y-8">TODO</Section>; | ||
const { balance: walletBalance } = useBalance(address); | ||
const { lockedBalance } = useLockedBalance(address); | ||
const { groupToStake } = useStakingBalances(address); | ||
const { totalRewards: _totalRewards } = useStakingRewards(address, groupToStake); | ||
|
||
const totalBalance = (walletBalance || 0n) + (lockedBalance || 0n); | ||
|
||
const showTxModal = useTransactionModal(); | ||
|
||
return ( | ||
<Section className="mt-6" containerClassName="space-y-8 min-w-[40rem]"> | ||
<h1 className="font-serif text-3xl">Dashboard</h1> | ||
<div className="flex items-center justify-between"> | ||
<div> | ||
<h2>Total Balance</h2> | ||
<Amount valueWei={totalBalance} className="mt-2 text-4xl" /> | ||
</div> | ||
<div className="flex space-x-2"> | ||
<SolidButton | ||
onClick={() => showTxModal(TxModalType.Lock, { defaultAction: LockActionType.Lock })} | ||
> | ||
<div className="flex items-center space-x-1.5"> | ||
<Image src={Lock} width={12} height={12} alt="" /> | ||
<span>Lock</span> | ||
</div> | ||
</SolidButton> | ||
<SolidButton | ||
onClick={() => showTxModal(TxModalType.Lock, { defaultAction: LockActionType.Unlock })} | ||
> | ||
<div className="flex items-center space-x-1.5"> | ||
<Image src={Unlock} width={12} height={12} alt="" /> | ||
<span>Unlock</span> | ||
</div> | ||
</SolidButton> | ||
<SolidButton | ||
onClick={() => | ||
showTxModal(TxModalType.Lock, { defaultAction: LockActionType.Withdraw }) | ||
} | ||
> | ||
<div className="flex items-center space-x-1.5"> | ||
<Image src={Withdraw} width={12} height={12} alt="" /> | ||
<span>Withdraw</span> | ||
</div> | ||
</SolidButton> | ||
</div> | ||
</div> | ||
</Section> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,86 @@ | ||
'use client'; | ||
import { useMemo } from 'react'; | ||
import { SolidButton } from 'src/components/buttons/SolidButton'; | ||
import { Card } from 'src/components/layout/Card'; | ||
import { Section } from 'src/components/layout/Section'; | ||
import { Modal, useModal } from 'src/components/menus/Modal'; | ||
import { Amount } from 'src/components/numbers/Amount'; | ||
import { StakeForm } from 'src/features/staking/StakeForm'; | ||
import { StatBox } from 'src/components/layout/StatBox'; | ||
import { useTransactionModal } from 'src/features/transactions/TransactionModal'; | ||
import { TxModalType } from 'src/features/transactions/types'; | ||
import { ValidatorGroupTable } from 'src/features/validators/ValidatorGroupTable'; | ||
import { ValidatorGroup, ValidatorStatus } from 'src/features/validators/types'; | ||
import { useValidatorGroups } from 'src/features/validators/useValidatorGroups'; | ||
import { bigIntMin } from 'src/utils/math'; | ||
import { objLength } from 'src/utils/objects'; | ||
|
||
export default function Index() { | ||
const { groups, totalVotes } = useValidatorGroups(); | ||
|
||
const { isModalOpen, openModal: _openModal, closeModal } = useModal(); | ||
|
||
return ( | ||
<> | ||
<div className="space-y-8"> | ||
<HeroSection totalVotes={totalVotes} groups={groups} /> | ||
<ListSection totalVotes={totalVotes} groups={groups} /> | ||
</div> | ||
<Modal isOpen={isModalOpen} close={closeModal}> | ||
<StakeForm /> | ||
</Modal> | ||
<Section className="mt-6"> | ||
<div className="space-y-8"> | ||
<HeroSection totalVotes={totalVotes} groups={groups} /> | ||
<ListSection totalVotes={totalVotes} groups={groups} /> | ||
</div> | ||
</Section> | ||
</> | ||
); | ||
} | ||
|
||
function HeroSection({ totalVotes, groups }: { totalVotes?: bigint; groups?: ValidatorGroup[] }) { | ||
const minVotes = useMemo(() => { | ||
if (!groups?.length) return 0n; | ||
let min = BigInt(1e40); | ||
const { minVotes, numValidators } = useMemo(() => { | ||
if (!groups?.length) return { minVotes: 0n, numValidators: 0 }; | ||
let minVotes = BigInt(1e40); | ||
let numValidators = 0; | ||
for (const g of groups) { | ||
numValidators += objLength(g.members); | ||
const numElectedMembers = Object.values(g.members).filter( | ||
(m) => m.status === ValidatorStatus.Elected, | ||
).length; | ||
if (!numElectedMembers) continue; | ||
const votesPerMember = g.votes / BigInt(numElectedMembers); | ||
min = bigIntMin(min, votesPerMember); | ||
minVotes = bigIntMin(minVotes, votesPerMember); | ||
} | ||
return min; | ||
return { minVotes, numValidators }; | ||
}, [groups]); | ||
|
||
const showStakeModal = useTransactionModal(TxModalType.Stake); | ||
|
||
return ( | ||
<Section className="bg-purple-500 text-white" containerClassName="all:px-0"> | ||
<div className="my-10 flex items-center justify-between gap-20 lg:gap-x-40 xl:gap-x-80"> | ||
<div className="flex w-80 flex-col space-y-6"> | ||
<h1 className="font-serif text-4xl">Discover Validators</h1> | ||
<p>Stake your CELO with validators to start earning rewards immediately.</p> | ||
<SolidButton onClick={showStakeModal}>{`Stake and earn 4%`}</SolidButton> | ||
</div> | ||
<div className="hidden grid-cols-2 grid-rows-2 gap-10 border border-white/20 p-6 md:grid"> | ||
<HeroStat label="Staking APY" text="6%" /> | ||
<HeroStat label="Validators Groups" text={groups?.length || 0} /> | ||
<HeroStat label="Elected Minimum Votes" amount={minVotes} /> | ||
<HeroStat label="Total Staked CELO" amount={totalVotes} /> | ||
</div> | ||
<div className="space-y-4"> | ||
<div className="flex items-center justify-between"> | ||
<h1 className="font-serif text-4xl">Discover Validators</h1> | ||
<SolidButton onClick={() => showStakeModal()} className="px-8"> | ||
Stake | ||
</SolidButton> | ||
</div> | ||
<div className="flex items-center justify-between gap-2 sm:gap-8"> | ||
<StatBox | ||
header="Total Staked CELO" | ||
valueWei={totalVotes} | ||
className="max-w-xs md:max-w-sm" | ||
/> | ||
<StatBox | ||
header="Elected Minimum Votes" | ||
valueWei={minVotes} | ||
className="max-w-xs md:max-w-sm" | ||
/> | ||
<StatBox className="hidden max-w-xs md:max-w-sm lg:flex"> | ||
<div className="flex gap-6 divide-x"> | ||
<div className="flex flex-col"> | ||
<h3 className="text-sm">Total Groups</h3> | ||
<div className="mt-2 font-serif text-xl md:text-2xl">{groups?.length || 0}</div> | ||
</div> | ||
<div className="flex flex-col pl-6"> | ||
<h3 className="text-sm">Total Validators</h3> | ||
<div className="mt-2 font-serif text-xl md:text-2xl">{numValidators}</div> | ||
</div> | ||
</div> | ||
</StatBox> | ||
</div> | ||
</Section> | ||
); | ||
} | ||
|
||
function HeroStat({ | ||
label, | ||
text, | ||
amount, | ||
}: { | ||
label: string; | ||
text?: string | number; | ||
amount?: bigint; | ||
}) { | ||
return ( | ||
<div className="flex flex-col"> | ||
<label>{label}</label> | ||
{!!text && <div className="mt-1 font-serif text-3xl">{text}</div>} | ||
{!!amount && ( | ||
<Amount valueWei={amount} className="mt-1 text-3xl" decimals={0} showSymbol={false} /> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
function ListSection({ totalVotes, groups }: { totalVotes?: bigint; groups?: ValidatorGroup[] }) { | ||
return ( | ||
<Section containerClassName="all:px-0"> | ||
<Card className="p-0" bodyClassName="p-0"> | ||
<ValidatorGroupTable groups={groups || []} totalVotes={totalVotes || 0n} /> | ||
</Card> | ||
</Section> | ||
); | ||
return <ValidatorGroupTable groups={groups || []} totalVotes={totalVotes || 0n} />; | ||
} |
Oops, something went wrong.