work on team enrollment

This commit is contained in:
yohlo
2025-09-16 09:24:21 -05:00
parent 9a105b30c6
commit cde74a04d5
45 changed files with 1244 additions and 457 deletions

View File

@@ -20,7 +20,6 @@ import { playerQueries } from "@/features/players/queries";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import FullScreenLoader from "@/components/full-screen-loader";
import { scan } from "react-scan";
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
@@ -106,12 +105,6 @@ function RootComponent() {
// todo: analytics -> process.env data-website-id
function RootDocument({ children }: { children: React.ReactNode }) {
React.useEffect(() => {
scan({
enabled: true,
});
}, []);
return (
<html
{...mantineHtmlProps}

View File

@@ -1,8 +1,7 @@
import { redirect, createFileRoute, Outlet } from "@tanstack/react-router";
import Layout from "@/features/core/components/layout";
import { useServerEvents } from "@/hooks/use-server-events";
import { Loader } from "@mantine/core";
import FullScreenLoader from "@/components/full-screen-loader";
import { Flex, Loader } from "@mantine/core";
export const Route = createFileRoute("/_authed")({
beforeLoad: ({ context }) => {
@@ -27,7 +26,9 @@ export const Route = createFileRoute("/_authed")({
},
pendingComponent: () => (
<Layout>
<FullScreenLoader />
<Flex w='100%' align="center">
<Loader />
</Flex>
</Layout>
),
});

View File

@@ -1,5 +1,5 @@
import Profile from "@/features/players/components/profile";
import { playerQueries } from "@/features/players/queries";
import { playerKeys, playerQueries } from "@/features/players/queries";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
@@ -31,7 +31,7 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
context?.auth.user.id === params.playerId ? "/settings" : undefined,
},
withPadding: false,
refresh: [playerQueries.details(params.playerId).queryKey],
refresh: [playerKeys.details(params.playerId), playerKeys.matches(params.playerId), playerKeys.stats(params.playerId)],
}),
component: () => {
const { playerId } = Route.useParams();

View File

@@ -1,5 +1,5 @@
import TeamProfile from "@/features/teams/components/team-profile";
import { teamQueries } from "@/features/teams/queries";
import { teamKeys, teamQueries } from "@/features/teams/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { redirect, createFileRoute } from "@tanstack/react-router";
@@ -20,7 +20,8 @@ export const Route = createFileRoute("/_authed/teams/$teamId")({
collapsed: true,
withBackButton: true,
},
refresh: [teamQueries.details(params.teamId).queryKey],
refresh: [teamKeys.details(params.teamId), teamKeys.matches(params.teamId), teamKeys.stats(params.teamId)],
withPadding: false
}),
component: () => {
const { teamId } = Route.useParams();

View File

@@ -20,7 +20,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
withBackButton: true,
settingsLink: context.auth.roles.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined
},
refresh: tournamentQueries.details(params.tournamentId).queryKey,
refresh: [tournamentQueries.details(params.tournamentId).queryKey],
withPadding: false
}),
component: RouteComponent,

View File

@@ -0,0 +1,116 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { superTokensRequestMiddleware } from '@/utils/supertokens';
import { pbAdmin } from '@/lib/pocketbase/client';
import { logger } from '@/lib/logger';
import { z } from 'zod';
const uploadSchema = z.object({
teamId: z.string().min(1, 'Team ID is required'),
});
export const ServerRoute = createServerFileRoute('/api/teams/upload-logo')
.middleware([superTokensRequestMiddleware])
.methods({
POST: async ({ request, context }) => {
try {
const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin");
if (!userId) return new Response('Unauthenticated', { status: 401 });
const formData = await request.formData();
const teamId = formData.get('teamId') as string;
const logoFile = formData.get('logo') as File;
const validationResult = uploadSchema.safeParse({ teamId });
if (!validationResult.success) {
return new Response(JSON.stringify({
error: 'Invalid input',
details: validationResult.error.issues
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
if (!logoFile || logoFile.size === 0) {
return new Response(JSON.stringify({
error: 'Logo file is required'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(logoFile.type)) {
return new Response(JSON.stringify({
error: 'Invalid file type. Only JPEG, PNG and GIF are allowed.'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const maxSize = 10 * 1024 * 1024;
if (logoFile.size > maxSize) {
return new Response(JSON.stringify({
error: 'File too large. Maximum size is 10MB.'
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const team = await pbAdmin.getTeam(teamId);
if (!team) {
return new Response(JSON.stringify({
error: 'Team not found'
}), {
status: 404,
headers: { 'Content-Type': 'application/json' }
});
}
if (!team.players.map(p => p.id).includes(context.userId) && !isAdmin)
return new Response('Unauthorized', { status: 403 });
logger.info('Uploading team logo', {
teamId,
fileName: logoFile.name,
fileSize: logoFile.size,
userId
});
const pbFormData = new FormData();
pbFormData.append('logo', logoFile);
const updatedTeam= await pbAdmin.updateTeam(teamId, pbFormData as any);
logger.info('Team logo uploaded successfully', {
teamId,
logo: updatedTeam.logo
});
return new Response(JSON.stringify({
success: true,
team: updatedTeam,
message: 'Logo uploaded successfully'
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error: any) {
logger.error('Error uploading team logo:', error);
return new Response(JSON.stringify({
error: 'Failed to upload logo',
message: error.message || 'Unknown error occurred'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
});