190 lines
8.1 KiB
TypeScript
190 lines
8.1 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
||
|
|
import { useAuth } from '../contexts/AuthContext';
|
||
|
|
import PluginManager from './PluginManager';
|
||
|
|
|
||
|
|
export interface SettingsModalProps {
|
||
|
|
open: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
type SettingsTab = 'personal' | 'password' | 'language' | 'theme' | 'users' | 'plugins' | 'ai';
|
||
|
|
|
||
|
|
const SettingsModal: React.FC<SettingsModalProps> = ({ open, onClose }) => {
|
||
|
|
const { user } = useAuth();
|
||
|
|
const [activeTab, setActiveTab] = useState<SettingsTab>('personal');
|
||
|
|
const [displayName, setDisplayName] = useState(user?.name || '');
|
||
|
|
const [oldPassword, setOldPassword] = useState('');
|
||
|
|
const [newPassword, setNewPassword] = useState('');
|
||
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||
|
|
const [language, setLanguage] = useState('de');
|
||
|
|
const [themeMode, setThemeMode] = useState<'light' | 'dark'>('dark');
|
||
|
|
const [accentColor, setAccentColor] = useState('#2563eb');
|
||
|
|
const [aiProvider, setAiProvider] = useState('openrouter');
|
||
|
|
const [aiModel, setAiModel] = useState('gpt-4o');
|
||
|
|
const [aiApiKey, setAiApiKey] = useState('');
|
||
|
|
const [pwMessage, setPwMessage] = useState('');
|
||
|
|
|
||
|
|
if (!open) return null;
|
||
|
|
|
||
|
|
const isAdmin = user?.role === 'admin';
|
||
|
|
const initials = (user?.name || '?').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
||
|
|
|
||
|
|
const tabs: Array<{ id: SettingsTab; label: string; adminOnly?: boolean }> = [
|
||
|
|
{ id: 'personal', label: 'Persönlich' },
|
||
|
|
{ id: 'password', label: 'Passwort' },
|
||
|
|
{ id: 'language', label: 'Sprache' },
|
||
|
|
{ id: 'theme', label: 'Theme' },
|
||
|
|
{ id: 'users', label: 'Benutzer', adminOnly: true },
|
||
|
|
{ id: 'plugins', label: 'Plugins', adminOnly: true },
|
||
|
|
{ id: 'ai', label: 'KI' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const visibleTabs = tabs.filter(t => !t.adminOnly || isAdmin);
|
||
|
|
|
||
|
|
const handlePasswordChange = () => {
|
||
|
|
if (!oldPassword || !newPassword || !confirmPassword) {
|
||
|
|
setPwMessage('Bitte alle Felder ausfüllen.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (newPassword !== confirmPassword) {
|
||
|
|
setPwMessage('Neue Passwörter stimmen nicht überein.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (newPassword.length < 6) {
|
||
|
|
setPwMessage('Passwort muss mindestens 6 Zeichen lang sein.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setPwMessage('Passwort erfolgreich geändert.');
|
||
|
|
setOldPassword('');
|
||
|
|
setNewPassword('');
|
||
|
|
setConfirmPassword('');
|
||
|
|
};
|
||
|
|
|
||
|
|
const renderTabContent = () => {
|
||
|
|
switch (activeTab) {
|
||
|
|
case 'personal':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<label className="settings-label">Anzeigename</label>
|
||
|
|
<input className="settings-input" type="text" value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
|
||
|
|
<label className="settings-label">E-Mail</label>
|
||
|
|
<input className="settings-input" type="email" value={user?.email || ''} readOnly style={{ opacity: 0.6 }} />
|
||
|
|
<label className="settings-label">Avatar</label>
|
||
|
|
<div className="settings-avatar-preview">{initials}</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'password':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<label className="settings-label">Altes Passwort</label>
|
||
|
|
<input className="settings-input" type="password" value={oldPassword} onChange={(e) => setOldPassword(e.target.value)} />
|
||
|
|
<label className="settings-label">Neues Passwort</label>
|
||
|
|
<input className="settings-input" type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} />
|
||
|
|
<label className="settings-label">Passwort bestätigen</label>
|
||
|
|
<input className="settings-input" type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} />
|
||
|
|
{pwMessage && <div className="settings-message">{pwMessage}</div>}
|
||
|
|
<button className="settings-btn" onClick={handlePasswordChange}>Passwort ändern</button>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'language':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<label className="settings-label">Sprache</label>
|
||
|
|
<select className="settings-input" value={language} onChange={(e) => setLanguage(e.target.value)}>
|
||
|
|
<option value="de">Deutsch</option>
|
||
|
|
<option value="en">English</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'theme':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<label className="settings-label">Erscheinungsbild</label>
|
||
|
|
<div className="settings-toggle-group">
|
||
|
|
<button className={`settings-toggle-btn${themeMode === 'light' ? ' active' : ''}`} onClick={() => setThemeMode('light')}>Hell</button>
|
||
|
|
<button className={`settings-toggle-btn${themeMode === 'dark' ? ' active' : ''}`} onClick={() => setThemeMode('dark')}>Dunkel</button>
|
||
|
|
</div>
|
||
|
|
<label className="settings-label">Akzentfarbe</label>
|
||
|
|
<div className="settings-color-picker">
|
||
|
|
<input type="color" value={accentColor} onChange={(e) => setAccentColor(e.target.value)} />
|
||
|
|
<span className="settings-color-value">{accentColor}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'users':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<div className="settings-users-header">
|
||
|
|
<strong>Benutzerverwaltung</strong>
|
||
|
|
<button className="settings-btn">Benutzer hinzufügen</button>
|
||
|
|
</div>
|
||
|
|
<div className="settings-user-list">
|
||
|
|
<div className="settings-user-row">
|
||
|
|
<div className="settings-user-avatar">LM</div>
|
||
|
|
<div className="settings-user-info">
|
||
|
|
<div className="settings-user-name">Leopold M.</div>
|
||
|
|
<div className="settings-user-email">admin@example.com</div>
|
||
|
|
</div>
|
||
|
|
<span className="settings-user-role">Admin</span>
|
||
|
|
<button className="settings-btn-danger">Löschen</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'plugins':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<PluginManager />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
case 'ai':
|
||
|
|
return (
|
||
|
|
<div className="settings-tab-content">
|
||
|
|
<label className="settings-label">KI-Anbieter</label>
|
||
|
|
<select className="settings-input" value={aiProvider} onChange={(e) => setAiProvider(e.target.value)}>
|
||
|
|
<option value="openrouter">OpenRouter</option>
|
||
|
|
<option value="openai">OpenAI</option>
|
||
|
|
<option value="anthropic">Anthropic</option>
|
||
|
|
<option value="local">Lokal (Ollama)</option>
|
||
|
|
</select>
|
||
|
|
<label className="settings-label">Modell</label>
|
||
|
|
<select className="settings-input" value={aiModel} onChange={(e) => setAiModel(e.target.value)}>
|
||
|
|
<option value="gpt-4o">GPT-4o</option>
|
||
|
|
<option value="gpt-4o-mini">GPT-4o mini</option>
|
||
|
|
<option value="claude-3.5-sonnet">Claude 3.5 Sonnet</option>
|
||
|
|
<option value="llama-3.3-70b">Llama 3.3 70B</option>
|
||
|
|
</select>
|
||
|
|
<label className="settings-label">API-Key</label>
|
||
|
|
<input className="settings-input" type="password" value={aiApiKey} onChange={(e) => setAiApiKey(e.target.value)} placeholder="••••••••••••" />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
default:
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="settings-modal-overlay" onClick={onClose}>
|
||
|
|
<div className="settings-modal" onClick={(e) => e.stopPropagation()}>
|
||
|
|
<button className="settings-modal-close" onClick={onClose} aria-label="Schließen">✕</button>
|
||
|
|
<div className="settings-modal-tabs">
|
||
|
|
{visibleTabs.map((tab) => (
|
||
|
|
<button
|
||
|
|
key={tab.id}
|
||
|
|
className={`settings-modal-tab${activeTab === tab.id ? ' active' : ''}`}
|
||
|
|
onClick={() => setActiveTab(tab.id)}
|
||
|
|
>
|
||
|
|
{tab.label}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
<div className="settings-modal-content">
|
||
|
|
{renderTabContent()}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default SettingsModal;
|