New picture link output, register modal
This commit is contained in:
parent
72a0ad6364
commit
215ed1bc7f
2
exam/dist/assets/i18n-DJgSTqOl.js
vendored
2
exam/dist/assets/i18n-DJgSTqOl.js
vendored
File diff suppressed because one or more lines are too long
2
exam/dist/assets/i18n-DyW0LrNj.js
vendored
Normal file
2
exam/dist/assets/i18n-DyW0LrNj.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
exam/dist/assets/index-DAvRi6Od.js
vendored
1
exam/dist/assets/index-DAvRi6Od.js
vendored
File diff suppressed because one or more lines are too long
1
exam/dist/assets/index-tFNBNNKb.js
vendored
Normal file
1
exam/dist/assets/index-tFNBNNKb.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
File diff suppressed because one or more lines are too long
1
exam/dist/assets/zustand-Cc7ZFYQr.js
vendored
1
exam/dist/assets/zustand-Cc7ZFYQr.js
vendored
@ -1 +0,0 @@
|
||||
import"./react-DXd9vB-a.js";
|
||||
1
exam/dist/assets/zustand-DA8f69qW.js
vendored
Normal file
1
exam/dist/assets/zustand-DA8f69qW.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import"./react-C_FdcE2X.js";
|
||||
10
exam/dist/index.html
vendored
10
exam/dist/index.html
vendored
@ -5,11 +5,11 @@
|
||||
<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-DAvRi6Od.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-DXd9vB-a.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-CxHUbSMi.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-xmxrKlZO.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DJgSTqOl.js">
|
||||
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-tFNBNNKb.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-C4H8cxTH.js">
|
||||
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-Duf7jkFs.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/index-D83Ey19k.css">
|
||||
</head>
|
||||
|
||||
13
exam/dist/locales/de/translation.json
vendored
13
exam/dist/locales/de/translation.json
vendored
@ -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"
|
||||
}
|
||||
|
||||
13
exam/dist/locales/en/translation.json
vendored
13
exam/dist/locales/en/translation.json
vendored
@ -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"
|
||||
}
|
||||
|
||||
2
exam/dist/stats.html
vendored
2
exam/dist/stats.html
vendored
File diff suppressed because one or more lines are too long
@ -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) => {
|
||||
|
||||
243
exam/react/src/components/Dialogs/Register/RegisterDialog.tsx
Normal file
243
exam/react/src/components/Dialogs/Register/RegisterDialog.tsx
Normal file
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user