import { useState, useEffect } from 'react' import { Plus, Trash2, Pencil, Check, X, Shield, Key, Settings } from 'lucide-react' import { Link } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import { api } from '../utils/api' const COLORS = ['#1D9E75','#378ADD','#D85A30','#EF9F27','#7F77DD','#D4537E','#639922','#888780'] function Avatar({ profile, size=36 }) { const initials = profile.name.split(' ').map(n=>n[0]).join('').toUpperCase().slice(0,2) return (
{initials}
) } function Toggle({ value, onChange, label, disabled=false }) { return (
{label}
!disabled&&onChange(!value)} style={{width:40,height:22,borderRadius:11,background:value?'var(--accent)':'var(--border)', position:'relative',cursor:disabled?'not-allowed':'pointer',transition:'background 0.2s', opacity:disabled?0.5:1}}>
) } function NewProfileForm({ onSave, onCancel }) { const [form, setForm] = useState({ name:'', pin:'', email:'', avatar_color:COLORS[0], sex:'m', height:'', auth_type:'pin', session_days:30 }) const [error, setError] = useState(null) const set = (k,v) => setForm(f=>({...f,[k]:v})) const handleSave = async () => { if (!form.name.trim()) return setError('Name eingeben') if (form.pin.length < 4) return setError('PIN mind. 4 Zeichen') try { await onSave({...form, height:parseFloat(form.height)||178}) } catch(e) { setError(e.message) } } return (
Neues Profil
set('name',e.target.value)} autoFocus/>
Farbe
{COLORS.map(c=>(
set('avatar_color',c)} style={{width:24,height:24,borderRadius:'50%',background:c,cursor:'pointer', border:`3px solid ${form.avatar_color===c?'white':'transparent'}`, boxShadow:form.avatar_color===c?`0 0 0 2px ${c}`:'none'}}/> ))}
set('height',e.target.value)}/> cm
set('email',e.target.value)}/>
set('pin',e.target.value)}/>
{error &&
{error}
}
) } function EmailEditor({ profileId, currentEmail, onSaved }) { const [email, setEmail] = useState(currentEmail||'') const [msg, setMsg] = useState(null) const save = async () => { const token = localStorage.getItem('bodytrack_token')||'' await fetch(`/api/admin/profiles/${profileId}/email`, { method:'PUT', headers:{'Content-Type':'application/json','X-Auth-Token':token}, body: JSON.stringify({email}) }) setMsg('✓ Gespeichert'); onSaved() setTimeout(()=>setMsg(null),2000) } return (
setEmail(e.target.value)} style={{flex:1}}/> {msg && {msg}}
) } function ProfileCard({ profile, currentId, onRefresh }) { const [expanded, setExpanded] = useState(false) const [perms, setPerms] = useState({ ai_enabled: profile.ai_enabled ?? 1, ai_limit_day: profile.ai_limit_day || '', export_enabled: profile.export_enabled ?? 1, role: profile.role || 'user', }) const [saving, setSaving] = useState(false) const [newPin, setNewPin] = useState('') const [pinMsg, setPinMsg] = useState(null) const isSelf = profile.id === currentId const savePerms = async () => { setSaving(true) try { await api.adminSetPermissions(profile.id, { ai_enabled: perms.ai_enabled, ai_limit_day: perms.ai_limit_day ? parseInt(perms.ai_limit_day) : null, export_enabled: perms.export_enabled, role: perms.role, }) await onRefresh() } finally { setSaving(false) } } const savePin = async () => { if (newPin.length < 4) return setPinMsg('Mind. 4 Zeichen') try { await fetch(`/api/admin/profiles/${profile.id}/pin`, { method:'PUT', headers:{'Content-Type':'application/json', 'X-Auth-Token': localStorage.getItem('bodytrack_token')||''}, body: JSON.stringify({pin: newPin}) }) setNewPin(''); setPinMsg('✓ PIN geändert') setTimeout(()=>setPinMsg(null),2000) } catch(e) { setPinMsg('Fehler: '+e.message) } } const deleteProfile = async () => { if (!confirm(`Profil "${profile.name}" und ALLE Daten löschen?`)) return await api.adminDeleteProfile(profile.id) await onRefresh() } return (
{profile.name} {profile.role==='admin' && 👑 Admin} {isSelf && Du}
KI: {profile.ai_enabled?`✓${profile.ai_limit_day?` (max ${profile.ai_limit_day}/Tag)`:''}` : '✗'} · Export: {profile.export_enabled?'✓':'✗'} · Calls heute: {profile.ai_calls_today||0}
{!isSelf && ( )}
{expanded && (
{/* Permissions */}
BERECHTIGUNGEN
Rolle
{['user','admin'].map(r=>( ))}
setPerms(p=>({...p,ai_enabled:v?1:0}))} label="KI-Analysen erlaubt"/> {!!perms.ai_enabled && (
setPerms(p=>({...p,ai_limit_day:e.target.value}))}/> /Tag
)} setPerms(p=>({...p,export_enabled:v?1:0}))} label="Daten-Export erlaubt"/> {/* Email */}
E-MAIL (für Recovery & Zusammenfassungen)
{/* PIN change */}
PIN / PASSWORT ÄNDERN
setNewPin(e.target.value)} style={{flex:1}}/>
{pinMsg &&
{pinMsg}
}
)}
) } function EmailSettings() { const [status, setStatus] = useState(null) const [testTo, setTestTo] = useState('') const [testing, setTesting] = useState(false) const [testMsg, setTestMsg] = useState(null) useEffect(()=>{ const token = localStorage.getItem('bodytrack_token')||'' fetch('/api/admin/email/status',{headers:{'X-Auth-Token':token}}) .then(r=>r.json()).then(setStatus) },[]) const sendTest = async () => { if (!testTo) return setTesting(true); setTestMsg(null) try { const token = localStorage.getItem('bodytrack_token')||'' const r = await fetch('/api/admin/email/test',{ method:'POST',headers:{'Content-Type':'application/json','X-Auth-Token':token}, body:JSON.stringify({to:testTo}) }) if(!r.ok) throw new Error((await r.json()).detail) setTestMsg('✓ Test-E-Mail gesendet!') } catch(e){ setTestMsg('✗ Fehler: '+e.message) } finally{ setTesting(false) } } return (
📧 E-Mail Konfiguration
{!status ?
: ( <>
{status.configured ? <>✓ Konfiguriert: {status.smtp_user} via {status.smtp_host} : <>⚠️ Nicht konfiguriert. SMTP-Einstellungen in der .env Datei setzen.}
{status.configured && ( <>
App-URL: {status.app_url}
Für korrekte Links in E-Mails (z.B. Recovery-Links). In .env als APP_URL setzen.
setTestTo(e.target.value)} style={{flex:1}}/>
{testMsg &&
{testMsg}
} )} {!status.configured && (
Füge folgende Zeilen zur .env Datei hinzu:
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=deine@gmail.com
SMTP_PASS=dein_app_passwort
APP_URL=http://192.168.2.49:3002
)} )}
) } export default function AdminPanel() { const { session } = useAuth() const [profiles, setProfiles] = useState([]) const [creating, setCreating] = useState(false) const [loading, setLoading] = useState(true) const load = () => api.adminListProfiles().then(data=>{ setProfiles(data); setLoading(false) }) useEffect(()=>{ load() },[]) const handleCreate = async (form) => { await api.adminCreateProfile(form) setCreating(false) await load() } if (loading) return
return (

Benutzerverwaltung

👑 Du bist Admin. Hier kannst du Profile verwalten, Berechtigungen setzen und KI-Limits konfigurieren.
{creating && ( setCreating(false)}/> )} {profiles.map(p=>( ))} {!creating && ( )} {/* Email Settings */} {/* v9c Subscription Management */}
Subscription-System (v9c)
Verwalte Tiers, Features und Limits für das neue Freemium-System.
) }