shinkan-jinkendo/frontend/src/pages/AccountSettingsPage.jsx
Lars a748f4607d
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / playwright-tests (push) Failing after 2m3s
refactor: improve email verification handling in components
- Introduced a utility function to standardize email verification checks across EmailVerificationBanner and AccountSettingsPage.
- Updated conditional logic to enhance clarity and maintainability regarding email verification status.
- Ensured consistent treatment of various representations of email verification status in user profiles.
2026-04-29 11:48:04 +02:00

263 lines
8.3 KiB
JavaScript

import { useState, useEffect } from 'react'
import { useAuth } from '../context/AuthContext'
import api from '../utils/api'
/**
* Persönliche Einstellungen (Anzeige/Name, Kontostatus, Passwort).
*/
function AccountSettingsPage() {
const { user, checkAuth } = useAuth()
const [name, setName] = useState('')
const [savingProfile, setSavingProfile] = useState(false)
const [newPw1, setNewPw1] = useState('')
const [newPw2, setNewPw2] = useState('')
const [savingPw, setSavingPw] = useState(false)
const [resendingVerify, setResendingVerify] = useState(false)
const [message, setMessage] = useState('')
const [error, setError] = useState('')
useEffect(() => {
setName(typeof user?.name === 'string' ? user.name : '')
}, [user])
/** API: boolean true / Legacy: fehlt oder false → als „nicht verifiziert“ behandeln */
const emailExplicitlyVerified =
user?.email_verified === true ||
user?.email_verified === 't' ||
user?.email_verified === 1 ||
user?.email_verified === 'true'
const showOk = (text) => {
setMessage(text)
setError('')
setTimeout(() => setMessage(''), 5000)
}
const showErr = (text) => {
setError(text)
setMessage('')
}
const handleSaveName = async (e) => {
e.preventDefault()
if (!user?.id) return
const trimmed = (name || '').trim()
if (trimmed.length < 2) {
showErr('Name sollte mindestens 2 Zeichen haben.')
return
}
setSavingProfile(true)
try {
await api.updateProfile(user.id, { name: trimmed })
await checkAuth()
showOk('Profilname gespeichert.')
} catch (err) {
showErr(err.message || 'Speichern fehlgeschlagen.')
} finally {
setSavingProfile(false)
}
}
const handleResendVerification = async () => {
const em = user?.email
if (!em) return
setResendingVerify(true)
try {
await api.resendVerification(em)
showOk('Falls diese Adresse einen unbestätigten Account hat: E-Mail ist unterwegs — Postfach prüfen.')
} catch (err) {
showErr(err.message || 'Konnte keine E-Mail senden.')
} finally {
setResendingVerify(false)
}
}
const handleChangePassword = async (e) => {
e.preventDefault()
if (newPw1.length < 4) {
showErr('Neues Passwort: mindestens 4 Zeichen.')
return
}
if (newPw1 !== newPw2) {
showErr('Die Passwörter stimmen nicht überein.')
return
}
setSavingPw(true)
try {
await api.changePassword(newPw1)
setNewPw1('')
setNewPw2('')
showOk('Passwort aktualisiert.')
} catch (err) {
showErr(err.message || 'Passwort konnte nicht geändert werden.')
} finally {
setSavingPw(false)
}
}
return (
<div className="page-padding" style={{ padding: '1rem', maxWidth: '640px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '0.35rem', fontSize: '1.5rem' }}>Einstellungen</h1>
<p style={{ color: 'var(--text2)', marginBottom: '1.25rem', fontSize: '0.95rem' }}>
Konto &amp; Sicherheit
</p>
{message && (
<div
style={{
padding: '0.75rem',
borderRadius: 'var(--radius, 12px)',
background: 'var(--accent-soft, rgba(29,158,117,0.15))',
color: 'var(--text1)',
marginBottom: '1rem',
border: '1px solid var(--accent)',
}}
>
{message}
</div>
)}
{error && (
<div
style={{
padding: '0.75rem',
borderRadius: 'var(--radius, 12px)',
background: 'rgba(216,90,48,0.15)',
color: 'var(--text1)',
marginBottom: '1rem',
border: '1px solid var(--danger)',
}}
>
{error}
</div>
)}
<div className="card" style={{ marginBottom: '1rem' }}>
<h2 style={{ margin: '0 0 0.75rem', fontSize: '1.1rem' }}>Profil</h2>
<div style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '1rem', lineHeight: 1.5 }}>
<strong style={{ color: 'var(--text1)' }}>E-Mail</strong>
<br />
{user?.email || '—'}{' '}
{emailExplicitlyVerified ? (
<span
style={{
marginLeft: '0.5rem',
fontSize: '0.75rem',
padding: '0.15rem 0.5rem',
borderRadius: '6px',
background: 'var(--accent-soft, rgba(29,158,117,0.2))',
color: 'var(--accent-dark, #085041)',
}}
>
bestätigt
</span>
) : (
<span
style={{
marginLeft: '0.5rem',
fontSize: '0.75rem',
padding: '0.15rem 0.5rem',
borderRadius: '6px',
background: 'var(--surface2)',
color: 'var(--text2)',
}}
>
noch nicht bestätigt
</span>
)}
{!emailExplicitlyVerified && user?.email ? (
<div style={{ marginTop: '0.75rem' }}>
<button
type="button"
className="btn btn-secondary"
disabled={resendingVerify}
onClick={handleResendVerification}
>
{resendingVerify ? 'Sende…' : 'Bestätigung erneut senden'}
</button>
</div>
) : null}
</div>
<form onSubmit={handleSaveName}>
<label className="form-label" htmlFor="settings-name">
Anzeigename
</label>
<input
id="settings-name"
type="text"
className="form-input"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Dein Name in der App"
autoComplete="nickname"
/>
<button
type="submit"
className="btn btn-primary"
disabled={savingProfile}
style={{ marginTop: '0.85rem' }}
>
{savingProfile ? 'Speichern…' : 'Name speichern'}
</button>
</form>
</div>
<div className="card" style={{ marginBottom: '1rem' }}>
<h2 style={{ margin: '0 0 0.75rem', fontSize: '1.1rem' }}>Rollen &amp; Tarif</h2>
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: '0.5rem 1rem', fontSize: '0.925rem' }}>
<strong style={{ color: 'var(--text2)' }}>Rolle</strong>
<span>{user?.role === 'admin' ? 'Administrator' : user?.role || 'trainer'}</span>
<strong style={{ color: 'var(--text2)' }}>Tier</strong>
<span style={{ textTransform: 'uppercase', letterSpacing: '0.03em', fontWeight: 600 }}>
{user?.tier || 'free'}
</span>
</div>
</div>
<div className="card">
<h2 style={{ margin: '0 0 0.75rem', fontSize: '1.1rem' }}>Passwort ändern</h2>
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '1rem', lineHeight: 1.5 }}>
Wähle ein neues Passwort (mindestens 4 Zeichen, wie beim Login gewohnt empfehlen wir längere Passwörter).
</p>
<form onSubmit={handleChangePassword}>
<div className="form-row" style={{ marginBottom: '0.75rem' }}>
<label className="form-label" htmlFor="settings-pw1">
Neues Passwort
</label>
<input
id="settings-pw1"
type="password"
className="form-input"
value={newPw1}
onChange={(e) => setNewPw1(e.target.value)}
autoComplete="new-password"
minLength={4}
/>
</div>
<div className="form-row" style={{ marginBottom: '0.75rem' }}>
<label className="form-label" htmlFor="settings-pw2">
Passwort wiederholen
</label>
<input
id="settings-pw2"
type="password"
className="form-input"
value={newPw2}
onChange={(e) => setNewPw2(e.target.value)}
autoComplete="new-password"
/>
</div>
<button type="submit" className="btn btn-secondary" disabled={savingPw}>
{savingPw ? 'Wird gespeichert…' : 'Passwort speichern'}
</button>
</form>
</div>
</div>
)
}
export default AccountSettingsPage