random imporvements

This commit is contained in:
yohlo
2025-08-24 12:53:34 -05:00
parent 1015f63f7e
commit 466a3365f0
6 changed files with 154 additions and 48 deletions

View File

@@ -17,7 +17,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
header: { header: {
collapsed: true, collapsed: true,
withBackButton: true, withBackButton: true,
settingsLink: context.auth.roles?.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined settingsLink: context.auth.roles.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined
}, },
refresh: { refresh: {
toRefresh: tournamentQueries.details(params.tournamentId).queryKey, toRefresh: tournamentQueries.details(params.tournamentId).queryKey,

View File

@@ -1,53 +1,159 @@
import { import {
ErrorComponent,
Link, Link,
rootRouteId, rootRouteId,
useMatch, useMatch,
useRouter, useRouter,
useNavigate,
} from '@tanstack/react-router' } from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router' import type { ErrorComponentProps } from '@tanstack/react-router'
import {
Box,
Button,
Text,
Title,
Stack,
Group,
Alert,
Collapse,
Code,
ThemeIcon
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useEffect } from 'react'
import toast from '@/lib/sonner'
import { logger } from '@/lib/logger'
import { ExclamationMarkIcon, XCircleIcon } from '@phosphor-icons/react'
export function DefaultCatchBoundary({ error }: ErrorComponentProps) { export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter() const router = useRouter()
const navigate = useNavigate()
const isRoot = useMatch({ const isRoot = useMatch({
strict: false, strict: false,
select: (state) => state.id === rootRouteId, select: (state) => state.id === rootRouteId,
}) })
const [detailsOpened, { toggle: toggleDetails }] = useDisclosure(false)
console.error('DefaultCatchBoundary Error:', error) const errorMessage = error?.message || 'Unknown error'
const errorStack = error?.stack || 'No stack trace available'
useEffect(() => {
logger.error('DefaultCatchBoundary | ', error)
if (errorMessage.toLowerCase().includes('unauthenticated')) {
toast.error('You\'ve been logged out')
navigate({ to: '/login' })
return
}
}, [error, errorMessage, navigate])
if (errorMessage.toLowerCase().includes('unauthorized')) {
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '50vh',
padding: 'var(--mantine-spacing-xl)',
}}
>
<Stack align="center" gap="lg">
<ThemeIcon color="red" size={80} radius="xl">
<XCircleIcon size={48} />
</ThemeIcon>
<Title order={2} ta="center">Access Denied</Title>
<Text size="lg" c="dimmed" ta="center">
You don't have permission to access this.
</Text>
<Group>
<Button
variant="light"
onClick={() => window.history.back()}
>
Go Back
</Button>
<Button
component={Link}
to="/"
variant="filled"
>
Home
</Button>
</Group>
</Stack>
</Box>
)
}
return ( return (
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6"> <Box
<ErrorComponent error={error} /> style={{
<div className="flex gap-2 items-center flex-wrap"> display: 'flex',
<button flexDirection: 'column',
onClick={() => { alignItems: 'center',
router.invalidate() justifyContent: 'center',
}} minHeight: '50vh',
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} padding: 'var(--mantine-spacing-xl)',
}}
>
<Stack align="center" gap="lg" maw={600}>
<ThemeIcon color="red" size={80} radius="xl">
<ExclamationMarkIcon size={48} />
</ThemeIcon>
<Title order={2} ta="center">Something went wrong</Title>
<Text size="lg" c="dimmed" ta="center">
There was an unexpected error. Please try again later.
</Text>
<Alert
variant="light"
color="red"
title="Error Details"
w="100%"
> >
Try Again <Text mb="sm">{errorMessage}</Text>
</button> <Button
{isRoot ? ( variant="subtle"
<Link size="compact-sm"
to="/" onClick={toggleDetails}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
> >
Home {detailsOpened ? 'Hide' : 'Show'} technical details
</Link> </Button>
) : ( <Collapse in={detailsOpened}>
<Link <Code block mt="md" p="md">
to="/" {errorStack}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`} </Code>
onClick={(e) => { </Collapse>
e.preventDefault() </Alert>
window.history.back()
}} <Group>
<Button
variant="light"
onClick={() => router.invalidate()}
> >
Go Back Try Again
</Link> </Button>
)} {isRoot ? (
</div> <Button
</div> component={Link}
to="/"
variant="filled"
>
Home
</Button>
) : (
<Button
variant="filled"
onClick={() => window.history.back()}
>
Go Back
</Button>
)}
</Group>
</Stack>
</Box>
) )
} }

View File

@@ -1,4 +1,4 @@
import { Box, Button, Text } from "@mantine/core"; import { Box, Button, Text, Title } from "@mantine/core";
import Header from "./header"; import Header from "./header";
import { testEvent } from "@/utils/test-event"; import { testEvent } from "@/utils/test-event";
import { Player } from "@/features/players/types"; import { Player } from "@/features/players/types";
@@ -17,10 +17,12 @@ const Profile = ({ player }: ProfileProps) => {
}, },
{ {
label: "Teams", label: "Teams",
content: <Text p="md">Panel 2 content</Text> content: <>
<TeamList teams={player.teams || []} />
</>
}, },
{ {
label: "Stats", label: "Tournaments",
content: <Text p="md">Panel 3 content</Text> content: <Text p="md">Panel 3 content</Text>
} }
]; ];
@@ -28,13 +30,7 @@ const Profile = ({ player }: ProfileProps) => {
return <> return <>
<Header player={player} /> <Header player={player} />
<Box m='sm' mt='lg'> <Box m='sm' mt='lg'>
<SwipeableTabs <SwipeableTabs tabs={tabs} />
tabs={tabs}
defaultTab={0}
onTabChange={(index, tab) => {
console.log(`Switched to ${tab.label} tab`);
}}
/>
</Box> </Box>
</>; </>;
}; };

View File

@@ -1,13 +1,16 @@
import { setUserMetadata, superTokensFunctionMiddleware } from "@/utils/supertokens"; import { setUserMetadata, superTokensFunctionMiddleware, verifySuperTokensSession } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { playerInputSchema, playerUpdateSchema } from "@/features/players/types"; import { playerInputSchema, playerUpdateSchema } from "@/features/players/types";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod"; import { z } from "zod";
import { logger } from "."; import { logger } from ".";
import { getWebRequest } from "@tanstack/react-start/server";
export const fetchMe = createServerFn() export const fetchMe = createServerFn()
.middleware([superTokensFunctionMiddleware]) .handler(async ({ response }) => {
.handler(async ({ context }) => { const request = getWebRequest();
const { context } = await verifySuperTokensSession(request, response);
if (!context || !context.userAuthId) return { user: undefined, roles: [], metadata: {} }; if (!context || !context.userAuthId) return { user: undefined, roles: [], metadata: {} };
try { try {

View File

@@ -28,7 +28,6 @@ export function createTournamentsService(pb: PocketBase) {
sort: "-created", sort: "-created",
}); });
console.log(result);
return result.map(transformTournament); return result.map(transformTournament);
}, },
async createTournament(data: TournamentInput): Promise<Tournament> { async createTournament(data: TournamentInput): Promise<Tournament> {

View File

@@ -10,7 +10,7 @@ import { refreshSession } from "supertokens-node/recipe/session";
const logger = new Logger('Middleware'); const logger = new Logger('Middleware');
const verifySuperTokensSession = async (request: Request, response?: ServerFnResponseType) => { export const verifySuperTokensSession = async (request: Request, response?: ServerFnResponseType) => {
const session = await getSessionForStart(request, { sessionRequired: false }); const session = await getSessionForStart(request, { sessionRequired: false });
if (session?.needsRefresh && response) { if (session?.needsRefresh && response) {
@@ -44,9 +44,10 @@ const verifySuperTokensSession = async (request: Request, response?: ServerFnRes
export const superTokensRequestMiddleware = createMiddleware({ type: 'request' }) export const superTokensRequestMiddleware = createMiddleware({ type: 'request' })
.server(async ({ next, request }) => { .server(async ({ next, request }) => {
const session = await verifySuperTokensSession(request); const session = await verifySuperTokensSession(request);
if (!session.context.userAuthId) { if (!session.context.userAuthId) {
logger.error('Unauthenticated user in API call.', session.context) logger.error('Unauthenticated user in API call.', session.context)
throw new Error("Unauthenticated");
} }
const context = { const context = {
@@ -65,6 +66,7 @@ export const superTokensFunctionMiddleware = createMiddleware({ type: 'function'
if (!session.context.userAuthId) { if (!session.context.userAuthId) {
logger.error('Unauthenticated user in server function.', session.context) logger.error('Unauthenticated user in server function.', session.context)
throw new Error("Unauthenticated");
} }
const context = { const context = {