Split 401

This commit is contained in:
Kilian Hofmann 2024-07-28 03:04:26 +02:00
parent 73c7dc1b5e
commit 96bdbfc62a
15 changed files with 77 additions and 75 deletions

1
exam/dist/assets/index-B93xlZhH.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,10 +5,10 @@
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>Vite + React + TS</title>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-tFNBNNKb.js"></script> <script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-B93xlZhH.js"></script>
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js"> <link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-C4H8cxTH.js"> <link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-C4H8cxTH.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-Duf7jkFs.js"> <link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-C0csOcmc.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DyW0LrNj.js"> <link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DyW0LrNj.js">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css"> <link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css"> <link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css">

View File

@ -1,23 +1,23 @@
{ {
"Unauthorized": "Keine Berechtigung", "Unauthorized": "Keine Berechtigung",
"Unauthorized_login": "Ungültige E-Mail oder Passwort", "NotAllowed_login": "Ungültige E-Mail oder Passwort",
"NotFound_user:login": "Benutzer existiert nicht", "NotFound_user:login": "Benutzer existiert nicht",
"MissingField_email:login": "E-Mail darf nicht leer sein", "MissingField_email:login": "E-Mail darf nicht leer sein",
"MissingField_password:login": "Passwort darf nicht leer sein", "MissingField_password:login": "Passwort darf nicht leer sein",
"Unauthorized_deletePost": "Keine Berechtigung", "NotAllowed_deletePost": "Keine Berechtigung",
"NotFound_post:deletePost": "Post nicht gefunden", "NotFound_post:deletePost": "Post nicht gefunden",
"Unauthorized_userUpdate": "Keine Berechtigung", "NotAllowed_userUpdate": "Keine Berechtigung",
"NotFound_user:userUpdate": "Benutzer nicht gefunden", "NotFound_user:userUpdate": "Benutzer nicht gefunden",
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon", "FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon", "FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
"Unauthorized_newPost": "Keine Berechtigung", "NotAllowed_newPost": "Keine Berechtigung",
"MissingField_content:newPost": "Beitrag darf nicht leer sein", "MissingField_content:newPost": "Beitrag darf nicht leer sein",
"Unauthorized_postUpdate": "Keine Berechtigung", "NotAllowed_postUpdate": "Keine Berechtigung",
"NotFound_post:postUpdate": "Post nicht gefunden", "NotFound_post:postUpdate": "Post nicht gefunden",
"Duplicate_user:register": "Ein Benutzer mit diesem Benutzernamen oder E-Mail existiert schon", "Duplicate_user:register": "Ein Benutzer mit diesem Benutzernamen oder E-Mail existiert schon",

View File

@ -1,23 +1,23 @@
{ {
"Unauthorized": "Unauthorized", "Unauthorized": "NotAllowed",
"Unauthorized_login": "Invalid email or password", "NotAllowed_login": "Invalid email or password",
"NotFound_user:login": "User does not exist", "NotFound_user:login": "User does not exist",
"MissingField_email:login": "E-Mail required", "MissingField_email:login": "E-Mail required",
"MissingField_password:login": "Password required", "MissingField_password:login": "Password required",
"Unauthorized_deletPost": "Unauthorized", "NotAllowed_deletPost": "NotAllowed",
"NotFound_post:deletePost": "Post not found", "NotFound_post:deletePost": "Post not found",
"Unauthorized_userUpdate": "Unauthorized", "NotAllowed_userUpdate": "NotAllowed",
"NotFound_user:userUpdate": "User not found", "NotFound_user:userUpdate": "User not found",
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists", "FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists", "FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
"Unauthorized_newPost": "Unauthorized", "NotAllowed_newPost": "NotAllowed",
"MissingField_content:newPost": "Content required", "MissingField_content:newPost": "Content required",
"Unauthorized_postUpdate": "Unauthorized", "NotAllowed_postUpdate": "NotAllowed",
"NotFound_post:postUpdate": "Post not found", "NotFound_post:postUpdate": "Post not found",
"Duplicate_user:register": "A user with this username or email already exists", "Duplicate_user:register": "A user with this username or email already exists",

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,23 @@
{ {
"Unauthorized": "Keine Berechtigung", "Unauthorized": "Keine Berechtigung",
"Unauthorized_login": "Ungültige E-Mail oder Passwort", "NotAllowed_login": "Ungültige E-Mail oder Passwort",
"NotFound_user:login": "Benutzer existiert nicht", "NotFound_user:login": "Benutzer existiert nicht",
"MissingField_email:login": "E-Mail darf nicht leer sein", "MissingField_email:login": "E-Mail darf nicht leer sein",
"MissingField_password:login": "Passwort darf nicht leer sein", "MissingField_password:login": "Passwort darf nicht leer sein",
"Unauthorized_deletePost": "Keine Berechtigung", "NotAllowed_deletePost": "Keine Berechtigung",
"NotFound_post:deletePost": "Post nicht gefunden", "NotFound_post:deletePost": "Post nicht gefunden",
"Unauthorized_userUpdate": "Keine Berechtigung", "NotAllowed_userUpdate": "Keine Berechtigung",
"NotFound_user:userUpdate": "Benutzer nicht gefunden", "NotFound_user:userUpdate": "Benutzer nicht gefunden",
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon", "FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon", "FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
"Unauthorized_newPost": "Keine Berechtigung", "NotAllowed_newPost": "Keine Berechtigung",
"MissingField_content:newPost": "Beitrag darf nicht leer sein", "MissingField_content:newPost": "Beitrag darf nicht leer sein",
"Unauthorized_postUpdate": "Keine Berechtigung", "NotAllowed_postUpdate": "Keine Berechtigung",
"NotFound_post:postUpdate": "Post nicht gefunden", "NotFound_post:postUpdate": "Post nicht gefunden",
"Duplicate_user:register": "Ein Benutzer mit diesem Benutzernamen oder E-Mail existiert schon", "Duplicate_user:register": "Ein Benutzer mit diesem Benutzernamen oder E-Mail existiert schon",

View File

@ -1,23 +1,23 @@
{ {
"Unauthorized": "Unauthorized", "Unauthorized": "NotAllowed",
"Unauthorized_login": "Invalid email or password", "NotAllowed_login": "Invalid email or password",
"NotFound_user:login": "User does not exist", "NotFound_user:login": "User does not exist",
"MissingField_email:login": "E-Mail required", "MissingField_email:login": "E-Mail required",
"MissingField_password:login": "Password required", "MissingField_password:login": "Password required",
"Unauthorized_deletPost": "Unauthorized", "NotAllowed_deletPost": "NotAllowed",
"NotFound_post:deletePost": "Post not found", "NotFound_post:deletePost": "Post not found",
"Unauthorized_userUpdate": "Unauthorized", "NotAllowed_userUpdate": "NotAllowed",
"NotFound_user:userUpdate": "User not found", "NotFound_user:userUpdate": "User not found",
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists", "FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists", "FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
"Unauthorized_newPost": "Unauthorized", "NotAllowed_newPost": "NotAllowed",
"MissingField_content:newPost": "Content required", "MissingField_content:newPost": "Content required",
"Unauthorized_postUpdate": "Unauthorized", "NotAllowed_postUpdate": "NotAllowed",
"NotFound_post:postUpdate": "Post not found", "NotFound_post:postUpdate": "Post not found",
"Duplicate_user:register": "A user with this username or email already exists", "Duplicate_user:register": "A user with this username or email already exists",

View File

@ -44,7 +44,8 @@ class ApiImpl {
public logOut = async (): Promise<boolean> => { public logOut = async (): Promise<boolean> => {
try { try {
return await (await this.postAuth('logout')).json(); if (this.token) return await (await this.postAuth('logout')).json();
return true;
} catch { } catch {
return false; return false;
} finally { } finally {

View File

@ -19,7 +19,7 @@ const ErrorComponent: FC<Props> = ({ error, context, color = 'error.main' }) =>
switch (error.code) { switch (error.code) {
case ERRORS.NOT_FOUND: case ERRORS.NOT_FOUND:
return <Typography color={color}>{t(error.code, { context: `${error.entity}:${context}` })}</Typography>; return <Typography color={color}>{t(error.code, { context: `${error.entity}:${context}` })}</Typography>;
case ERRORS.UNAUTHORIZED: case ERRORS.NOT_ALLOWED:
return <Typography color={color}>{t(error.code, { context })}</Typography>; return <Typography color={color}>{t(error.code, { context })}</Typography>;
case ERRORS.FAILED_UPDATE: case ERRORS.FAILED_UPDATE:
return error.fields.map((field: string, index: number) => ( return error.fields.map((field: string, index: number) => (

View File

@ -1,6 +1,7 @@
export enum ERRORS { export enum ERRORS {
NOT_FOUND = 'NotFound', NOT_FOUND = 'NotFound',
UNAUTHORIZED = 'Unauthorized', UNAUTHORIZED = 'Unauthorized',
NOT_ALLOWED = 'NotAllowed',
FAILED_UPDATE = 'FailedUpdate', FAILED_UPDATE = 'FailedUpdate',
MISSING_FIELD = 'MissingField', MISSING_FIELD = 'MissingField',
DUPLICATE = 'Duplicate', DUPLICATE = 'Duplicate',

View File

@ -71,11 +71,9 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
</MenuItem>, </MenuItem>,
] ]
) : register ? ( ) : register ? (
<> <RegisterDialog open={register} onClose={() => setRegister(false)} />
<RegisterDialog open={register} onClose={() => setRegister(false)} />
</>
) : ( ) : (
<> <Box>
<LoginForm handleClose={_handleClose} /> <LoginForm handleClose={_handleClose} />
<Box sx={{ padding: 1 }}> <Box sx={{ padding: 1 }}>
<Trans i18nKey="Register prompt"> <Trans i18nKey="Register prompt">
@ -92,7 +90,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
<Typography component="span" /> <Typography component="span" />
</Trans> </Trans>
</Box> </Box>
</> </Box>
)} )}
</Menu> </Menu>
); );

View File

@ -1,7 +1,9 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider, useQueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
import { RouterProvider, createRouter } from '@tanstack/react-router'; import { ErrorRouteComponent, RouterProvider, createRouter, useRouter } from '@tanstack/react-router';
import { StrictMode } from 'react'; import { StrictMode, useEffect } from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import Api from './api/Api';
import { ERRORS } from './components/Error/Errors';
// Import the generated route tree // Import the generated route tree
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
@ -19,6 +21,40 @@ import '@fontsource/roboto/700.css';
// Query Client // Query Client
const queryClient = new QueryClient(); const queryClient = new QueryClient();
//TODO: REAUTH HERE
//TODO: Make nice
export const Error: ErrorRouteComponent = ({ error }) => {
const router = useRouter();
const queryErrorResetBoundary = useQueryErrorResetBoundary();
const queryClient = useQueryClient();
useEffect(() => {
// Reset the query error boundary
queryErrorResetBoundary.reset();
}, [queryErrorResetBoundary]);
//TODO: Split display, show something went wrong with retry on all, show session expired and back to main on auth
if ('code' in error && error.code === ERRORS.NOT_ALLOWED) {
Api.logOut().finally(() => {
queryClient.clear();
router.invalidate();
});
}
return (
<div>
{error.message}
<button
onClick={() => {
router.invalidate();
}}
>
retry
</button>
</div>
);
};
// Create a new router instance // Create a new router instance
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
@ -30,6 +66,7 @@ const router = createRouter({
// This will ensure that the loader is always called when the route is preloaded or visited // This will ensure that the loader is always called when the route is preloaded or visited
defaultPreloadStaleTime: 0, defaultPreloadStaleTime: 0,
basepath: process.env.NODE_ENV === 'development' ? 'phpCourse/exam/dist' : '/phpCourse/exam', basepath: process.env.NODE_ENV === 'development' ? 'phpCourse/exam/dist' : '/phpCourse/exam',
defaultErrorComponent: Error,
}); });
// Register the router instance for type safety // Register the router instance for type safety
@ -47,7 +84,7 @@ if (!rootElement.innerHTML) {
<StrictMode> <StrictMode>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} /> <RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} /> {process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider> </QueryClientProvider>
</StrictMode> </StrictMode>
); );

View File

@ -1,16 +1,10 @@
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { QueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query'; import { QueryClient } from '@tanstack/react-query';
import { createRootRouteWithContext, ErrorRouteComponent, Outlet, redirect, useRouter } from '@tanstack/react-router'; import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools'; import { TanStackRouterDevtools } from '@tanstack/router-devtools';
import { useEffect } from 'react';
import Api from '../api/Api';
import { ERRORS } from '../components/Error/Errors';
import Header from '../components/Header/Header'; import Header from '../components/Header/Header';
import { ROUTES } from '../types/Routes';
const Root = () => { const Root = () => {
//TODO: REAUTH HERE
return ( return (
<> <>
<Header /> <Header />
@ -24,35 +18,6 @@ const Root = () => {
); );
}; };
//TODO: Make nice
const Error: ErrorRouteComponent = ({ error }) => {
const router = useRouter();
const queryErrorResetBoundary = useQueryErrorResetBoundary();
useEffect(() => {
// Reset the query error boundary
queryErrorResetBoundary.reset();
}, [queryErrorResetBoundary]);
if ('code' in error && error.code === ERRORS.UNAUTHORIZED) {
Api.logOut();
redirect({ to: ROUTES.INDEX });
}
return (
<div>
{error.message}
<button
onClick={() => {
router.invalidate();
}}
>
retry
</button>
</div>
);
};
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
component: Root, component: Root,
errorComponent: Error,
}); });