New picture link output, register modal
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
"Unauthorized_postUpdate": "Keine Berechtigung",
|
||||
"NotFound_post:postUpdate": "Post nicht gefunden",
|
||||
|
||||
"Duplicate_user:register": "Ein Benutzer mit diesem Benutzernamen oder E-Mail existiert schon",
|
||||
|
||||
"username": "Benutzername",
|
||||
"email": "E-Mail",
|
||||
"password": "Passwort",
|
||||
@@ -71,5 +73,14 @@
|
||||
"Upload named": "{{name}} gewählt",
|
||||
"Avatar": "Avatar {{name}}",
|
||||
"Remove": "Entfernen",
|
||||
"or": "oder"
|
||||
"or": "oder",
|
||||
|
||||
"Leave comment header": "Du benötigst ein Konto um einen Kommentar zu hinterlassen.",
|
||||
"Leave comment action": "Klicke oben rechts auf das Icon um dich anzumelden oder zu registrieren.",
|
||||
|
||||
"Register prompt": "<0>Noch kein Konto?</0> <1>Registriere</1> <2>dich jetzt!</2>",
|
||||
"Register": "Konto anlegen",
|
||||
"Confirm header": "Fast geschafft!",
|
||||
"Confirm mail": "Prüfe dein E-Mail Postfach auf eine Bestätigungsmail.",
|
||||
"Close": "Schließen"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"Unauthorized_postUpdate": "Unauthorized",
|
||||
"NotFound_post:postUpdate": "Post not found",
|
||||
|
||||
"Duplicate_user:register": "A user with this username or email already exists",
|
||||
|
||||
"username": "username",
|
||||
"email": "email",
|
||||
"password": "password",
|
||||
@@ -72,5 +74,14 @@
|
||||
"Upload named": "{{name}} chosen",
|
||||
"Avatar": "Avatar {{name}}",
|
||||
"Remove": "Remove",
|
||||
"or": "or"
|
||||
"or": "or",
|
||||
|
||||
"Leave comment header": "You need an account to leave a Comment.",
|
||||
"Leave comment action": "Click on the icon in the top right corner to log in or register.",
|
||||
|
||||
"Register prompt": "<0>No account yet?</0> <1>Register</1> <2>now!</2>",
|
||||
"Register": "Create account",
|
||||
"Confirm header": "Almost there!",
|
||||
"Confirm mail": "Check your email for a confirmation mail.",
|
||||
"Close": "Close"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { POST_LIMIT, PROFILE_POST_LIMIT } from '../constanst';
|
||||
import { PostAuth, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
|
||||
import { User, UserImageUpdate, UserUpdate } from '../types/User';
|
||||
import { PostAuth, PostCreate, PostDelete, PostListAuth, PostListNonAuth, PostNew, PostUpdate } from '../types/Post';
|
||||
import { User, UserCreate, UserImageUpdate, UserUpdate } from '../types/User';
|
||||
|
||||
const BASE = 'https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/';
|
||||
|
||||
@@ -88,8 +88,8 @@ class ApiImpl {
|
||||
return user;
|
||||
};
|
||||
|
||||
public newPost = async (data: PostUpdate): Promise<PostNew> => {
|
||||
return await (await this.postAuth(`posts?l=${POST_LIMIT}`, { ...data } as Record<string, unknown>)).json();
|
||||
public newPost = async (data: PostCreate): Promise<PostNew> => {
|
||||
return await (await this.postAuth(`posts?l=${POST_LIMIT}`, data as unknown as Record<string, unknown>)).json();
|
||||
};
|
||||
|
||||
public updatePost = async (data: PostUpdate, id: number): Promise<PostAuth> => {
|
||||
@@ -100,6 +100,10 @@ class ApiImpl {
|
||||
return await (await this.getAuth(`users/${id}/posts?l=${PROFILE_POST_LIMIT}&s=desc`)).json();
|
||||
};
|
||||
|
||||
public createUser = async (data: UserCreate): Promise<User> => {
|
||||
return await (await this.post(`register`, data as unknown as Record<string, unknown>)).json();
|
||||
};
|
||||
|
||||
/* Internal */
|
||||
|
||||
private post = async (endpoint: string, body?: Record<string, unknown>, headers?: HeadersInit) => {
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import { Done } from '@mui/icons-material';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
TextField,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { FC, FormEvent, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../../api/Api';
|
||||
import { UserCreate } from '../../../types/User';
|
||||
import ErrorComponent from '../../Error/ErrorComponent';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const RegisterDialog: FC<Props> = ({ open, onClose }) => {
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [error, setError] = useState<any>();
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: ({ data }: { data: UserCreate }) => {
|
||||
return Api.createUser(data);
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
|
||||
|
||||
const form = useForm<UserCreate>({
|
||||
defaultValues: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
try {
|
||||
createMutation.mutate(
|
||||
{ data: value },
|
||||
{
|
||||
onSuccess: () => setError(undefined),
|
||||
onError: setError,
|
||||
}
|
||||
);
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
setError(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
form.reset();
|
||||
setError(undefined);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (createMutation.isSuccess)
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} fullWidth fullScreen={fullScreen}>
|
||||
<DialogContent>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Done color="action" sx={{ fontSize: '200px' }} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography variant="h5">{t('Confirm header')}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Typography>{t('Confirm mail')}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>{t('Close')}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
fullWidth
|
||||
fullScreen={fullScreen}
|
||||
PaperProps={{
|
||||
component: 'form',
|
||||
encType: 'multipart/form-data',
|
||||
onSubmit: (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
form.handleSubmit();
|
||||
},
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLFormElement>) => {
|
||||
if (event.key === 'Tab') {
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
noValidate: true,
|
||||
}}
|
||||
>
|
||||
<DialogTitle>{t('Register')}</DialogTitle>
|
||||
<DialogContent sx={{ gap: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<form.Field
|
||||
name="username"
|
||||
validators={{
|
||||
onChange: ({ value }) => (!value ? t('Username required') : undefined),
|
||||
onChangeAsyncDebounceMs: 250,
|
||||
onChangeAsync: async ({ value }) => {
|
||||
return !value && t('Username required');
|
||||
},
|
||||
}}
|
||||
children={(field) => {
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
size="small"
|
||||
label={t('Username')}
|
||||
required
|
||||
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
|
||||
autoComplete="new-username"
|
||||
fullWidth
|
||||
margin="dense"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form.Field
|
||||
name="email"
|
||||
validators={{
|
||||
onChange: ({ value }) => (!value ? t('Email required') : undefined),
|
||||
onChangeAsyncDebounceMs: 250,
|
||||
onChangeAsync: async ({ value }) => {
|
||||
return !value && t('Email required');
|
||||
},
|
||||
}}
|
||||
children={(field) => {
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
size="small"
|
||||
label={t('Email')}
|
||||
required
|
||||
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
|
||||
type="email"
|
||||
autoComplete="new-username"
|
||||
inputMode="email"
|
||||
fullWidth
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form.Field
|
||||
name="password"
|
||||
validators={{
|
||||
onChange: ({ value }) => (!value ? t('Password required') : undefined),
|
||||
onChangeAsyncDebounceMs: 250,
|
||||
onChangeAsync: async ({ value }) => {
|
||||
return !value && t('Password required');
|
||||
},
|
||||
}}
|
||||
children={(field) => {
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
size="small"
|
||||
label={t('Password')}
|
||||
required
|
||||
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
fullWidth
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<form.Subscribe
|
||||
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||
children={([canSubmit]) => (
|
||||
<>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!canSubmit || createMutation.isPending}
|
||||
variant="contained"
|
||||
endIcon={createMutation.isPending && <CircularProgress color="inherit" size="20px" />}
|
||||
>
|
||||
{t('Register')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{error && <ErrorComponent error={error} context="register" />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterDialog;
|
||||
@@ -25,7 +25,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { t } from 'i18next';
|
||||
import { FC, FormEvent, useState } from 'react';
|
||||
import Api from '../../../api/Api';
|
||||
import { STORAGE_PATH } from '../../../constanst';
|
||||
import { User, UserImageUpdate } from '../../../types/User';
|
||||
import ErrorComponent from '../../Error/ErrorComponent';
|
||||
|
||||
@@ -110,8 +109,8 @@ const UserImageDialog: FC<Props> = ({ user, open, onClose }) => {
|
||||
formState.image
|
||||
? URL.createObjectURL(formState.image)
|
||||
: formState.predefined
|
||||
? `${STORAGE_PATH}profilbilder/default/${formState.predefined}.svg`
|
||||
: `${STORAGE_PATH}${user.image}`
|
||||
? `profilbilder/default/${formState.predefined}.svg`
|
||||
: `${user.image}`
|
||||
}
|
||||
sx={{ width: '100px', height: '100px' }}
|
||||
>
|
||||
|
||||
@@ -13,8 +13,6 @@ interface Props {
|
||||
const ErrorComponent: FC<Props> = ({ error, context, color = 'error.main' }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
console.log(error, context);
|
||||
|
||||
if (!error) return null;
|
||||
|
||||
if (error.code) {
|
||||
@@ -35,6 +33,8 @@ const ErrorComponent: FC<Props> = ({ error, context, color = 'error.main' }) =>
|
||||
{t(error.code, { context: `${field}:${context}` })}
|
||||
</Typography>
|
||||
));
|
||||
case ERRORS.DUPLICATE:
|
||||
return <Typography color={color}>{t(error.code, { context: `${error.entity}:${context}` })}</Typography>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@ export enum ERRORS {
|
||||
UNAUTHORIZED = 'Unauthorized',
|
||||
FAILED_UPDATE = 'FailedUpdate',
|
||||
MISSING_FIELD = 'MissingField',
|
||||
DUPLICATE = 'Duplicate',
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useNavigate } from '@tanstack/react-router';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../../api/Api';
|
||||
import { PostUpdate } from '../../../types/Post';
|
||||
import { PostCreate } from '../../../types/Post';
|
||||
import ErrorComponent from '../../Error/ErrorComponent';
|
||||
|
||||
const PostForm: FC = () => {
|
||||
@@ -18,12 +18,12 @@ const PostForm: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const newMutation = useMutation({
|
||||
mutationFn: ({ data }: { data: PostUpdate }) => {
|
||||
mutationFn: ({ data }: { data: PostCreate }) => {
|
||||
return Api.newPost(data);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<PostUpdate>({
|
||||
const form = useForm<PostCreate>({
|
||||
defaultValues: {
|
||||
content: '',
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Link, useRouterState } from '@tanstack/react-router';
|
||||
import { cloneElement, FC, ReactElement, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../api/Api';
|
||||
import { STORAGE_PATH } from '../../constanst';
|
||||
import { User } from '../../types/User';
|
||||
import LanguageMenu from '../Menus/Language/LanguageMenu';
|
||||
import UserMenu from '../Menus/User/UserMenu';
|
||||
@@ -62,7 +61,7 @@ const Header: FC = () => {
|
||||
</IconButton>
|
||||
{user ? (
|
||||
<IconButton onClick={(event) => setAnchorUserMenu(event.currentTarget)} sx={{ p: 0 }}>
|
||||
<Avatar alt={user.username} src={`${STORAGE_PATH}/${user.image}`}>
|
||||
<Avatar alt={user.username} src={`${user.image}`}>
|
||||
<Person />
|
||||
</Avatar>
|
||||
</IconButton>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Menu, MenuItem } from '@mui/material';
|
||||
import { Box, Link, Menu, MenuItem, Typography } from '@mui/material';
|
||||
import { useMatch, useNavigate, useRouter } from '@tanstack/react-router';
|
||||
import { t } from 'i18next';
|
||||
import { FC } from 'react';
|
||||
import { FC, useState } from 'react';
|
||||
import { Trans } from 'react-i18next/TransWithoutContext';
|
||||
import Api from '../../../api/Api';
|
||||
import { ROUTES } from '../../../types/Routes';
|
||||
import RegisterDialog from '../../Dialogs/Register/RegisterDialog';
|
||||
import LoginForm from '../../Forms/Login/LoginForm';
|
||||
|
||||
interface Props {
|
||||
@@ -12,12 +14,19 @@ interface Props {
|
||||
}
|
||||
|
||||
const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
||||
const [register, setRegister] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const router = useRouter();
|
||||
const match = useMatch({ from: '/profile/', strict: true, shouldThrow: false });
|
||||
|
||||
const user = Api.getAuthenticatedUser();
|
||||
|
||||
const _handleClose = () => {
|
||||
setRegister(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
@@ -31,7 +40,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
onClose={_handleClose}
|
||||
sx={{
|
||||
'& .MuiMenu-paper': {
|
||||
minWidth: '240px',
|
||||
@@ -45,7 +54,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
||||
key="profile"
|
||||
onClick={() => {
|
||||
navigate({ to: ROUTES.PROFILE });
|
||||
handleClose();
|
||||
_handleClose();
|
||||
}}
|
||||
>
|
||||
{t('Profile')}
|
||||
@@ -55,14 +64,35 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
|
||||
onClick={async () => {
|
||||
await Api.logOut();
|
||||
router.invalidate();
|
||||
handleClose();
|
||||
_handleClose();
|
||||
}}
|
||||
>
|
||||
{t('Log out')}
|
||||
</MenuItem>,
|
||||
]
|
||||
) : register ? (
|
||||
<>
|
||||
<RegisterDialog open={register} onClose={() => setRegister(false)} />
|
||||
</>
|
||||
) : (
|
||||
<LoginForm handleClose={handleClose} />
|
||||
<>
|
||||
<LoginForm handleClose={_handleClose} />
|
||||
<Box sx={{ padding: 1 }}>
|
||||
<Trans i18nKey="Register prompt">
|
||||
<Typography component="span" />
|
||||
<Link
|
||||
sx={{ cursor: 'pointer' }}
|
||||
variant="body1"
|
||||
underline="hover"
|
||||
onClick={() => {
|
||||
setRegister(true);
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
<Typography component="span" />
|
||||
</Trans>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,6 @@ import { Link, useNavigate } from '@tanstack/react-router';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../api/Api';
|
||||
import { STORAGE_PATH } from '../../constanst';
|
||||
import { PostAuth, PostNonAuth } from '../../types/Post';
|
||||
import convertDate from '../../utils/date';
|
||||
import PostEditDialog from '../Dialogs/PostEdit/PostEditDialog';
|
||||
@@ -55,19 +54,19 @@ const Post: FC<Props> = ({ post, disableActions }) => {
|
||||
!disableActions && 'id' in post.user ? (
|
||||
post.user.id !== Api.getAuthenticatedUser()?.id ? (
|
||||
<MUILink component={Link} to="/profile/$id" params={{ id: post.user.id }}>
|
||||
<Avatar alt={post.user.username} src={`${STORAGE_PATH}${post.user.image}`}>
|
||||
<Avatar alt={post.user.username} src={`${post.user.image}`}>
|
||||
<Person />
|
||||
</Avatar>
|
||||
</MUILink>
|
||||
) : (
|
||||
<MUILink component={Link} to="/profile">
|
||||
<Avatar alt={post.user.username} src={`${STORAGE_PATH}${post.user.image}`}>
|
||||
<Avatar alt={post.user.username} src={`${post.user.image}`}>
|
||||
<Person />
|
||||
</Avatar>
|
||||
</MUILink>
|
||||
)
|
||||
) : (
|
||||
<Avatar alt={post.user.username} src={`${STORAGE_PATH}${post.user.image}`}>
|
||||
<Avatar alt={post.user.username} src={`${post.user.image}`}>
|
||||
<Person />
|
||||
</Avatar>
|
||||
)
|
||||
|
||||
@@ -19,7 +19,6 @@ import convertDate from '../../utils/date';
|
||||
import UserEditDialog from '../Dialogs/UserEdit/UserEditDialog';
|
||||
import UserImageDialog from '../Dialogs/UserImage/UserImageDialog';
|
||||
import Post from '../Post/Post';
|
||||
import { STORAGE_PATH } from '../../constanst';
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
@@ -41,7 +40,7 @@ const Profile: FC<Props> = ({ user, posts, canEdit }) => {
|
||||
<Grid container spacing={2}>
|
||||
<Grid item sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center' }}>
|
||||
<IconButton onClick={() => setImageOpen(true)}>
|
||||
<Avatar alt={user.username} src={`${STORAGE_PATH}${user.image}`} sx={{ width: '100px', height: '100px' }}>
|
||||
<Avatar alt={user.username} src={`${user.image}`} sx={{ width: '100px', height: '100px' }}>
|
||||
<Person sx={{ width: '60px', height: '60px' }} />
|
||||
</Avatar>
|
||||
</IconButton>
|
||||
@@ -79,7 +78,7 @@ const Profile: FC<Props> = ({ user, posts, canEdit }) => {
|
||||
</Divider>
|
||||
</Grid>
|
||||
{posts.map((post) => (
|
||||
<Grid item xs={12}>
|
||||
<Grid key={`post_${post.id}`} item xs={12}>
|
||||
<Post post={post} disableActions />
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export const POST_LIMIT = 15;
|
||||
export const PROFILE_POST_LIMIT = 3;
|
||||
export const POST_CHAR_LIMIT = 250;
|
||||
export const STORAGE_PATH = '/phpCourse/exam/storage/';
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Divider, Grid, Pagination, PaginationItem, Snackbar } from '@mui/material';
|
||||
import {
|
||||
Divider,
|
||||
Grid,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Snackbar,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
||||
import { useEffect } from 'react';
|
||||
@@ -14,6 +23,8 @@ const Home = () => {
|
||||
const { data: postsQuery, isFetching } = useSuspenseQuery(postsQueryOptions(page));
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const oneSibling = useMediaQuery(theme.breakpoints.not('xs'), { noSsr: true });
|
||||
|
||||
useEffect(() => {
|
||||
if ((page ?? 0) >= postsQuery.pages) {
|
||||
@@ -33,15 +44,11 @@ const Home = () => {
|
||||
<Grid item xs={12}>
|
||||
<Divider variant="middle" />
|
||||
</Grid>
|
||||
{Api.hasAuth() && (
|
||||
<Grid item xs={12}>
|
||||
<PostForm />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Pagination
|
||||
page={(page ?? 0) + 1}
|
||||
count={postsQuery.pages}
|
||||
siblingCount={oneSibling ? 1 : 0}
|
||||
color="primary"
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
@@ -55,6 +62,16 @@ const Home = () => {
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
{Api.hasAuth() ? (
|
||||
<Grid item xs={12}>
|
||||
<PostForm />
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h5">{t('Leave comment header')}</Typography>
|
||||
<Typography>{t('Leave comment action')}</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,6 @@ const ProfilePage = () => {
|
||||
const { data: profilePostsQuery, isFetching: isFetchingPosts } = useSuspenseQuery(
|
||||
profilePostsQueryOptions(Api.getAuthenticatedUser()?.id ?? 0)
|
||||
);
|
||||
console.log(profilePostsQuery.data);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -37,3 +37,7 @@ export interface PostListAuth {
|
||||
export interface PostUpdate {
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export interface PostCreate {
|
||||
content: string;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ export interface UserUpdate {
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface UserCreate {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface Login {
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
Reference in New Issue
Block a user