random imporvements
This commit is contained in:
@@ -17,7 +17,7 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
|
||||
header: {
|
||||
collapsed: 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: {
|
||||
toRefresh: tournamentQueries.details(params.tournamentId).queryKey,
|
||||
|
||||
@@ -1,53 +1,159 @@
|
||||
import {
|
||||
ErrorComponent,
|
||||
Link,
|
||||
rootRouteId,
|
||||
useMatch,
|
||||
useRouter,
|
||||
useNavigate,
|
||||
} 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) {
|
||||
const router = useRouter()
|
||||
const navigate = useNavigate()
|
||||
const isRoot = useMatch({
|
||||
strict: false,
|
||||
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 (
|
||||
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
|
||||
<ErrorComponent error={error} />
|
||||
<div className="flex gap-2 items-center flex-wrap">
|
||||
<button
|
||||
onClick={() => {
|
||||
router.invalidate()
|
||||
}}
|
||||
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||
<Box
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '50vh',
|
||||
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
|
||||
</button>
|
||||
{isRoot ? (
|
||||
<Link
|
||||
to="/"
|
||||
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||
<Text mb="sm">{errorMessage}</Text>
|
||||
<Button
|
||||
variant="subtle"
|
||||
size="compact-sm"
|
||||
onClick={toggleDetails}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to="/"
|
||||
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
window.history.back()
|
||||
}}
|
||||
{detailsOpened ? 'Hide' : 'Show'} technical details
|
||||
</Button>
|
||||
<Collapse in={detailsOpened}>
|
||||
<Code block mt="md" p="md">
|
||||
{errorStack}
|
||||
</Code>
|
||||
</Collapse>
|
||||
</Alert>
|
||||
|
||||
<Group>
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={() => router.invalidate()}
|
||||
>
|
||||
Go Back
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
Try Again
|
||||
</Button>
|
||||
{isRoot ? (
|
||||
<Button
|
||||
component={Link}
|
||||
to="/"
|
||||
variant="filled"
|
||||
>
|
||||
Home
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Button, Text } from "@mantine/core";
|
||||
import { Box, Button, Text, Title } from "@mantine/core";
|
||||
import Header from "./header";
|
||||
import { testEvent } from "@/utils/test-event";
|
||||
import { Player } from "@/features/players/types";
|
||||
@@ -17,10 +17,12 @@ const Profile = ({ player }: ProfileProps) => {
|
||||
},
|
||||
{
|
||||
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>
|
||||
}
|
||||
];
|
||||
@@ -28,13 +30,7 @@ const Profile = ({ player }: ProfileProps) => {
|
||||
return <>
|
||||
<Header player={player} />
|
||||
<Box m='sm' mt='lg'>
|
||||
<SwipeableTabs
|
||||
tabs={tabs}
|
||||
defaultTab={0}
|
||||
onTabChange={(index, tab) => {
|
||||
console.log(`Switched to ${tab.label} tab`);
|
||||
}}
|
||||
/>
|
||||
<SwipeableTabs tabs={tabs} />
|
||||
</Box>
|
||||
</>;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { setUserMetadata, superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { setUserMetadata, superTokensFunctionMiddleware, verifySuperTokensSession } from "@/utils/supertokens";
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { playerInputSchema, playerUpdateSchema } from "@/features/players/types";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { z } from "zod";
|
||||
import { logger } from ".";
|
||||
import { getWebRequest } from "@tanstack/react-start/server";
|
||||
|
||||
export const fetchMe = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context }) => {
|
||||
.handler(async ({ response }) => {
|
||||
const request = getWebRequest();
|
||||
const { context } = await verifySuperTokensSession(request, response);
|
||||
|
||||
if (!context || !context.userAuthId) return { user: undefined, roles: [], metadata: {} };
|
||||
|
||||
try {
|
||||
|
||||
@@ -28,7 +28,6 @@ export function createTournamentsService(pb: PocketBase) {
|
||||
sort: "-created",
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
return result.map(transformTournament);
|
||||
},
|
||||
async createTournament(data: TournamentInput): Promise<Tournament> {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { refreshSession } from "supertokens-node/recipe/session";
|
||||
|
||||
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 });
|
||||
|
||||
if (session?.needsRefresh && response) {
|
||||
@@ -44,9 +44,10 @@ const verifySuperTokensSession = async (request: Request, response?: ServerFnRes
|
||||
export const superTokensRequestMiddleware = createMiddleware({ type: 'request' })
|
||||
.server(async ({ next, request }) => {
|
||||
const session = await verifySuperTokensSession(request);
|
||||
|
||||
|
||||
if (!session.context.userAuthId) {
|
||||
logger.error('Unauthenticated user in API call.', session.context)
|
||||
throw new Error("Unauthenticated");
|
||||
}
|
||||
|
||||
const context = {
|
||||
@@ -65,6 +66,7 @@ export const superTokensFunctionMiddleware = createMiddleware({ type: 'function'
|
||||
|
||||
if (!session.context.userAuthId) {
|
||||
logger.error('Unauthenticated user in server function.', session.context)
|
||||
throw new Error("Unauthenticated");
|
||||
}
|
||||
|
||||
const context = {
|
||||
|
||||
Reference in New Issue
Block a user