Skip to content

Commit

Permalink
Implement stake and reward tables
Browse files Browse the repository at this point in the history
Create StackedBarChart component
  • Loading branch information
jmrossy committed Jan 25, 2024
1 parent 97f2ad4 commit 1613ce8
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 82 deletions.
57 changes: 8 additions & 49 deletions src/app/account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@ import { useBalance } from 'src/features/account/hooks';
import { LockActionType, LockedBalances } from 'src/features/locking/types';
import { useLockedStatus } from 'src/features/locking/useLockedStatus';
import { getTotalLockedCelo, getTotalUnlockedCelo } from 'src/features/locking/utils';
import { ActiveStakesTable } from 'src/features/staking/ActiveStakesTable';
import { RewardsTable } from 'src/features/staking/rewards/RewardsTable';
import { useStakingRewards } from 'src/features/staking/rewards/useStakingRewards';
import { GroupToStake, StakingBalances } from 'src/features/staking/types';
import { useStakingBalances } from 'src/features/staking/useStakingBalances';
import { useTransactionModal } from 'src/features/transactions/TransactionModal';
import { TxModalType } from 'src/features/transactions/types';
import { ValidatorGroupLogo } from 'src/features/validators/ValidatorGroupLogo';
import { ValidatorGroup } from 'src/features/validators/types';
import { useValidatorGroups } from 'src/features/validators/useValidatorGroups';
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 { tableClasses } from 'src/styles/common';
import { shortenAddress } from 'src/utils/addresses';
import { usePageInvariant } from 'src/utils/navigation';
import { useAccount } from 'wagmi';

Expand Down Expand Up @@ -158,6 +157,7 @@ function AccountStat({
function TableTabs({
groupToStake,
addressToGroup,
groupToReward,
}: {
groupToStake?: GroupToStake;
addressToGroup?: Record<Address, ValidatorGroup>;
Expand All @@ -167,7 +167,7 @@ function TableTabs({

return (
<div>
<div className="flex space-x-10 border-b border-taupe-300 pb-4">
<div className="flex space-x-10 border-b border-taupe-300 pb-2">
<TabHeaderButton isActive={tab === 'stakes'} onClick={() => setTab('stakes')}>
<span className="text-sm">Stakes</span>
</TabHeaderButton>
Expand All @@ -179,53 +179,12 @@ function TableTabs({
</TabHeaderButton>
</div>
{tab === 'stakes' && (
<ActiveStakes groupToStake={groupToStake} addressToGroup={addressToGroup} />
<ActiveStakesTable groupToStake={groupToStake} addressToGroup={addressToGroup} />
)}
{tab === 'rewards' && (
<RewardsTable groupToReward={groupToReward} addressToGroup={addressToGroup} />
)}
{tab === 'rewards' && <div>TODO</div>}
{tab === 'delegations' && <div>TODO</div>}
</div>
);
}

function ActiveStakes({
groupToStake,
addressToGroup,
}: {
groupToStake?: GroupToStake;
addressToGroup?: Record<Address, ValidatorGroup>;
}) {
const rows = Object.entries(groupToStake || {}) as Array<[Address, GroupToStake[Address]]>;

return (
<table className="mt-2 w-full">
<thead>
<tr>
<th className={tableClasses.th}>Name</th>
<th className={tableClasses.th}>Amount</th>
<th className={tableClasses.th}>%</th>
<th className={tableClasses.th}></th>
</tr>
</thead>
<tbody>
{rows.map(([address, stake]) => (
<tr key={address}>
<td className={tableClasses.td}>
<div className="flex items-center">
<ValidatorGroupLogo address={address} size={28} />
<div className="ml-2 flex flex-col">
<span>{addressToGroup?.[address]?.name || 'Unknown'}</span>
<span>{shortenAddress(address)}</span>
</div>
</div>
</td>
<td className={tableClasses.td}>
{formatNumberString(stake.active + stake.pending, 2, true) + ' CELO'}
</td>
<td className={tableClasses.td}>0%</td>
<td className={tableClasses.td}>TODO</td>
</tr>
))}
</tbody>
</table>
);
}
37 changes: 11 additions & 26 deletions src/app/staking/[address]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SolidButton } from 'src/components/buttons/SolidButton';
import { TabHeaderButton } from 'src/components/buttons/TabHeaderButton';
import { TextLink } from 'src/components/buttons/TextLink';
import { HeatmapLines } from 'src/components/charts/Heatmap';
import { sortAndCombineChartData } from 'src/components/charts/chartData';
import { ArrowIcon } from 'src/components/icons/Arrow';
import { Checkmark } from 'src/components/icons/Checkmark';
import { Circle } from 'src/components/icons/Circle';
Expand Down Expand Up @@ -214,7 +215,7 @@ function DetailsSection({ group }: { group?: ValidatorGroup }) {

return (
<div>
<div className="flex space-x-10 border-b border-taupe-300 pb-4">
<div className="flex space-x-10 border-b border-taupe-300 pb-2">
<TabHeaderButton
isActive={tab === 'members'}
onClick={() => setTab('members')}
Expand Down Expand Up @@ -271,7 +272,7 @@ function Members({ group }: { group?: ValidatorGroup }) {
<tr key={member.address}>
<td className={tableClasses.td}>
<div className="flex items-center">
<Identicon address={member.address} size={28} />
<Identicon address={member.address} size={24} />
<span className="ml-2">
{isMobile ? shortenAddress(member.address) : member.address}
</span>
Expand Down Expand Up @@ -304,19 +305,12 @@ function Stakers({ group }: { group?: ValidatorGroup }) {

const chartData = useMemo(() => {
if (!stakers) return null;
if (!objLength(stakers)) return [{ title: 'No Stakers', value: 1, color: Color.Grey }];
let sortedStakers = Object.entries(stakers).sort((a, b) => b[1] - a[1]);
if (sortedStakers.length > 5) {
const topStakers = sortedStakers.slice(0, 5);
const otherStakers = sortedStakers.slice(5);
sortedStakers = [
...topStakers,
['Others', otherStakers.reduce((acc, cur) => acc + cur[1], 0)],
];
}
return sortedStakers.map(([address, amount], i) => {
return { title: address, value: amount, color: PIE_CHART_COLORS[i] };
});
if (!objLength(stakers)) return [{ label: 'No Stakers', value: 1, color: Color.Grey }];
const rawData = Object.entries(stakers).map(([address, amount]) => ({
label: address,
value: amount,
}));
return sortAndCombineChartData(rawData);
}, [stakers]);

if (!chartData?.length) {
Expand All @@ -339,12 +333,12 @@ function Stakers({ group }: { group?: ValidatorGroup }) {
</thead>
<tbody>
{chartData.map((data) => (
<tr key={data.title}>
<tr key={data.label}>
<td className={tableClasses.td}>
<div className="flex items-center space-x-2">
<Circle fill={data.color} size={10} />
<span>
{data.title === 'Others' ? 'Other stakers' : shortenAddress(data.title)}
{data.label === 'Others' ? 'Other stakers' : shortenAddress(data.label)}
</span>
</div>
</td>
Expand Down Expand Up @@ -379,12 +373,3 @@ function getStakersHeaderCount(group?: ValidatorGroup) {
if (group.votes < 20000) return '1';
else return '10+';
}

const PIE_CHART_COLORS = [
Color.Forest,
Color.Citrus,
Color.Lotus,
Color.Lavender,
Color.Sky,
Color.Grey,
];
4 changes: 2 additions & 2 deletions src/components/buttons/TabHeaderButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ export function TabHeaderButton({
{!isNullish(count) && (
<div
className={clsx(
'ml-2 w-10 rounded-full border border-purple-500 text-sm font-light',
'ml-2 w-10 rounded-full border border-purple-500 text-xs font-light',
(hover || isActive) && 'bg-purple-500 text-white',
)}
>
{count}
</div>
)}
{isActive && (
<span className="absolute -bottom-4 left-0 right-0 z-10 hidden h-[2px] bg-purple-500 md:block"></span>
<span className="absolute -bottom-[0.6rem] left-0 right-0 z-10 h-[2px] bg-purple-500"></span>
)}
</button>
);
Expand Down
31 changes: 31 additions & 0 deletions src/components/charts/StackedBarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ChartDataItem } from 'src/components/charts/chartData';
import { Color } from 'src/styles/Color';

export const PLACEHOLDER_BAR_CHART_ITEM = {
label: 'None',
value: 0,
percentage: 100,
color: Color.Grey,
};

export function StackedBarChart({
data,
}: {
data: Array<ChartDataItem & { percentage?: number; color: string }>;
}) {
return (
<div className="flex border border-taupe-300 p-px">
{data.map((item, index) => (
<div
key={index}
style={{
width: `${item.percentage || 0}%`,
backgroundColor: item.color,
}}
className="tooltip h-2"
data-tip={item.label}
></div>
))}
</div>
);
}
23 changes: 23 additions & 0 deletions src/components/charts/chartData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CHART_COLORS } from 'src/styles/Color';
import { sum } from 'src/utils/math';

export interface ChartDataItem {
label: string;
value: number;
percentage?: number;
}

export function sortAndCombineChartData(data: Array<ChartDataItem>) {
const maxNumItems = CHART_COLORS.length - 1;
let sortedData = data.sort((a, b) => b.value - a.value);
if (sortedData.length > maxNumItems) {
const topData = sortedData.slice(0, maxNumItems);
const combinedData = {
label: 'Others',
value: sum(sortedData.slice(maxNumItems).map((d) => d.value)),
percentage: sum(sortedData.slice(maxNumItems).map((d) => d.percentage || 0)),
};
sortedData = [...topData, combinedData];
}
return sortedData.map((d, i) => ({ ...d, color: CHART_COLORS[i] }));
}
2 changes: 1 addition & 1 deletion src/components/nav/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function Header() {

return (
<header
className={`sticky top-0 z-10 w-full border-b border-taupe-300 bg-taupe-100 px-3 transition-all duration-500 ease-in-out sm:px-5 ${
className={`sticky top-0 z-20 w-full border-b border-taupe-300 bg-taupe-100 px-3 transition-all duration-500 ease-in-out sm:px-5 ${
collapseHeader ? 'py-1' : 'py-2 sm:py-2.5'
}`}
>
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function NavBar({ collapsed }: { collapsed?: boolean }) {
{l.to === pathname && (
<div
className={`absolute h-0.5 w-full bg-black transition-all duration-500 ${
collapsed ? '-bottom-3' : '-bottom-4'
collapsed ? '-bottom-3' : '-bottom-[1.15rem]'
}`}
></div>
)}
Expand Down
11 changes: 10 additions & 1 deletion src/config/wagmi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ledgerWallet,
metaMaskWallet,
omniWallet,
rainbowWallet,
trustWallet,
walletConnectWallet,
} from '@rainbow-me/rainbowkit/wallets';
Expand All @@ -25,7 +26,15 @@ const connectors = connectorsForWallets(
[
{
groupName: 'Recommended for Celo',
wallets: [metaMaskWallet, walletConnectWallet, valora, omniWallet, trustWallet, ledgerWallet],
wallets: [
metaMaskWallet,
walletConnectWallet,
valora,
rainbowWallet,
omniWallet,
trustWallet,
ledgerWallet,
],
},
],
{ appName: config.appName, projectId: config.walletConnectProjectId },
Expand Down
Loading

0 comments on commit 1613ce8

Please sign in to comment.