216 lines
6.7 KiB
TypeScript
216 lines
6.7 KiB
TypeScript
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;
|