231 lines
7.2 KiB
TypeScript

import { CloudUpload, Delete, Person } from '@mui/icons-material';
import {
Avatar,
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
FormControl,
Grid,
IconButton,
InputLabel,
MenuItem,
Select,
TextField,
Typography,
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 { User, UserImageUpdate } from '../../../types/User';
import ErrorComponent from '../../Error/ErrorComponent';
interface Props {
user: User;
open: boolean;
onClose: () => void;
}
const UserImageDialog: FC<Props> = ({ user, open, onClose }) => {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const [error, setError] = useState<any>();
const updateMutation = useMutation({
mutationFn: ({ data, id }: { data: UserImageUpdate; id?: number }) => {
return Api.updateUserImage(data, id);
},
});
const form = useForm<UserImageUpdate>({
onSubmit: async ({ value }) => {
try {
updateMutation.mutate(
{ data: value, id: Api.getAuthenticatedUser()?.id === user.id ? undefined : user.id },
{
onSuccess: () => {
handleClose();
const queryKey = Api.getAuthenticatedUser()?.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 theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.only('xs'), { noSsr: true });
const queryClient = useQueryClient();
const formState = form.useStore((state) => ({ image: state.values.image, predefined: state.values.predefined }));
const handleClose = () => {
form.reset();
setError(undefined);
onClose();
};
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('Edit image')}</DialogTitle>
<DialogContent sx={{ gap: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center' }}>
<Avatar
alt={user.username}
src={
formState.image
? URL.createObjectURL(formState.image)
: formState.predefined
? `profilbilder/default/${formState.predefined}.svg`
: `${user.image}`
}
sx={{ width: '100px', height: '100px' }}
>
<Person sx={{ width: '60px', height: '60px' }} />
</Avatar>
</Grid>
<Grid item xs={12}>
<Box sx={{ display: 'flex', gap: 2 }}>
<Button
component="label"
role={undefined}
variant="contained"
tabIndex={-1}
startIcon={<CloudUpload />}
fullWidth
>
<form.Field
name="image"
children={(field) => (
<>
<Box sx={{ textOverflow: 'ellipsis', textWrap: 'nowrap', overflow: 'hidden' }}>
{!field.state.value ? t('Upload image') : t('Upload named', { name: field.state.value?.name })}
</Box>
<TextField
name={field.name}
onBlur={field.handleBlur}
value={!field.state.value ? '' : undefined}
//eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange={(e) => field.handleChange((e.target as any).files[0])}
size="small"
type="file"
required
sx={{ display: 'none' }}
autoComplete="off"
/>
</>
)}
/>
</Button>
<IconButton color="error" onClick={() => form.setFieldValue('image', undefined)}>
<Delete />
</IconButton>
</Box>
</Grid>
<Grid item xs={12}>
<Divider variant="middle">
<Typography sx={{ opacity: 0.36 }}>{t('or')}</Typography>
</Divider>
</Grid>
<Grid item xs={12}>
<form.Field
name="predefined"
children={(field) => (
<FormControl fullWidth>
<InputLabel size="small">{t('Predefined')}</InputLabel>
<Select
name={field.name}
value={field.state.value ?? ''}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
size="small"
label={t('Predefined')}
autoComplete="off"
fullWidth
//renderValue={(selected) => selected ?? 'Keine Auswahl'}
>
<MenuItem value="" selected>
Keine Auswahl
</MenuItem>
{[...Array(10).keys()].map((i) => (
<MenuItem key={`avatar-${i + 1}`} value={`avatar-${i + 1}`}>
{t('Avatar', { name: i + 1 })}
</MenuItem>
))}
</Select>
</FormControl>
)}
/>
</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 UserImageDialog;