Email Edit

This commit is contained in:
Kilian Hofmann 2024-07-27 03:39:52 +02:00
parent 12f7176467
commit d71eaf2ef2
15 changed files with 229 additions and 175 deletions

1
exam/dist/assets/index-BhgwoArk.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

125
exam/dist/assets/mui-CsmJ6if2.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,10 +5,10 @@
<link rel="icon" type="image/svg+xml" href="/phpCourse/exam/dist/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-DNzu9OIf.js"></script>
<script type="module" crossorigin src="/phpCourse/exam/dist/assets/index-BhgwoArk.js"></script>
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/react-DXd9vB-a.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-BZej3Yg3.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-DeUNQvBN.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/mui-CsmJ6if2.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/tanstack-DXVkKUs1.js">
<link rel="modulepreload" crossorigin href="/phpCourse/exam/dist/assets/i18n-DJgSTqOl.js">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/mui-CKDNpdid.css">
<link rel="stylesheet" crossorigin href="/phpCourse/exam/dist/assets/index-D83Ey19k.css">

View File

@ -9,7 +9,8 @@
"Unauthorized_userUpdate": "Keine Berechtigung",
"NotFound_user:userUpdate": "Benutzer nicht gefunden",
"FailedUpdate_userUpdate": "{{name}} konnte nicht aktualisiert werden",
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
"username": "Benutzername",
"email": "E-Mail",
@ -47,5 +48,7 @@
"Confirm post delete body": "Möchtest du diesen Post von {{name}} wirklich Löschen?",
"Deleting": "Löscht...",
"Edit data": "Daten ändern"
"Edit data": "Daten ändern",
"Recent posts": "Letzten Posts"
}

View File

@ -9,7 +9,8 @@
"Unauthorized_userUpdate": "Unauthorized",
"NotFound_user:userUpdate": "User not found",
"FailedUpdate_userUpdate": "Failed to update {{name}}",
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
"username": "username",
"email": "email",
@ -48,5 +49,7 @@
"Deleting": "Deleting...",
"Edit data": "Edit date"
"Edit data": "Edit date",
"Recent posts": "Recent posts"
}

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,8 @@
"Unauthorized_userUpdate": "Keine Berechtigung",
"NotFound_user:userUpdate": "Benutzer nicht gefunden",
"FailedUpdate_userUpdate": "{{name}} konnte nicht aktualisiert werden",
"FailedUpdate_Duplicate:username:userUpdate": "Ein Benutzer mit diesem Benutzernamen existiert schon",
"FailedUpdate_Duplicate:email:userUpdate": "Ein Benutzer mit dieser E-Mail existiert schon",
"username": "Benutzername",
"email": "E-Mail",
@ -47,5 +48,7 @@
"Confirm post delete body": "Möchtest du diesen Post von {{name}} wirklich Löschen?",
"Deleting": "Löscht...",
"Edit data": "Daten ändern"
"Edit data": "Daten ändern",
"Recent posts": "Letzten Posts"
}

View File

@ -9,7 +9,8 @@
"Unauthorized_userUpdate": "Unauthorized",
"NotFound_user:userUpdate": "User not found",
"FailedUpdate_userUpdate": "Failed to update {{name}}",
"FailedUpdate_Duplicate:userUpdate": "A user with this username already exists",
"FailedUpdate_Duplicate:email:userUpdate": "A user with this email already exists",
"username": "username",
"email": "email",
@ -48,5 +49,7 @@
"Deleting": "Deleting...",
"Edit data": "Edit date"
"Edit data": "Edit date",
"Recent posts": "Recent posts"
}

View File

@ -22,9 +22,9 @@ const ErrorComponent: FC<Props> = ({ error, context, color = 'error.main' }) =>
case ERRORS.UNAUTHORIZED:
return <Typography color={color}>{t(error.code, { context })}</Typography>;
case ERRORS.FAILEDUPDATE:
return error.fields.map((field: string) => (
return error.fields.map((field: string, index: number) => (
<Typography key={`error_${field}`} color={color}>
{t(error.code, { context, name: t(field) })}
{t(error.code, { context: `${error.reasons[index]}:${field}:${context}` })}
</Typography>
));
}

View File

@ -36,6 +36,7 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
const form = useForm<UserUpdate>({
defaultValues: {
username: user.username,
email: user.email,
},
onSubmit: async ({ value }) => {
try {
@ -121,6 +122,36 @@ const UserEditDialog: FC<Props> = ({ user, open, onClose }) => {
);
}}
/>
<form.Field
name="email"
validators={{
onChange: ({ value }) => (!value ? t('Email required') : undefined),
onChangeAsyncDebounceMs: 250,
onChangeAsync: async ({ value }) => {
return !value && t('Email 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('Email')}
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

View File

@ -1,5 +1,5 @@
import { Menu, MenuItem } from '@mui/material';
import { useNavigate, useRouter } from '@tanstack/react-router';
import { useMatch, useNavigate, useRouter } from '@tanstack/react-router';
import { t } from 'i18next';
import { FC } from 'react';
import Api from '../../../api/Api';
@ -14,6 +14,7 @@ interface Props {
const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
const navigate = useNavigate();
const router = useRouter();
const match = useMatch({ from: '/profile/', strict: true, shouldThrow: false });
const user = Api.getAuthenticatedUser();
@ -40,6 +41,7 @@ const UserMenu: FC<Props> = ({ anchorEl, handleClose }) => {
{user ? (
[
<MenuItem
selected={!!match}
key="profile"
onClick={() => {
navigate({ to: ROUTES.PROFILE });

View File

@ -1,5 +1,5 @@
import { Person } from '@mui/icons-material';
import { Avatar, Box, Button, Card, CardActions, CardContent, Grid, Typography } from '@mui/material';
import { Avatar, Box, Button, Card, CardActions, CardContent, Divider, Grid, Typography } from '@mui/material';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { User } from '../../types/User';
@ -17,37 +17,46 @@ const Profile: FC<Props> = ({ user, canEdit }) => {
const { t } = useTranslation();
return (
<Card>
<CardContent>
<Grid container spacing={2}>
<Grid item sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center' }}>
<Avatar alt={user.username} src={`storage/${user.image}`} sx={{ width: '100px', height: '100px' }}>
<Person sx={{ width: '60px', height: '60px' }} />
</Avatar>
</Grid>
<Grid item sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '120px 1fr', columnGap: 1 }}>
<Typography fontWeight="bold">{t('Username')}:</Typography>
<Typography>{user.username}</Typography>
<Typography fontWeight="bold">{t('Email')}:</Typography>
<Typography>{user.email}</Typography>
<Typography fontWeight="bold">{t('Member since')}:</Typography>
<Typography>{convertDate(user.memberSince)}</Typography>
<Typography fontWeight="bold">{t('Post count')}:</Typography>
<Typography>{user.postCount}</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
<CardActions>
{canEdit && (
<Button size="small" onClick={() => setEditOpen(true)}>
{t('Edit')}
</Button>
)}
</CardActions>
<UserEditDialog user={user} open={editOpen} onClose={() => setEditOpen(false)} />
</Card>
<Grid container sx={{ justifyContent: 'center' }} spacing={2}>
<Grid item>
<Card>
<CardContent>
<Grid container spacing={2}>
<Grid item sx={{ display: 'flex', flexGrow: 1, justifyContent: 'center' }}>
<Avatar alt={user.username} src={`storage/${user.image}`} sx={{ width: '100px', height: '100px' }}>
<Person sx={{ width: '60px', height: '60px' }} />
</Avatar>
</Grid>
<Grid item sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '120px 1fr', columnGap: 1 }}>
<Typography fontWeight="bold">{t('Username')}:</Typography>
<Typography>{user.username}</Typography>
<Typography fontWeight="bold">{t('Email')}:</Typography>
<Typography>{user.email}</Typography>
<Typography fontWeight="bold">{t('Member since')}:</Typography>
<Typography>{convertDate(user.memberSince)}</Typography>
<Typography fontWeight="bold">{t('Post count')}:</Typography>
<Typography>{user.postCount}</Typography>
</Box>
</Grid>
</Grid>
</CardContent>
<CardActions>
{canEdit && (
<Button size="small" onClick={() => setEditOpen(true)}>
{t('Edit')}
</Button>
)}
</CardActions>
<UserEditDialog user={user} open={editOpen} onClose={() => setEditOpen(false)} />
</Card>
</Grid>
<Grid item xs={12}>
<Divider variant="middle">
<Typography sx={{ opacity: 0.36 }}>{t('Recent posts')}</Typography>
</Divider>
</Grid>
</Grid>
);
};