.
This commit is contained in:
@@ -17,6 +17,7 @@ import EditOrganizationPage from "./pages/Organization/EditOrganizationPage.jsx"
|
|||||||
import CompanyPage from "./pages/Company/CompanyPage.jsx";
|
import CompanyPage from "./pages/Company/CompanyPage.jsx";
|
||||||
import EditCompanyPage from "./pages/Company/EditCompanyPage.jsx";
|
import EditCompanyPage from "./pages/Company/EditCompanyPage.jsx";
|
||||||
import CalenderPostPage from "./pages/Calender/CalenderPostPage.jsx";
|
import CalenderPostPage from "./pages/Calender/CalenderPostPage.jsx";
|
||||||
|
import ProfilePage from "./pages/Auth/ProfilePage.jsx";
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@@ -76,6 +77,14 @@ export default function App() {
|
|||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="profile"
|
||||||
|
element={
|
||||||
|
<PrivateRoute allowedRoles={['ADMIN', 'REPORTER', 'SITE_OWNER']}>
|
||||||
|
<ProfilePage />
|
||||||
|
</PrivateRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="calenderPosts"
|
path="calenderPosts"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
|
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
|
||||||
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip,
|
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip, IconButton
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import PeopleIcon from '@mui/icons-material/People';
|
import PeopleIcon from '@mui/icons-material/People';
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
@@ -169,8 +169,10 @@ export default function Layout() {
|
|||||||
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
|
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
|
||||||
{getActiveLabel(location.pathname)}
|
{getActiveLabel(location.pathname)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Tooltip title="Account">
|
<Tooltip title="Profil">
|
||||||
|
<IconButton onClick={() => navigate('/profile')} sx={{ p: 0 }}>
|
||||||
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
|
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
|
||||||
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</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="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="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth />
|
||||||
<TextField label="Adresse" name="address" value={form.address} 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="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth />
|
||||||
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ export default function EditOrganizationPage() {
|
|||||||
<TextField label="Telefonnummer" name="phone" value={form.phone} onChange={handleChange} required fullWidth />
|
<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="Email Adresse" name="email" value={form.email} onChange={handleChange} required fullWidth />
|
||||||
<TextField label="Adresse" name="address" value={form.address} 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="Webseite" name="website" value={form.website} onChange={handleChange} required fullWidth />
|
||||||
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
<TextField label="Kontaktperson" name="contactPerson" value={form.contactPerson} onChange={handleChange} required fullWidth />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
|||||||
Reference in New Issue
Block a user