Post delete, Profile edit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ERRORS } from './Errors';
|
||||
|
||||
interface Props {
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error: any;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
const ErrorComponent: FC<Props> = ({ error, context }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!error) return null;
|
||||
|
||||
if (error.code) {
|
||||
switch (error.code) {
|
||||
case ERRORS.NOT_FOUND:
|
||||
return <Typography color="error.main">{t(error.code, { context: `${error.entity}:${context}` })}</Typography>;
|
||||
case ERRORS.UNAUTHORIZED:
|
||||
return <Typography color="error.main">{t(error.code, { context })}</Typography>;
|
||||
case ERRORS.FAILEDUPDATE:
|
||||
return error.fields.map((field: string) => (
|
||||
<Typography key={`error_${field}`} color="error.main">
|
||||
{t(error.code, { context, name: t(field) })}
|
||||
</Typography>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return <Typography color="error.main">{t(error?.message ?? 'Unknown', { context })}</Typography>;
|
||||
};
|
||||
|
||||
export default ErrorComponent;
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum ERRORS {
|
||||
NOT_FOUND = 'NotFound',
|
||||
UNAUTHORIZED = 'Unauthorized',
|
||||
FAILEDUPDATE = 'FailedUpdate',
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||
import { Box, Button, TextField } from '@mui/material';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useRouter } from '@tanstack/react-router';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../../api/Api';
|
||||
import handleError from '../../../utils/errors';
|
||||
import ErrorComponent from '../../Error/ErrorComponent';
|
||||
|
||||
interface Props {
|
||||
handleClose: () => void;
|
||||
@@ -119,7 +119,7 @@ const LoginForm: FC<Props> = ({ handleClose }) => {
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
{error && <Typography color="error.main">{handleError(error, t, 'login')}</Typography>}
|
||||
{error && <ErrorComponent error={error} context="login" />}
|
||||
</Box>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
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 { User, UserUpdate } from '../../../types/User';
|
||||
import ErrorComponent from '../../Error/ErrorComponent';
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const UserEditDialog: 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: UserUpdate; id?: number }) => {
|
||||
return Api.updateUser(data, id);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<UserUpdate>({
|
||||
defaultValues: {
|
||||
username: user.username,
|
||||
},
|
||||
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 handleClose = () => {
|
||||
form.reset();
|
||||
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 data')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<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(',') : ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</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 UserEditDialog;
|
||||
@@ -16,27 +16,29 @@ import {
|
||||
Snackbar,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Api from '../../api/Api';
|
||||
import { PostAuth, PostNonAuth } from '../../types/Post';
|
||||
import convertDate from '../../utils/date';
|
||||
import handleError from '../../utils/errors';
|
||||
import ErrorComponent from '../Error/ErrorComponent';
|
||||
|
||||
interface Props {
|
||||
post: PostNonAuth | PostAuth;
|
||||
}
|
||||
|
||||
const Post: FC<Props> = ({ post }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: number) => {
|
||||
return Api.deletePost(id);
|
||||
},
|
||||
});
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -104,7 +106,13 @@ const Post: FC<Props> = ({ post }) => {
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={() => {
|
||||
deleteMutation.mutate(post.id);
|
||||
deleteMutation.mutate(post.id, {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['posts'],
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
@@ -117,9 +125,10 @@ const Post: FC<Props> = ({ post }) => {
|
||||
</CardActions>
|
||||
<Snackbar open={deleteMutation.isError} autoHideDuration={2000} onClose={() => deleteMutation.reset()}>
|
||||
<Alert severity="error" variant="filled" sx={{ width: '100%' }}>
|
||||
{deleteMutation.isError && handleError(deleteMutation.error, t, 'delete')}
|
||||
{deleteMutation.isError && <ErrorComponent error={deleteMutation.error} context="delete" />}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Snackbar open={deleteMutation.isPending} message={t('Deleting')} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Person } from '@mui/icons-material';
|
||||
import { Avatar, Box, Button, Card, CardActions, CardContent, Grid, Typography } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { User } from '../../types/User';
|
||||
import convertDate from '../../utils/date';
|
||||
import UserEditDialog from '../Forms/UserEdit/UserEditDialog';
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
@@ -11,6 +12,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const Profile: FC<Props> = ({ user, canEdit }) => {
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -36,7 +39,14 @@ const Profile: FC<Props> = ({ user, canEdit }) => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>{canEdit && <Button size="small">{t('Edit')}</Button>}</CardActions>
|
||||
<CardActions>
|
||||
{canEdit && (
|
||||
<Button size="small" onClick={() => setEditOpen(true)}>
|
||||
{t('Edit')}
|
||||
</Button>
|
||||
)}
|
||||
</CardActions>
|
||||
<UserEditDialog user={user} open={editOpen} onClose={() => setEditOpen(false)} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user