i18n
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
MagnifyingGlassIcon,
|
||||
UsersIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { Tournament } from "@/features/tournaments/types";
|
||||
import TeamList from "./team-list";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
@@ -27,10 +28,11 @@ interface TeamEditSheetProps {
|
||||
|
||||
const TeamEditSheet = ({ teamId, isOpen, onClose }: TeamEditSheetProps) => {
|
||||
const { data: team } = useTeam(teamId);
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<Sheet
|
||||
title={team ? `Edit ${team.name}` : "Edit Team"}
|
||||
title={team ? t`Edit ${team.name}` : t`Edit Team`}
|
||||
opened={isOpen}
|
||||
onChange={onClose}
|
||||
>
|
||||
@@ -56,6 +58,7 @@ interface ManageTeamsProps {
|
||||
const ManageTeams = ({ tournament }: ManageTeamsProps) => {
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null);
|
||||
const { t } = useLingui();
|
||||
|
||||
const {
|
||||
isOpen: editTeamOpened,
|
||||
@@ -109,10 +112,10 @@ const ManageTeams = ({ tournament }: ManageTeamsProps) => {
|
||||
<UsersIcon size={32} />
|
||||
</ThemeIcon>
|
||||
<Title order={3} c="dimmed">
|
||||
No Teams Enrolled
|
||||
<Trans>No Teams Enrolled</Trans>
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
This tournament has no enrolled teams yet.
|
||||
<Trans>This tournament has no enrolled teams yet.</Trans>
|
||||
</Text>
|
||||
</Stack>
|
||||
</Container>
|
||||
@@ -124,7 +127,7 @@ const ManageTeams = ({ tournament }: ManageTeamsProps) => {
|
||||
<Container size="100%" px={0}>
|
||||
<Stack gap="xs">
|
||||
<TextInput
|
||||
placeholder="Search teams by name or player..."
|
||||
placeholder={t`Search teams by name or player...`}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
leftSection={<MagnifyingGlassIcon size={16} />}
|
||||
@@ -134,7 +137,7 @@ const ManageTeams = ({ tournament }: ManageTeamsProps) => {
|
||||
|
||||
<Box px="md">
|
||||
<Text size="xs" c="dimmed">
|
||||
{filteredTeams.length} of {teams.length} teams
|
||||
<Trans>{filteredTeams.length} of {teams.length} teams</Trans>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -145,7 +148,7 @@ const ManageTeams = ({ tournament }: ManageTeamsProps) => {
|
||||
|
||||
{filteredTeams.length === 0 && search && (
|
||||
<Text ta="center" c="dimmed" py="xl">
|
||||
No teams found matching "{search}"
|
||||
<Trans>No teams found matching "{search}"</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Box,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { useTeam } from "../queries";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
|
||||
@@ -19,7 +20,7 @@ const RegionalTeamCard = ({ teamId }: RegionalTeamCardProps) => {
|
||||
return (
|
||||
<Paper p="sm" withBorder radius="md">
|
||||
<Text c="red" ta="center" size="sm">
|
||||
Failed to load team
|
||||
<Trans>Failed to load team</Trans>
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Divider,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { useTeam } from "../queries";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import SongSummary from "./team-form/song-summary";
|
||||
@@ -22,7 +23,7 @@ const TeamCard = ({ teamId }: TeamCardProps) => {
|
||||
return (
|
||||
<Paper p="sm" withBorder radius="md">
|
||||
<Text c="red" ta="center" size="sm">
|
||||
Failed to load team
|
||||
<Trans>Failed to load team</Trans>
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ColorPicker, TextInput, Stack, Group, ColorSwatch, Text } from '@mantine/core';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
const presetColors = [
|
||||
'#FF0000', '#00FF00', '#0000FF', '#FFFF00',
|
||||
@@ -16,8 +17,10 @@ interface TeamColorPickerProps {
|
||||
const TeamColorPicker: React.FC<TeamColorPickerProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
label = "Select Color"
|
||||
label
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
const displayLabel = label ?? t`Select Color`;
|
||||
const [customHex, setCustomHex] = useState(value || '');
|
||||
|
||||
const isValidHex = useMemo(() => {
|
||||
@@ -41,7 +44,7 @@ const TeamColorPicker: React.FC<TeamColorPickerProps> = ({
|
||||
|
||||
return (
|
||||
<Stack gap="md" p="md" w="100%">
|
||||
<Text fw={500}>{label}</Text>
|
||||
<Text fw={500}>{displayLabel}</Text>
|
||||
|
||||
<ColorPicker
|
||||
value={value || '#000000'}
|
||||
@@ -57,11 +60,11 @@ const TeamColorPicker: React.FC<TeamColorPickerProps> = ({
|
||||
<Group gap="xs" align="flex-end">
|
||||
<TextInput
|
||||
style={{ flex: 1 }}
|
||||
label="Custom Hex Code"
|
||||
label={t`Custom Hex Code`}
|
||||
placeholder="#FF0000"
|
||||
value={customHex}
|
||||
onChange={handleHexInputChange}
|
||||
error={customHex && !isValidHex ? 'Invalid hex color format' : undefined}
|
||||
error={customHex && !isValidHex ? t`Invalid hex color format` : undefined}
|
||||
w="100%"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Stack, Text, Group, TextInput, Button } from "@mantine/core";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface DurationPickerProps {
|
||||
songDurationMs: number;
|
||||
@@ -16,8 +17,9 @@ interface IncrementButtonsProps {
|
||||
}
|
||||
|
||||
const IncrementButtons = ({ onAdjust, disabled, isPositive = true }: IncrementButtonsProps) => {
|
||||
const { t } = useLingui();
|
||||
const increments = [1, 5, 30, 60];
|
||||
const labels = ["1s", "5s", "30s", "1m"];
|
||||
const labels = [t`1s`, t`5s`, t`30s`, t`1m`];
|
||||
|
||||
return (
|
||||
<Group gap={3} wrap="nowrap" flex={1}>
|
||||
@@ -51,6 +53,7 @@ const DurationPicker = ({
|
||||
onChange,
|
||||
disabled = false,
|
||||
}: DurationPickerProps) => {
|
||||
const { t } = useLingui();
|
||||
const songDurationSeconds = Math.floor(songDurationMs / 1000);
|
||||
|
||||
const formatTime = useCallback((seconds: number) => {
|
||||
@@ -91,16 +94,16 @@ const DurationPicker = ({
|
||||
const getValidationMessage = useCallback((start: number, end: number) => {
|
||||
const duration = end - start;
|
||||
if (start < 0 || end > songDurationSeconds) {
|
||||
return "Segment must be within song duration";
|
||||
return t`Segment must be within song duration`;
|
||||
}
|
||||
if (duration < 10) {
|
||||
return "Segment must be at least 10 seconds";
|
||||
return t`Segment must be at least 10 seconds`;
|
||||
}
|
||||
if (duration > 15) {
|
||||
return "Segment must be no more than 15 seconds";
|
||||
return t`Segment must be no more than 15 seconds`;
|
||||
}
|
||||
return null;
|
||||
}, [songDurationSeconds]);
|
||||
}, [songDurationSeconds, t]);
|
||||
|
||||
const updateTimes = useCallback((newStart: number, newEnd: number) => {
|
||||
const clampedStart = Math.max(0, Math.min(newStart, songDurationSeconds - 10));
|
||||
@@ -166,14 +169,14 @@ const DurationPicker = ({
|
||||
return (
|
||||
<Stack gap="sm" opacity={disabled ? 0.5 : 1}>
|
||||
<Text size="sm" fw={500} c={disabled ? "dimmed" : undefined} ta="center">
|
||||
Walkout Segment ({segmentDuration}s)
|
||||
<Trans>Walkout Segment ({segmentDuration}s)</Trans>
|
||||
</Text>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Stack gap={4}>
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="xs" fw={500} c={disabled ? "dimmed" : undefined}>
|
||||
Start
|
||||
<Trans>Start</Trans>
|
||||
</Text>
|
||||
<TextInput
|
||||
value={startInputValue}
|
||||
@@ -209,7 +212,7 @@ const DurationPicker = ({
|
||||
<Stack gap={4}>
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="xs" fw={500} c={disabled ? "dimmed" : undefined}>
|
||||
End
|
||||
<Trans>End</Trans>
|
||||
</Text>
|
||||
<TextInput
|
||||
value={endInputValue}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TeamInput } from "../../types";
|
||||
import { teamKeys } from "../../queries";
|
||||
import SongPicker from "./song-picker";
|
||||
import PlayersPicker from "./players-picker";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface TeamFormProps {
|
||||
close: () => void;
|
||||
@@ -31,6 +32,7 @@ const TeamForm = ({
|
||||
onSubmit
|
||||
}: TeamFormProps) => {
|
||||
const isEditMode = !!teamId;
|
||||
const { t } = useLingui();
|
||||
|
||||
const config: UseFormInput<TeamInput> = {
|
||||
initialValues: {
|
||||
@@ -49,16 +51,16 @@ const TeamForm = ({
|
||||
},
|
||||
onSubmitPreventDefault: "always",
|
||||
validate: {
|
||||
name: isNotEmpty("Name is required"),
|
||||
name: isNotEmpty(t`Name is required`),
|
||||
players: (value: string[]) =>
|
||||
value.length > 1 && value[1] !== ""
|
||||
? undefined
|
||||
: "Players are required",
|
||||
song_name: isNotEmpty("Song is required"),
|
||||
: t`Players are required`,
|
||||
song_name: isNotEmpty(t`Song is required`),
|
||||
song_start: (value, values) => {
|
||||
if (values.song_name && values.song_id) {
|
||||
if (value === undefined || value === null) {
|
||||
return "Song start time is required";
|
||||
return t`Song start time is required`;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -66,15 +68,15 @@ const TeamForm = ({
|
||||
song_end: (value, values) => {
|
||||
if (values.song_name && values.song_id) {
|
||||
if (value === undefined || value === null) {
|
||||
return "Song end time is required";
|
||||
return t`Song end time is required`;
|
||||
}
|
||||
if (values.song_start !== undefined && value !== undefined) {
|
||||
const duration = value - values.song_start;
|
||||
if (duration < 10) {
|
||||
return "Song segment must be at least 10 seconds";
|
||||
return t`Song segment must be at least 10 seconds`;
|
||||
}
|
||||
if (duration > 15) {
|
||||
return "Song segment must be no more than 15 seconds";
|
||||
return t`Song segment must be no more than 15 seconds`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,8 +102,8 @@ const TeamForm = ({
|
||||
|
||||
const mutation = isEditMode ? updateTeam : createTeam;
|
||||
const errorMessage = isEditMode
|
||||
? "Failed to update team"
|
||||
: "Failed to create team";
|
||||
? t`Failed to update team`
|
||||
: t`Failed to create team`;
|
||||
|
||||
mutation(teamData, {
|
||||
onSuccess: async (team: any) => {
|
||||
@@ -143,7 +145,7 @@ const TeamForm = ({
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || "Failed to upload logo");
|
||||
throw new Error(error.error || t`Failed to upload logo`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
@@ -160,8 +162,8 @@ const TeamForm = ({
|
||||
);
|
||||
} catch (error: any) {
|
||||
const logoErrorMessage = isEditMode
|
||||
? `Team updated but logo upload failed: ${error.message}`
|
||||
: `Team created but logo upload failed: ${error.message}`;
|
||||
? t`Team updated but logo upload failed: ${error.message}`
|
||||
: t`Team created but logo upload failed: ${error.message}`;
|
||||
toast.error(logoErrorMessage);
|
||||
logger.error("Team logo upload error", error);
|
||||
}
|
||||
@@ -182,20 +184,20 @@ const TeamForm = ({
|
||||
},
|
||||
});
|
||||
},
|
||||
[isEditMode, createTeam, updateTeam, queryClient]
|
||||
[isEditMode, createTeam, updateTeam, queryClient, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<SlidePanel
|
||||
onSubmit={form.onSubmit(handleSubmit)}
|
||||
onCancel={close}
|
||||
submitText={isEditMode ? "Update Team" : "Create Team"}
|
||||
cancelText="Cancel"
|
||||
submitText={isEditMode ? t`Update Team` : t`Create Team`}
|
||||
cancelText={t`Cancel`}
|
||||
loading={isPending}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Name"
|
||||
label={t`Name`}
|
||||
withAsterisk
|
||||
disabled={isEditMode}
|
||||
key={form.key("name")}
|
||||
@@ -205,7 +207,7 @@ const TeamForm = ({
|
||||
<FileInput
|
||||
key={form.key("logo")}
|
||||
accept="image/png,image/jpeg,image/gif,image/jpg"
|
||||
label={isEditMode ? "Change Logo" : "Logo"}
|
||||
label={isEditMode ? t`Change Logo` : t`Logo`}
|
||||
leftSection={<LinkIcon size={16} />}
|
||||
{...form.getInputProps("logo")}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useAuth } from "@/contexts/auth-context";
|
||||
import { useUnenrolledPlayers } from "@/features/players/queries";
|
||||
import { Autocomplete, Stack, Text, TextInput } from "@mantine/core";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface PlayersPickerProps {
|
||||
tournamentId: string;
|
||||
@@ -13,6 +14,7 @@ interface PlayersPickerProps {
|
||||
}
|
||||
|
||||
const PlayersPicker = ({ tournamentId, value = [], onChange, disabled, error }: PlayersPickerProps) => {
|
||||
const { t } = useLingui();
|
||||
const { roles, user } = useAuth();
|
||||
const isAdmin = roles.includes("Admin");
|
||||
|
||||
@@ -40,7 +42,7 @@ const PlayersPicker = ({ tournamentId, value = [], onChange, disabled, error }:
|
||||
return (
|
||||
<Stack>
|
||||
<Autocomplete
|
||||
label="Player 1"
|
||||
label={t`Player 1`}
|
||||
data={options}
|
||||
disabled
|
||||
value={playerOne}
|
||||
@@ -48,7 +50,7 @@ const PlayersPicker = ({ tournamentId, value = [], onChange, disabled, error }:
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
/>
|
||||
<Autocomplete
|
||||
label="Player 2"
|
||||
label={t`Player 2`}
|
||||
data={options}
|
||||
disabled={disabled}
|
||||
value={playerTwo}
|
||||
|
||||
@@ -8,6 +8,7 @@ import SongSearch from "./song-search";
|
||||
import DurationPicker from "./duration-picker";
|
||||
import SongSummary from "./song-summary";
|
||||
import { MusicNoteIcon } from "@phosphor-icons/react";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface Song {
|
||||
song_id: string;
|
||||
@@ -26,6 +27,7 @@ interface SongPickerProps {
|
||||
}
|
||||
|
||||
const SongPicker = ({ form, error }: SongPickerProps) => {
|
||||
const { t } = useLingui();
|
||||
const currentSong = useMemo((): Song | undefined => {
|
||||
const values = form.getValues();
|
||||
if (values.song_id && values.song_name) {
|
||||
@@ -63,9 +65,9 @@ const SongPicker = ({ form, error }: SongPickerProps) => {
|
||||
error={error}
|
||||
Component={SongPickerComponent}
|
||||
componentProps={{}}
|
||||
title={"Select Song"}
|
||||
label={"Walkout Song"}
|
||||
placeholder={"Select your walkout song"}
|
||||
title={t`Select Song`}
|
||||
label={t`Walkout Song`}
|
||||
placeholder={t`Select your walkout song`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -76,6 +78,7 @@ interface SongPickerComponentProps {
|
||||
}
|
||||
|
||||
const SongPickerComponent = ({ value: song, onChange }: SongPickerComponentProps) => {
|
||||
const { t } = useLingui();
|
||||
const handleSongSelect = (track: SpotifyTrack) => {
|
||||
const defaultStart = 0;
|
||||
const defaultEnd = Math.min(15, Math.floor(track.duration_ms / 1000));
|
||||
@@ -106,7 +109,7 @@ const SongPickerComponent = ({ value: song, onChange }: SongPickerComponentProps
|
||||
|
||||
return (
|
||||
<Stack styles={{ root: { width: '100%' }}}>
|
||||
<Text size="lg" fw={500}>Search for a Song</Text>
|
||||
<Text size="lg" fw={500}><Trans>Search for a Song</Trans></Text>
|
||||
|
||||
<SongSearch onChange={handleSongSelect} />
|
||||
|
||||
@@ -122,10 +125,10 @@ const SongPickerComponent = ({ value: song, onChange }: SongPickerComponentProps
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text size="sm" fw={500} c={song?.song_name ? undefined : "dimmed"}>
|
||||
{song?.song_name || "No song selected"}
|
||||
{song?.song_name || t`No song selected`}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{song?.song_artist || "Select a song to see details"}
|
||||
{song?.song_artist || t`Select a song to see details`}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{song?.song_album || ""}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Text, Group, Avatar, Box } from "@mantine/core";
|
||||
import { SpotifyTrack } from "@/lib/spotify/types";
|
||||
import Typeahead, { TypeaheadOption } from "@/components/typeahead";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface SongSearchProps {
|
||||
onChange: (track: SpotifyTrack) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const SongSearch = ({ onChange, placeholder = "Search for songs..." }: SongSearchProps) => {
|
||||
const SongSearch = ({ onChange, placeholder }: SongSearchProps) => {
|
||||
const { t } = useLingui();
|
||||
const resolvedPlaceholder = placeholder ?? t`Search for songs...`;
|
||||
const searchSpotifyTracks = async (query: string): Promise<TypeaheadOption<SpotifyTrack>[]> => {
|
||||
try {
|
||||
// Use a default search term when query is empty to show popular tracks
|
||||
@@ -72,7 +75,7 @@ const SongSearch = ({ onChange, placeholder = "Search for songs..." }: SongSearc
|
||||
searchFn={searchSpotifyTracks}
|
||||
renderOption={renderOption}
|
||||
format={formatTrack}
|
||||
placeholder={placeholder}
|
||||
placeholder={resolvedPlaceholder}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Group, Avatar, Text, Box } from "@mantine/core";
|
||||
import { MusicNoteIcon } from "@phosphor-icons/react";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
|
||||
interface Song {
|
||||
song_id: string;
|
||||
@@ -27,7 +28,7 @@ const SongSummary = ({ song }: SongSummaryProps) => {
|
||||
return (
|
||||
<Group gap="xs" c="dimmed">
|
||||
<MusicNoteIcon size={16} />
|
||||
<Text size="sm" c="dimmed">No song selected</Text>
|
||||
<Text size="sm" c="dimmed"><Trans>No song selected</Trans></Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,28 +7,31 @@ import { useTeam, useTeamMatches, useTeamStats } from "../../queries";
|
||||
import MatchList from "@/features/matches/components/match-list";
|
||||
import PlayerList from "@/features/players/components/player-list";
|
||||
import { MatchListSkeleton } from "./skeleton";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
|
||||
interface ProfileProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const TeamProfile = ({ id }: ProfileProps) => {
|
||||
const { t } = useLingui();
|
||||
const { data: team } = useTeam(id);
|
||||
const { data: matches, isLoading: matchesLoading } = useTeamMatches(id);
|
||||
const { data: stats, isLoading: statsLoading, error: statsError } = useTeamStats(id);
|
||||
if (!team) return <Text p="md">Team not found</Text>;
|
||||
if (!team) return <Text p="md"><Trans>Team not found</Trans></Text>;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Overview",
|
||||
label: t`Overview`,
|
||||
value: "overview",
|
||||
content: <>
|
||||
<Stack px="md">
|
||||
<Text size="md" fw={700}>Players</Text>
|
||||
<Text size="md" fw={700}><Trans>Players</Trans></Text>
|
||||
<PlayerList players={team.players} />
|
||||
</Stack>
|
||||
<Divider my="md" />
|
||||
<Stack>
|
||||
<Text px="md" size="md" fw={700}>Statistics</Text>
|
||||
<Text px="md" size="md" fw={700}><Trans>Statistics</Trans></Text>
|
||||
{statsLoading ? (
|
||||
<StatsSkeleton />
|
||||
) : (
|
||||
@@ -38,7 +41,8 @@ const TeamProfile = ({ id }: ProfileProps) => {
|
||||
</>,
|
||||
},
|
||||
{
|
||||
label: "Matches",
|
||||
label: t`Matches`,
|
||||
value: "matches",
|
||||
content: matchesLoading ? (
|
||||
<MatchListSkeleton />
|
||||
) : (
|
||||
@@ -46,7 +50,8 @@ const TeamProfile = ({ id }: ProfileProps) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Tournaments",
|
||||
label: t`Tournaments`,
|
||||
value: "tournaments",
|
||||
content: (
|
||||
<>
|
||||
<TournamentList tournaments={team.tournaments || []} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import SwipeableTabs from "@/components/swipeable-tabs";
|
||||
import TournamentList from "@/features/tournaments/components/tournament-list";
|
||||
import { StatsSkeleton } from "@/components/stats-overview";
|
||||
import HeaderSkeleton from "./header-skeleton";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
|
||||
const MatchCardSkeleton = ({ opacity = 1 }: { opacity?: number }) => (
|
||||
<Paper px="md" py="md" withBorder radius="md" style={{ opacity }}>
|
||||
@@ -63,17 +64,21 @@ export const OverviewSkeleton = () => (
|
||||
);
|
||||
|
||||
const ProfileSkeleton = () => {
|
||||
const { t } = useLingui();
|
||||
const tabs = [
|
||||
{
|
||||
label: "Overview",
|
||||
label: t`Overview`,
|
||||
value: "overview",
|
||||
content: <OverviewSkeleton />,
|
||||
},
|
||||
{
|
||||
label: "Matches",
|
||||
label: t`Matches`,
|
||||
value: "matches",
|
||||
content: <MatchListSkeleton />,
|
||||
},
|
||||
{
|
||||
label: "Tournaments",
|
||||
label: t`Tournaments`,
|
||||
value: "tournaments",
|
||||
content: <TournamentList tournaments={[]} loading />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { listPlayers } from '@/features/players/server';
|
||||
import { useServerSuspenseQuery } from '@/lib/tanstack-query/hooks';
|
||||
|
||||
@@ -15,6 +16,7 @@ export const playerQueries = {
|
||||
};
|
||||
|
||||
export const useAvailablePlayers = (excludedPlayerIds: string[] = []) => {
|
||||
const { t } = useLingui();
|
||||
const { data: allPlayers } = useServerSuspenseQuery(playerQueries.all());
|
||||
|
||||
const availablePlayers = useMemo(() => {
|
||||
@@ -30,9 +32,9 @@ export const useAvailablePlayers = (excludedPlayerIds: string[] = []) => {
|
||||
const playerOptions = useMemo(() =>
|
||||
availablePlayers.map(player => ({
|
||||
value: player.id,
|
||||
label: `${player.first_name} ${player.last_name}`.trim() || 'Unnamed Player'
|
||||
label: `${player.first_name} ${player.last_name}`.trim() || t`Unnamed Player`
|
||||
})),
|
||||
[availablePlayers]
|
||||
[availablePlayers, t]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { createTeam } from "@/features/teams/server";
|
||||
import { TeamInput } from "@/features/teams/types";
|
||||
import { logger } from "@/lib/logger";
|
||||
@@ -6,13 +7,14 @@ import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
|
||||
const useCreateTeam = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: TeamInput) => createTeam({ data }),
|
||||
onMutate: (data) => {
|
||||
logger.info('Creating team', data);
|
||||
},
|
||||
successMessage: 'Team created successfully!',
|
||||
successMessage: t`Team created successfully!`,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { useLingui } from "@lingui/react/macro";
|
||||
import { updateTeam } from "@/features/teams/server";
|
||||
import { TeamUpdateInput } from "@/features/teams/types";
|
||||
import { useServerMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { logger } from "@/lib/logger";
|
||||
|
||||
const useUpdateTeam = (teamId: string) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
return useServerMutation({
|
||||
mutationFn: (data: TeamUpdateInput) =>
|
||||
mutationFn: (data: TeamUpdateInput) =>
|
||||
updateTeam({ data: { id: teamId, updates: data } }),
|
||||
onMutate: (data) => {
|
||||
logger.info('Updating team', { teamId, updates: Object.keys(data) });
|
||||
},
|
||||
successMessage: 'Team updated successfully!',
|
||||
successMessage: t`Team updated successfully!`,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user