vega-frontend-monorepo/apps/trading/client-pages/competitions/competitions-team.tsx

256 lines
7.1 KiB
TypeScript
Raw Normal View History

import { useState, type ButtonHTMLAttributes } from 'react';
import { Link, useParams } from 'react-router-dom';
import orderBy from 'lodash/orderBy';
import { Splash, truncateMiddle, Loader } from '@vegaprotocol/ui-toolkit';
import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types';
import classNames from 'classnames';
import { useT } from '../../lib/use-t';
import { Table } from '../../components/table';
import { formatNumber, getDateTimeFormat } from '@vegaprotocol/utils';
import {
useTeam,
type TeamStats as ITeamStats,
type Team as TeamType,
type Member,
type TeamGame,
} from '../../lib/hooks/use-team';
import { DApp, EXPLORER_PARTIES, useLinks } from '@vegaprotocol/environment';
import { TeamAvatar } from '../../components/competitions/team-avatar';
import { TeamStats } from '../../components/competitions/team-stats';
import { usePageTitle } from '../../lib/hooks/use-page-title';
import { ErrorBoundary } from '../../components/error-boundary';
import { LayoutWithGradient } from '../../components/layouts-inner';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { JoinTeam } from './join-team';
import { UpdateTeamButton } from './update-team-button';
export const CompetitionsTeam = () => {
const t = useT();
const { teamId } = useParams<{ teamId: string }>();
usePageTitle([t('Competitions'), t('Team')]);
return (
<ErrorBoundary feature="team">
<TeamPageContainer teamId={teamId} />
</ErrorBoundary>
);
};
const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
const t = useT();
const { pubKey } = useVegaWallet();
const { data, team, partyTeam, stats, members, games, loading, refetch } =
useTeam(teamId, pubKey || undefined);
// only show spinner on first load so when users join teams its smoother
if (!data && loading) {
return (
<Splash>
<Loader />
</Splash>
);
}
if (!team) {
return (
<Splash>
<p>{t('Page not found')}</p>
</Splash>
);
}
return (
<TeamPage
team={team}
partyTeam={partyTeam}
stats={stats}
members={members}
games={games}
refetch={refetch}
/>
);
};
const TeamPage = ({
team,
partyTeam,
stats,
members,
games,
refetch,
}: {
team: TeamType;
partyTeam?: TeamType;
stats?: ITeamStats;
members?: Member[];
games?: TeamGame[];
refetch: () => void;
}) => {
const t = useT();
const [showGames, setShowGames] = useState(true);
return (
<LayoutWithGradient>
<header className="flex gap-3 lg:gap-4 pt-5 lg:pt-10">
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<div className="flex flex-col items-start gap-1 lg:gap-3">
<h1
className="calt text-2xl lg:text-3xl xl:text-5xl"
data-testid="team-name"
>
{team.name}
</h1>
<div className="flex gap-2">
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
</div>
</div>
</header>
<TeamStats stats={stats} members={members} games={games} />
<section>
<div className="flex gap-4 lg:gap-8 mb-4 border-b border-default">
<ToggleButton
active={showGames}
onClick={() => setShowGames(true)}
data-testid="games-toggle"
>
{t('Games ({{count}})', { count: games ? games.length : 0 })}
</ToggleButton>
<ToggleButton
active={!showGames}
onClick={() => setShowGames(false)}
data-testid="members-toggle"
>
{t('Members ({{count}})', {
count: members ? members.length : 0,
})}
</ToggleButton>
</div>
{showGames ? <Games games={games} /> : <Members members={members} />}
</section>
</LayoutWithGradient>
);
};
const Games = ({ games }: { games?: TeamGame[] }) => {
const t = useT();
if (!games?.length) {
return <p>{t('No games')}</p>;
}
return (
<Table
columns={[
{ name: 'rank', displayName: t('Rank') },
{
name: 'epoch',
displayName: t('Epoch'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{ name: 'type', displayName: t('Type') },
{ name: 'amount', displayName: t('Amount earned') },
{
name: 'participatingTeams',
displayName: t('No. of participating teams'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{
name: 'participatingMembers',
displayName: t('No. of participating members'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
]}
data={games.map((game) => ({
rank: game.team.rank,
epoch: game.epoch,
type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
amount: formatNumber(game.team.totalRewardsEarned),
participatingTeams: game.entities.length,
participatingMembers: game.numberOfParticipants,
}))}
noCollapse={true}
/>
);
};
const Members = ({ members }: { members?: Member[] }) => {
const t = useT();
if (!members?.length) {
return <p>{t('No members')}</p>;
}
const data = orderBy(
members.map((m) => ({
referee: <RefereeLink pubkey={m.referee} isCreator={m.isCreator} />,
rewards: formatNumber(m.totalQuantumRewards),
volume: formatNumber(m.totalQuantumVolume),
gamesPlayed: formatNumber(m.totalGamesPlayed),
joinedAt: getDateTimeFormat().format(new Date(m.joinedAt)),
joinedAtEpoch: Number(m.joinedAtEpoch),
})),
'joinedAtEpoch',
'desc'
);
return (
<Table
columns={[
{ name: 'referee', displayName: t('Member ID') },
{ name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') },
{
name: 'joinedAt',
displayName: t('Joined at'),
},
{
name: 'joinedAtEpoch',
displayName: t('Joined epoch'),
},
]}
data={data}
noCollapse={true}
/>
);
};
const RefereeLink = ({
pubkey,
isCreator,
}: {
pubkey: string;
isCreator: boolean;
}) => {
const t = useT();
const linkCreator = useLinks(DApp.Explorer);
const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey));
return (
<>
<Link to={link} target="_blank" className="underline underline-offset-4">
{truncateMiddle(pubkey)}
</Link>{' '}
<span className="text-muted text-xs">{isCreator ? t('Owner') : ''}</span>
</>
);
};
const ToggleButton = ({
active,
...props
}: ButtonHTMLAttributes<HTMLButtonElement> & { active: boolean }) => {
return (
<button
{...props}
className={classNames('relative top-px uppercase border-b-2 py-4', {
'text-muted border-transparent': !active,
'border-vega-yellow': active,
})}
/>
);
};