diff --git a/apps/trading/client-pages/competitions/competitions-home.tsx b/apps/trading/client-pages/competitions/competitions-home.tsx index 24217c02ee..253b94894a 100644 --- a/apps/trading/client-pages/competitions/competitions-home.tsx +++ b/apps/trading/client-pages/competitions/competitions-home.tsx @@ -16,6 +16,8 @@ import { CompetitionsLeaderboard } from '../../components/competitions/competiti import { useTeams } from '../../lib/hooks/use-teams'; import take from 'lodash/take'; import { usePageTitle } from '../../lib/hooks/use-page-title'; +import { TeamCard } from '../../components/competitions/team-card'; +import { useMyTeam } from '../../lib/hooks/use-my-team'; export const CompetitionsHome = () => { const t = useT(); @@ -33,6 +35,13 @@ export const CompetitionsHome = () => { const { data: teamsData, loading: teamsLoading } = useTeams(); + const { + team: myTeam, + stats: myTeamStats, + games: myTeamGames, + rank: myTeamRank, + } = useMyTeam(); + return ( @@ -43,68 +52,83 @@ export const CompetitionsHome = () => {

- {/** Get started */} -

{t('Get started')}

+ {/** Team card */} + {myTeam ? ( + <> +

{t('My team')}

+
+ +
+ + ) : ( + <> + {/** Get started */} +

{t('Get started')}

- - { - e.preventDefault(); - navigate(Links.COMPETITIONS_CREATE_TEAM()); - }} - data-testid="create-public-team-button" - > - {t('Create a public team')} - - } - /> - { - e.preventDefault(); - navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO()); - }} - data-testid="create-private-team-button" - > - {t('Create a private team')} - - } - /> - { - e.preventDefault(); - navigate(Links.COMPETITIONS_TEAMS()); - }} - data-testid="choose-team-button" - > - {t('Choose a team')} - - } - /> - + + { + e.preventDefault(); + navigate(Links.COMPETITIONS_CREATE_TEAM()); + }} + data-testId="create-public-team-button" + > + {t('Create a public team')} + + } + /> + { + e.preventDefault(); + navigate(Links.COMPETITIONS_CREATE_TEAM_SOLO()); + }} + > + {t('Create a private team')} + + } + /> + { + e.preventDefault(); + navigate(Links.COMPETITIONS_TEAMS()); + }} + > + {t('Choose a team')} + + } + /> + + + )} {/** List of available games */}

{t('Games')}

diff --git a/apps/trading/client-pages/competitions/update-team-button.tsx b/apps/trading/client-pages/competitions/update-team-button.tsx index b2a650fd65..0ea312f17f 100644 --- a/apps/trading/client-pages/competitions/update-team-button.tsx +++ b/apps/trading/client-pages/competitions/update-team-button.tsx @@ -1,16 +1,24 @@ -import { useVegaWallet } from '@vegaprotocol/wallet'; import { type Team } from '../../lib/hooks/use-team'; +import { type ComponentProps } from 'react'; +import { useVegaWallet } from '@vegaprotocol/wallet'; import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit'; import { Links } from '../../lib/links'; import { useT } from '../../lib/use-t'; -export const UpdateTeamButton = ({ team }: { team: Team }) => { +export const UpdateTeamButton = ({ + team, + size = 'medium', +}: { + team: Pick; + size?: ComponentProps['size']; +}) => { const t = useT(); const { pubKey, isReadOnly } = useVegaWallet(); if (pubKey && !isReadOnly && pubKey === team.referrer) { return ( ) => { +export const Box = ({ + children, + backgroundImage, + ...props +}: HTMLAttributes & { backgroundImage?: string }) => { return (
) => { BORDER_COLOR, GRADIENT, 'border rounded-lg', - 'p-6', + 'relative p-6 overflow-hidden', props.className )} - /> + > + {Boolean(backgroundImage?.length) && ( +
+ )} + + {children} +
); }; diff --git a/apps/trading/components/competitions/team-avatar.tsx b/apps/trading/components/competitions/team-avatar.tsx index 8074ed525f..7e8be0ecb0 100644 --- a/apps/trading/components/competitions/team-avatar.tsx +++ b/apps/trading/components/competitions/team-avatar.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; const NUM_AVATARS = 20; const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png'; -const getFallbackAvatar = (teamId: string) => { +export const getFallbackAvatar = (teamId: string) => { const avatarId = ((parseInt(teamId, 16) % NUM_AVATARS) + 1) .toString() .padStart(2, '0'); // between 01 - 20 diff --git a/apps/trading/components/competitions/team-card.tsx b/apps/trading/components/competitions/team-card.tsx new file mode 100644 index 0000000000..69307dfc64 --- /dev/null +++ b/apps/trading/components/competitions/team-card.tsx @@ -0,0 +1,154 @@ +import { type TeamGame, type TeamStats } from '../../lib/hooks/use-team'; +import { type TeamsFieldsFragment } from '../../lib/hooks/__generated__/Teams'; +import { TeamAvatar, getFallbackAvatar } from './team-avatar'; +import { FavoriteGame, Stat } from './team-stats'; +import { useT } from '../../lib/use-t'; +import { formatNumberRounded } from '@vegaprotocol/utils'; +import BigNumber from 'bignumber.js'; +import { Box } from './box'; +import { Intent, Tooltip, TradingAnchorButton } from '@vegaprotocol/ui-toolkit'; +import { Links } from '../../lib/links'; +import orderBy from 'lodash/orderBy'; +import { take } from 'lodash'; +import { DispatchMetricLabels } from '@vegaprotocol/types'; +import classNames from 'classnames'; +import { UpdateTeamButton } from '../../client-pages/competitions/update-team-button'; + +export const TeamCard = ({ + rank, + team, + stats, + games, +}: { + rank: number; + team: TeamsFieldsFragment; + stats?: TeamStats; + games?: TeamGame[]; +}) => { + const t = useT(); + + const lastGames = take( + orderBy( + games?.map((g) => ({ + rank: g.team.rank, + metric: g.team.rewardMetric, + epoch: g.epoch, + })), + (i) => i.epoch, + 'desc' + ), + 5 + ); + + return ( +
+ {/** Card */} + + +

+ {team.name} +

+ {games && } + + {t('Profile')} + + +
+ + {/** Tiles */} + +
+ + + + + +
+ +
+
+ {t('Last {{games}} games result', { + replace: { games: lastGames.length || '' }, + })} +
+
+ {lastGames.length === 0 && t('None available')} + {lastGames.map((game, i) => ( + + + + ))} +
+
+
+
+ ); +}; + +/** + * Sets the english ordinal for given rank only if the current language is set + * to english. + */ +const RankLabel = ({ rank }: { rank: number }) => { + const t = useT(); + return t('place', { count: rank, ordinal: true }); +}; diff --git a/apps/trading/components/competitions/team-stats.tsx b/apps/trading/components/competitions/team-stats.tsx index d875e85e8d..ced4ad18c9 100644 --- a/apps/trading/components/competitions/team-stats.tsx +++ b/apps/trading/components/competitions/team-stats.tsx @@ -15,6 +15,7 @@ import { } from '../../lib/hooks/use-team'; import { useT } from '../../lib/use-t'; import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types'; +import classNames from 'classnames'; export const TeamStats = ({ stats, @@ -102,7 +103,13 @@ const LatestResults = ({ games }: { games: TeamGame[] }) => { ); }; -const FavoriteGame = ({ games }: { games: TeamGame[] }) => { +export const FavoriteGame = ({ + games, + noLabel = false, +}: { + games: TeamGame[]; + noLabel?: boolean; +}) => { const t = useT(); const rewardMetrics = games.map( @@ -128,7 +135,13 @@ const FavoriteGame = ({ games }: { games: TeamGame[] }) => { return (
-
{t('Favorite game')}
+
+ {t('Favorite game')} +
{ ); }; -const StatSection = ({ children }: { children: ReactNode }) => { +export const StatSection = ({ children }: { children: ReactNode }) => { return (
{children} @@ -150,11 +163,11 @@ const StatSection = ({ children }: { children: ReactNode }) => { ); }; -const StatSectionSeparator = () => { +export const StatSectionSeparator = () => { return
; }; -const StatList = ({ children }: { children: ReactNode }) => { +export const StatList = ({ children }: { children: ReactNode }) => { return (
{children} @@ -162,19 +175,21 @@ const StatList = ({ children }: { children: ReactNode }) => { ); }; -const Stat = ({ +export const Stat = ({ value, label, tooltip, valueTestId, + className, }: { value: ReactNode; label: ReactNode; tooltip?: string; valueTestId?: string; + className?: classNames.Argument; }) => { return ( -
+
{value}
diff --git a/apps/trading/lib/hooks/Teams.graphql b/apps/trading/lib/hooks/Teams.graphql index be308f3bfa..b256c6a3c2 100644 --- a/apps/trading/lib/hooks/Teams.graphql +++ b/apps/trading/lib/hooks/Teams.graphql @@ -1,15 +1,20 @@ +fragment TeamsFields on Team { + teamId + referrer + name + teamUrl + avatarUrl + createdAt + createdAtEpoch + closed + totalMembers +} + query Teams($teamId: ID, $partyId: ID) { teams(teamId: $teamId, partyId: $partyId) { edges { node { - teamId - referrer - name - teamUrl - avatarUrl - createdAt - createdAtEpoch - closed + ...TeamsFields } } } diff --git a/apps/trading/lib/hooks/__generated__/Teams.ts b/apps/trading/lib/hooks/__generated__/Teams.ts index 08b2f14326..ea610632bb 100644 --- a/apps/trading/lib/hooks/__generated__/Teams.ts +++ b/apps/trading/lib/hooks/__generated__/Teams.ts @@ -3,33 +3,40 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; +export type TeamsFieldsFragment = { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, totalMembers: number }; + export type TeamsQueryVariables = Types.Exact<{ teamId?: Types.InputMaybe; partyId?: Types.InputMaybe; }>; -export type TeamsQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean } }> } | null }; - +export type TeamsQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, totalMembers: number } }> } | null }; +export const TeamsFieldsFragmentDoc = gql` + fragment TeamsFields on Team { + teamId + referrer + name + teamUrl + avatarUrl + createdAt + createdAtEpoch + closed + totalMembers +} + `; export const TeamsDocument = gql` query Teams($teamId: ID, $partyId: ID) { teams(teamId: $teamId, partyId: $partyId) { edges { node { - teamId - referrer - name - teamUrl - avatarUrl - createdAt - createdAtEpoch - closed + ...TeamsFields } } } } - `; + ${TeamsFieldsFragmentDoc}`; /** * __useTeamsQuery__ diff --git a/apps/trading/lib/hooks/use-my-team.ts b/apps/trading/lib/hooks/use-my-team.ts new file mode 100644 index 0000000000..dac0ab6f27 --- /dev/null +++ b/apps/trading/lib/hooks/use-my-team.ts @@ -0,0 +1,25 @@ +import { useVegaWallet } from '@vegaprotocol/wallet'; +import compact from 'lodash/compact'; +import first from 'lodash/first'; +import { useTeamsQuery } from './__generated__/Teams'; +import { useTeam } from './use-team'; +import { useTeams } from './use-teams'; + +export const useMyTeam = () => { + const { pubKey } = useVegaWallet(); + const { data: teams } = useTeams(); + + const { data: maybeMyTeam } = useTeamsQuery({ + variables: { + partyId: pubKey, + }, + skip: !pubKey, + fetchPolicy: 'cache-and-network', + }); + + const team = first(compact(maybeMyTeam?.teams?.edges.map((n) => n.node))); + const rank = teams.findIndex((t) => t.teamId === team?.teamId) + 1; + const { games, stats } = useTeam(team?.teamId); + + return { team, stats, games, rank }; +}; diff --git a/libs/i18n/src/locales/en/trading.json b/libs/i18n/src/locales/en/trading.json index ed36da9732..82a2e0aeae 100644 --- a/libs/i18n/src/locales/en/trading.json +++ b/libs/i18n/src/locales/en/trading.json @@ -417,6 +417,7 @@ "myVolume_other": "My volume (last {{count}} epochs)", "numberEpochs": "{{count}} epochs", "numberEpochs_one": "{{count}} epoch", + "Rewards earned": "Rewards earned", "Rewards paid out": "Rewards paid out", "{{reward}}x": "{{reward}}x", "userActive": "{{active}} trader: {{count}} epochs so far", @@ -431,5 +432,19 @@ "{{assetSymbol}} Reward pot": "{{assetSymbol}} Reward pot", "{{checkedAssets}} Assets": "{{checkedAssets}} Assets", "{{distance}} ago": "{{distance}} ago", - "{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision" + "{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision", + "My team": "My team", + "Profile": "Profile", + "Last {{games}} games result_one": "Last game result", + "Last {{games}} games result_other": "Last {{games}} games result", + "Leaderboard": "Leaderboard", + "View all teams": "View all teams", + "Competitions": "Competitions", + "Be a team player! Participate in games and work together to rake in as much profit to win.": "Be a team player! Participate in games and work together to rake in as much profit to win.", + "Create a public team": "Create a public team", + "Create a private team": "Create a private team", + "Choose a team": "Choose a team", + "Join a team": "Join a team", + "Solo team / lone wolf": "Solo team / lone wolf", + "Choose a team to get involved": "Choose a team to get involved" }