- dock h2h source corrected to match_dock; badge_viewed breakdown via value - stats filter guards unified, ref-based, double-tap safe - emoji picker reuses handleReactionClick; comment and import tidy
114 lines
3.9 KiB
TypeScript
114 lines
3.9 KiB
TypeScript
import { Box, Stack, Text, Divider, Group, Button } from "@mantine/core";
|
|
import { Suspense, useState, useDeferredValue, useRef } from "react";
|
|
import { Trans, useLingui } from "@lingui/react/macro";
|
|
import { Link } from "@tanstack/react-router";
|
|
import Header from "./header";
|
|
import SwipeableTabs from "@/components/swipeable-tabs";
|
|
import { usePlayer, usePlayerMatches, usePlayerStats } from "../../queries";
|
|
import TeamList from "@/features/teams/components/team-list";
|
|
import StatsOverview, { StatsSkeleton } from "@/components/stats-overview";
|
|
import MatchList from "@/features/matches/components/match-list";
|
|
import BadgeShowcase from "@/features/badges/components/badge-showcase";
|
|
import BadgeShowcaseSkeleton from "@/features/badges/components/badge-showcase-skeleton";
|
|
import { track } from "@/lib/telemetry/client";
|
|
|
|
interface ProfileProps {
|
|
id: string;
|
|
}
|
|
|
|
const StatsWithFilter = ({ id }: { id: string }) => {
|
|
const [viewType, setViewType] = useState<'all' | 'mainline' | 'regional'>('all');
|
|
const deferredViewType = useDeferredValue(viewType);
|
|
const isStale = viewType !== deferredViewType;
|
|
|
|
const viewTypeRef = useRef(viewType);
|
|
const changeViewType = (next: typeof viewType) => {
|
|
if (viewTypeRef.current === next) return;
|
|
viewTypeRef.current = next;
|
|
track("stats_filtered", { value: next, source: "profile" });
|
|
setViewType(next);
|
|
};
|
|
|
|
return (
|
|
<Stack>
|
|
<Group gap="xs" px="md" justify="space-between" align="center">
|
|
<Text size="md" fw={700}><Trans>Statistics</Trans></Text>
|
|
<Group gap="xs">
|
|
<Button variant={viewType === 'all' ? 'filled' : 'light'} size="compact-xs" onClick={() => changeViewType('all')}><Trans>All</Trans></Button>
|
|
<Button variant={viewType === 'mainline' ? 'filled' : 'light'} size="compact-xs" onClick={() => changeViewType('mainline')}><Trans>Mainline</Trans></Button>
|
|
<Button variant={viewType === 'regional' ? 'filled' : 'light'} size="compact-xs" onClick={() => changeViewType('regional')}><Trans>Regional</Trans></Button>
|
|
</Group>
|
|
</Group>
|
|
<Box style={{ opacity: isStale ? 0.6 : 1, transition: 'opacity 150ms' }}>
|
|
<Suspense fallback={<StatsSkeleton />}>
|
|
<StatsContent id={id} viewType={deferredViewType} />
|
|
</Suspense>
|
|
</Box>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
const StatsContent = ({ id, viewType }: { id: string; viewType: 'all' | 'mainline' | 'regional' }) => {
|
|
const { data: stats } = usePlayerStats(id, viewType);
|
|
return <StatsOverview statsData={stats} />;
|
|
};
|
|
|
|
const Profile = ({ id }: ProfileProps) => {
|
|
const { t } = useLingui();
|
|
const { data: player } = usePlayer(id);
|
|
const { data: matches } = usePlayerMatches(id);
|
|
|
|
const tabs = [
|
|
{
|
|
label: t`Overview`,
|
|
value: "overview",
|
|
content: <>
|
|
<Stack px="md">
|
|
<Group justify="space-between" align="center">
|
|
<Text size="md" fw={700}><Trans>Badges</Trans></Text>
|
|
<Button
|
|
component={Link}
|
|
to="/badges"
|
|
variant="subtle"
|
|
size="compact-sm"
|
|
>
|
|
<Trans>View all badges</Trans>
|
|
</Button>
|
|
</Group>
|
|
<Suspense fallback={<BadgeShowcaseSkeleton />}>
|
|
<BadgeShowcase playerId={id} />
|
|
</Suspense>
|
|
</Stack>
|
|
<Divider my="md" />
|
|
<StatsWithFilter id={id} />
|
|
</>,
|
|
},
|
|
{
|
|
label: t`Matches`,
|
|
value: "matches",
|
|
content: <MatchList matches={matches || []} />,
|
|
},
|
|
{
|
|
label: t`Teams`,
|
|
value: "teams",
|
|
content: <TeamList teams={player.teams || []} />,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<Header player={player} />
|
|
<Box mt="lg">
|
|
<SwipeableTabs
|
|
tabs={tabs}
|
|
onTabChange={(_, tab) => {
|
|
if (tab.value === "matches") track("match_history_viewed", { source: "profile" });
|
|
}}
|
|
/>
|
|
</Box>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Profile;
|