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

Feat: Testnet points leaderboard #11

Merged
merged 10 commits into from
Dec 4, 2024
14 changes: 14 additions & 0 deletions apps/staking/app/leaderboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { ReactNode } from 'react';
import { siteMetadata } from '@/lib/metadata';

export function generateMetadata() {
return siteMetadata({
title: 'Testnet Leaderboard',
description:
'Track the top-performing wallets in the Session Testnet Incentive Program. Rankings are based on total points earned through running and staking to nodes.',
});
}

export default async function RootLayout({ children }: { children: ReactNode }) {
return children;
}
111 changes: 111 additions & 0 deletions apps/staking/app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { toast } from '@session/ui/lib/toast';
import { PubKey } from '@session/ui/components/PubKey';
import { formatNumber, formatPercentage } from '@/lib/locale-client';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@session/ui/ui/table';
import Typography from '@session/ui/components/Typography';
import { useWallet } from '@session/wallet/hooks/wallet-hooks';
import { areHexesEqual } from '@session/util-crypto/string';
import Link from 'next/link';
import { Loading } from '@session/ui/components/loading';
import { cn } from '@session/ui/lib/utils';

// TODO: Delete route after testnet incentive program is over

function smartFormatPercentage(decimalPercent: number) {
const maximumFractionDigits = decimalPercent > 0.0001 ? 4 : 6;
return formatPercentage(decimalPercent, { maximumFractionDigits });
}

export default function PointsPage() {
const { address } = useWallet();
const { data, isLoading, isError } = useQuery({
queryKey: ['points'],
queryFn: async () => {
const res = await fetch(process.env.NEXT_PUBLIC_POINTS_PROGRAM_API!);

if (!res.ok) {
toast.error('Failed to fetch points');
}

const data = await res.json();

return (
Object.entries(data.wallets) as Array<
[
string,
{
score: number;
percent: number;
},
]
>
).sort((a, b) => b[1].score - a[1].score);
},
});

return (
<div className="mt-10 flex flex-col items-center gap-6">
<div className="flex max-w-xl flex-col items-center gap-4 text-center">
<Typography variant="h1">Testnet Leaderboard</Typography>
<Typography variant="p">
Track the top-performing wallets in the{' '}
<Link
target="_blank"
href="https://token.getsession.org/testnet-incentive-program"
className="text-session-green underline"
>
Session Testnet Incentive Program
</Link>
. Rankings are based on total points earned through running and staking to nodes.
</Typography>
</div>
{isLoading ? (
<Loading />
) : isError ? (
<span>Something went wrong</span>
) : (
<Table className="w-max max-w-[90vw]">
<TableHeader>
<TableRow className="text-sm md:text-lg">
<TableHead className="hidden md:table-cell">Rank</TableHead>
<TableHead>Wallet Address</TableHead>
<TableHead>Points</TableHead>
<TableHead className="text-right">Percent</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data?.map(([wallet, { score, percent }], i) => (
<TableRow
key={wallet}
className={cn(
'text-sm md:text-base',
areHexesEqual(wallet, address)
? 'bg-session-green text-session-black hover:bg-session-green-dark'
: 'hover:bg-session-green hover:text-session-black hover:selection:bg-session-black hover:selection:text-session-green'
)}
>
<TableCell className="hidden font-bold md:block">{i + 1}</TableCell>
<TableCell className="w-max">
<PubKey pubKey={wallet} />
</TableCell>
<TableCell>{formatNumber(score)}</TableCell>
<TableCell className="p-0 py-4 pr-1 text-right md:pe-1">
{smartFormatPercentage(percent / 10000)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
);
}
9 changes: 6 additions & 3 deletions apps/staking/app/mystakes/modules/TestnetPointsModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Address } from 'viem';
import { useQuery } from '@tanstack/react-query';
import { toast } from '@session/ui/lib/toast';
import { areHexesEqual } from '@session/util-crypto/string';
import { formatNumber } from '@/lib/locale-client';
import { formatNumber, formatPercentage } from '@/lib/locale-client';

const noPointsObject = {
score: 0,
Expand Down Expand Up @@ -56,7 +56,7 @@ export default function TestnetPointsModule(params?: { addressOverride?: Address
},
});

const points = data?.score ? `${formatNumber(data.score)} points` : null;
const points = `${formatNumber(data?.score ?? 0)} points`;

return (
<Module>
Expand All @@ -65,7 +65,10 @@ export default function TestnetPointsModule(params?: { addressOverride?: Address
link: externalLink(URL.SESSION_TOKEN_COMMUNITY_SNAPSHOT),
})}
</ModuleTooltip>
<ModuleTitle>{titleFormat('format', { title })}</ModuleTitle>
<ModuleTitle>
{titleFormat('format', { title })}
{` (${formatPercentage((data?.percent ?? 0) / 10000)})`}
</ModuleTitle>
<ModuleDynamicQueryText
status={status as QUERY_STATUS}
fallback={0}
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/components/ui/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { cn } from '../../lib/utils';

const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<div className="relative overflow-auto">
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
</div>
)
Expand Down Expand Up @@ -44,7 +44,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
<tr
ref={ref}
className={cn(
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
'hover:bg-muted/50 data-[state=selected]:bg-muted border-session-white border-b transition-colors',
className
)}
{...props}
Expand Down
Loading