ErrorComponent, 404Component, Theming
This commit is contained in:
Vendored
-5
File diff suppressed because one or more lines are too long
Vendored
+5
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+3
-3
@@ -5,10 +5,10 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-BaG4uuqL.js"></script>
|
||||
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-C9ueyNrZ.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C_FdcE2X.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-aBip8mmu.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-DojtBDN6.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-4z7lf7t1.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-Do102PZ-.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/zustand-DAXCIHlT.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-W-kxdzA-.js">
|
||||
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
|
||||
|
||||
+10
-1
@@ -86,5 +86,14 @@
|
||||
|
||||
"Dark": "Dunkel",
|
||||
"Light": "Hell",
|
||||
"System": "System"
|
||||
"System": "System",
|
||||
|
||||
"Confirm success header": "Benutzer aktiviert!",
|
||||
"Confirm error header": "Benutzer existiert nicht oder ist schon aktiviert.",
|
||||
"Confirm pending header": "Benutzer wird aktiviert...",
|
||||
"Back to main": "Zurück zur Hauptseite",
|
||||
|
||||
"Page not found": "Die Seite konnte nicht gefunden werden.",
|
||||
"Session expired": "Deine Sitzung ist abgelaufen.",
|
||||
"General error": "Da ist wohl was schief gelaufen."
|
||||
}
|
||||
|
||||
+10
-1
@@ -87,5 +87,14 @@
|
||||
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"System": "System"
|
||||
"System": "System",
|
||||
|
||||
"Confirm success header": "User confirmed!",
|
||||
"Confirm error header": "User does not exist or is already confirmed.",
|
||||
"confirm pending header": "User is getting confirmed...",
|
||||
"Back to main": "Back to the front page",
|
||||
|
||||
"Page not found": "This page was not found",
|
||||
"Session expired": "Your session has expired.",
|
||||
"General error": "Looks like something went wrong."
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -86,5 +86,14 @@
|
||||
|
||||
"Dark": "Dunkel",
|
||||
"Light": "Hell",
|
||||
"System": "System"
|
||||
"System": "System",
|
||||
|
||||
"Confirm success header": "Benutzer aktiviert!",
|
||||
"Confirm error header": "Benutzer existiert nicht oder ist schon aktiviert.",
|
||||
"Confirm pending header": "Benutzer wird aktiviert...",
|
||||
"Back to main": "Zurück zur Hauptseite",
|
||||
|
||||
"Page not found": "Die Seite konnte nicht gefunden werden.",
|
||||
"Session expired": "Deine Sitzung ist abgelaufen.",
|
||||
"General error": "Da ist wohl was schief gelaufen."
|
||||
}
|
||||
|
||||
@@ -87,5 +87,14 @@
|
||||
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"System": "System"
|
||||
"System": "System",
|
||||
|
||||
"Confirm success header": "User confirmed!",
|
||||
"Confirm error header": "User does not exist or is already confirmed.",
|
||||
"confirm pending header": "User is getting confirmed...",
|
||||
"Back to main": "Back to the front page",
|
||||
|
||||
"Page not found": "This page was not found",
|
||||
"Session expired": "Your session has expired.",
|
||||
"General error": "Looks like something went wrong."
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ interface ApiContext {
|
||||
|
||||
user?: (id?: number) => Promise<User>;
|
||||
createUser?: (data: UserCreate) => Promise<User>;
|
||||
confirmUser?: (code: string) => Promise<User>;
|
||||
updateUser?: (data: UserUpdate, id?: number) => Promise<User>;
|
||||
updateUserImage?: (data: UserImageUpdate, id?: number) => Promise<User>;
|
||||
|
||||
@@ -48,6 +49,7 @@ export const useApi = () => {
|
||||
|
||||
user,
|
||||
createUser,
|
||||
confirmUser,
|
||||
updateUser,
|
||||
updateUserImage,
|
||||
|
||||
@@ -63,6 +65,7 @@ export const useApi = () => {
|
||||
deletePost &&
|
||||
user &&
|
||||
createUser &&
|
||||
confirmUser &&
|
||||
updateUser &&
|
||||
updateUserImage &&
|
||||
userPosts
|
||||
@@ -81,6 +84,7 @@ export const useApi = () => {
|
||||
|
||||
user,
|
||||
createUser,
|
||||
confirmUser,
|
||||
updateUser,
|
||||
updateUserImage,
|
||||
|
||||
@@ -146,7 +150,7 @@ export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ ch
|
||||
};
|
||||
|
||||
const updatePost = async (data: PostUpdate, id: number): Promise<PostAuth> => {
|
||||
return await (await reAuth(() => patch(`posts/${id}`, data as Record<string, unknown>))).json();
|
||||
return await (await reAuth(() => patchAuth(`posts/${id}`, data as Record<string, unknown>))).json();
|
||||
};
|
||||
|
||||
const deletePost = async (id: number): Promise<PostDelete> => {
|
||||
@@ -161,8 +165,14 @@ export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ ch
|
||||
return await (await post(`register`, data as unknown as Record<string, unknown>)).json();
|
||||
};
|
||||
|
||||
const confirmUser = async (code: string): Promise<User> => {
|
||||
return await (await patch(`register`, { code })).json();
|
||||
};
|
||||
|
||||
const updateUser = async (data: UserUpdate, id?: number): Promise<User> => {
|
||||
const _user = await (await reAuth(() => patch(`users/${id ?? 'self'}`, data as Record<string, unknown>))).json();
|
||||
const _user = await (
|
||||
await reAuth(() => patchAuth(`users/${id ?? 'self'}`, data as Record<string, unknown>))
|
||||
).json();
|
||||
setAuthenticatedUser(_user);
|
||||
return _user;
|
||||
};
|
||||
@@ -247,6 +257,17 @@ export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ ch
|
||||
};
|
||||
|
||||
const patch = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||
const response = await fetch(`${BASE}${endpoint}`, {
|
||||
mode: 'cors',
|
||||
method: 'patch',
|
||||
headers: headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) return response;
|
||||
throw await response.json();
|
||||
};
|
||||
|
||||
const patchAuth = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||
const response = await fetch(`${BASE}${endpoint}`, {
|
||||
mode: 'cors',
|
||||
method: 'patch',
|
||||
@@ -313,6 +334,7 @@ export const ApiProvider: FC<PropsWithChildren<Record<string, unknown>>> = ({ ch
|
||||
|
||||
user,
|
||||
createUser,
|
||||
confirmUser,
|
||||
updateUser,
|
||||
updateUserImage,
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
import { Grid, Link as MUILink, Typography } from '@mui/material';
|
||||
import { useQueryErrorResetBoundary } from '@tanstack/react-query';
|
||||
import { ErrorRouteComponent as TSErrorRouteComponent, useNavigate, useRouter } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import HeaderlessLayout from '../../components/Layouts/HeaderlessLayout';
|
||||
import { ROUTES } from '../../types/Routes';
|
||||
import { ERRORS } from './Errors';
|
||||
|
||||
const ErrorRouterComponent: TSErrorRouteComponent = ({ error }) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
const queryErrorResetBoundary = useQueryErrorResetBoundary();
|
||||
|
||||
useEffect(() => {
|
||||
// Reset the query error boundary
|
||||
console.log(queryErrorResetBoundary.isReset());
|
||||
queryErrorResetBoundary.reset();
|
||||
}, [queryErrorResetBoundary]);
|
||||
|
||||
return (
|
||||
<HeaderlessLayout>
|
||||
<Grid container spacing={2} sx={{ marginTop: 0 }}>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<ErrorOutline sx={{ fontSize: '200px' }} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="h4" sx={{ textAlign: 'center' }}>
|
||||
{t('code' in error && error.code === ERRORS.UNAUTHORIZED ? 'Session expired' : 'General error')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<MUILink
|
||||
variant="h6"
|
||||
underline="none"
|
||||
onClick={() => {
|
||||
console.log('CLICK AS WELL');
|
||||
router.invalidate();
|
||||
navigate({ to: ROUTES.INDEX });
|
||||
}}
|
||||
>
|
||||
{t('Back to main')}
|
||||
</MUILink>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</HeaderlessLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorRouterComponent;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { ReactNode } from '@tanstack/react-router';
|
||||
import { FC } from 'react';
|
||||
import Footer from '../Footer/Footer';
|
||||
import Header from '../Header/Header';
|
||||
|
||||
const HeaderLayout: FC<{ children?: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Box sx={{ maxWidth: '800px', flexGrow: 1 }}>{children}</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderLayout;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { FC, ReactNode } from 'react';
|
||||
import Footer from '../Footer/Footer';
|
||||
|
||||
const HeaderlessLayout: FC<{ children?: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Box sx={{ maxWidth: '800px', flexGrow: 1 }}>{children}</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderlessLayout;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Grid, Link as MUILink, Typography } from '@mui/material';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import HeaderlessLayout from '../../components/Layouts/HeaderlessLayout';
|
||||
|
||||
const NotFoundComponent: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderlessLayout>
|
||||
<Grid container spacing={2} sx={{ marginTop: 0 }}>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="404">404</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="h4" sx={{ textAlign: 'center' }}>
|
||||
{t('Page not found')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<MUILink component={Link} to="/" variant="h6" underline="none">
|
||||
{t('Back to main')}
|
||||
</MUILink>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</HeaderlessLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundComponent;
|
||||
@@ -3,18 +3,15 @@ import { useApi } from '../api/Api';
|
||||
|
||||
export const profileSelfQueryOptions = (Api: ReturnType<typeof useApi>) =>
|
||||
queryOptions({
|
||||
queryKey: ['profile'],
|
||||
queryFn: async () => await Api.user(),
|
||||
queryKey: ['profile', { id: Api.authenticatedUser?.id }],
|
||||
queryFn: async () => ({
|
||||
user: await Api.user(),
|
||||
posts: await Api.userPosts(Api.authenticatedUser?.id ?? 0),
|
||||
}),
|
||||
});
|
||||
|
||||
export const profileQueryOptions = (Api: ReturnType<typeof useApi>, id?: number) =>
|
||||
queryOptions({
|
||||
queryKey: ['profile', { id }],
|
||||
queryFn: async () => await Api.user(id),
|
||||
});
|
||||
|
||||
export const profilePostsQueryOptions = (Api: ReturnType<typeof useApi>, id: number) =>
|
||||
queryOptions({
|
||||
queryKey: ['profilePosts', { id }],
|
||||
queryFn: async () => await Api.userPosts(id),
|
||||
queryFn: async () => ({ user: await Api.user(id), posts: await Api.userPosts(id) }),
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as ProfileIndexImport } from './routes/profile/index'
|
||||
import { Route as ConfirmIndexImport } from './routes/confirm/index'
|
||||
import { Route as ProfileIdImport } from './routes/profile/$id'
|
||||
|
||||
// Create/Update Routes
|
||||
@@ -27,6 +28,11 @@ const ProfileIndexRoute = ProfileIndexImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ConfirmIndexRoute = ConfirmIndexImport.update({
|
||||
path: '/confirm/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ProfileIdRoute = ProfileIdImport.update({
|
||||
path: '/profile/$id',
|
||||
getParentRoute: () => rootRoute,
|
||||
@@ -50,6 +56,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof ProfileIdImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/confirm/': {
|
||||
id: '/confirm/'
|
||||
path: '/confirm'
|
||||
fullPath: '/confirm'
|
||||
preLoaderRoute: typeof ConfirmIndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/profile/': {
|
||||
id: '/profile/'
|
||||
path: '/profile'
|
||||
@@ -65,6 +78,7 @@ declare module '@tanstack/react-router' {
|
||||
export const routeTree = rootRoute.addChildren({
|
||||
IndexRoute,
|
||||
ProfileIdRoute,
|
||||
ConfirmIndexRoute,
|
||||
ProfileIndexRoute,
|
||||
})
|
||||
|
||||
@@ -78,6 +92,7 @@ export const routeTree = rootRoute.addChildren({
|
||||
"children": [
|
||||
"/",
|
||||
"/profile/$id",
|
||||
"/confirm/",
|
||||
"/profile/"
|
||||
]
|
||||
},
|
||||
@@ -87,6 +102,9 @@ export const routeTree = rootRoute.addChildren({
|
||||
"/profile/$id": {
|
||||
"filePath": "profile/$id.tsx"
|
||||
},
|
||||
"/confirm/": {
|
||||
"filePath": "confirm/index.tsx"
|
||||
},
|
||||
"/profile/": {
|
||||
"filePath": "profile/index.tsx"
|
||||
}
|
||||
|
||||
@@ -1,53 +1,22 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { QueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
||||
import { createRootRouteWithContext, ErrorRouteComponent, Outlet, useRouter } from '@tanstack/react-router';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||
import { useEffect } from 'react';
|
||||
import { useApi } from '../api/Api';
|
||||
import Footer from '../components/Footer/Footer';
|
||||
import Header from '../components/Header/Header';
|
||||
import ErrorRouterComponent from '../components/Error/ErrorRouterComponent';
|
||||
import NotFoundComponent from '../components/NotFound/NotFoundComponent';
|
||||
|
||||
const Root = () => {
|
||||
return (
|
||||
<Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<Header />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Box sx={{ maxWidth: '800px', flexGrow: 1 }}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Footer />
|
||||
<Outlet />
|
||||
{process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorComponent: ErrorRouteComponent = ({ error }) => {
|
||||
const router = useRouter();
|
||||
const queryErrorResetBoundary = useQueryErrorResetBoundary();
|
||||
|
||||
useEffect(() => {
|
||||
// Reset the query error boundary
|
||||
queryErrorResetBoundary.reset();
|
||||
}, [queryErrorResetBoundary]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{error.message}
|
||||
<button
|
||||
onClick={() => {
|
||||
// Invalidate the route to reload the loader, and reset any router error boundaries
|
||||
router.invalidate();
|
||||
}}
|
||||
>
|
||||
retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Route = createRootRouteWithContext<{ queryClient: QueryClient; Api: ReturnType<typeof useApi> }>()({
|
||||
component: Root,
|
||||
errorComponent: ErrorComponent,
|
||||
notFoundComponent: NotFoundComponent,
|
||||
errorComponent: ErrorRouterComponent,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Done, Error } from '@mui/icons-material';
|
||||
import { CircularProgress, Grid, Link as MUILink, Typography } from '@mui/material';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useApi } from '../../api/Api';
|
||||
import HeaderlessLayout from '../../components/Layouts/HeaderlessLayout';
|
||||
import { ROUTES } from '../../types/Routes';
|
||||
|
||||
const Home = () => {
|
||||
const Api = useApi();
|
||||
const { code } = Route.useSearch();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const confirmMutation = useMutation({
|
||||
mutationFn: ({ code: _code }: { code: string }) => {
|
||||
return Api.confirmUser(_code);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (code) setTimeout(() => confirmMutation.mutate({ code }), 1000);
|
||||
}, []); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (!code) {
|
||||
navigate({ to: '/' });
|
||||
}
|
||||
}, [code]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<HeaderlessLayout>
|
||||
<Grid container spacing={2} sx={{ marginTop: 0 }}>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{confirmMutation.isSuccess && <Done color="action" sx={{ fontSize: '200px' }} />}
|
||||
{confirmMutation.isError && <Error color="action" sx={{ fontSize: '200px' }} />}
|
||||
{(confirmMutation.isPending || confirmMutation.isIdle) && <CircularProgress size={200} />}
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{confirmMutation.isSuccess && <Typography variant="h5">{t('Confirm success header')}</Typography>}
|
||||
{confirmMutation.isError && (
|
||||
<Typography variant="h5" color="error">
|
||||
{t('Confirm error header')}
|
||||
</Typography>
|
||||
)}
|
||||
{(confirmMutation.isPending || confirmMutation.isIdle) && (
|
||||
<Typography variant="h5">{t('Confirm pending header')}</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
{!confirmMutation.isPending && !confirmMutation.isIdle && (
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<MUILink component={Link} to="/" variant="h6" underline="none">
|
||||
{t('Back to main')}
|
||||
</MUILink>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</HeaderlessLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const Route = createFileRoute(`${ROUTES.CONFIRM}/`)({
|
||||
validateSearch: (search: Record<string, unknown>): { code?: string } => {
|
||||
return {
|
||||
code: search?.code !== undefined ? (search?.code as string) : undefined,
|
||||
};
|
||||
},
|
||||
component: Home,
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useApi } from '../api/Api';
|
||||
import PostForm from '../components/Forms/Post/PostForm';
|
||||
import HeaderLayout from '../components/Layouts/HeaderLayout';
|
||||
import Post from '../components/Post/Post';
|
||||
import { postsQueryOptions } from '../queries/postsQuery';
|
||||
import { ROUTES } from '../types/Routes';
|
||||
@@ -34,7 +35,7 @@ const Home = () => {
|
||||
}, [page]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderLayout>
|
||||
<Snackbar open={isFetching} message={t('Updating')} />
|
||||
<Grid container spacing={2} sx={{ marginTop: 0 }}>
|
||||
{postsQuery.data.map((post) => (
|
||||
@@ -74,7 +75,7 @@ const Home = () => {
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
</HeaderLayout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||
import { t } from 'i18next';
|
||||
import { useApi } from '../../api/Api';
|
||||
import { ERRORS } from '../../components/Error/Errors';
|
||||
import HeaderLayout from '../../components/Layouts/HeaderLayout';
|
||||
import Profile from '../../components/Profile/Profile';
|
||||
import { profilePostsQueryOptions, profileQueryOptions } from '../../queries/profileQuery';
|
||||
import { profileQueryOptions } from '../../queries/profileQuery';
|
||||
import { PostAuth } from '../../types/Post';
|
||||
import { ROUTES } from '../../types/Routes';
|
||||
|
||||
@@ -13,40 +14,24 @@ const ProfilePage = () => {
|
||||
const Api = useApi();
|
||||
const { id } = Route.useParams();
|
||||
const {
|
||||
data: profileQuery,
|
||||
isFetching: isFetchingProfile,
|
||||
error: errorProfile,
|
||||
failureReason: failureReasonProfile,
|
||||
} = useSuspenseQuery(profileQueryOptions(Api));
|
||||
const {
|
||||
data: profilePostsQuery,
|
||||
isFetching: isFetchingPosts,
|
||||
error: errorPosts,
|
||||
failureReason: failureReasonPosts,
|
||||
} = useSuspenseQuery(profilePostsQueryOptions(Api, id));
|
||||
data: { user, posts },
|
||||
isFetching,
|
||||
error,
|
||||
failureReason,
|
||||
} = useSuspenseQuery(profileQueryOptions(Api, id));
|
||||
|
||||
if (failureReasonProfile && 'code' in failureReasonProfile && failureReasonProfile.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReasonProfile;
|
||||
if (failureReason && 'code' in failureReason && failureReason.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReason;
|
||||
}
|
||||
if (errorProfile && 'code' in errorProfile && errorProfile.code === ERRORS.UNAUTHORIZED) {
|
||||
throw errorProfile;
|
||||
}
|
||||
if (failureReasonPosts && 'code' in failureReasonPosts && failureReasonPosts.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReasonPosts;
|
||||
}
|
||||
if (errorPosts && 'code' in errorPosts && errorPosts.code === ERRORS.UNAUTHORIZED) {
|
||||
throw errorPosts;
|
||||
if (error && 'code' in error && error.code === ERRORS.UNAUTHORIZED) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
||||
<Profile
|
||||
user={profileQuery}
|
||||
posts={profilePostsQuery.data as PostAuth[]}
|
||||
canEdit={Api.authenticatedUser?.isAdmin}
|
||||
/>
|
||||
</>
|
||||
<HeaderLayout>
|
||||
<Snackbar open={isFetching} message={t('Updating')} />
|
||||
<Profile user={user} posts={posts.data as PostAuth[]} canEdit={Api.authenticatedUser?.isAdmin} />
|
||||
</HeaderLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,7 +42,6 @@ export const Route = createFileRoute(`${ROUTES.PROFILE}/$id`)({
|
||||
},
|
||||
loader: ({ context: { queryClient, Api }, params: { id } }) => {
|
||||
queryClient.ensureQueryData(profileQueryOptions(Api, id));
|
||||
queryClient.ensureQueryData(profilePostsQueryOptions(Api, id));
|
||||
},
|
||||
beforeLoad: ({ params: { id }, context: { Api } }) => {
|
||||
if (!Api.hasAuth) throw redirect({ to: ROUTES.INDEX });
|
||||
|
||||
@@ -4,51 +4,39 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||
import { t } from 'i18next';
|
||||
import { useApi } from '../../api/Api';
|
||||
import { ERRORS } from '../../components/Error/Errors';
|
||||
import HeaderLayout from '../../components/Layouts/HeaderLayout';
|
||||
import Profile from '../../components/Profile/Profile';
|
||||
import { profilePostsQueryOptions, profileSelfQueryOptions } from '../../queries/profileQuery';
|
||||
import { profileSelfQueryOptions } from '../../queries/profileQuery';
|
||||
import { PostAuth } from '../../types/Post';
|
||||
import { ROUTES } from '../../types/Routes';
|
||||
|
||||
const ProfilePage = () => {
|
||||
const Api = useApi();
|
||||
const {
|
||||
data: profileQuery,
|
||||
isFetching: isFetchingProfile,
|
||||
error: errorProfile,
|
||||
failureReason: failureReasonProfile,
|
||||
data: { user, posts },
|
||||
isFetching,
|
||||
error,
|
||||
failureReason,
|
||||
} = useSuspenseQuery(profileSelfQueryOptions(Api));
|
||||
const {
|
||||
data: profilePostsQuery,
|
||||
isFetching: isFetchingPosts,
|
||||
error: errorPosts,
|
||||
failureReason: failureReasonPosts,
|
||||
} = useSuspenseQuery(profilePostsQueryOptions(Api, Api.authenticatedUser?.id ?? 0));
|
||||
|
||||
if (failureReasonProfile && 'code' in failureReasonProfile && failureReasonProfile.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReasonProfile;
|
||||
if (failureReason && 'code' in failureReason && failureReason.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReason;
|
||||
}
|
||||
if (errorProfile && 'code' in errorProfile && errorProfile.code === ERRORS.UNAUTHORIZED) {
|
||||
throw errorProfile;
|
||||
}
|
||||
if (failureReasonPosts && 'code' in failureReasonPosts && failureReasonPosts.code === ERRORS.UNAUTHORIZED) {
|
||||
throw failureReasonPosts;
|
||||
}
|
||||
if (errorPosts && 'code' in errorPosts && errorPosts.code === ERRORS.UNAUTHORIZED) {
|
||||
throw errorPosts;
|
||||
if (error && 'code' in error && error.code === ERRORS.UNAUTHORIZED) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Snackbar open={isFetchingProfile || isFetchingPosts} message={t('Updating')} />
|
||||
<Profile user={profileQuery} posts={profilePostsQuery.data as PostAuth[]} canEdit={true} />
|
||||
</>
|
||||
<HeaderLayout>
|
||||
<Snackbar open={isFetching} message={t('Updating')} />
|
||||
<Profile user={user} posts={posts.data as PostAuth[]} canEdit={true} />
|
||||
</HeaderLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const Route = createFileRoute(`${ROUTES.PROFILE}/`)({
|
||||
loader: ({ context: { queryClient, Api } }) => {
|
||||
queryClient.ensureQueryData(profileSelfQueryOptions(Api));
|
||||
queryClient.ensureQueryData(profilePostsQueryOptions(Api, Api.authenticatedUser?.id ?? 0));
|
||||
},
|
||||
beforeLoad: ({ context: { Api } }) => {
|
||||
if (!Api.hasAuth) throw redirect({ to: ROUTES.INDEX });
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import typography from '../overrides/typography';
|
||||
import MuiSnackbarContent from '../overrides/MuiSnackbarContent';
|
||||
|
||||
const darkTheme = createTheme({
|
||||
let darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
@@ -26,15 +28,14 @@ const darkTheme = createTheme({
|
||||
paper: '#0d1019',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
darkTheme = createTheme(darkTheme, {
|
||||
typography: {
|
||||
...typography(darkTheme),
|
||||
},
|
||||
components: {
|
||||
MuiSnackbarContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: undefined,
|
||||
color: 'text.primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
...MuiSnackbarContent,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { createTheme } from '@mui/material';
|
||||
import typography from '../overrides/typography';
|
||||
import MuiSnackbarContent from '../overrides/MuiSnackbarContent';
|
||||
|
||||
const lightTheme = createTheme({
|
||||
let lightTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
@@ -26,15 +28,14 @@ const lightTheme = createTheme({
|
||||
paper: '#ffffff',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
lightTheme = createTheme(lightTheme, {
|
||||
typography: {
|
||||
...typography(lightTheme),
|
||||
},
|
||||
components: {
|
||||
MuiSnackbarContent: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: undefined,
|
||||
color: 'text.primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
...MuiSnackbarContent,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Components } from '@mui/material';
|
||||
|
||||
const MuiSnackbarContent: Components['MuiSnackbarContent'] = {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: undefined,
|
||||
color: 'text.primary',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default MuiSnackbarContent;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Palette, Theme } from '@mui/material';
|
||||
import { TypographyOptions } from '@mui/material/styles/createTypography';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface TypographyVariants {
|
||||
'404': React.CSSProperties;
|
||||
}
|
||||
|
||||
// allow configuration using `createTheme`
|
||||
interface TypographyVariantsOptions {
|
||||
'404'?: React.CSSProperties;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the Typography's variant prop options
|
||||
declare module '@mui/material/Typography' {
|
||||
interface TypographyPropsVariantOverrides {
|
||||
'404': true;
|
||||
}
|
||||
}
|
||||
|
||||
const typography = (theme: Theme): TypographyOptions | ((palette: Palette) => TypographyOptions) => ({
|
||||
'404': {
|
||||
fontSize: '6rem',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
fontSize: '12rem',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
fontSize: '18rem',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default typography;
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum ROUTES {
|
||||
INDEX = '/',
|
||||
PROFILE = '/profile',
|
||||
CONFIRM = '/confirm',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user