Web CAD - complete TypeScript source (React 18 frontend, Node.js backend, CRDT collaboration, KI Copilot)
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user