2024-07-27 16:42:03 +02:00

176 lines
5.0 KiB
TypeScript

import {
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
LinearProgress,
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, useState } from 'react';
import Api from '../../../api/Api';
import { PostAuth, PostUpdate } from '../../../types/Post';
import ErrorComponent from '../../Error/ErrorComponent';
interface Props {
post: PostAuth;
open: boolean;
onClose: () => void;
}
const PostEditDialog: FC<Props> = ({ post, open, onClose }) => {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const [error, setError] = useState<any>();
const [characterCount, setCharacterCount] = useState(post.content.length);
const updateMutation = useMutation({
mutationFn: ({ data, id }: { data: PostUpdate; id: number }) => {
return Api.updatePost(data, id);
},
});
const form = useForm<PostUpdate>({
defaultValues: {
content: post.content.replaceAll('<br />', ''),
},
onSubmit: async ({ value }) => {
try {
updateMutation.mutate(
{ data: value, id: post.id },
{
onSuccess: () => {
handleClose();
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
onError: setError,
}
);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
setError(error);
}
},
});
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
const queryClient = useQueryClient();
const handleClose = () => {
form.reset();
setError(undefined);
onClose();
};
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 post')}</DialogTitle>
<DialogContent>
<form.Field
name="content"
validators={{
onChange: ({ value }) => (!value ? t('Content required') : undefined),
onChangeAsyncDebounceMs: 250,
onChangeAsync: async ({ value }) => {
return !value && t('Content required');
},
}}
children={(field) => {
return (
<>
<TextField
variant="outlined"
multiline
minRows={3}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => {
if (e.target.value.length <= 250) {
setCharacterCount(e.target.value.length);
field.handleChange(e.target.value);
}
}}
size="small"
label={t('Comment')}
required
error={field.state.meta.isTouched && field.state.meta.errors.length > 0}
helperText={field.state.meta.isTouched ? field.state.meta.errors.join(',') : ''}
autoComplete="off"
margin="dense"
fullWidth
/>
<LinearProgress
variant="determinate"
value={(characterCount / 250) * 100}
color={characterCount === 250 ? 'error' : characterCount >= 200 ? 'warning' : 'primary'}
/>
</>
);
}}
/>
</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="postUpdate" />
</DialogContent>
)}
</Dialog>
);
};
export default PostEditDialog;