231 lines
7.2 KiB
TypeScript
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;
|