Compare commits

..

No commits in common. "45778d83d04d0403f8a918ae8a1c1d6db9d24c33" and "478f2429f5146c68092a3bae7f5589cab49c299b" have entirely different histories.

14 changed files with 80 additions and 373 deletions

File diff suppressed because one or more lines are too long

9
exam/dist/assets/index-DBCDWqoJ.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GuestBook</title>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-BFREkcQ0.js"></script>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-DBCDWqoJ.js"></script>
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-C9_qfvjK.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-BnAUJOoN.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-BqkrhB-y.js">

View File

@ -97,9 +97,5 @@
"Session expired": "Deine Sitzung ist abgelaufen.",
"General error": "Da ist wohl was schief gelaufen.",
"Favicon": "Gästebuch Icons erstellt von Smashicons - Flaticon",
"Password confirm": "Passwort bestätigen",
"Password match": "Passwörter stimmen nicht überein",
"Change password": "Passwort ändern"
"Favicon": "Gästebuch Icons erstellt von Smashicons - Flaticon"
}

View File

@ -98,9 +98,5 @@
"Session expired": "Your session has expired.",
"General error": "Looks like something went wrong.",
"Favicon": "Guests book icons created by Smashicons - Flaticon",
"Password confirm": "Confirm password",
"Password match": "Password do not match",
"Change password": "Change password"
"Favicon": "Guests book icons created by Smashicons - Flaticon"
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
{
"name": "react",
"private": true,
"version": "1.1.0",
"version": "1.0.2",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -97,9 +97,5 @@
"Session expired": "Deine Sitzung ist abgelaufen.",
"General error": "Da ist wohl was schief gelaufen.",
"Favicon": "Gästebuch Icons erstellt von Smashicons - Flaticon",
"Password confirm": "Passwort bestätigen",
"Password match": "Passwörter stimmen nicht überein",
"Change password": "Passwort ändern"
"Favicon": "Gästebuch Icons erstellt von Smashicons - Flaticon"
}

View File

@ -98,9 +98,5 @@
"Session expired": "Your session has expired.",
"General error": "Looks like something went wrong.",
"Favicon": "Guests book icons created by Smashicons - Flaticon",
"Password confirm": "Confirm password",
"Password match": "Password do not match",
"Change password": "Change password"
"Favicon": "Guests book icons created by Smashicons - Flaticon"
}

View File

@ -40,23 +40,16 @@ const RegisterDialog: FC<Props> = ({ open, onClose }) => {
},
});
const form = useForm<UserCreate & { passwordConfirm: string }>({
const form = useForm<UserCreate>({
defaultValues: {
username: '',
email: '',
password: '',
passwordConfirm: '',
},
onSubmit: async ({ value }) => {
try {
createMutation.mutate(
{
data: {
username: value.username,
email: value.email,
password: value.password,
},
},
{ data: value },
{
onSuccess: () => setError(undefined),
onError: setError,
@ -120,7 +113,7 @@ const RegisterDialog: FC<Props> = ({ open, onClose }) => {
}}
>
<DialogTitle>{t('Register')}</DialogTitle>
<DialogContent>
<DialogContent sx={{ gap: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<form.Field
@ -222,48 +215,6 @@ const RegisterDialog: FC<Props> = ({ open, onClose }) => {
}}
/>
</Grid>
<Grid item xs={12}>
<form.Field
name="passwordConfirm"
validators={{
onChangeListenTo: ['password'],
onChange: ({ value, fieldApi }) =>
!value
? t('Password required')
: value !== fieldApi.form.getFieldValue('password')
? t('Password match')
: undefined,
onChangeAsyncDebounceMs: 250,
onChangeAsync: async ({ value, fieldApi }) =>
!value
? t('Password required')
: value !== fieldApi.form.getFieldValue('password')
? t('Password match')
: undefined,
}}
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 confirm')}
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]}

View File

@ -5,7 +5,6 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
TextField,
useMediaQuery,
useTheme,
@ -99,72 +98,66 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
>
<DialogTitle>{t('Edit data')}</DialogTitle>
<DialogContent>
<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
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
size="small"
label={t('Username')}
required
margin="dense"
autoComplete="new-username"
fullWidth
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
/>
</>
);
}}
/>
</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
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
size="small"
label={t('Email')}
required
margin="dense"
autoComplete="new-email"
fullWidth
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
/>
</>
);
}}
/>
</Grid>
</Grid>
<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
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
size="small"
label={t('Username')}
required
margin="dense"
autoComplete="new-username"
fullWidth
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
/>
</>
);
}}
/>
<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
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
size="small"
label={t('Email')}
required
margin="dense"
autoComplete="new-email"
fullWidth
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
/>
</>
);
}}
/>
</DialogContent>
<DialogActions>
<form.Subscribe

View File

@ -1,215 +0,0 @@
import {
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
TextField,
useMediaQuery,
useTheme,
} from '@mui/material';
import { useForm } from '@tanstack/react-form';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { t } from 'i18next';
import { FC, FormEvent, useEffect, useState } from 'react';
import { useApi } from '../../../api/Api';
import { User, UserUpdate } from '../../../types/User';
import ErrorComponent from '../../Error/ErrorComponent';
interface Props {
user: User;
open: boolean;
onClose: () => void;
}
const UserPasswordDialog: FC<Props> = ({ user, open, onClose }) => {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const [error, setError] = useState<any>();
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
const queryClient = useQueryClient();
const Api = useApi();
const updateMutation = useMutation({
mutationFn: ({ data, id }: { data: UserUpdate; id?: number }) => {
return Api.updateUser(data, id);
},
});
const form = useForm<UserUpdate & { passwordConfirm: string }>({
defaultValues: {
password: '',
passwordConfirm: '',
},
onSubmit: async ({ value }) => {
try {
updateMutation.mutate(
{ data: { password: value.password }, id: Api.authenticatedUser?.id === user.id ? undefined : user.id },
{
onSuccess: () => {
handleClose();
const queryKey = Api.authenticatedUser?.id === user.id ? ['profile'] : ['profile', { id: user.id }];
queryClient.invalidateQueries({ queryKey });
},
onError: setError,
}
);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (_error: any) {
setError(_error);
}
},
});
const handleClose = () => {
form.reset();
setError(undefined);
onClose();
};
useEffect(() => {
if (!Api.hasAuth) handleClose();
}, [Api.hasAuth]); //eslint-disable-line react-hooks/exhaustive-deps
return (
<Dialog
open={open}
onClose={handleClose}
fullWidth
fullScreen={fullScreen}
PaperProps={{
component: 'form',
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('Edit data')}</DialogTitle>
<DialogContent>
<Grid container spacing={2}>
<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.Field
name="passwordConfirm"
validators={{
onChangeListenTo: ['password'],
onChange: ({ value, fieldApi }) =>
!value
? t('Password required')
: value !== fieldApi.form.getFieldValue('password')
? t('Password match')
: undefined,
onChangeAsyncDebounceMs: 250,
onChangeAsync: async ({ value, fieldApi }) =>
!value
? t('Password required')
: value !== fieldApi.form.getFieldValue('password')
? t('Password match')
: undefined,
}}
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 confirm')}
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>
</DialogContent>
<DialogActions>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit]) => (
<>
<Button
variant="outlined"
onClick={() => {
handleClose();
}}
>
{t('Cancel')}
</Button>
<Button
type="submit"
disabled={!canSubmit || updateMutation.isPending}
autoFocus
variant="contained"
endIcon={updateMutation.isPending && <CircularProgress color="inherit" size="20px" />}
>
{t('Save')}
</Button>
</>
)}
/>
</DialogActions>
{error && (
<DialogContent>
<ErrorComponent error={error} context="userUpdate" />
</DialogContent>
)}
</Dialog>
);
};
export default UserPasswordDialog;

View File

@ -17,8 +17,7 @@ import { PostAuth } from '../../types/Post';
import { User } from '../../types/User';
import convertDate from '../../utils/date';
import UserEditDialog from '../Dialogs/UserEdit/UserEditDialog';
import UserImageDialog from '../Dialogs/UserEdit/UserImageDialog';
import UserPasswordDialog from '../Dialogs/UserEdit/UserPasswordDialog';
import UserImageDialog from '../Dialogs/UserImage/UserImageDialog';
import Post from '../Post/Post';
interface Props {
@ -30,7 +29,6 @@ interface Props {
const Profile: FC<Props> = ({ user, posts, canEdit }) => {
const [editOpen, setEditOpen] = useState(false);
const [imageOpen, setImageOpen] = useState(false);
const [passwordOpen, setPasswordOpen] = useState(false);
const { t } = useTranslation();
@ -67,12 +65,8 @@ const Profile: FC<Props> = ({ user, posts, canEdit }) => {
<Button size="small" onClick={() => setEditOpen(true)}>
{t('Edit')}
</Button>
<Button size="small" color="secondary" onClick={() => setPasswordOpen(true)}>
{t('Change password')}
</Button>
<UserEditDialog user={user} open={editOpen} onClose={() => setEditOpen(false)} />
<UserImageDialog user={user} open={imageOpen} onClose={() => setImageOpen(false)} />
<UserPasswordDialog user={user} open={passwordOpen} onClose={() => setPasswordOpen(false)} />
</>
)}
</CardActions>