Compare commits
No commits in common. "a515c447e01a854b46b1fbcc54ff832d2883d43d" and "ff0e19276852cfedfca0a9ff9d3b2179d263ff96" have entirely different histories.
a515c447e0
...
ff0e192768
@ -139,7 +139,6 @@ class Post implements JsonSerializable
|
|||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$stmt = $db->prepare("DELETE FROM egb_gaestebuch WHERE id = :ID");
|
$stmt = $db->prepare("DELETE FROM egb_gaestebuch WHERE id = :ID");
|
||||||
$stmt->bindValue(":ID", $this->id);
|
$stmt->bindValue(":ID", $this->id);
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
exam/react/log.txt
Normal file
BIN
exam/react/log.txt
Normal file
Binary file not shown.
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"NotFound_user:login": "Benutzer existiert nicht",
|
"NotFound_user:login": "Benutzer existiert nicht",
|
||||||
"Unauthorized_login": "Ungültige E-Mail oder Passwort",
|
"Unauthorized_login": "Ungültige E-Mail oder Passwort",
|
||||||
"Unauthorized_delete": "Keine Berechtigung",
|
|
||||||
"NotFound_post:delete": "Post nicht gefunden",
|
|
||||||
|
|
||||||
"GuestBook": "Gästebuch",
|
"GuestBook": "Gästebuch",
|
||||||
|
|
||||||
@ -20,13 +18,5 @@
|
|||||||
|
|
||||||
"Username": "Benutzername",
|
"Username": "Benutzername",
|
||||||
"Member since": "Mitglied seit",
|
"Member since": "Mitglied seit",
|
||||||
"Post count": "Anzahl Posts",
|
"Post count": "Anzahl Posts"
|
||||||
|
|
||||||
"Edit": "Bearbeiten",
|
|
||||||
"Delete": "Löschen",
|
|
||||||
"Yes": "Ja",
|
|
||||||
"No": "Nein",
|
|
||||||
|
|
||||||
"Confirm post delete title": "Diesen Post löschen?",
|
|
||||||
"Confirm post delete body": "Möchtest du diesen Post von {{name}} wirklich Löschen?"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
"NotFound_user:login": "User does not exist",
|
"NotFound_user:login": "User does not exist",
|
||||||
"Unauthorized_login": "Invalid email or password",
|
"Unauthorized_login": "Invalid email or password",
|
||||||
|
|
||||||
"Unauthorized_delete": "Unauthorized",
|
|
||||||
"NotFound_post:delete": "Post not found",
|
|
||||||
|
|
||||||
"GuestBook": "GuestBook",
|
"GuestBook": "GuestBook",
|
||||||
|
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
@ -21,13 +18,5 @@
|
|||||||
|
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"Member since": "Member since",
|
"Member since": "Member since",
|
||||||
"Post count": "Post count",
|
"Post count": "Post count"
|
||||||
|
|
||||||
"Edit": "Edit",
|
|
||||||
"Delete": "Delete",
|
|
||||||
"Yes": "Yes",
|
|
||||||
"No": "No",
|
|
||||||
|
|
||||||
"Confirm post delete title": "Delete this post?",
|
|
||||||
"Confirm post delete body": "Do you really want to delete this post by {{name}}?"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { PostAuth, PostListAuth, PostListNonAuth } from '../types/Post';
|
import { PostListAuth, PostListNonAuth } from '../types/Post';
|
||||||
import { User } from '../types/User';
|
import { User } from '../types/User';
|
||||||
|
|
||||||
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
||||||
@ -27,6 +27,7 @@ class ApiImpl {
|
|||||||
public logIn = async (email: string, password: string): Promise<void> => {
|
public logIn = async (email: string, password: string): Promise<void> => {
|
||||||
const { user, token } = await (await this.post('login', { email, password })).json();
|
const { user, token } = await (await this.post('login', { email, password })).json();
|
||||||
this.self = user;
|
this.self = user;
|
||||||
|
this.isAdmin = user.isAdmin;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,17 +50,15 @@ class ApiImpl {
|
|||||||
return await (await this.get(url)).json();
|
return await (await this.get(url)).json();
|
||||||
};
|
};
|
||||||
|
|
||||||
public deletePost = async (id: number): Promise<PostAuth> => {
|
|
||||||
return await (await this.delete(`posts/${id}`)).json();
|
|
||||||
};
|
|
||||||
|
|
||||||
public user = async (id?: number): Promise<User> => {
|
public user = async (id?: number): Promise<User> => {
|
||||||
return await (await this.getAuth(`users/${id ?? this.self?.id}`)).json();
|
return await (await this.getAuth(`users/${id ?? this.self?.id}`)).json();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Internal */
|
private post = async (
|
||||||
|
endpoint: string,
|
||||||
private post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
body: Record<string, unknown> | undefined = undefined,
|
||||||
|
headers: HeadersInit | undefined = undefined
|
||||||
|
) => {
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -70,7 +69,11 @@ class ApiImpl {
|
|||||||
throw await response.json();
|
throw await response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
private postAuth = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
private postAuth = async (
|
||||||
|
endpoint: string,
|
||||||
|
body: Record<string, unknown> | undefined = undefined,
|
||||||
|
headers: HeadersInit | undefined = undefined
|
||||||
|
) => {
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -81,7 +84,7 @@ class ApiImpl {
|
|||||||
throw await response.json();
|
throw await response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
private get = async (endpoint: string, headers?: HeadersInit) => {
|
private get = async (endpoint: string, headers: HeadersInit | undefined = undefined) => {
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -91,7 +94,7 @@ class ApiImpl {
|
|||||||
throw await response.json();
|
throw await response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
private getAuth = async (endpoint: string, headers?: HeadersInit) => {
|
private getAuth = async (endpoint: string, headers: HeadersInit | undefined = undefined) => {
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
const response = await fetch(`${BASE}${endpoint}`, {
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -100,16 +103,6 @@ class ApiImpl {
|
|||||||
if (response.ok) return response;
|
if (response.ok) return response;
|
||||||
throw await response.json();
|
throw await response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
private delete = async (endpoint: string, headers?: HeadersInit) => {
|
|
||||||
const response = await fetch(`${BASE}${endpoint}`, {
|
|
||||||
mode: 'cors',
|
|
||||||
method: 'delete',
|
|
||||||
headers: { token: this.token ?? '', ...headers },
|
|
||||||
});
|
|
||||||
if (response.ok) return response;
|
|
||||||
throw await response.json();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Api = new ApiImpl();
|
const Api = new ApiImpl();
|
||||||
|
|||||||
@ -52,9 +52,10 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
|||||||
name="email"
|
name="email"
|
||||||
validators={{
|
validators={{
|
||||||
onChange: ({ value }) => (!value ? t('Email required') : undefined),
|
onChange: ({ value }) => (!value ? t('Email required') : undefined),
|
||||||
onChangeAsyncDebounceMs: 250,
|
onChangeAsyncDebounceMs: 500,
|
||||||
onChangeAsync: async ({ value }) => {
|
onChangeAsync: async ({ value }) => {
|
||||||
return !value && t('Email required');
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return value.includes('error') && 'No "error" allowed in email';
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
children={(field) => {
|
children={(field) => {
|
||||||
@ -83,9 +84,10 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
|||||||
name="password"
|
name="password"
|
||||||
validators={{
|
validators={{
|
||||||
onChange: ({ value }) => (!value ? t('Password required') : undefined),
|
onChange: ({ value }) => (!value ? t('Password required') : undefined),
|
||||||
onChangeAsyncDebounceMs: 250,
|
onChangeAsyncDebounceMs: 500,
|
||||||
onChangeAsync: async ({ value }) => {
|
onChangeAsync: async ({ value }) => {
|
||||||
return !value && t('Password required');
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return value.includes('error') && 'No "error" allowed in password';
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
children={(field) => {
|
children={(field) => {
|
||||||
@ -119,7 +121,7 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{error && <Typography color="error.main">{handleError(error, t, 'login')}</Typography>}
|
{error && <Typography color="error.main">{handleError(error, 'login', t)}</Typography>}
|
||||||
</Box>
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AccountCircle, Person, Translate } from '@mui/icons-material';
|
import { AccountCircle, Translate } from '@mui/icons-material';
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
Avatar,
|
Avatar,
|
||||||
@ -57,9 +57,7 @@ const Header: FC = () => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
{user ? (
|
{user ? (
|
||||||
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
|
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
|
||||||
<Avatar alt={user.username} src={`storage/${user.image}`}>
|
<Avatar alt={user.username} src={`storage/${user.image}`} />
|
||||||
<Person />
|
|
||||||
</Avatar>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
) : (
|
) : (
|
||||||
<IconButton size="large" onClick={(event) => setAnchorUserMenu(event.currentTarget)} color="inherit">
|
<IconButton size="large" onClick={(event) => setAnchorUserMenu(event.currentTarget)} color="inherit">
|
||||||
|
|||||||
@ -50,8 +50,8 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
|||||||
</MenuItem>,
|
</MenuItem>,
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key="logout"
|
key="logout"
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
await Api.logOut();
|
Api.logOut();
|
||||||
router.invalidate();
|
router.invalidate();
|
||||||
handleClose();
|
handleClose();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,67 +1,25 @@
|
|||||||
import { Person } from '@mui/icons-material';
|
import { Avatar, Card, CardContent, CardHeader, Link as MUILink, Typography } from '@mui/material';
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardActions,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
DialogContentText,
|
|
||||||
DialogTitle,
|
|
||||||
Link as MUILink,
|
|
||||||
Snackbar,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import { Link } from '@tanstack/react-router';
|
import { Link } from '@tanstack/react-router';
|
||||||
import { FC, useState } from 'react';
|
import { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import Api from '../../api/Api';
|
import Api from '../../api/Api';
|
||||||
import { PostAuth, PostNonAuth } from '../../types/Post';
|
import { PostAuth, PostNonAuth } from '../../types/Post';
|
||||||
import convertDate from '../../utils/date';
|
import convertDate from '../../utils/date';
|
||||||
import handleError from '../../utils/errors';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
post: PostNonAuth | PostAuth;
|
post: PostNonAuth | PostAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Post: FC<Props> = ({ post }) => {
|
const Post: FC<Props> = ({ post }) => {
|
||||||
const deleteMutation = useMutation({
|
|
||||||
mutationFn: (id: number) => {
|
|
||||||
return Api.deletePost(id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
avatar={
|
avatar={
|
||||||
'id' in post.user ? (
|
'id' in post.user ? (
|
||||||
post.user.id !== Api.getAuthenticatedUser()?.id ? (
|
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }} underline="none">
|
||||||
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
<Avatar alt={post.user.username} src={`storage/${post.user.image}`} />
|
||||||
<Avatar alt={post.user.username} src={`storage/${post.user.image}`}>
|
|
||||||
<Person />
|
|
||||||
</Avatar>
|
|
||||||
</MUILink>
|
</MUILink>
|
||||||
) : (
|
) : (
|
||||||
<MUILink component={Link} to="/profile">
|
<Avatar alt={post.user.username} src={`storage/${post.user.image}`} />
|
||||||
<Avatar alt={post.user.username} src={`storage/${post.user.image}`}>
|
|
||||||
<Person />
|
|
||||||
</Avatar>
|
|
||||||
</MUILink>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Avatar alt={post.user.username} src={`storage/${post.user.image}`}>
|
|
||||||
<Person />
|
|
||||||
</Avatar>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
@ -84,42 +42,6 @@ const Post: FC<Props> = ({ post }) => {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography>{post.content}</Typography>
|
<Typography>{post.content}</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardActions>
|
|
||||||
{Api.isAdmin() && (
|
|
||||||
<>
|
|
||||||
<Button size="small" color="error" onClick={() => setOpen(true)}>
|
|
||||||
{t('Delete')}
|
|
||||||
</Button>
|
|
||||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
|
||||||
<DialogTitle>{t('Confirm post delete title')}</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>{t('Confirm post delete body', { name: post.user.username })}</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => setOpen(false)} autoFocus variant="contained">
|
|
||||||
{t('No')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="error"
|
|
||||||
onClick={() => {
|
|
||||||
deleteMutation.mutate(post.id);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Yes')}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardActions>
|
|
||||||
<Snackbar open={deleteMutation.isError} autoHideDuration={2000} onClose={() => deleteMutation.reset()}>
|
|
||||||
<Alert severity="error" variant="filled" sx={{ width: '100%' }}>
|
|
||||||
{deleteMutation.isError && handleError(deleteMutation.error, t, 'delete')}
|
|
||||||
</Alert>
|
|
||||||
</Snackbar>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Person } from '@mui/icons-material';
|
import { Avatar, Box, Grid, Typography } from '@mui/material';
|
||||||
import { Avatar, Box, Button, Card, CardActions, CardContent, Grid, Typography } from '@mui/material';
|
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { User } from '../../types/User';
|
import { User } from '../../types/User';
|
||||||
@ -7,37 +6,28 @@ import convertDate from '../../utils/date';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
canEdit?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Profile: FC<Props> = ({ user, canEdit }) => {
|
const Profile: FC<Props> = ({ user }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Box>
|
||||||
<CardContent>
|
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center' }}>
|
<Grid item sx={{ display: 'grid', gridTemplateColumns: 'fit-content(100%) 1fr', columnGap: 2, rowGap: 1 }}>
|
||||||
<Avatar alt={user.username} src={`storage/${user.image}`} sx={{ width: '100px', height: '100px' }}>
|
<Box sx={{ gridColumn: '1/3', display: 'flex', justifyContent: 'center' }}>
|
||||||
<Person sx={{ width: '60px', height: '60px' }} />
|
<Avatar alt={user.username} src={`storage/${user.image}`} sx={{ width: 100, height: 100 }} />
|
||||||
</Avatar>
|
</Box>
|
||||||
</Grid>
|
|
||||||
<Grid item sx={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<Box sx={{ display: 'grid', gridTemplateColumns: '120px 1fr', columnGap: 1 }}>
|
|
||||||
<Typography fontWeight="bold">{t('Username')}:</Typography>
|
<Typography fontWeight="bold">{t('Username')}:</Typography>
|
||||||
<Typography>{user.username}</Typography>
|
<Typography>{user.username}</Typography>
|
||||||
<Typography fontWeight="bold">{t('Email')}:</Typography>
|
|
||||||
<Typography>{user.email}</Typography>
|
|
||||||
<Typography fontWeight="bold">{t('Member since')}:</Typography>
|
<Typography fontWeight="bold">{t('Member since')}:</Typography>
|
||||||
<Typography>{convertDate(user.memberSince)}</Typography>
|
<Typography>{convertDate(user.memberSince)}</Typography>
|
||||||
<Typography fontWeight="bold">{t('Post count')}:</Typography>
|
<Typography fontWeight="bold">{t('Post count')}:</Typography>
|
||||||
<Typography>{user.postCount}</Typography>
|
<Typography>{user.postCount}</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item></Grid>
|
||||||
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</CardContent>
|
|
||||||
<CardActions>{canEdit && <Button size="small">{t('Edit')}</Button>}</CardActions>
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Box } from '@mui/material';
|
|
||||||
import { QueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
import { QueryClient, useQueryErrorResetBoundary } from '@tanstack/react-query';
|
||||||
import { createRootRouteWithContext, ErrorRouteComponent, Outlet, redirect, useRouter } from '@tanstack/react-router';
|
import { createRootRouteWithContext, ErrorRouteComponent, Outlet, redirect, useRouter } from '@tanstack/react-router';
|
||||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||||
@ -14,11 +13,7 @@ const Root = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Box sx={{ maxWidth: '800px' }}>
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
{process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
|
{process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const Home = () => {
|
|||||||
<Snackbar open={isFetching} message={t('Updating')} />
|
<Snackbar open={isFetching} message={t('Updating')} />
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{postsQuery.data.map((post) => (
|
{postsQuery.data.map((post) => (
|
||||||
<Grid item xs={12} key={post.id}>
|
<Grid key={post.id} item xs={12} md={6} lg={4} sx={{ display: 'flex' }}>
|
||||||
<Post post={post} />
|
<Post post={post} />
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const ProfilePage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Snackbar open={isFetching} message={t('Updating')} />
|
<Snackbar open={isFetching} message={t('Updating')} />
|
||||||
<Profile user={profileQuery} canEdit={Api.isAdmin()} />
|
<Profile user={profileQuery} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -30,9 +30,8 @@ export const Route = createFileRoute(`${ROUTES.PROFILE}/$id`)({
|
|||||||
stringify: ({ id }) => ({ id: id.toString() }),
|
stringify: ({ id }) => ({ id: id.toString() }),
|
||||||
},
|
},
|
||||||
loader: ({ context: { queryClient }, params: { id } }) => queryClient.ensureQueryData(profileQueryOptions(id)),
|
loader: ({ context: { queryClient }, params: { id } }) => queryClient.ensureQueryData(profileQueryOptions(id)),
|
||||||
beforeLoad: ({ params: { id } }) => {
|
beforeLoad: () => {
|
||||||
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
|
if (!Api.hasAuth()) throw redirect({ to: ROUTES.INDEX });
|
||||||
if (id === Api.getAuthenticatedUser()?.id) throw redirect({ to: ROUTES.PROFILE });
|
|
||||||
},
|
},
|
||||||
component: ProfilePage,
|
component: ProfilePage,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const ProfilePage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Snackbar open={isFetching} message={t('Updating')} />
|
<Snackbar open={isFetching} message={t('Updating')} />
|
||||||
<Profile user={profileQuery} canEdit={true} />
|
<Profile user={profileQuery} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,11 +15,10 @@ export enum ERRORS {
|
|||||||
const handleError = (
|
const handleError = (
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
error: any,
|
error: any,
|
||||||
|
context?: string,
|
||||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
t: TFunction<'translation', undefined> | ((..._in: any) => any) = (..._in: any) => _in,
|
t: TFunction<'translation', undefined> | ((..._in: any) => any) = (..._in: any) => _in
|
||||||
context?: string
|
|
||||||
): string => {
|
): string => {
|
||||||
console.log(context);
|
|
||||||
if (!error) return t('', {});
|
if (!error) return t('', {});
|
||||||
|
|
||||||
if (error.code) {
|
if (error.code) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user