added settings page
This commit is contained in:
parent
55c17a5c9e
commit
4d0928f609
10 changed files with 581 additions and 51 deletions
|
|
@ -2,16 +2,44 @@ import React, { useState, useEffect, useRef } from 'react'
|
|||
import { useMsal } from '@azure/msal-react'
|
||||
import { FaSignOutAlt } from 'react-icons/fa'
|
||||
import styles from './SidebarStyles/SidebarUser.module.css'
|
||||
import { useCurrentUser } from '../../hooks/useUsers'
|
||||
import { useCurrentUser, useUser, User } from '../../hooks/useUsers'
|
||||
import { SidebarUserProps } from './sidebarTypes';
|
||||
|
||||
const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
||||
const { instance } = useMsal();
|
||||
const { user, isLoading, error, logout } = useCurrentUser();
|
||||
const { user: currentUser, isLoading: currentUserLoading, logout } = useCurrentUser();
|
||||
const { getUser } = useUser();
|
||||
|
||||
// Local state for user data fetched directly via API
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [userLoading, setUserLoading] = useState(false);
|
||||
const [userError, setUserError] = useState<string | null>(null);
|
||||
const hasLoadedUser = useRef(false);
|
||||
|
||||
const [showLogoutMenu, setShowLogoutMenu] = useState(false);
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
const userSectionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Fetch user data directly using the /api/users/{userId} endpoint
|
||||
const fetchUserData = async () => {
|
||||
if (!currentUser?.id || hasLoadedUser.current) return;
|
||||
|
||||
hasLoadedUser.current = true;
|
||||
setUserLoading(true);
|
||||
setUserError(null);
|
||||
|
||||
try {
|
||||
const userData = await getUser(currentUser.id);
|
||||
setUser(userData);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user data in sidebar:', error);
|
||||
setUserError(typeof error === 'string' ? error : 'Failed to load user data');
|
||||
hasLoadedUser.current = false; // Reset on error to allow retry
|
||||
} finally {
|
||||
setUserLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to get initials from full name
|
||||
const getInitials = (fullName: string): string => {
|
||||
return fullName
|
||||
|
|
@ -44,6 +72,28 @@ const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Fetch user data when currentUser is available
|
||||
useEffect(() => {
|
||||
if (currentUser?.id && !hasLoadedUser.current) {
|
||||
fetchUserData();
|
||||
}
|
||||
}, [currentUser?.id]);
|
||||
|
||||
// Listen for user updates from settings page
|
||||
useEffect(() => {
|
||||
const handleUserUpdate = () => {
|
||||
hasLoadedUser.current = false; // Reset flag
|
||||
if (currentUser?.id) {
|
||||
fetchUserData();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('userInfoUpdated', handleUserUpdate);
|
||||
return () => {
|
||||
window.removeEventListener('userInfoUpdated', handleUserUpdate);
|
||||
};
|
||||
}, [currentUser?.id]);
|
||||
|
||||
// Close popup when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
|
|
@ -61,7 +111,7 @@ const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
|||
};
|
||||
}, [showLogoutMenu]);
|
||||
|
||||
if (isLoading) {
|
||||
if (currentUserLoading || userLoading) {
|
||||
return (
|
||||
<div className={`${styles.user_section} ${isMinimized ? styles.minimized : ''}`}>
|
||||
<div className={styles.userContainer}>Lädt...</div>
|
||||
|
|
@ -69,7 +119,7 @@ const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
|||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (userError) {
|
||||
return (
|
||||
<div className={`${styles.user_section} ${isMinimized ? styles.minimized : ''}`}>
|
||||
<div className={styles.userContainer}>Fehler beim Laden des Benutzerprofils</div>
|
||||
|
|
|
|||
0
src/components/settings/settingsUser.module.css
Normal file
0
src/components/settings/settingsUser.module.css
Normal file
0
src/components/settings/settingsUser.tsx
Normal file
0
src/components/settings/settingsUser.tsx
Normal file
|
|
@ -111,7 +111,7 @@ export function useOrgUsers() {
|
|||
}
|
||||
};
|
||||
|
||||
const updateUser = async (userId: string, userData: UserUpdateData) => {
|
||||
const updateUser = async (userId: string, userData: User) => {
|
||||
await request({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'put',
|
||||
|
|
@ -161,7 +161,7 @@ export function useUser() {
|
|||
});
|
||||
};
|
||||
|
||||
const updateUser = async (userId: string, userData: UserUpdateData): Promise<User> => {
|
||||
const updateUser = async (userId: string, userData: User): Promise<User> => {
|
||||
return await request({
|
||||
url: `/api/users/${userId}`,
|
||||
method: 'put',
|
||||
|
|
|
|||
|
|
@ -21,6 +21,25 @@ export default {
|
|||
'settings.theme.dark': 'Dunkel',
|
||||
'settings.theme.toggle.light': 'Zu hellem Modus wechseln',
|
||||
'settings.theme.toggle.dark': 'Zu dunklem Modus wechseln',
|
||||
'settings.userinfo': 'Benutzerinformationen',
|
||||
'settings.userinfo.description': 'Verwalten Sie Ihre Kontoinformationen',
|
||||
'settings.userinfo.username': 'Benutzername',
|
||||
'settings.userinfo.fullname': 'Vollständiger Name',
|
||||
'settings.userinfo.email': 'E-Mail-Adresse',
|
||||
'settings.userinfo.language': 'Sprache',
|
||||
'settings.userinfo.privilege': 'Berechtigungsstufe',
|
||||
'settings.userinfo.enabled': 'Kontostatus',
|
||||
'settings.userinfo.auth_authority': 'Authentifizierungsanbieter',
|
||||
'settings.userinfo.enabled.true': 'Aktiv',
|
||||
'settings.userinfo.enabled.false': 'Inaktiv',
|
||||
'settings.userinfo.loading': 'Benutzerinformationen werden geladen...',
|
||||
'settings.userinfo.error': 'Fehler beim Laden der Benutzerinformationen',
|
||||
'settings.userinfo.save': 'Änderungen speichern',
|
||||
'settings.userinfo.saving': 'Speichern...',
|
||||
'settings.userinfo.success': 'Benutzerinformationen erfolgreich aktualisiert',
|
||||
'settings.userinfo.update_error': 'Fehler beim Aktualisieren der Benutzerinformationen',
|
||||
'settings.userinfo.managed_by': 'Verwaltet von {provider}',
|
||||
'settings.userinfo.managed_note': 'Dieses Feld wird von {provider} verwaltet und kann nicht geändert werden',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
|
|
@ -305,6 +324,12 @@ export default {
|
|||
'files.upload.error': 'Beim Hochladen ist ein Fehler aufgetreten.',
|
||||
'files.upload.unexpected_error': 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.',
|
||||
|
||||
// Files Page Upload Actions
|
||||
'files.drop_zone': 'Dateien hier ablegen',
|
||||
'files.upload_button': 'Dateien hochladen',
|
||||
'files.uploading_button': 'Wird hochgeladen...',
|
||||
'files.upload_aria_label': 'Dateien hochladen',
|
||||
|
||||
// Files Page
|
||||
'files.title': 'Dateien',
|
||||
'files.table.title': 'Dateien',
|
||||
|
|
|
|||
|
|
@ -21,6 +21,25 @@ export default {
|
|||
'settings.theme.dark': 'Dark',
|
||||
'settings.theme.toggle.light': 'Switch to light mode',
|
||||
'settings.theme.toggle.dark': 'Switch to dark mode',
|
||||
'settings.userinfo': 'User Information',
|
||||
'settings.userinfo.description': 'Manage your account information',
|
||||
'settings.userinfo.username': 'Username',
|
||||
'settings.userinfo.fullname': 'Full Name',
|
||||
'settings.userinfo.email': 'Email Address',
|
||||
'settings.userinfo.language': 'Language',
|
||||
'settings.userinfo.privilege': 'Privilege Level',
|
||||
'settings.userinfo.enabled': 'Account Status',
|
||||
'settings.userinfo.auth_authority': 'Authentication Provider',
|
||||
'settings.userinfo.enabled.true': 'Active',
|
||||
'settings.userinfo.enabled.false': 'Inactive',
|
||||
'settings.userinfo.loading': 'Loading user information...',
|
||||
'settings.userinfo.error': 'Error loading user information',
|
||||
'settings.userinfo.save': 'Save Changes',
|
||||
'settings.userinfo.saving': 'Saving...',
|
||||
'settings.userinfo.success': 'User information updated successfully',
|
||||
'settings.userinfo.update_error': 'Error updating user information',
|
||||
'settings.userinfo.managed_by': 'Managed by {provider}',
|
||||
'settings.userinfo.managed_note': 'This field is managed by {provider} and cannot be changed',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
|
|
@ -306,6 +325,12 @@ export default {
|
|||
'files.upload.error': 'An error occurred while uploading.',
|
||||
'files.upload.unexpected_error': 'An unexpected error occurred while uploading.',
|
||||
|
||||
// Files Page Upload Actions
|
||||
'files.drop_zone': 'Drop files here',
|
||||
'files.upload_button': 'Upload Files',
|
||||
'files.uploading_button': 'Uploading...',
|
||||
'files.upload_aria_label': 'Upload files',
|
||||
|
||||
// Files Page
|
||||
'files.title': 'Files',
|
||||
'files.table.title': 'Files',
|
||||
|
|
|
|||
|
|
@ -21,6 +21,25 @@ export default {
|
|||
'settings.theme.dark': 'Sombre',
|
||||
'settings.theme.toggle.light': 'Passer en mode clair',
|
||||
'settings.theme.toggle.dark': 'Passer en mode sombre',
|
||||
'settings.userinfo': 'Informations utilisateur',
|
||||
'settings.userinfo.description': 'Gérez vos informations de compte',
|
||||
'settings.userinfo.username': 'Nom d\'utilisateur',
|
||||
'settings.userinfo.fullname': 'Nom complet',
|
||||
'settings.userinfo.email': 'Adresse e-mail',
|
||||
'settings.userinfo.language': 'Langue',
|
||||
'settings.userinfo.privilege': 'Niveau de privilège',
|
||||
'settings.userinfo.enabled': 'Statut du compte',
|
||||
'settings.userinfo.auth_authority': 'Fournisseur d\'authentification',
|
||||
'settings.userinfo.enabled.true': 'Actif',
|
||||
'settings.userinfo.enabled.false': 'Inactif',
|
||||
'settings.userinfo.loading': 'Chargement des informations utilisateur...',
|
||||
'settings.userinfo.error': 'Erreur lors du chargement des informations utilisateur',
|
||||
'settings.userinfo.save': 'Enregistrer les modifications',
|
||||
'settings.userinfo.saving': 'Enregistrement...',
|
||||
'settings.userinfo.success': 'Informations utilisateur mises à jour avec succès',
|
||||
'settings.userinfo.update_error': 'Erreur lors de la mise à jour des informations utilisateur',
|
||||
'settings.userinfo.managed_by': 'Géré par {provider}',
|
||||
'settings.userinfo.managed_note': 'Ce champ est géré par {provider} et ne peut pas être modifié',
|
||||
|
||||
// Languages
|
||||
'language.german': 'Deutsch',
|
||||
|
|
@ -306,6 +325,12 @@ export default {
|
|||
'files.upload.error': 'Une erreur s\'est produite lors du téléchargement.',
|
||||
'files.upload.unexpected_error': 'Une erreur inattendue s\'est produite lors du téléchargement.',
|
||||
|
||||
// Files Page Upload Actions
|
||||
'files.drop_zone': 'Déposer les fichiers ici',
|
||||
'files.upload_button': 'Télécharger des fichiers',
|
||||
'files.uploading_button': 'Téléchargement...',
|
||||
'files.upload_aria_label': 'Télécharger des fichiers',
|
||||
|
||||
// Files Page
|
||||
'files.title': 'Fichiers',
|
||||
'files.table.title': 'Fichiers',
|
||||
|
|
|
|||
|
|
@ -74,17 +74,17 @@ function Dateien() {
|
|||
onClick={triggerFilePicker}
|
||||
>
|
||||
<span className={styles.dropZoneText}>
|
||||
{isDragOver ? 'Drop files here' : 'Drop files here'}
|
||||
{t('files.drop_zone')}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className={sharedStyles.primaryButton}
|
||||
onClick={triggerFilePicker}
|
||||
disabled={uploadingFile}
|
||||
aria-label="Upload files"
|
||||
aria-label={t('files.upload_aria_label')}
|
||||
>
|
||||
<span className={sharedStyles.buttonIcon}><IoMdCloudUpload /></span>
|
||||
{uploadingFile ? 'Uploading...' : 'Upload Files'}
|
||||
{uploadingFile ? t('files.uploading_button') : t('files.upload_button')}
|
||||
</button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,52 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import styles from './HomeStyles/Einstellungen.module.css';
|
||||
import sharedStyles from '../../components/PageManager/pages.module.css';
|
||||
import { useLanguage, Language } from '../../contexts/LanguageContext';
|
||||
import { useCurrentUser, useUser, User } from '../../hooks/useUsers';
|
||||
|
||||
function Einstellungen() {
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const { currentLanguage, setLanguage, t, isLoading } = useLanguage();
|
||||
const { user: currentUser, isLoading: currentUserLoading } = useCurrentUser();
|
||||
const { getUser, updateUser, isLoading: updateLoading } = useUser();
|
||||
|
||||
// Local state for user data fetched directly via API
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [userLoading, setUserLoading] = useState(false);
|
||||
const [userError, setUserError] = useState<string | null>(null);
|
||||
|
||||
// Form state for user info
|
||||
const [userForm, setUserForm] = useState({
|
||||
username: '',
|
||||
fullName: '',
|
||||
email: '',
|
||||
language: 'en' as Language,
|
||||
privilege: '',
|
||||
enabled: true
|
||||
});
|
||||
const [updateMessage, setUpdateMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
||||
const [isUpdating, setIsUpdating] = useState(false); // Flag to prevent form reset during update
|
||||
const hasLoadedUser = useRef(false);
|
||||
|
||||
// Fetch user data directly using the /api/users/{userId} endpoint
|
||||
const fetchUserData = async () => {
|
||||
if (!currentUser?.id || hasLoadedUser.current) return;
|
||||
|
||||
hasLoadedUser.current = true;
|
||||
setUserLoading(true);
|
||||
setUserError(null);
|
||||
|
||||
try {
|
||||
const userData = await getUser(currentUser.id);
|
||||
setUser(userData);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user data:', error);
|
||||
setUserError(typeof error === 'string' ? error : 'Failed to load user data');
|
||||
hasLoadedUser.current = false; // Reset on error to allow retry
|
||||
} finally {
|
||||
setUserLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Sync component state with current theme on mount
|
||||
useEffect(() => {
|
||||
|
|
@ -13,6 +54,28 @@ function Einstellungen() {
|
|||
const prefersDark = savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
setIsDarkMode(prefersDark);
|
||||
}, []);
|
||||
|
||||
// Fetch user data when currentUser is available
|
||||
useEffect(() => {
|
||||
if (currentUser?.id && !hasLoadedUser.current) {
|
||||
fetchUserData();
|
||||
}
|
||||
}, [currentUser?.id]);
|
||||
|
||||
// Update form when user data is loaded (but not during an active update)
|
||||
useEffect(() => {
|
||||
if (user && !isUpdating) {
|
||||
console.log('🔄 Updating form with user data:', user);
|
||||
setUserForm({
|
||||
username: user.username || '',
|
||||
fullName: user.fullName || '',
|
||||
email: user.email || '',
|
||||
language: (user.language as Language) || currentLanguage,
|
||||
privilege: user.privilege || '',
|
||||
enabled: user.enabled
|
||||
});
|
||||
}
|
||||
}, [user, currentLanguage, isUpdating]);
|
||||
|
||||
const applyTheme = (isDark: boolean) => {
|
||||
if (isDark) {
|
||||
|
|
@ -31,14 +94,93 @@ function Einstellungen() {
|
|||
applyTheme(newIsDarkMode);
|
||||
localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
const handleLanguageChange = async (language: Language) => {
|
||||
if (language === currentLanguage) return;
|
||||
|
||||
const handleUserFormChange = (field: keyof typeof userForm, value: string | boolean) => {
|
||||
setUserForm(prev => ({ ...prev, [field]: value }));
|
||||
setUpdateMessage(null); // Clear any previous messages
|
||||
|
||||
// Language change will be handled when the form is submitted, not immediately
|
||||
};
|
||||
|
||||
const handleSaveUserInfo = async () => {
|
||||
if (!user) return;
|
||||
|
||||
setIsUpdating(true); // Prevent form reset during update
|
||||
|
||||
try {
|
||||
await setLanguage(language);
|
||||
// Create complete User object with updated form data
|
||||
// Only include editable fields based on authentication authority
|
||||
const completeUserData: User = {
|
||||
id: user.id,
|
||||
username: user.authenticationAuthority === 'local' ? userForm.username : user.username,
|
||||
fullName: user.authenticationAuthority === 'local' ? userForm.fullName : user.fullName,
|
||||
email: user.authenticationAuthority === 'local' ? userForm.email : user.email,
|
||||
language: userForm.language, // Language is always editable
|
||||
privilege: userForm.privilege,
|
||||
enabled: userForm.enabled,
|
||||
authenticationAuthority: user.authenticationAuthority,
|
||||
mandateId: user.mandateId
|
||||
};
|
||||
|
||||
// Update user via API - this returns the updated user
|
||||
const updatedUser = await updateUser(user.id, completeUserData);
|
||||
|
||||
if (updatedUser) {
|
||||
console.log('✅ User update successful:', updatedUser);
|
||||
|
||||
// Update local user state with the returned data
|
||||
setUser(updatedUser);
|
||||
|
||||
// Update frontend language if it was changed
|
||||
const newLanguage = updatedUser.language as Language;
|
||||
if (newLanguage && newLanguage !== currentLanguage) {
|
||||
try {
|
||||
await setLanguage(newLanguage);
|
||||
console.log('🌍 Frontend language updated to:', newLanguage);
|
||||
} catch (error) {
|
||||
console.error('Failed to change frontend language:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Success: Update form with the actual returned data to ensure consistency
|
||||
setUserForm({
|
||||
username: updatedUser.username || '',
|
||||
fullName: updatedUser.fullName || '',
|
||||
email: updatedUser.email || '',
|
||||
language: newLanguage || currentLanguage,
|
||||
privilege: updatedUser.privilege || '',
|
||||
enabled: updatedUser.enabled
|
||||
});
|
||||
|
||||
console.log('📝 Form updated with new data');
|
||||
|
||||
// Dispatch event to notify other components (like sidebar) that user data was updated
|
||||
window.dispatchEvent(new CustomEvent('userInfoUpdated'));
|
||||
|
||||
setUpdateMessage({ type: 'success', text: t('settings.userinfo.success') });
|
||||
} else {
|
||||
throw new Error('No updated user data returned from server');
|
||||
}
|
||||
|
||||
// Clear message after 3 seconds
|
||||
setTimeout(() => setUpdateMessage(null), 3000);
|
||||
} catch (error) {
|
||||
console.error('Failed to change language:', error);
|
||||
console.error('Failed to update user info:', error);
|
||||
setUpdateMessage({ type: 'error', text: t('settings.userinfo.update_error') });
|
||||
|
||||
// Reset form to original user data on error
|
||||
if (user) {
|
||||
setUserForm({
|
||||
username: user.username || '',
|
||||
fullName: user.fullName || '',
|
||||
email: user.email || '',
|
||||
language: (user.language as Language) || currentLanguage,
|
||||
privilege: user.privilege || '',
|
||||
enabled: user.enabled
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsUpdating(false); // Re-enable form sync
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -51,12 +193,12 @@ function Einstellungen() {
|
|||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
if (isLoading || currentUserLoading || userLoading) {
|
||||
return (
|
||||
<div className={sharedStyles.pageContainer}>
|
||||
<div className={sharedStyles.pageCard}>
|
||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||||
{t('common.loading')}
|
||||
{isLoading ? t('common.loading') : t('settings.userinfo.loading')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -70,6 +212,146 @@ function Einstellungen() {
|
|||
<div className={sharedStyles.horizontalDivider}></div>
|
||||
|
||||
<div className={sharedStyles.contentArea}>
|
||||
{/* User Information Section */}
|
||||
|
||||
{userError && (
|
||||
<div className={styles.errorMessage}>
|
||||
{t('settings.userinfo.error')}: {typeof userError === 'string' ? userError : 'An error occurred'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{user && (
|
||||
<div className={styles.userInfoForm}>
|
||||
<span className={styles.settingLabel}>{t('settings.userinfo')}</span>
|
||||
<span className={styles.settingDescription}>
|
||||
{t('settings.userinfo.description')}
|
||||
</span>
|
||||
<div className={styles.formRow}>
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>
|
||||
{t('settings.userinfo.username')}
|
||||
{user.authenticationAuthority !== 'local' && (
|
||||
<span className={styles.fieldNote}>({t('settings.userinfo.managed_by').replace('{provider}', user.authenticationAuthority)})</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={userForm.username}
|
||||
onChange={(e) => handleUserFormChange('username', e.target.value)}
|
||||
placeholder={t('settings.userinfo.username')}
|
||||
readOnly={user.authenticationAuthority !== 'local'}
|
||||
title={user.authenticationAuthority !== 'local' ?
|
||||
t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) :
|
||||
undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>
|
||||
{t('settings.userinfo.fullname')}
|
||||
{user.authenticationAuthority !== 'local' && (
|
||||
<span className={styles.fieldNote}>({t('settings.userinfo.managed_by').replace('{provider}', user.authenticationAuthority)})</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={userForm.fullName}
|
||||
onChange={(e) => handleUserFormChange('fullName', e.target.value)}
|
||||
placeholder={t('settings.userinfo.fullname')}
|
||||
readOnly={user.authenticationAuthority !== 'local'}
|
||||
title={user.authenticationAuthority !== 'local' ?
|
||||
t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) :
|
||||
undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>
|
||||
{t('settings.userinfo.email')}
|
||||
{user.authenticationAuthority !== 'local' && (
|
||||
<span className={styles.fieldNote}>({t('settings.userinfo.managed_by').replace('{provider}', user.authenticationAuthority)})</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className={styles.formInput}
|
||||
value={userForm.email}
|
||||
onChange={(e) => handleUserFormChange('email', e.target.value)}
|
||||
placeholder={t('settings.userinfo.email')}
|
||||
readOnly={user.authenticationAuthority !== 'local'}
|
||||
title={user.authenticationAuthority !== 'local' ?
|
||||
t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) :
|
||||
undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>
|
||||
{t('settings.language')}
|
||||
<span className={styles.fieldNote}>({t('settings.language.description')})</span>
|
||||
</label>
|
||||
<select
|
||||
className={styles.formSelect}
|
||||
value={userForm.language}
|
||||
onChange={(e) => handleUserFormChange('language', e.target.value)}
|
||||
aria-label={t('settings.language')}
|
||||
>
|
||||
<option value="de">{getLanguageLabel('de')}</option>
|
||||
<option value="en">{getLanguageLabel('en')}</option>
|
||||
<option value="fr">{getLanguageLabel('fr')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>{t('settings.userinfo.privilege')}</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={userForm.privilege}
|
||||
readOnly
|
||||
placeholder={t('settings.userinfo.privilege')}
|
||||
title="This field is read-only"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formField}>
|
||||
<label className={styles.fieldLabel}>{t('settings.userinfo.auth_authority')}</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={user.authenticationAuthority}
|
||||
readOnly
|
||||
placeholder={t('settings.userinfo.auth_authority')}
|
||||
title="This field is read-only"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{updateMessage && (
|
||||
<div className={`${styles.updateMessage} ${updateMessage.type === 'success' ? styles.successMessage : styles.errorMessage}`}>
|
||||
{updateMessage.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.formActions}>
|
||||
<button
|
||||
className={styles.saveButton}
|
||||
onClick={handleSaveUserInfo}
|
||||
disabled={updateLoading}
|
||||
>
|
||||
{updateLoading ? t('settings.userinfo.saving') : t('settings.userinfo.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Theme Setting */}
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>{t('settings.theme')}</span>
|
||||
|
|
@ -94,35 +376,7 @@ function Einstellungen() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>{t('settings.language')}</span>
|
||||
<span className={styles.settingDescription}>
|
||||
{t('settings.language.description')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<select
|
||||
className={styles.languageSelect}
|
||||
value={currentLanguage}
|
||||
onChange={(e) => handleLanguageChange(e.target.value as Language)}
|
||||
aria-label={t('settings.language')}
|
||||
>
|
||||
<option value="de">{getLanguageLabel('de')}</option>
|
||||
<option value="en">{getLanguageLabel('en')}</option>
|
||||
<option value="fr">{getLanguageLabel('fr')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className={styles.settingsSection}>
|
||||
<h2 className={styles.sectionTitle}>{t('settings.about')}</h2>
|
||||
<div className={styles.settingItem}>
|
||||
<div className={styles.settingInfo}>
|
||||
<span className={styles.settingLabel}>{t('settings.version')}</span>
|
||||
<span className={styles.settingDescription}>1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@
|
|||
align-items: center;
|
||||
padding: 20px;
|
||||
background: var(--color-bg);
|
||||
border-radius: 20px;
|
||||
border: 2px solid var(--color-surface);
|
||||
border-radius: 25px;
|
||||
border: 1px solid var(--color-primary);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 25px;
|
||||
border: 2px solid var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
|
|
@ -57,8 +57,7 @@
|
|||
|
||||
.themeToggle:hover {
|
||||
border-color: var(--color-secondary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(63, 81, 181, 0.15);
|
||||
box-shadow: 0 4px 12px rgba(63, 81, 181, 0.1);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -123,6 +122,140 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
/* User Information Form Styles */
|
||||
.userInfoForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
background: var(--color-bg);
|
||||
border-radius: 25px;
|
||||
border: 1px solid var(--color-primary);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.formRow {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.formField {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.fieldLabel {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.fieldNote {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-primary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.formInput,
|
||||
.formSelect {
|
||||
padding: 12px 16px;
|
||||
border-radius: 25px;
|
||||
border: 1px solid var(--color-primary);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-family);
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.formInput:focus,
|
||||
.formSelect:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(63, 81, 181, 0.1);
|
||||
}
|
||||
|
||||
.formInput:hover,
|
||||
.formSelect:hover {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.formInput[readonly] {
|
||||
background: var(--color-gray-light);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.formSelect option {
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.formActions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
padding: 12px 24px;
|
||||
border-radius: 25px;
|
||||
border: none;
|
||||
background: var(--color-secondary);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-family: var(--font-family);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.saveButton:hover:not(:disabled) {
|
||||
background: var(--color-secondary);
|
||||
border-color: var(--color-secondary);
|
||||
box-shadow: 0 4px 12px rgba(63, 81, 181, 0.3);
|
||||
}
|
||||
|
||||
.saveButton:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.updateMessage {
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.successMessage {
|
||||
background-color: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
border: 1px solid #81c784;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
background-color: #fce4ec;
|
||||
color: #c2185b;
|
||||
border: 1px solid #f48fb1;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.einstellungenContainer {
|
||||
|
|
@ -142,4 +275,22 @@
|
|||
.themeToggle {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.formRow {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.formField {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.formActions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.saveButton {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue