random imporvements
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
</>;
|
</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -47,6 +47,7 @@ export const superTokensRequestMiddleware = createMiddleware({ type: '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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user