Merge pull request 'Development' (#25) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 40s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m7s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s

Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
2026-08-31 12:35:31 -05:00
24 changed files with 336 additions and 334 deletions
+17 -8
View File
@@ -1,5 +1,5 @@
// Grant or revoke the "Admin" role for a user by phone number (US +1 assumed).
// Usage: bun run scripts/make-admin.ts <phone> [--revoke]
// Grant or revoke a role for a user by phone number (US +1 assumed).
// Usage: bun run scripts/make-admin.ts <phone> [--role Admin|Developer] [--revoke]
import "dotenv/config";
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
@@ -26,10 +26,19 @@ SuperTokens.init({
],
});
const VALID_ROLES = ["Admin", "Developer"] as const;
const raw = process.argv[2];
const revoke = process.argv.includes("--revoke");
const roleFlagIndex = process.argv.indexOf("--role");
const role = roleFlagIndex === -1 ? "Admin" : process.argv[roleFlagIndex + 1];
if (!raw) {
console.error("Usage: bun run scripts/make-admin.ts <phone> [--revoke]");
console.error("Usage: bun run scripts/make-admin.ts <phone> [--role Admin|Developer] [--revoke]");
process.exit(1);
}
if (!VALID_ROLES.includes(role as (typeof VALID_ROLES)[number])) {
console.error(`Invalid role "${role}". Valid roles: ${VALID_ROLES.join(", ")}`);
process.exit(1);
}
@@ -60,15 +69,15 @@ if (!user) {
process.exit(1);
}
await UserRoles.createNewRoleOrAddPermissions("Admin", []);
await UserRoles.createNewRoleOrAddPermissions(role, []);
if (revoke) {
const res = await UserRoles.removeUserRole("public", user.id, "Admin");
console.log(`Removed Admin from ${matched} (user ${user.id}):`, res.status);
const res = await UserRoles.removeUserRole("public", user.id, role);
console.log(`Removed ${role} from ${matched} (user ${user.id}):`, res.status);
} else {
const res = await UserRoles.addRoleToUser("public", user.id, "Admin");
const res = await UserRoles.addRoleToUser("public", user.id, role);
console.log(
`Granted Admin to ${matched} (user ${user.id}):`,
`Granted ${role} to ${matched} (user ${user.id}):`,
res.status === "OK"
? res.didUserAlreadyHaveRole
? "already had it"
+7 -1
View File
@@ -1,7 +1,8 @@
import { Outlet, createFileRoute, useNavigate, useRouterState } from "@tanstack/react-router";
import { Outlet, createFileRoute, redirect, useNavigate, useRouterState } from "@tanstack/react-router";
import { Tabs } from "@mantine/core";
import { Trans } from "@lingui/react/macro";
import { msg } from "@lingui/core/macro";
import { isDeveloperRole } from "@/features/core/utils/roles";
const TAB_ROUTES = {
usage: "/admin/telemetry/usage",
@@ -15,6 +16,11 @@ type TabValue = keyof typeof TAB_ROUTES;
export const Route = createFileRoute("/_authed/admin/telemetry")({
component: TelemetryLayout,
beforeLoad: ({ context }) => {
if (!isDeveloperRole(context.auth?.roles)) {
throw redirect({ to: "/admin" });
}
},
loader: () => ({
header: {
title: msg`Telemetry`,
+2 -1
View File
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { msg } from "@lingui/core/macro";
import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
@@ -19,7 +20,7 @@ export const Route = createFileRoute("/api/teams/upload-logo")({
const i18n = localizedFor(context.metadata);
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
if (!userId) return new Response("Unauthenticated", { status: 401 });
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { msg } from "@lingui/core/macro";
import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
@@ -19,7 +20,7 @@ export const Route = createFileRoute("/api/tournaments/upload-logo")({
const i18n = localizedFor(context.metadata);
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
if (!userId) return new Response("Unauthenticated", { status: 401 });
if (!isAdmin) return new Response("Unauthorized", { status: 403 });
+35
View File
@@ -0,0 +1,35 @@
import { Box } from "@mantine/core";
import { FootballHelmetIcon } from "@phosphor-icons/react";
const HeadToHeadIcon = ({ size = 14 }: { size?: number }) => (
<Box
component="span"
style={{
position: "relative",
display: "inline-block",
width: size * (27.5 / 14),
height: size * (16 / 14),
}}
>
<FootballHelmetIcon
size={size}
style={{
position: "absolute",
left: 0,
top: 0,
transform: "rotate(25deg)",
}}
/>
<FootballHelmetIcon
size={size}
style={{
position: "absolute",
right: 0,
top: 0,
transform: "scaleX(-1) rotate(25deg)",
}}
/>
</Box>
);
export default HeadToHeadIcon;
+2 -2
View File
@@ -1,4 +1,4 @@
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod";
@@ -17,7 +17,7 @@ const activitySearchParamsSchema = z.object({
export const searchActivities = createServerFn()
.validator(activitySearchParamsSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<ActivityListResult>(async () => {
const result = await pbAdmin.searchActivities(data);
@@ -1,7 +1,6 @@
import { List, Text } from "@mantine/core";
import { Plural, Trans, useLingui } from "@lingui/react/macro";
import {
DatabaseIcon,
TreeStructureIcon,
TrophyIcon,
MedalIcon,
@@ -10,12 +9,14 @@ import {
} from "@phosphor-icons/react";
import ListLink from "@/components/list-link";
import ListButton from "@/components/list-button";
import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server";
import { useServerQuery } from "@/lib/tanstack-query/hooks";
import toast from "@/lib/sonner";
export const ToolsList = () => {
const { t } = useLingui();
const isDeveloper = useIsDeveloper();
const { data: migrationStatus, refetch: refetchMigrationStatus } = useServerQuery({
queryKey: ["admin", "badge-migration-status"],
@@ -51,11 +52,13 @@ export const ToolsList = () => {
Icon={TrophyIcon}
to="/admin/tournaments"
/>
<ListLink
label={t`Telemetry`}
Icon={PulseIcon}
to="/admin/telemetry/usage"
/>
{isDeveloper && (
<ListLink
label={t`Telemetry`}
Icon={PulseIcon}
to="/admin/telemetry/usage"
/>
)}
<ListLink
label={t`Award Badges`}
Icon={CrownIcon}
@@ -91,13 +94,6 @@ export const ToolsList = () => {
<Trans>Badge recalculation failed. Check the server logs.</Trans>
</Text>
)}
<ListButton
label={t`Open Pocketbase`}
Icon={DatabaseIcon}
onClick={() =>
window.location.replace(process.env.POCKETBASE_URL! + "/_/")
}
/>
<ListLink
label={t`Bracket Preview`}
Icon={TreeStructureIcon}
+2 -23
View File
@@ -13,7 +13,7 @@ import {
Tooltip,
} from "@mantine/core";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { FootballHelmetIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useNavigate } from "@tanstack/react-router";
import { Match } from "@/features/matches/types";
import TeamAvatar from "@/components/team-avatar";
@@ -188,28 +188,7 @@ const MatchDock = ({ match, onClose }: MatchDockProps) => {
w={40}
style={{ flexShrink: 0 }}
>
<Group
style={{ position: "relative", width: 27.5, height: 16 }}
>
<FootballHelmetIcon
size={14}
style={{
position: "absolute",
left: 0,
top: 0,
transform: "rotate(25deg)",
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: "absolute",
right: 0,
top: 0,
transform: "scaleX(-1) rotate(25deg)",
}}
/>
</Group>
<HeadToHeadIcon />
</ActionIcon>
</Tooltip>
)}
@@ -0,0 +1,7 @@
import { useAuth } from "@/contexts/auth-context";
import { isDeveloperRole } from "../utils/roles";
export const useIsDeveloper = (): boolean => {
const { roles } = useAuth();
return isDeveloperRole(roles);
};
+5 -1
View File
@@ -1,4 +1,8 @@
export const ADMIN_ROLE = "Admin";
export const DEVELOPER_ROLE = "Developer";
export const isDeveloperRole = (roles?: string[] | null): boolean =>
!!roles?.includes(DEVELOPER_ROLE);
export const isAdminRole = (roles?: string[] | null): boolean =>
!!roles?.includes(ADMIN_ROLE);
!!roles?.includes(ADMIN_ROLE) || isDeveloperRole(roles);
+2 -21
View File
@@ -1,5 +1,5 @@
import { Text, Group, Stack, Paper, Indicator, Box, Tooltip, ActionIcon } from "@mantine/core";
import { FootballHelmetIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useNavigate } from "@tanstack/react-router";
import { Match } from "../types";
import TeamAvatar from "@/components/team-avatar";
@@ -94,26 +94,7 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
aria-label={t`View head-to-head`}
w={40}
>
<Group style={{ position: 'relative', width: 27.5, height: 16 }}>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
left: 0,
top: 0,
transform: 'rotate(25deg)',
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
right: 0,
top: 0,
transform: 'scaleX(-1) rotate(25deg)',
}}
/>
</Group>
<HeadToHeadIcon />
</ActionIcon>
</Tooltip>
)}
@@ -1,7 +1,8 @@
import Sheet from "@/components/sheet/sheet";
import { useAuth } from "@/contexts/auth-context";
import { Flex, Title, ActionIcon, Stack, Button, Box } from "@mantine/core";
import { PencilIcon, FootballHelmetIcon } from "@phosphor-icons/react";
import { Flex, Title, ActionIcon, Stack, Button } from "@mantine/core";
import { PencilIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useMemo } from "react";
import { useLingui } from "@lingui/react/macro";
import NameUpdateForm from "./name-form";
@@ -64,26 +65,7 @@ const Header = ({ player }: HeaderProps) => {
w={40}
display={!owner ? 'block' : 'none'}
>
<Box style={{ position: 'relative', width: 27.5, height: 16 }}>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
left: 0,
top: 0,
transform: 'rotate(25deg)'
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
right: 0,
top: 0,
transform: 'scaleX(-1) rotate(25deg)'
}}
/>
</Box>
<HeadToHeadIcon />
</ActionIcon>
</Flex>
</Flex>
@@ -7,7 +7,8 @@ import {
Stack,
Text,
} from "@mantine/core";
import { CaretLeftIcon, CaretRightIcon, InfoIcon } from "@phosphor-icons/react";
import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import WizardOrbIcon from "@/components/wizard-orb-icon";
import { Trans, useLingui } from "@lingui/react/macro";
import { useNavigate } from "@tanstack/react-router";
@@ -228,7 +229,7 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
disabled={!activeResolved?.home && !activeResolved?.away}
aria-label={t`Matchup details`}
>
<InfoIcon size={16} />
<HeadToHeadIcon />
</ActionIcon>
</Group>
<Group gap="sm" align="center" wrap="nowrap">
@@ -3,7 +3,7 @@ import { Box, Title, Stack, Switch, Button, Text, Group } from "@mantine/core";
import { Trans, useLingui } from "@lingui/react/macro";
import { useWebPush } from "@/hooks/use-web-push";
import { useAuth } from "@/contexts/auth-context";
import { useIsAdmin } from "@/features/core/hooks/use-is-admin";
import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { updateUserTelemetryAlerts } from "@/features/settings/server";
import toast from "@/lib/sonner";
@@ -40,7 +40,7 @@ const TelemetryAlertsSwitch = () => {
export function NotificationsSection() {
const { supported, configured, permission, subscribed, busy, enable, disable, sendTest } =
useWebPush();
const isAdmin = useIsAdmin();
const isDeveloper = useIsDeveloper();
const { t } = useLingui();
if (!supported || !configured) {
@@ -99,7 +99,7 @@ export function NotificationsSection() {
disabled={busy}
label={t`Enable notifications on this device`}
/>
{subscribed && isAdmin && <TelemetryAlertsSwitch />}
{subscribed && isDeveloper && <TelemetryAlertsSwitch />}
{subscribed && (
<Group>
<Button
+2 -2
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start";
import {
superTokensAdminFunctionMiddleware,
superTokensDeveloperFunctionMiddleware,
superTokensFunctionMiddleware,
} from "@/utils/supertokens";
@@ -145,7 +145,7 @@ export const updateUserAccentColor = createServerFn({ method: "POST" })
export const updateUserTelemetryAlerts = createServerFn({ method: "POST" })
.validator((data: boolean) => data === true)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ context, data }) => {
const { userAuthId, metadata } = context;
if (!userAuthId) return;
+4 -3
View File
@@ -1,4 +1,5 @@
import { superTokensFunctionMiddleware, superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod";
@@ -35,8 +36,8 @@ export const createTeam = createServerFn()
.handler(async ({ data, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
//if (!isAdmin && !data.players.includes(userId)) {
// throw new Error("You can only create teams that include yourself as a player");
//}
@@ -57,7 +58,7 @@ export const updateTeam = createServerFn()
.handler(async ({ data: { id, updates }, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(id);
if (!team) {
+10 -10
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { pbAdmin } from "@/lib/pocketbase/client";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import {
@@ -37,7 +37,7 @@ const clientEventSearchSchema = z.object({
export const searchClientEvents = createServerFn()
.validator(clientEventSearchSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientEvent>>(async () => {
const result = await pbAdmin.searchClientEvents(data);
@@ -58,7 +58,7 @@ const clientErrorSearchSchema = z.object({
export const searchClientErrors = createServerFn()
.validator(clientErrorSearchSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientError>>(async () => {
const result = await pbAdmin.searchClientErrors(data);
@@ -77,7 +77,7 @@ const errorGroupsSchema = z.object({
// not on the next rollup tick.
export const getClientErrorGroups = createServerFn()
.validator(errorGroupsSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<ClientErrorGroup[]>(async () => {
const { from, to, includeResolved = false, limit = 50 } = data;
@@ -141,7 +141,7 @@ export const getClientErrorGroups = createServerFn()
export const resolveClientErrorGroup = createServerFn({ method: "POST" })
.validator(z.object({ groupHash: z.string(), resolved: z.boolean().optional() }))
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<{ updated: number }>(async () => {
const updated = await pbAdmin.resolveErrorsByGroup(data.groupHash, data.resolved ?? true);
@@ -158,7 +158,7 @@ const rollupRangeSchema = z.object({
export const getRollupRange = createServerFn()
.validator(rollupRangeSchema)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<RollupRow[]>(async () => pbAdmin.getRollupRange(data))
);
@@ -167,7 +167,7 @@ export const listTelemetryAlerts = createServerFn()
.validator(
z.object({ page: z.number().optional(), perPage: z.number().max(200).optional() })
)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<TelemetryAlert>>(async () =>
pbAdmin.listTelemetryAlerts(data.page, data.perPage)
@@ -197,7 +197,7 @@ export const listSessions = createServerFn()
perPage: z.number().max(100).optional(),
})
)
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<SessionSummary>>(async () => {
const { days = 7, page = 1, perPage = 30 } = data;
@@ -285,7 +285,7 @@ const MAX_TIMELINE_ENTRIES = 500;
export const getSessionTimeline = createServerFn()
.validator(z.object({ sessionId: z.string().min(8).max(64) }))
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<SessionTimeline>(async () => {
const events = await pbAdmin.searchClientEvents({
@@ -426,7 +426,7 @@ export const getSessionTimeline = createServerFn()
);
export const getTelemetryRuntimeStatus = createServerFn()
.middleware([superTokensAdminFunctionMiddleware])
.middleware([superTokensDeveloperFunctionMiddleware])
.handler(async () =>
toServerResult<TelemetryRuntimeStatus>(async () => {
ensureTelemetryScheduler();
+3 -2
View File
@@ -1,4 +1,5 @@
import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client";
import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types";
@@ -46,7 +47,7 @@ export const getTournament = createServerFn()
.validator(z.string())
.middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId, context }) => {
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
return toServerResult(() => pbAdmin.getTournament(tournamentId, isAdmin));
});
@@ -64,7 +65,7 @@ export const enrollTeam = createServerFn()
.handler(async ({ data: { tournamentId, teamId }, context }) =>
toServerResult(async () => {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(teamId);
if (!team) { throw new Error('Team not found'); }
+11 -11
View File
@@ -2,7 +2,7 @@ import { msg } from "@lingui/core/macro";
import { pbAdmin } from "@/lib/pocketbase/client";
import { sendPushToPlayer } from "@/lib/push";
import { Logger } from "@/lib/logger";
import { ADMIN_ROLE } from "@/features/core/utils/roles";
import { DEVELOPER_ROLE } from "@/features/core/utils/roles";
const logger = new Logger("Telemetry > Alerts");
@@ -22,23 +22,23 @@ const fnFailureThreshold = (): number => {
const pbTimestamp = (date: Date): string => date.toISOString().replace("T", " ");
const ADMIN_CACHE_TTL_MS = 10 * 60 * 1000;
let adminPlayersCache: { playerIds: string[]; expiresAt: number } | null = null;
const DEVELOPER_CACHE_TTL_MS = 10 * 60 * 1000;
let developerPlayersCache: { playerIds: string[]; expiresAt: number } | null = null;
const getAdminPlayerIds = async (): Promise<string[]> => {
const getDeveloperPlayerIds = async (): Promise<string[]> => {
const now = Date.now();
if (adminPlayersCache && adminPlayersCache.expiresAt > now) {
return adminPlayersCache.playerIds;
if (developerPlayersCache && developerPlayersCache.expiresAt > now) {
return developerPlayersCache.playerIds;
}
const UserRoles = (await import("supertokens-node/recipe/userroles")).default;
const UserMetadata = (await import("supertokens-node/recipe/usermetadata")).default;
const response = await UserRoles.getUsersThatHaveRole("public", ADMIN_ROLE);
const response = await UserRoles.getUsersThatHaveRole("public", DEVELOPER_ROLE);
const users = response.status === "OK" ? response.users : [];
const playerIds: string[] = [];
for (const userId of users) {
try {
// Opt-in: only admins who enabled telemetry alerts in settings.
// Opt-in: only developers who enabled telemetry alerts in settings.
const { metadata } = await UserMetadata.getUserMetadata(userId);
if (metadata?.telemetryAlerts !== true) continue;
const player = await pbAdmin.getPlayerByAuthId(userId);
@@ -46,7 +46,7 @@ const getAdminPlayerIds = async (): Promise<string[]> => {
} catch {}
}
adminPlayersCache = { playerIds, expiresAt: now + ADMIN_CACHE_TTL_MS };
developerPlayersCache = { playerIds, expiresAt: now + DEVELOPER_CACHE_TTL_MS };
return playerIds;
};
@@ -71,8 +71,8 @@ const fireAlert = async (candidate: AlertCandidate) => {
value: candidate.value,
});
const admins = await getAdminPlayerIds();
for (const playerId of admins) {
const developers = await getDeveloperPlayerIds();
for (const playerId of developers) {
try {
await sendPushToPlayer(playerId, {
title: candidate.title,
+40 -44
View File
@@ -572,7 +572,7 @@ msgstr "Ø Sieg-Marge"
msgid "Award Badge"
msgstr "Abzeichen verleihen"
#: src/features/admin/components/dashboard/tools-list.tsx:60
#: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges"
msgstr "Abzeichen verleihen"
@@ -606,15 +606,15 @@ msgstr "Zurück"
msgid "Badge awarded to {playerName}"
msgstr "Abzeichen an {playerName} verliehen"
#: src/features/admin/components/dashboard/tools-list.tsx:91
#: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs."
msgstr "Abzeichen-Neuberechnung fehlgeschlagen. Prüfe die Server-Logs."
#: src/features/admin/components/dashboard/tools-list.tsx:39
#: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running"
msgstr "Abzeichen-Neuberechnung läuft bereits"
#: src/features/admin/components/dashboard/tools-list.tsx:38
#: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background"
msgstr "Abzeichen-Neuberechnung im Hintergrund gestartet"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Turnierbaum"
#: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102
#: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview"
msgstr "Turnierbaum-Vorschau"
@@ -1118,7 +1118,7 @@ msgstr "Fehlerspitzen und fehlschlagende Funktionen, als Push an dieses Konto"
msgid "Error: {errorMessage}"
msgstr "Fehler: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57
#: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors"
msgstr "Fehler"
@@ -1186,7 +1186,7 @@ msgstr "Aktives Gerät konnte nicht festgelegt werden"
msgid "Failed to set volume"
msgstr "Lautstärke konnte nicht festgelegt werden"
#: src/features/admin/components/dashboard/tools-list.tsx:42
#: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation"
msgstr "Abzeichen-Neuberechnung konnte nicht gestartet werden"
@@ -1198,8 +1198,8 @@ msgstr "Team konnte nicht aktualisiert werden"
msgid "Failed to update tournament"
msgstr "Turnier konnte nicht aktualisiert werden"
#: src/app/routes/api/teams/upload-logo.ts:141
#: src/app/routes/api/tournaments/upload-logo.ts:139
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "Logo konnte nicht hochgeladen werden"
msgid "Failure"
msgstr "Fehler"
#: src/app/routes/api/teams/upload-logo.ts:80
#: src/app/routes/api/tournaments/upload-logo.ts:81
#: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB."
msgstr "Datei zu groß. Maximale Größe ist 10 MB."
@@ -1264,7 +1264,7 @@ msgstr "Funktion"
msgid "Function Name"
msgstr "Funktionsname"
#: src/app/routes/_authed/admin/telemetry.tsx:51
#: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions"
msgstr "Funktionen"
@@ -1335,11 +1335,11 @@ msgstr "Hast du schon mal mitgespielt?"
#: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:224
#: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:233
#: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:97
#: src/features/players/components/profile/header.tsx:79
msgid "Head to Head"
msgstr "Direktvergleich"
@@ -1401,8 +1401,8 @@ msgstr "Falscher Code"
msgid "Invalid action"
msgstr "Ungültige Aktion"
#: src/app/routes/api/teams/upload-logo.ts:66
#: src/app/routes/api/tournaments/upload-logo.ts:67
#: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Ungültiger Dateityp. Nur JPEG, PNG und GIF sind erlaubt."
@@ -1410,8 +1410,8 @@ msgstr "Ungültiger Dateityp. Nur JPEG, PNG und GIF sind erlaubt."
msgid "Invalid hex color format"
msgstr "Ungültiges Hex-Farbformat"
#: src/app/routes/api/teams/upload-logo.ts:34
#: src/app/routes/api/tournaments/upload-logo.ts:35
#: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input"
msgstr "Ungültige Eingabe"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Der Nachname muss 3-20 Zeichen lang sein und darf nur Buchstaben enthalten"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79
#: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Letzte Neuberechnung: {0, plural, one {# Abzeichen verdient} other {# Abzeichen verdient}}"
@@ -1504,8 +1504,8 @@ msgstr "Ort ist erforderlich"
msgid "Logo"
msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47
#: src/app/routes/api/tournaments/upload-logo.ts:48
#: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required"
msgstr "Logo-Datei ist erforderlich"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Team-Songs/Logos verwalten"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50
#: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments"
msgstr "Turniere verwalten"
@@ -1630,11 +1630,11 @@ msgstr "Matches für Regionals sind ungeordnet"
msgid "Matches Played"
msgstr "Gespielte Matches"
#: src/features/predictions/components/prediction-editor.tsx:252
#: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup"
msgstr "Matchup"
#: src/features/predictions/components/prediction-editor.tsx:229
#: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details"
msgstr "Matchup-Details"
@@ -1646,7 +1646,7 @@ msgstr "MAU"
msgid "Me"
msgstr "Ich"
#: src/features/admin/components/dashboard/tools-list.tsx:65
#: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress"
msgstr "Abzeichen-Fortschritt migrieren"
@@ -1712,7 +1712,7 @@ msgstr "Neuer Client-Fehler"
msgid "New Player"
msgstr "Neuer Spieler"
#: src/features/predictions/components/prediction-editor.tsx:211
#: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match"
msgstr "Nächstes Match"
@@ -1908,10 +1908,6 @@ msgstr "Sobald du angemeldet bist, kannst du andere Free Agents und ihre Telefon
msgid "One time code"
msgstr "Einmalcode"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Pocketbase öffnen"
#: src/features/bracket/components/match-report.tsx:273
msgid "Opponent"
msgstr "Gegner"
@@ -2047,7 +2043,7 @@ msgstr "Spieler erfolgreich entfernt"
msgid "Player Stats"
msgstr "Spielerstatistiken"
#: src/app/routes/_authed/admin/telemetry.tsx:48
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29
msgid "Players"
msgstr "Spieler"
@@ -2083,7 +2079,7 @@ msgstr "Tipps"
msgid "Predictions are open"
msgstr "Tipps sind offen"
#: src/features/predictions/components/prediction-editor.tsx:201
#: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match"
msgstr "Vorheriges Spiel"
@@ -2122,7 +2118,7 @@ msgstr "Reaktionen"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72
#: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Abzeichen werden im Hintergrund neu berechnet ({0}/{1})"
@@ -2366,7 +2362,7 @@ msgstr "Serverfunktionen"
msgid "serverFn name"
msgstr "serverFn-Name"
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions"
msgstr "Sitzungen"
@@ -2520,7 +2516,7 @@ msgstr "Status"
#: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:243
#: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit"
msgstr "Absenden"
@@ -2581,7 +2577,7 @@ msgstr "Team-Details"
msgid "Team enrolled successfully!"
msgstr "Team erfolgreich angemeldet!"
#: src/app/routes/api/teams/upload-logo.ts:93
#: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found"
msgstr "Team nicht gefunden"
@@ -2615,8 +2611,8 @@ msgstr "Teams"
msgid "Teams created successfully!"
msgstr "Teams erfolgreich erstellt!"
#: src/app/routes/_authed/admin/telemetry.tsx:20
#: src/features/admin/components/dashboard/tools-list.tsx:55
#: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry"
msgstr "Telemetrie"
@@ -2730,7 +2726,7 @@ msgstr "Turnier erstellt, aber Logo-Upload fehlgeschlagen: {0}"
msgid "Tournament created successfully!"
msgstr "Turnier erfolgreich erstellt!"
#: src/app/routes/api/tournaments/upload-logo.ts:94
#: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found"
msgstr "Turnier nicht gefunden"
@@ -2784,8 +2780,8 @@ msgstr "Team abmelden"
msgid "Unknown error"
msgstr "Unbekannter Fehler"
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred"
msgstr "Unbekannter Fehler aufgetreten"
@@ -2810,7 +2806,7 @@ msgstr "Bevorstehend"
msgid "Update Match"
msgstr "Match aktualisieren"
#: src/features/players/components/profile/header.tsx:92
#: src/features/players/components/profile/header.tsx:74
msgid "Update Name"
msgstr "Namen aktualisieren"
@@ -2824,7 +2820,7 @@ msgstr "Team aktualisieren"
msgid "Update Tournament"
msgstr "Turnier aktualisieren"
#: src/app/routes/_authed/admin/telemetry.tsx:45
#: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage"
msgstr "Nutzung"
+40 -44
View File
@@ -572,7 +572,7 @@ msgstr "Avg Win Margin"
msgid "Award Badge"
msgstr "Award Badge"
#: src/features/admin/components/dashboard/tools-list.tsx:60
#: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges"
msgstr "Award Badges"
@@ -606,15 +606,15 @@ msgstr "Back"
msgid "Badge awarded to {playerName}"
msgstr "Badge awarded to {playerName}"
#: src/features/admin/components/dashboard/tools-list.tsx:91
#: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs."
msgstr "Badge recalculation failed. Check the server logs."
#: src/features/admin/components/dashboard/tools-list.tsx:39
#: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running"
msgstr "Badge recalculation is already running"
#: src/features/admin/components/dashboard/tools-list.tsx:38
#: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background"
msgstr "Badge recalculation started in the background"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102
#: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview"
msgstr "Bracket Preview"
@@ -1118,7 +1118,7 @@ msgstr "Error spikes and failing functions, pushed to this account"
msgid "Error: {errorMessage}"
msgstr "Error: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57
#: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors"
msgstr "Errors"
@@ -1186,7 +1186,7 @@ msgstr "Failed to set active device"
msgid "Failed to set volume"
msgstr "Failed to set volume"
#: src/features/admin/components/dashboard/tools-list.tsx:42
#: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation"
msgstr "Failed to start badge recalculation"
@@ -1198,8 +1198,8 @@ msgstr "Failed to update team"
msgid "Failed to update tournament"
msgstr "Failed to update tournament"
#: src/app/routes/api/teams/upload-logo.ts:141
#: src/app/routes/api/tournaments/upload-logo.ts:139
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "Failed to upload logo"
msgid "Failure"
msgstr "Failure"
#: src/app/routes/api/teams/upload-logo.ts:80
#: src/app/routes/api/tournaments/upload-logo.ts:81
#: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB."
msgstr "File too large. Maximum size is 10MB."
@@ -1264,7 +1264,7 @@ msgstr "Function"
msgid "Function Name"
msgstr "Function Name"
#: src/app/routes/_authed/admin/telemetry.tsx:51
#: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions"
msgstr "Functions"
@@ -1335,11 +1335,11 @@ msgstr "Have you played before?"
#: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:224
#: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:233
#: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:97
#: src/features/players/components/profile/header.tsx:79
msgid "Head to Head"
msgstr "Head to Head"
@@ -1401,8 +1401,8 @@ msgstr "Incorrect code"
msgid "Invalid action"
msgstr "Invalid action"
#: src/app/routes/api/teams/upload-logo.ts:66
#: src/app/routes/api/tournaments/upload-logo.ts:67
#: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Invalid file type. Only JPEG, PNG and GIF are allowed."
@@ -1410,8 +1410,8 @@ msgstr "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgid "Invalid hex color format"
msgstr "Invalid hex color format"
#: src/app/routes/api/teams/upload-logo.ts:34
#: src/app/routes/api/tournaments/upload-logo.ts:35
#: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input"
msgstr "Invalid input"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Last name must be 3-20 characters long and contain only letters"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79
#: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
@@ -1504,8 +1504,8 @@ msgstr "Location is required"
msgid "Logo"
msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47
#: src/app/routes/api/tournaments/upload-logo.ts:48
#: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required"
msgstr "Logo file is required"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Manage Team Songs/Logos"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50
#: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments"
msgstr "Manage Tournaments"
@@ -1630,11 +1630,11 @@ msgstr "Matches for regionals are unordered"
msgid "Matches Played"
msgstr "Matches Played"
#: src/features/predictions/components/prediction-editor.tsx:252
#: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup"
msgstr "Matchup"
#: src/features/predictions/components/prediction-editor.tsx:229
#: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details"
msgstr "Matchup details"
@@ -1646,7 +1646,7 @@ msgstr "MAU"
msgid "Me"
msgstr "Me"
#: src/features/admin/components/dashboard/tools-list.tsx:65
#: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress"
msgstr "Migrate Badge Progress"
@@ -1712,7 +1712,7 @@ msgstr "New client error"
msgid "New Player"
msgstr "New Player"
#: src/features/predictions/components/prediction-editor.tsx:211
#: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match"
msgstr "Next match"
@@ -1908,10 +1908,6 @@ msgstr "Once enrolled, you can view other free agents and their phone number in
msgid "One time code"
msgstr "One time code"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Open Pocketbase"
#: src/features/bracket/components/match-report.tsx:273
msgid "Opponent"
msgstr "Opponent"
@@ -2047,7 +2043,7 @@ msgstr "Player removed successfully"
msgid "Player Stats"
msgstr "Player Stats"
#: src/app/routes/_authed/admin/telemetry.tsx:48
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29
msgid "Players"
msgstr "Players"
@@ -2083,7 +2079,7 @@ msgstr "Predictions"
msgid "Predictions are open"
msgstr "Predictions are open"
#: src/features/predictions/components/prediction-editor.tsx:201
#: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match"
msgstr "Previous match"
@@ -2122,7 +2118,7 @@ msgstr "Reactions"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72
#: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculating badges in the background ({0}/{1})"
@@ -2366,7 +2362,7 @@ msgstr "Server functions"
msgid "serverFn name"
msgstr "serverFn name"
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions"
msgstr "Sessions"
@@ -2520,7 +2516,7 @@ msgstr "Status"
#: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:243
#: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit"
msgstr "Submit"
@@ -2581,7 +2577,7 @@ msgstr "Team Details"
msgid "Team enrolled successfully!"
msgstr "Team enrolled successfully!"
#: src/app/routes/api/teams/upload-logo.ts:93
#: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found"
msgstr "Team not found"
@@ -2615,8 +2611,8 @@ msgstr "Teams"
msgid "Teams created successfully!"
msgstr "Teams created successfully!"
#: src/app/routes/_authed/admin/telemetry.tsx:20
#: src/features/admin/components/dashboard/tools-list.tsx:55
#: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry"
msgstr "Telemetry"
@@ -2730,7 +2726,7 @@ msgstr "Tournament created but logo upload failed: {0}"
msgid "Tournament created successfully!"
msgstr "Tournament created successfully!"
#: src/app/routes/api/tournaments/upload-logo.ts:94
#: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found"
msgstr "Tournament not found"
@@ -2784,8 +2780,8 @@ msgstr "Unenroll Team"
msgid "Unknown error"
msgstr "Unknown error"
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred"
msgstr "Unknown error occurred"
@@ -2810,7 +2806,7 @@ msgstr "Upcoming"
msgid "Update Match"
msgstr "Update Match"
#: src/features/players/components/profile/header.tsx:92
#: src/features/players/components/profile/header.tsx:74
msgid "Update Name"
msgstr "Update Name"
@@ -2824,7 +2820,7 @@ msgstr "Update Team"
msgid "Update Tournament"
msgstr "Update Tournament"
#: src/app/routes/_authed/admin/telemetry.tsx:45
#: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage"
msgstr "Usage"
+40 -44
View File
@@ -572,7 +572,7 @@ msgstr "Prom. Margen de Victoria"
msgid "Award Badge"
msgstr "Otorgar insignia"
#: src/features/admin/components/dashboard/tools-list.tsx:60
#: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges"
msgstr "Otorgar insignias"
@@ -606,15 +606,15 @@ msgstr "Atrás"
msgid "Badge awarded to {playerName}"
msgstr "Insignia otorgada a {playerName}"
#: src/features/admin/components/dashboard/tools-list.tsx:91
#: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs."
msgstr "Falló el recálculo de insignias. Revisa los registros del servidor."
#: src/features/admin/components/dashboard/tools-list.tsx:39
#: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running"
msgstr "El recálculo de insignias ya está en curso"
#: src/features/admin/components/dashboard/tools-list.tsx:38
#: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background"
msgstr "Recálculo de insignias iniciado en segundo plano"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102
#: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview"
msgstr "Vista previa del bracket"
@@ -1118,7 +1118,7 @@ msgstr "Picos de errores y funciones fallando, enviados a esta cuenta"
msgid "Error: {errorMessage}"
msgstr "Error: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57
#: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors"
msgstr "Errores"
@@ -1186,7 +1186,7 @@ msgstr "No se pudo establecer el dispositivo activo"
msgid "Failed to set volume"
msgstr "No se pudo ajustar el volumen"
#: src/features/admin/components/dashboard/tools-list.tsx:42
#: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation"
msgstr "No se pudo iniciar el recálculo de insignias"
@@ -1198,8 +1198,8 @@ msgstr "No se pudo actualizar el equipo"
msgid "Failed to update tournament"
msgstr "No se pudo actualizar el torneo"
#: src/app/routes/api/teams/upload-logo.ts:141
#: src/app/routes/api/tournaments/upload-logo.ts:139
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "No se pudo subir el logo"
msgid "Failure"
msgstr "Fallo"
#: src/app/routes/api/teams/upload-logo.ts:80
#: src/app/routes/api/tournaments/upload-logo.ts:81
#: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB."
msgstr "El archivo es demasiado grande. El tamaño máximo es 10MB."
@@ -1264,7 +1264,7 @@ msgstr "Función"
msgid "Function Name"
msgstr "Nombre de la función"
#: src/app/routes/_authed/admin/telemetry.tsx:51
#: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions"
msgstr "Funciones"
@@ -1335,11 +1335,11 @@ msgstr "¿Ya has jugado antes?"
#: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:224
#: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:233
#: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:97
#: src/features/players/components/profile/header.tsx:79
msgid "Head to Head"
msgstr "Cara a cara"
@@ -1401,8 +1401,8 @@ msgstr "Código incorrecto"
msgid "Invalid action"
msgstr "Acción no válida"
#: src/app/routes/api/teams/upload-logo.ts:66
#: src/app/routes/api/tournaments/upload-logo.ts:67
#: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Tipo de archivo no válido. Solo se permiten JPEG, PNG y GIF."
@@ -1410,8 +1410,8 @@ msgstr "Tipo de archivo no válido. Solo se permiten JPEG, PNG y GIF."
msgid "Invalid hex color format"
msgstr "Formato de color hexadecimal no válido"
#: src/app/routes/api/teams/upload-logo.ts:34
#: src/app/routes/api/tournaments/upload-logo.ts:35
#: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input"
msgstr "Entrada no válida"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "El apellido debe tener entre 3 y 20 caracteres y contener solo letras"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79
#: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Último recálculo: {0, plural, one {# insignia obtenida} other {# insignias obtenidas}}"
@@ -1504,8 +1504,8 @@ msgstr "La ubicación es obligatoria"
msgid "Logo"
msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47
#: src/app/routes/api/tournaments/upload-logo.ts:48
#: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required"
msgstr "El archivo del logo es obligatorio"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Administrar canciones/logos del equipo"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50
#: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments"
msgstr "Administrar torneos"
@@ -1630,11 +1630,11 @@ msgstr "Los partidos de regionales no tienen un orden específico"
msgid "Matches Played"
msgstr "Partidos jugados"
#: src/features/predictions/components/prediction-editor.tsx:252
#: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup"
msgstr "Enfrentamiento"
#: src/features/predictions/components/prediction-editor.tsx:229
#: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details"
msgstr "Detalles del enfrentamiento"
@@ -1646,7 +1646,7 @@ msgstr "MAU"
msgid "Me"
msgstr "Yo"
#: src/features/admin/components/dashboard/tools-list.tsx:65
#: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress"
msgstr "Migrar progreso de insignias"
@@ -1712,7 +1712,7 @@ msgstr "Nuevo error de cliente"
msgid "New Player"
msgstr "Nuevo jugador"
#: src/features/predictions/components/prediction-editor.tsx:211
#: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match"
msgstr "Siguiente partido"
@@ -1908,10 +1908,6 @@ msgstr "Una vez inscrito, puedes ver a otros agentes libres y su número de tel
msgid "One time code"
msgstr "Código de un solo uso"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Abrir Pocketbase"
#: src/features/bracket/components/match-report.tsx:273
msgid "Opponent"
msgstr "Rival"
@@ -2047,7 +2043,7 @@ msgstr "Jugador eliminado con éxito"
msgid "Player Stats"
msgstr "Estadísticas de jugador"
#: src/app/routes/_authed/admin/telemetry.tsx:48
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29
msgid "Players"
msgstr "Jugadores"
@@ -2083,7 +2079,7 @@ msgstr "Predicciones"
msgid "Predictions are open"
msgstr "Las predicciones están abiertas"
#: src/features/predictions/components/prediction-editor.tsx:201
#: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match"
msgstr "Partido anterior"
@@ -2122,7 +2118,7 @@ msgstr "Reacciones"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72
#: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculando insignias en segundo plano ({0}/{1})"
@@ -2366,7 +2362,7 @@ msgstr "Funciones del servidor"
msgid "serverFn name"
msgstr "nombre de serverFn"
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions"
msgstr "Sesiones"
@@ -2520,7 +2516,7 @@ msgstr "Estado"
#: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:243
#: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit"
msgstr "Enviar"
@@ -2581,7 +2577,7 @@ msgstr "Detalles del equipo"
msgid "Team enrolled successfully!"
msgstr "¡Equipo inscrito exitosamente!"
#: src/app/routes/api/teams/upload-logo.ts:93
#: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found"
msgstr "Equipo no encontrado"
@@ -2615,8 +2611,8 @@ msgstr "Equipos"
msgid "Teams created successfully!"
msgstr "¡Equipos creados exitosamente!"
#: src/app/routes/_authed/admin/telemetry.tsx:20
#: src/features/admin/components/dashboard/tools-list.tsx:55
#: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry"
msgstr "Telemetría"
@@ -2730,7 +2726,7 @@ msgstr "Torneo creado, pero falló la carga del logo: {0}"
msgid "Tournament created successfully!"
msgstr "¡Torneo creado exitosamente!"
#: src/app/routes/api/tournaments/upload-logo.ts:94
#: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found"
msgstr "Torneo no encontrado"
@@ -2784,8 +2780,8 @@ msgstr "Cancelar inscripción del equipo"
msgid "Unknown error"
msgstr "Error desconocido"
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred"
msgstr "Ocurrió un error desconocido"
@@ -2810,7 +2806,7 @@ msgstr "Próximos"
msgid "Update Match"
msgstr "Actualizar partido"
#: src/features/players/components/profile/header.tsx:92
#: src/features/players/components/profile/header.tsx:74
msgid "Update Name"
msgstr "Actualizar nombre"
@@ -2824,7 +2820,7 @@ msgstr "Actualizar equipo"
msgid "Update Tournament"
msgstr "Actualizar torneo"
#: src/app/routes/_authed/admin/telemetry.tsx:45
#: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage"
msgstr "Uso"
+40 -44
View File
@@ -572,7 +572,7 @@ msgstr "平均勝差"
msgid "Award Badge"
msgstr "バッジを付与"
#: src/features/admin/components/dashboard/tools-list.tsx:60
#: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges"
msgstr "バッジを付与"
@@ -606,15 +606,15 @@ msgstr "戻る"
msgid "Badge awarded to {playerName}"
msgstr "{playerName}にバッジを付与しました"
#: src/features/admin/components/dashboard/tools-list.tsx:91
#: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs."
msgstr "バッジの再計算に失敗しました。サーバーログを確認してください。"
#: src/features/admin/components/dashboard/tools-list.tsx:39
#: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running"
msgstr "バッジの再計算はすでに実行中です"
#: src/features/admin/components/dashboard/tools-list.tsx:38
#: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background"
msgstr "バックグラウンドでバッジの再計算を開始しました"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "ブラケット"
#: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102
#: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview"
msgstr "ブラケットプレビュー"
@@ -1118,7 +1118,7 @@ msgstr "エラーの急増や失敗している関数をこのアカウントに
msgid "Error: {errorMessage}"
msgstr "エラー: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57
#: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors"
msgstr "エラー"
@@ -1186,7 +1186,7 @@ msgstr "アクティブなデバイスの設定に失敗しました"
msgid "Failed to set volume"
msgstr "音量の設定に失敗しました"
#: src/features/admin/components/dashboard/tools-list.tsx:42
#: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation"
msgstr "バッジの再計算を開始できませんでした"
@@ -1198,8 +1198,8 @@ msgstr "チームの更新に失敗しました"
msgid "Failed to update tournament"
msgstr "トーナメントの更新に失敗しました"
#: src/app/routes/api/teams/upload-logo.ts:141
#: src/app/routes/api/tournaments/upload-logo.ts:139
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "ロゴのアップロードに失敗しました"
msgid "Failure"
msgstr "失敗"
#: src/app/routes/api/teams/upload-logo.ts:80
#: src/app/routes/api/tournaments/upload-logo.ts:81
#: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB."
msgstr "ファイルサイズが大きすぎます。最大サイズは10MBです。"
@@ -1264,7 +1264,7 @@ msgstr "関数"
msgid "Function Name"
msgstr "関数名"
#: src/app/routes/_authed/admin/telemetry.tsx:51
#: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions"
msgstr "関数"
@@ -1335,11 +1335,11 @@ msgstr "参加したことはありますか?"
#: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:224
#: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:233
#: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:97
#: src/features/players/components/profile/header.tsx:79
msgid "Head to Head"
msgstr "対戦成績"
@@ -1401,8 +1401,8 @@ msgstr "コードが正しくありません"
msgid "Invalid action"
msgstr "無効な操作です"
#: src/app/routes/api/teams/upload-logo.ts:66
#: src/app/routes/api/tournaments/upload-logo.ts:67
#: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "無効なファイル形式です。JPEG、PNG、GIFのみ使用できます。"
@@ -1410,8 +1410,8 @@ msgstr "無効なファイル形式です。JPEG、PNG、GIFのみ使用でき
msgid "Invalid hex color format"
msgstr "無効なHEXカラー形式です"
#: src/app/routes/api/teams/upload-logo.ts:34
#: src/app/routes/api/tournaments/upload-logo.ts:35
#: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input"
msgstr "無効な入力です"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "姓は3〜20文字の英字のみで入力してください"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79
#: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "前回の再計算:{0, plural, one {# 個のバッジを獲得} other {# 個のバッジを獲得}}"
@@ -1504,8 +1504,8 @@ msgstr "場所は必須です"
msgid "Logo"
msgstr "ロゴ"
#: src/app/routes/api/teams/upload-logo.ts:47
#: src/app/routes/api/tournaments/upload-logo.ts:48
#: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required"
msgstr "ロゴファイルは必須です"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "チームの曲・ロゴを管理"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50
#: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments"
msgstr "トーナメント管理"
@@ -1630,11 +1630,11 @@ msgstr "地区予選の試合は順不同です"
msgid "Matches Played"
msgstr "試合数"
#: src/features/predictions/components/prediction-editor.tsx:252
#: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup"
msgstr "対戦カード"
#: src/features/predictions/components/prediction-editor.tsx:229
#: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details"
msgstr "対戦カードの詳細"
@@ -1646,7 +1646,7 @@ msgstr "MAU"
msgid "Me"
msgstr "自分"
#: src/features/admin/components/dashboard/tools-list.tsx:65
#: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress"
msgstr "バッジ進捗を移行"
@@ -1712,7 +1712,7 @@ msgstr "新しいクライアントエラー"
msgid "New Player"
msgstr "新しいプレイヤー"
#: src/features/predictions/components/prediction-editor.tsx:211
#: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match"
msgstr "次の試合"
@@ -1908,10 +1908,6 @@ msgstr "登録が完了すると、他のフリーエージェントとその電
msgid "One time code"
msgstr "ワンタイムコード"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Pocketbaseを開く"
#: src/features/bracket/components/match-report.tsx:273
msgid "Opponent"
msgstr "対戦相手"
@@ -2047,7 +2043,7 @@ msgstr "プレイヤーを削除しました"
msgid "Player Stats"
msgstr "プレイヤースタッツ"
#: src/app/routes/_authed/admin/telemetry.tsx:48
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29
msgid "Players"
msgstr "プレイヤー"
@@ -2083,7 +2079,7 @@ msgstr "予想"
msgid "Predictions are open"
msgstr "予想受付中"
#: src/features/predictions/components/prediction-editor.tsx:201
#: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match"
msgstr "前の試合"
@@ -2122,7 +2118,7 @@ msgstr "リアクション"
#. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72
#: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})"
msgstr "バックグラウンドでバッジを再計算中({0}/{1})"
@@ -2366,7 +2362,7 @@ msgstr "サーバー関数"
msgid "serverFn name"
msgstr "serverFn名"
#: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions"
msgstr "セッション数"
@@ -2520,7 +2516,7 @@ msgstr "ステータス"
#: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:243
#: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit"
msgstr "送信"
@@ -2581,7 +2577,7 @@ msgstr "チーム詳細"
msgid "Team enrolled successfully!"
msgstr "チームを登録しました!"
#: src/app/routes/api/teams/upload-logo.ts:93
#: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found"
msgstr "チームが見つかりません"
@@ -2615,8 +2611,8 @@ msgstr "チーム"
msgid "Teams created successfully!"
msgstr "チームを作成しました!"
#: src/app/routes/_authed/admin/telemetry.tsx:20
#: src/features/admin/components/dashboard/tools-list.tsx:55
#: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry"
msgstr "テレメトリー"
@@ -2730,7 +2726,7 @@ msgstr "トーナメントを作成しましたが、ロゴのアップロード
msgid "Tournament created successfully!"
msgstr "トーナメントを作成しました!"
#: src/app/routes/api/tournaments/upload-logo.ts:94
#: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found"
msgstr "トーナメントが見つかりません"
@@ -2784,8 +2780,8 @@ msgstr "チームを登録解除"
msgid "Unknown error"
msgstr "不明なエラー"
#: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred"
msgstr "不明なエラーが発生しました"
@@ -2810,7 +2806,7 @@ msgstr "開催予定"
msgid "Update Match"
msgstr "試合を更新"
#: src/features/players/components/profile/header.tsx:92
#: src/features/players/components/profile/header.tsx:74
msgid "Update Name"
msgstr "名前を更新"
@@ -2824,7 +2820,7 @@ msgstr "チームを更新"
msgid "Update Tournament"
msgstr "トーナメントを更新"
#: src/app/routes/_authed/admin/telemetry.tsx:45
#: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage"
msgstr "利用状況"
+46 -32
View File
@@ -5,7 +5,7 @@ import {
} from "@tanstack/react-start";
import { getRequest } from "@tanstack/react-start/server";
import { Logger } from "@/lib/logger";
import { isAdminRole } from "@/features/core/utils/roles";
import { isAdminRole, isDeveloperRole } from "@/features/core/utils/roles";
import z from "zod";
const logger = new Logger("Middleware");
@@ -70,41 +70,55 @@ export const superTokensFunctionMiddleware = createMiddleware({
}
});
export const superTokensAdminFunctionMiddleware = createMiddleware({
type: "function",
}).server(async ({ next, serverFnMeta }) => {
const request = getRequest();
const createRoleFunctionMiddleware = (
hasRole: (roles?: string[] | null) => boolean,
label: string
) =>
createMiddleware({
type: "function",
}).server(async ({ next, serverFnMeta }) => {
const request = getRequest();
try {
const context = await getSessionContext(request, { isServerFunction: true });
try {
const context = await getSessionContext(request, { isServerFunction: true });
if (isAdminRole(context.roles)) {
return next({ context });
if (hasRole(context.roles)) {
return next({ context });
}
logger.error(`Unauthorized user in ${label} function.`, {
userAuthId: context.userAuthId,
roles: context.roles,
});
const { recordDeniedServerFn } = await import("./activities");
recordDeniedServerFn(request, context.metadata, serverFnMeta);
throw new Error("Unauthorized");
} catch (error: any) {
if (error.message === "SESSION_REFRESH_REQUIRED") {
throw new Response(
JSON.stringify({
error: "SESSION_REFRESH_REQUIRED",
message: "Session needs to be refreshed"
}),
{
status: 401,
headers: { "Content-Type": "application/json" }
}
);
}
throw error;
}
});
logger.error("Unauthorized user in admin function.", {
userAuthId: context.userAuthId,
roles: context.roles,
});
const { recordDeniedServerFn } = await import("./activities");
recordDeniedServerFn(request, context.metadata, serverFnMeta);
throw new Error("Unauthorized");
} catch (error: any) {
if (error.message === "SESSION_REFRESH_REQUIRED") {
throw new Response(
JSON.stringify({
error: "SESSION_REFRESH_REQUIRED",
message: "Session needs to be refreshed"
}),
{
status: 401,
headers: { "Content-Type": "application/json" }
}
);
}
throw error;
}
});
export const superTokensAdminFunctionMiddleware = createRoleFunctionMiddleware(
isAdminRole,
"admin"
);
export const superTokensDeveloperFunctionMiddleware = createRoleFunctionMiddleware(
isDeveloperRole,
"developer"
);
export const setUserMetadata = createServerFn({ method: "POST" })
.validator(