- 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.
263 lines
8.3 KiB
JavaScript
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 & 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 & 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
|