.
This commit is contained in:
@@ -17,6 +17,7 @@ import EditOrganizationPage from "./pages/Organization/EditOrganizationPage.jsx"
|
||||
import CompanyPage from "./pages/Company/CompanyPage.jsx";
|
||||
import EditCompanyPage from "./pages/Company/EditCompanyPage.jsx";
|
||||
import CalenderPostPage from "./pages/Calender/CalenderPostPage.jsx";
|
||||
import ProfilePage from "./pages/Auth/ProfilePage.jsx";
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
@@ -76,6 +77,14 @@ export default function App() {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="profile"
|
||||
element={
|
||||
<PrivateRoute allowedRoles={['ADMIN', 'REPORTER', 'SITE_OWNER']}>
|
||||
<ProfilePage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="calenderPosts"
|
||||
element={
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
||||
import {
|
||||
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
|
||||
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip,
|
||||
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip, IconButton
|
||||
} from '@mui/material';
|
||||
import PeopleIcon from '@mui/icons-material/People';
|
||||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
@@ -169,8 +169,10 @@ export default function Layout() {
|
||||
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
|
||||
{getActiveLabel(location.pathname)}
|
||||
</Typography>
|
||||
<Tooltip title="Account">
|
||||
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
|
||||
<Tooltip title="Profil">
|
||||
<IconButton onClick={() => navigate('/profile')} sx={{ p: 0 }}>
|
||||
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
156
src/pages/Auth/ProfilePage.jsx
Normal file
156
src/pages/Auth/ProfilePage.jsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box, Card, CardContent, TextField, Button, Typography,
|
||||
Alert, CircularProgress, Divider,
|
||||
} from '@mui/material';
|
||||
import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined';
|
||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||
import axiosInstance from '../../api/axiosInstance';
|
||||
|
||||
export default function ProfilePage() {
|
||||
|
||||
const [nickname, setNickname] = useState('');
|
||||
const [nicknameLoading, setNicknameLoading] = useState(false);
|
||||
const [nicknameError, setNicknameError] = useState('');
|
||||
const [nicknameSuccess, setNicknameSuccess] = useState('');
|
||||
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [passwordLoading, setPasswordLoading] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [passwordSuccess, setPasswordSuccess] = useState('');
|
||||
|
||||
const handleNicknameSave = async (e) => {
|
||||
e.preventDefault();
|
||||
setNicknameError('');
|
||||
setNicknameSuccess('');
|
||||
if (!nickname.trim()) {
|
||||
setNicknameError('Bitte einen Nickname eingeben.');
|
||||
return;
|
||||
}
|
||||
setNicknameLoading(true);
|
||||
try {
|
||||
await axiosInstance.patch('/users/nickname', { nickname });
|
||||
setNicknameSuccess('Nickname erfolgreich gespeichert.');
|
||||
} catch (err) {
|
||||
setNicknameError(err.response?.data?.error ?? 'Fehler beim Speichern.');
|
||||
} finally {
|
||||
setNicknameLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordSave = async (e) => {
|
||||
e.preventDefault();
|
||||
setPasswordError('');
|
||||
setPasswordSuccess('');
|
||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||
setPasswordError('Bitte alle Felder ausfüllen.');
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
setPasswordError('Neue Passwörter stimmen nicht überein.');
|
||||
return;
|
||||
}
|
||||
if (newPassword.length < 8) {
|
||||
setPasswordError('Neues Passwort muss mindestens 8 Zeichen haben.');
|
||||
return;
|
||||
}
|
||||
setPasswordLoading(true);
|
||||
try {
|
||||
await axiosInstance.patch('/users/password', { currentPassword, newPassword });
|
||||
setPasswordSuccess('Passwort erfolgreich geändert.');
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
setConfirmPassword('');
|
||||
} catch (err) {
|
||||
setPasswordError(err.response?.data?.error ?? 'Fehler beim Ändern des Passworts.');
|
||||
} finally {
|
||||
setPasswordLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, maxWidth: 480 }}>
|
||||
|
||||
{/* ── Karte: Nickname ── */}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<AccountCircleOutlinedIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight={600}>Nickname ändern</Typography>
|
||||
</Box>
|
||||
|
||||
{nicknameError && <Alert severity="error" sx={{ mb: 2 }}>{nicknameError}</Alert>}
|
||||
{nicknameSuccess && <Alert severity="success" sx={{ mb: 2 }}>{nicknameSuccess}</Alert>}
|
||||
|
||||
<Box component="form" onSubmit={handleNicknameSave} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
label="Neuer Nickname"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
disabled={nicknameLoading}
|
||||
>
|
||||
{nicknameLoading ? <CircularProgress size={22} color="inherit" /> : 'Speichern'}
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* ── Karte: Passwort ── */}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<LockOutlinedIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight={600}>Passwort ändern</Typography>
|
||||
</Box>
|
||||
|
||||
{passwordError && <Alert severity="error" sx={{ mb: 2 }}>{passwordError}</Alert>}
|
||||
{passwordSuccess && <Alert severity="success" sx={{ mb: 2 }}>{passwordSuccess}</Alert>}
|
||||
|
||||
<Box component="form" onSubmit={handlePasswordSave} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
label="Aktuelles Passwort"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
<Divider />
|
||||
<TextField
|
||||
label="Neues Passwort"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
fullWidth
|
||||
helperText="Mindestens 8 Zeichen"
|
||||
/>
|
||||
<TextField
|
||||
label="Neues Passwort bestätigen"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
fullWidth
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
disabled={passwordLoading}
|
||||
>
|
||||
{passwordLoading ? <CircularProgress size={22} color="inherit" /> : 'Passwort ändern'}
|
||||
</Button>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -183,6 +183,9 @@ export default function EditCompanyPage() {
|
||||
<TextField label="Telefonnummer" name="phone" value={form.phone} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Adresse" name="address" value={form.address} onChange={handleChange} required fullWidth />
|
||||
<Alert severity="info" sx={{ maxWidth: 560, mb: 3 }}>
|
||||
Website muss mit "https://" anfangen
|
||||
</Alert>
|
||||
<TextField label="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
||||
<FormControlLabel
|
||||
|
||||
@@ -185,6 +185,9 @@ export default function EditOrganizationPage() {
|
||||
<TextField label="Telefonnummer" name="phone" value={form.phone} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Adresse" name="address" value={form.address} onChange={handleChange} required fullWidth />
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
Website muss mit "https://" anfangen
|
||||
</Alert>
|
||||
<TextField label="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth />
|
||||
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
||||
<FormControlLabel
|
||||
|
||||
Reference in New Issue
Block a user