feat(trading): team profile improvements (#6054)

Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
This commit is contained in:
Art 2024-03-20 18:17:05 +01:00 committed by GitHub
parent 204871b81c
commit 62ecaaa9ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 153 additions and 15 deletions

View File

@ -9,6 +9,10 @@ import {
Button, Button,
VegaIcon, VegaIcon,
VegaIconNames, VegaIconNames,
Tooltip,
TradingAnchorButton,
Intent,
CopyWithTooltip,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { TransferStatus, type Asset } from '@vegaprotocol/types'; import { TransferStatus, type Asset } from '@vegaprotocol/types';
import classNames from 'classnames'; import classNames from 'classnames';
@ -18,6 +22,7 @@ import {
addDecimalsFormatNumberQuantum, addDecimalsFormatNumberQuantum,
formatNumber, formatNumber,
getDateTimeFormat, getDateTimeFormat,
removePaginationWrapper,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { import {
useTeam, useTeam,
@ -52,6 +57,7 @@ import {
ActiveRewardCard, ActiveRewardCard,
DispatchMetricInfo, DispatchMetricInfo,
} from '../../components/rewards-container/reward-card'; } from '../../components/rewards-container/reward-card';
import { usePartyProfilesQuery } from '../../components/vega-wallet-connect-button/__generated__/PartyProfiles';
export const CompetitionsTeam = () => { export const CompetitionsTeam = () => {
const t = useT(); const t = useT();
@ -140,11 +146,25 @@ const TeamPage = ({
const t = useT(); const t = useT();
const [showGames, setShowGames] = useState(true); const [showGames, setShowGames] = useState(true);
const createdAt = new Date(team.createdAt);
const closedIndicator = team.closed ? (
<div className="border rounded border-vega-clight-300 dark:border-vega-cdark-300 px-1 pt-[1px] flex items-baseline gap-1">
<VegaIcon name={VegaIconNames.LOCK} size={10} />
<span>{t('Private')}</span>
</div>
) : (
<div className="border rounded border-vega-clight-300 dark:border-vega-cdark-300 px-1 pt-[1px] flex items-baseline gap-1">
<VegaIcon name={VegaIconNames.GLOBE} size={10} />
<span>{t('Public')}</span>
</div>
);
return ( return (
<LayoutWithGradient> <LayoutWithGradient>
<header className="flex gap-3 lg:gap-4 pt-5 lg:pt-10"> <header className="flex gap-3 lg:gap-4 pt-5 lg:pt-10">
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} /> <TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
<div className="flex flex-col items-start gap-1 lg:gap-3"> <div className="flex flex-col items-start gap-1 lg:gap-2">
<h1 <h1
className="calt text-2xl lg:text-3xl xl:text-5xl" className="calt text-2xl lg:text-3xl xl:text-5xl"
data-testid="team-name" data-testid="team-name"
@ -154,6 +174,38 @@ const TeamPage = ({
<div className="flex gap-2"> <div className="flex gap-2">
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} /> <JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} /> <UpdateTeamButton team={team} />
{team.teamUrl && team.teamUrl.length > 0 && (
<Tooltip description={t("Visit the team's page.")}>
<span>
<TradingAnchorButton
intent={Intent.Info}
target="_blank"
referrerPolicy="no-referrer"
href={team.teamUrl}
>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={16} />
</TradingAnchorButton>
</span>
</Tooltip>
)}
<CopyWithTooltip
description={t('Copy this page url.')}
text={globalThis.location.href}
>
<button className="h-10 w-7">
<VegaIcon name={VegaIconNames.COPY} size={16} />
</button>
</CopyWithTooltip>
</div>
<div className="flex gap-2 items-baseline text-xs text-muted font-alpha calt">
{closedIndicator}
<div className="">
{t('Created at')}:{' '}
<span className="text-vega-cdark-600 dark:text-vega-clight-600 ">
{getDateTimeFormat().format(createdAt)}
</span>{' '}
({t('epoch')}: {team.createdAtEpoch})
</div>
</div> </div>
</div> </div>
</header> </header>
@ -331,13 +383,30 @@ const Games = ({
const Members = ({ members }: { members?: Member[] }) => { const Members = ({ members }: { members?: Member[] }) => {
const t = useT(); const t = useT();
const partyIds = members?.map((m) => m.referee) || [];
const { data: profilesData } = usePartyProfilesQuery({
variables: {
partyIds,
},
skip: partyIds.length === 0,
});
const profiles = removePaginationWrapper(
profilesData?.partiesProfilesConnection?.edges
);
if (!members?.length) { if (!members?.length) {
return <p>{t('No members')}</p>; return <p>{t('No members')}</p>;
} }
const data = orderBy( const data = orderBy(
members.map((m) => ({ members.map((m) => ({
referee: <RefereeLink pubkey={m.referee} isCreator={m.isCreator} />, referee: (
<RefereeLink
pubkey={m.referee}
isCreator={m.isCreator}
profiles={profiles}
/>
),
rewards: formatNumber(m.totalQuantumRewards), rewards: formatNumber(m.totalQuantumRewards),
volume: formatNumber(m.totalQuantumVolume), volume: formatNumber(m.totalQuantumVolume),
gamesPlayed: formatNumber(m.totalGamesPlayed), gamesPlayed: formatNumber(m.totalGamesPlayed),
@ -351,7 +420,7 @@ const Members = ({ members }: { members?: Member[] }) => {
return ( return (
<Table <Table
columns={[ columns={[
{ name: 'referee', displayName: t('Member ID') }, { name: 'referee', displayName: t('Member') },
{ name: 'rewards', displayName: t('Rewards earned') }, { name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') }, { name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') }, { name: 'gamesPlayed', displayName: t('Games played') },
@ -373,21 +442,43 @@ const Members = ({ members }: { members?: Member[] }) => {
const RefereeLink = ({ const RefereeLink = ({
pubkey, pubkey,
isCreator, isCreator,
profiles,
}: { }: {
pubkey: string; pubkey: string;
isCreator: boolean; isCreator: boolean;
profiles?: { partyId: string; alias: string }[];
}) => { }) => {
const t = useT(); const t = useT();
const linkCreator = useLinks(DApp.Explorer); const linkCreator = useLinks(DApp.Explorer);
const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey)); const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey));
const alias = profiles?.find((p) => p.partyId === pubkey)?.alias;
return ( return (
<> <div className="flex items-baseline gap-2">
<Link to={link} target="_blank" className="underline underline-offset-4"> <Link to={link} target="_blank" className="underline underline-offset-4">
{truncateMiddle(pubkey)} {alias || truncateMiddle(pubkey)}
</Link>{' '} </Link>
<span className="text-muted text-xs">{isCreator ? t('Owner') : ''}</span> {!alias && (
</> <Tooltip
description={t(
'You can set your pubkey alias by using the key selector in the top right corner.'
)}
>
<button className="text-muted text-xs">
<VegaIcon name={VegaIconNames.QUESTION_MARK} size={14} />
</button>
</Tooltip>
)}
{alias && (
<span className="text-muted text-xs">{truncateMiddle(pubkey)}</span>
)}
{isCreator && (
<span className="text-muted text-xs border border-vega-clight-300 dark:border-vega-cdark-300 rounded px-1 py-[1px]">
{t('Owner')}
</span>
)}
</div>
); );
}; };

View File

@ -70,6 +70,12 @@ export const JoinButton = ({
}) => { }) => {
const t = useT(); const t = useT();
/**
* A team cannot be joined (closed) when set as such
* and the currently connected pubkey is not whitelisted.
*/
const isTeamClosed = team.closed && !team.allowList.includes(pubKey || '');
if (!pubKey || isReadOnly) { if (!pubKey || isReadOnly) {
return ( return (
<Tooltip description={t('Connect your wallet to join the team')}> <Tooltip description={t('Connect your wallet to join the team')}>
@ -79,8 +85,9 @@ export const JoinButton = ({
</Tooltip> </Tooltip>
); );
} }
// Party is the creator of a team // Party is the creator of a team
else if (partyTeam && partyTeam.referrer === pubKey) { if (partyTeam && partyTeam.referrer === pubKey) {
// Party is the creator of THIS team // Party is the creator of THIS team
if (partyTeam.teamId === team.teamId) { if (partyTeam.teamId === team.teamId) {
return ( return (
@ -105,8 +112,24 @@ export const JoinButton = ({
); );
} }
} }
// Party is in a team, but not this one // Party is in a team, but not this one
else if (partyTeam && partyTeam.teamId !== team.teamId) { if (partyTeam && partyTeam.teamId !== team.teamId) {
// This team is closed.
if (isTeamClosed) {
return (
<Tooltip description={t('You cannot join a private team')}>
<Button
intent={Intent.Primary}
data-testid="switch-team-button"
disabled={true}
>
{t('Switch team')}{' '}
</Button>
</Tooltip>
);
}
// This team is open.
return ( return (
<Button <Button
onClick={() => onJoin('switch')} onClick={() => onJoin('switch')}
@ -117,8 +140,9 @@ export const JoinButton = ({
</Button> </Button>
); );
} }
// Joined. Current party is already in this team // Joined. Current party is already in this team
else if (partyTeam && partyTeam.teamId === team.teamId) { if (partyTeam && partyTeam.teamId === team.teamId) {
return ( return (
<Button intent={Intent.None} disabled={true}> <Button intent={Intent.None} disabled={true}>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
@ -131,6 +155,17 @@ export const JoinButton = ({
); );
} }
// This team is closed.
if (isTeamClosed) {
return (
<Tooltip description={t('You cannot join a closed team')}>
<Button intent={Intent.Primary} disabled={true}>
{t('Join team')}
</Button>
</Tooltip>
);
}
// This team is open.
return ( return (
<Button onClick={() => onJoin('join')} intent={Intent.Primary}> <Button onClick={() => onJoin('join')} intent={Intent.Primary}>
{t('Join team')} {t('Join team')}

View File

@ -67,7 +67,7 @@ export const CompetitionsLeaderboard = ({
), ),
earned: num(td.totalQuantumRewards), earned: num(td.totalQuantumRewards),
games: num(td.totalGamesPlayed), games: num(td.totalGamesPlayed),
status: td.closed ? t('Closed') : t('Open'), status: td.closed ? t('Private') : t('Public'),
volume: num(td.totalQuantumVolume), volume: num(td.totalQuantumVolume),
}; };
})} })}

View File

@ -242,6 +242,7 @@ def test_team_page_games_table(team_page: Tuple[Page, str, str, VegaServiceNull]
expect(page.get_by_test_id("games-toggle")).to_have_text("Results (10)") expect(page.get_by_test_id("games-toggle")).to_have_text("Results (10)")
expect(page.get_by_test_id("rank-0")).to_have_text("1") expect(page.get_by_test_id("rank-0")).to_have_text("1")
expect(page.get_by_test_id("epoch-0")).to_have_text("19") expect(page.get_by_test_id("epoch-0")).to_have_text("19")
expect(page.get_by_test_id("endtime-0")).to_be_visible()
expect(page.get_by_test_id("type-0")).to_have_text( expect(page.get_by_test_id("type-0")).to_have_text(
"Price maker fees paid • tDAI " "Price maker fees paid • tDAI "
) )
@ -256,6 +257,9 @@ def test_team_page_members_table(team_page: Tuple[Page, str, str, VegaServiceNul
page.get_by_test_id("members-toggle").click() page.get_by_test_id("members-toggle").click()
expect(page.get_by_test_id("members-toggle")).to_have_text("Members (4)") expect(page.get_by_test_id("members-toggle")).to_have_text("Members (4)")
expect(page.get_by_test_id("referee-0")).to_be_visible() expect(page.get_by_test_id("referee-0")).to_be_visible()
expect(page.get_by_test_id("icon-question-mark").nth(0)).to_be_visible()
expect(page.get_by_test_id("referee-3").locator(".text-muted").nth(1)
).to_have_text("Owner")
expect(page.get_by_test_id("joinedAt-0")).to_be_visible() expect(page.get_by_test_id("joinedAt-0")).to_be_visible()
expect(page.get_by_test_id("joinedAtEpoch-0")).to_have_text("9") expect(page.get_by_test_id("joinedAtEpoch-0")).to_have_text("9")
@ -263,6 +267,8 @@ def test_team_page_members_table(team_page: Tuple[Page, str, str, VegaServiceNul
def test_team_page_headline(team_page: Tuple[Page, str, str, VegaServiceNull]): def test_team_page_headline(team_page: Tuple[Page, str, str, VegaServiceNull]):
page, team_name, team_id, vega = team_page page, team_name, team_id, vega = team_page
expect(page.get_by_test_id("team-name")).to_have_text(team_name) expect(page.get_by_test_id("team-name")).to_have_text(team_name)
expect(page.get_by_test_id("icon-open-external").nth(1)).to_be_visible()
expect(page.get_by_test_id("icon-copy")).to_be_visible()
expect(page.get_by_test_id("members-count-stat")).to_have_text("4") expect(page.get_by_test_id("members-count-stat")).to_have_text("4")
expect(page.get_by_test_id("total-games-stat")).to_have_text("1") expect(page.get_by_test_id("total-games-stat")).to_have_text("1")
@ -295,7 +301,7 @@ def test_leaderboard(competitions_page: Tuple[Page, str, VegaServiceNull]):
page.get_by_test_id("rank-1").locator(".text-vega-clight-500") page.get_by_test_id("rank-1").locator(".text-vega-clight-500")
).to_have_count(1) ).to_have_count(1)
expect(page.get_by_test_id("team-1")).to_have_text(team_name) expect(page.get_by_test_id("team-1")).to_have_text(team_name)
expect(page.get_by_test_id("status-1")).to_have_text("Open") expect(page.get_by_test_id("status-1")).to_have_text("Public")
# FIXME: the numbers are different we need to clarify this with the backend # FIXME: the numbers are different we need to clarify this with the backend
# expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160") # expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160")
@ -320,7 +326,6 @@ def test_game_card(competitions_page: Tuple[Page, str, VegaServiceNull]):
"Price maker fees paid • tDAI" "Price maker fees paid • tDAI"
) )
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs") expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
page.pause()
expect(game_1.get_by_test_id("scope")).to_have_text("Eligible") expect(game_1.get_by_test_id("scope")).to_have_text("Eligible")
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("-") expect(game_1.get_by_test_id("staking-requirement")).to_have_text("-")
expect(game_1.get_by_test_id("average-position")).to_have_text("-") expect(game_1.get_by_test_id("average-position")).to_have_text("-")

View File

@ -18,6 +18,7 @@
"Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction": "Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction", "Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction": "Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction",
"Are you sure you want to join team: {{team}}": "Are you sure you want to join team: {{team}}", "Are you sure you want to join team: {{team}}": "Are you sure you want to join team: {{team}}",
"As a team creator, you cannot switch teams": "As a team creator, you cannot switch teams", "As a team creator, you cannot switch teams": "As a team creator, you cannot switch teams",
"You cannot join a private team": "You cannot join a private team",
"Assessed over": "Assessed over", "Assessed over": "Assessed over",
"Asset (1)": "Asset (1)", "Asset (1)": "Asset (1)",
"Assets": "Assets", "Assets": "Assets",
@ -213,6 +214,8 @@
"Market triggers cancellation or governance vote has passed to cancel": "Market triggers cancellation or governance vote has passed to cancel", "Market triggers cancellation or governance vote has passed to cancel": "Market triggers cancellation or governance vote has passed to cancel",
"Markets": "Markets", "Markets": "Markets",
"Member ID": "Member ID", "Member ID": "Member ID",
"Member": "Member",
"You can set your pubkey alias by using the key selector in the top right corner.": "You can set your pubkey alias by using the key selector in the top right corner.",
"Members": "Members", "Members": "Members",
"Members ({{count}})": "Members ({{count}})", "Members ({{count}})": "Members ({{count}})",
"Menu": "Menu", "Menu": "Menu",
@ -496,5 +499,9 @@
"{{distance}} ago": "{{distance}} ago", "{{distance}} ago": "{{distance}} ago",
"{{entity}} scope": "{{entity}} scope", "{{entity}} scope": "{{entity}} scope",
"{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision", "{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision",
"{{reward}}x": "{{reward}}x" "{{reward}}x": "{{reward}}x",
"Private": "Private",
"Public": "Public",
"Copy this page url.": "Copy this page url.",
"Visit the team's page.": "Visit the team's page."
} }