mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
admin has to confirm current password as well on self password change
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
// frontend/src/pages/Settings/Settings.tsx - UPDATED WITH NEW STYLES
|
||||
// frontend/src/pages/Settings/Settings.tsx - UPDATED WITH VALIDATION STRATEGY
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
import { employeeService } from '../../services/employeeService';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
import { useBackendValidation } from '../../hooks/useBackendValidation';
|
||||
import AvailabilityManager from '../Employees/components/AvailabilityManager';
|
||||
import { Employee } from '../../models/Employee';
|
||||
import { styles } from './type/SettingsType';
|
||||
@@ -10,11 +11,12 @@ import { styles } from './type/SettingsType';
|
||||
const Settings: React.FC = () => {
|
||||
const { user: currentUser, updateUser } = useAuth();
|
||||
const { showNotification } = useNotification();
|
||||
const { executeWithValidation, clearErrors, isSubmitting } = useBackendValidation();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'availability'>('profile');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showAvailabilityManager, setShowAvailabilityManager] = useState(false);
|
||||
|
||||
// Profile form state - updated for firstname/lastname
|
||||
|
||||
// Profile form state
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
firstname: currentUser?.firstname || '',
|
||||
lastname: currentUser?.lastname || ''
|
||||
@@ -73,7 +75,7 @@ const Settings: React.FC = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
// Password visibility handlers for current password
|
||||
// Password visibility handlers
|
||||
const handleCurrentPasswordMouseDown = () => {
|
||||
currentPasswordTimeoutRef.current = setTimeout(() => {
|
||||
setShowCurrentPassword(true);
|
||||
@@ -88,7 +90,6 @@ const Settings: React.FC = () => {
|
||||
setShowCurrentPassword(false);
|
||||
};
|
||||
|
||||
// Password visibility handlers for new password
|
||||
const handleNewPasswordMouseDown = () => {
|
||||
newPasswordTimeoutRef.current = setTimeout(() => {
|
||||
setShowNewPassword(true);
|
||||
@@ -103,7 +104,6 @@ const Settings: React.FC = () => {
|
||||
setShowNewPassword(false);
|
||||
};
|
||||
|
||||
// Password visibility handlers for confirm password
|
||||
const handleConfirmPasswordMouseDown = () => {
|
||||
confirmPasswordTimeoutRef.current = setTimeout(() => {
|
||||
setShowConfirmPassword(true);
|
||||
@@ -129,7 +129,6 @@ const Settings: React.FC = () => {
|
||||
cleanup();
|
||||
};
|
||||
|
||||
// Prevent context menu
|
||||
const handleContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
@@ -138,40 +137,46 @@ const Settings: React.FC = () => {
|
||||
e.preventDefault();
|
||||
if (!currentUser) return;
|
||||
|
||||
// Validation
|
||||
if (!profileForm.firstname.trim() || !profileForm.lastname.trim()) {
|
||||
// BASIC FRONTEND VALIDATION: Only check required fields
|
||||
if (!profileForm.firstname.trim()) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Vorname und Nachname sind erforderlich'
|
||||
message: 'Vorname ist erforderlich'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profileForm.lastname.trim()) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Nachname ist erforderlich'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
await employeeService.updateEmployee(currentUser.id, {
|
||||
firstname: profileForm.firstname.trim(),
|
||||
lastname: profileForm.lastname.trim()
|
||||
});
|
||||
// Use executeWithValidation to handle backend validation
|
||||
await executeWithValidation(async () => {
|
||||
const updatedEmployee = await employeeService.updateEmployee(currentUser.id, {
|
||||
firstname: profileForm.firstname.trim(),
|
||||
lastname: profileForm.lastname.trim()
|
||||
});
|
||||
|
||||
// Update the auth context with new user data
|
||||
const updatedUser = await employeeService.getEmployee(currentUser.id);
|
||||
updateUser(updatedUser);
|
||||
// Update the auth context with new user data
|
||||
updateUser(updatedEmployee);
|
||||
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Profil erfolgreich aktualisiert'
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Profil erfolgreich aktualisiert'
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: error.message || 'Profil konnte nicht aktualisiert werden'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
// Backend validation errors are already handled by executeWithValidation
|
||||
// We only need to handle unexpected errors here
|
||||
console.error('Unexpected error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,7 +184,25 @@ const Settings: React.FC = () => {
|
||||
e.preventDefault();
|
||||
if (!currentUser) return;
|
||||
|
||||
// Validation
|
||||
// BASIC FRONTEND VALIDATION: Only check minimum requirements
|
||||
if (!passwordForm.currentPassword) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Aktuelles Passwort ist erforderlich'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.newPassword) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Neues Passwort ist erforderlich'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.newPassword.length < 6) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
@@ -199,34 +222,30 @@ const Settings: React.FC = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Use the actual password change endpoint
|
||||
await employeeService.changePassword(currentUser.id, {
|
||||
currentPassword: passwordForm.currentPassword,
|
||||
newPassword: passwordForm.newPassword
|
||||
});
|
||||
// Use executeWithValidation to handle backend validation
|
||||
await executeWithValidation(async () => {
|
||||
await employeeService.changePassword(currentUser.id, {
|
||||
currentPassword: passwordForm.currentPassword,
|
||||
newPassword: passwordForm.newPassword,
|
||||
confirmPassword: passwordForm.confirmPassword
|
||||
});
|
||||
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Passwort erfolgreich geändert'
|
||||
});
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Passwort erfolgreich geändert'
|
||||
});
|
||||
|
||||
// Clear password form
|
||||
setPasswordForm({
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
// Clear password form
|
||||
setPasswordForm({
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
});
|
||||
} catch (error: any) {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: error.message || 'Passwort konnte nicht geändert werden'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
// Backend validation errors are already handled by executeWithValidation
|
||||
console.error('Unexpected error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -243,12 +262,18 @@ const Settings: React.FC = () => {
|
||||
setShowAvailabilityManager(false);
|
||||
};
|
||||
|
||||
// Clear validation errors when switching tabs
|
||||
const handleTabChange = (tab: 'profile' | 'password' | 'availability') => {
|
||||
clearErrors();
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
if (!currentUser) {
|
||||
return <div style={{
|
||||
textAlign: 'center',
|
||||
padding: '3rem',
|
||||
color: '#666',
|
||||
fontSize: '1.1rem'
|
||||
return <div style={{
|
||||
textAlign: 'center',
|
||||
padding: '3rem',
|
||||
color: '#666',
|
||||
fontSize: '1.1rem'
|
||||
}}>Nicht eingeloggt</div>;
|
||||
}
|
||||
|
||||
@@ -270,10 +295,10 @@ const Settings: React.FC = () => {
|
||||
<h1 style={styles.title}>Einstellungen</h1>
|
||||
<div style={styles.subtitle}>Verwalten Sie Ihre Kontoeinstellungen und Präferenzen</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={styles.tabs}>
|
||||
<button
|
||||
onClick={() => setActiveTab('profile')}
|
||||
onClick={() => handleTabChange('profile')}
|
||||
style={{
|
||||
...styles.tab,
|
||||
...(activeTab === 'profile' ? styles.tabActive : {})
|
||||
@@ -299,9 +324,9 @@ const Settings: React.FC = () => {
|
||||
<span style={{ fontSize: '0.8rem', opacity: 0.7, marginTop: '2px' }}>Persönliche Informationen</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => setActiveTab('password')}
|
||||
onClick={() => handleTabChange('password')}
|
||||
style={{
|
||||
...styles.tab,
|
||||
...(activeTab === 'password' ? styles.tabActive : {})
|
||||
@@ -327,9 +352,9 @@ const Settings: React.FC = () => {
|
||||
<span style={{ fontSize: '0.8rem', opacity: 0.7, marginTop: '2px' }}>Sicherheitseinstellungen</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => setActiveTab('availability')}
|
||||
onClick={() => handleTabChange('availability')}
|
||||
style={{
|
||||
...styles.tab,
|
||||
...(activeTab === 'availability' ? styles.tabActive : {})
|
||||
@@ -369,7 +394,7 @@ const Settings: React.FC = () => {
|
||||
Verwalten Sie Ihre persönlichen Informationen und Kontaktdaten
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<form onSubmit={handleProfileUpdate} style={{ marginTop: '2rem' }}>
|
||||
<div style={styles.formGrid}>
|
||||
{/* Read-only information */}
|
||||
@@ -480,28 +505,28 @@ const Settings: React.FC = () => {
|
||||
<div style={styles.actions}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !profileForm.firstname.trim() || !profileForm.lastname.trim()}
|
||||
disabled={isSubmitting || !profileForm.firstname.trim() || !profileForm.lastname.trim()}
|
||||
style={{
|
||||
...styles.button,
|
||||
...styles.buttonPrimary,
|
||||
...((loading || !profileForm.firstname.trim() || !profileForm.lastname.trim()) ? styles.buttonDisabled : {})
|
||||
...((isSubmitting || !profileForm.firstname.trim() || !profileForm.lastname.trim()) ? styles.buttonDisabled : {})
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!loading && profileForm.firstname.trim() && profileForm.lastname.trim()) {
|
||||
if (!isSubmitting && profileForm.firstname.trim() && profileForm.lastname.trim()) {
|
||||
e.currentTarget.style.background = styles.buttonPrimaryHover.background;
|
||||
e.currentTarget.style.transform = styles.buttonPrimaryHover.transform;
|
||||
e.currentTarget.style.boxShadow = styles.buttonPrimaryHover.boxShadow;
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!loading && profileForm.firstname.trim() && profileForm.lastname.trim()) {
|
||||
if (!isSubmitting && profileForm.firstname.trim() && profileForm.lastname.trim()) {
|
||||
e.currentTarget.style.background = styles.buttonPrimary.background;
|
||||
e.currentTarget.style.transform = 'none';
|
||||
e.currentTarget.style.boxShadow = styles.buttonPrimary.boxShadow;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? '⏳ Wird gespeichert...' : 'Profil aktualisieren'}
|
||||
{isSubmitting ? '⏳ Wird gespeichert...' : 'Profil aktualisieren'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -517,7 +542,7 @@ const Settings: React.FC = () => {
|
||||
Aktualisieren Sie Ihr Passwort für erhöhte Sicherheit
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<form onSubmit={handlePasswordUpdate} style={{ marginTop: '2rem' }}>
|
||||
<div style={styles.formGridCompact}>
|
||||
{/* Current Password Field */}
|
||||
@@ -657,28 +682,28 @@ const Settings: React.FC = () => {
|
||||
<div style={styles.actions}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword}
|
||||
disabled={isSubmitting || !passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword}
|
||||
style={{
|
||||
...styles.button,
|
||||
...styles.buttonPrimary,
|
||||
...((loading || !passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) ? styles.buttonDisabled : {})
|
||||
...((isSubmitting || !passwordForm.currentPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) ? styles.buttonDisabled : {})
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!loading && passwordForm.currentPassword && passwordForm.newPassword && passwordForm.confirmPassword) {
|
||||
if (!isSubmitting && passwordForm.currentPassword && passwordForm.newPassword && passwordForm.confirmPassword) {
|
||||
e.currentTarget.style.background = styles.buttonPrimaryHover.background;
|
||||
e.currentTarget.style.transform = styles.buttonPrimaryHover.transform;
|
||||
e.currentTarget.style.boxShadow = styles.buttonPrimaryHover.boxShadow;
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!loading && passwordForm.currentPassword && passwordForm.newPassword && passwordForm.confirmPassword) {
|
||||
if (!isSubmitting && passwordForm.currentPassword && passwordForm.newPassword && passwordForm.confirmPassword) {
|
||||
e.currentTarget.style.background = styles.buttonPrimary.background;
|
||||
e.currentTarget.style.transform = 'none';
|
||||
e.currentTarget.style.boxShadow = styles.buttonPrimary.boxShadow;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? '⏳ Wird geändert...' : 'Passwort ändern'}
|
||||
{isSubmitting ? '⏳ Wird geändert...' : 'Passwort ändern'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -694,16 +719,16 @@ const Settings: React.FC = () => {
|
||||
Legen Sie Ihre persönliche Verfügbarkeit für Schichtpläne fest
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={styles.availabilityCard}>
|
||||
<div style={styles.availabilityIcon}>📅</div>
|
||||
<h3 style={styles.availabilityTitle}>Verfügbarkeit verwalten</h3>
|
||||
<p style={styles.availabilityDescription}>
|
||||
Hier können Sie Ihre persönliche Verfügbarkeit für Schichtpläne festlegen.
|
||||
Legen Sie für jeden Tag und jede Schicht fest, ob Sie bevorzugt, möglicherweise
|
||||
Legen Sie für jeden Tag und jede Schicht fest, ob Sie bevorzugt, möglicherweise
|
||||
oder nicht verfügbar sind.
|
||||
</p>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => setShowAvailabilityManager(true)}
|
||||
style={{
|
||||
|
||||
@@ -1,275 +1,275 @@
|
||||
// frontend/src/pages/Settings/type/SettingsType.tsx - CORRECTED
|
||||
// frontend/src/pages/Settings/type/SettingsType.tsx
|
||||
export const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
minHeight: 'calc(100vh - 120px)',
|
||||
background: '#FBFAF6',
|
||||
padding: '2rem',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
gap: '2rem',
|
||||
},
|
||||
sidebar: {
|
||||
width: '280px',
|
||||
background: '#FBFAF6',
|
||||
borderRadius: '16px',
|
||||
border: '1px solid rgba(255, 255, 255, 0.8)',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04)',
|
||||
padding: '1.5rem',
|
||||
height: 'fit-content',
|
||||
position: 'sticky' as const,
|
||||
top: '2rem',
|
||||
},
|
||||
header: {
|
||||
marginBottom: '2rem',
|
||||
paddingBottom: '1.5rem',
|
||||
borderBottom: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 0.5rem 0',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '0.95rem',
|
||||
color: '#666',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
tabs: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: '0.5rem',
|
||||
},
|
||||
tab: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
padding: '1rem 1.25rem',
|
||||
background: 'transparent',
|
||||
color: '#666',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
textAlign: 'left' as const,
|
||||
width: '100%',
|
||||
},
|
||||
tabActive: {
|
||||
background: '#51258f',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 4px 12px rgba(26, 19, 37, 0.15)',
|
||||
},
|
||||
tabHover: {
|
||||
background: 'rgba(81, 37, 143, 0.1)',
|
||||
color: '#1a1325',
|
||||
transform: 'translateX(4px)',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
background: '#FBFAF6',
|
||||
padding: '2.5rem',
|
||||
borderRadius: '16px',
|
||||
border: '1px solid rgba(255, 255, 255, 0.8)',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
minHeight: '100px',
|
||||
},
|
||||
section: {
|
||||
marginBottom: '2rem',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 0.5rem 0',
|
||||
},
|
||||
sectionDescription: {
|
||||
color: '#666',
|
||||
fontSize: '1rem',
|
||||
margin: 0,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
formGrid: {
|
||||
display: 'grid',
|
||||
gap: '1.5rem',
|
||||
},
|
||||
formGridCompact: {
|
||||
display: 'grid',
|
||||
gap: '1.5rem',
|
||||
maxWidth: '500px',
|
||||
},
|
||||
infoCard: {
|
||||
padding: '1.5rem',
|
||||
background: 'rgba(26, 19, 37, 0.02)',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
infoCardTitle: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: 600,
|
||||
color: '#1a1325',
|
||||
margin: '0 0 1rem 0',
|
||||
},
|
||||
infoGrid: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '1rem',
|
||||
},
|
||||
field: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: '0.5rem',
|
||||
width: '100%',
|
||||
},
|
||||
fieldLabel: {
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
},
|
||||
fieldInputContainer: {
|
||||
position: 'relative' as const,
|
||||
width: '100%',
|
||||
},
|
||||
fieldInput: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: '#FBFAF6',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldInputWithIcon: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: '#FBFAF6',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
paddingRight: '40px',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldInputDisabled: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid rgba(26, 19, 37, 0.1)',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: 'rgba(26, 19, 37, 0.05)',
|
||||
color: '#666',
|
||||
cursor: 'not-allowed',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldHint: {
|
||||
fontSize: '0.8rem',
|
||||
color: '#888',
|
||||
marginTop: '0.25rem',
|
||||
width: '100%',
|
||||
},
|
||||
passwordToggleButton: {
|
||||
position: 'absolute' as const,
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '5px',
|
||||
borderRadius: '4px',
|
||||
transition: 'background-color 0.2s',
|
||||
userSelect: 'none' as const,
|
||||
WebkitUserSelect: 'none' as const,
|
||||
touchAction: 'manipulation' as const,
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: '2.5rem',
|
||||
paddingTop: '1.5rem',
|
||||
borderTop: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
button: {
|
||||
padding: '0.875rem 2rem',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
position: 'relative' as const,
|
||||
overflow: 'hidden' as const,
|
||||
},
|
||||
buttonPrimary: {
|
||||
background: '#1a1325',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 2px 8px rgba(26, 19, 37, 0.2)',
|
||||
},
|
||||
buttonPrimaryHover: {
|
||||
background: '#24163a',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow: '0 4px 16px rgba(26, 19, 37, 0.3)',
|
||||
},
|
||||
buttonDisabled: {
|
||||
background: '#ccc',
|
||||
color: '#666',
|
||||
cursor: 'not-allowed',
|
||||
transform: 'none',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
availabilityCard: {
|
||||
padding: '3rem 2rem',
|
||||
textAlign: 'center' as const,
|
||||
background: 'rgba(26, 19, 37, 0.03)',
|
||||
borderRadius: '16px',
|
||||
border: '2px dashed rgba(26, 19, 37, 0.1)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
},
|
||||
availabilityIcon: {
|
||||
fontSize: '3rem',
|
||||
marginBottom: '1.5rem',
|
||||
opacity: 0.8,
|
||||
},
|
||||
availabilityTitle: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 1rem 0',
|
||||
},
|
||||
availabilityDescription: {
|
||||
color: '#666',
|
||||
marginBottom: '2rem',
|
||||
lineHeight: 1.6,
|
||||
maxWidth: '500px',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
infoHint: {
|
||||
padding: '1.25rem',
|
||||
background: 'rgba(26, 19, 37, 0.05)',
|
||||
border: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
borderRadius: '12px',
|
||||
fontSize: '0.9rem',
|
||||
color: '#161718',
|
||||
textAlign: 'left' as const,
|
||||
maxWidth: '400px',
|
||||
margin: '0 auto',
|
||||
},
|
||||
infoList: {
|
||||
margin: '0.75rem 0 0 1rem',
|
||||
padding: 0,
|
||||
listStyle: 'none',
|
||||
},
|
||||
infoListItem: {
|
||||
marginBottom: '0.5rem',
|
||||
position: 'relative' as const,
|
||||
paddingLeft: '1rem',
|
||||
},
|
||||
};
|
||||
container: {
|
||||
display: 'flex',
|
||||
minHeight: 'calc(100vh - 120px)',
|
||||
background: '#FBFAF6',
|
||||
padding: '2rem',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
gap: '2rem',
|
||||
},
|
||||
sidebar: {
|
||||
width: '280px',
|
||||
background: '#FBFAF6',
|
||||
borderRadius: '16px',
|
||||
border: '1px solid rgba(255, 255, 255, 0.8)',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04)',
|
||||
padding: '1.5rem',
|
||||
height: 'fit-content',
|
||||
position: 'sticky' as const,
|
||||
top: '2rem',
|
||||
},
|
||||
header: {
|
||||
marginBottom: '2rem',
|
||||
paddingBottom: '1.5rem',
|
||||
borderBottom: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 0.5rem 0',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '0.95rem',
|
||||
color: '#666',
|
||||
fontWeight: 400,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
tabs: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: '0.5rem',
|
||||
},
|
||||
tab: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
padding: '1rem 1.25rem',
|
||||
background: 'transparent',
|
||||
color: '#666',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 500,
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
textAlign: 'left' as const,
|
||||
width: '100%',
|
||||
},
|
||||
tabActive: {
|
||||
background: '#51258f',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 4px 12px rgba(26, 19, 37, 0.15)',
|
||||
},
|
||||
tabHover: {
|
||||
background: 'rgba(81, 37, 143, 0.1)',
|
||||
color: '#1a1325',
|
||||
transform: 'translateX(4px)',
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
background: '#FBFAF6',
|
||||
padding: '2.5rem',
|
||||
borderRadius: '16px',
|
||||
border: '1px solid rgba(255, 255, 255, 0.8)',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.06), 0 1px 4px rgba(0, 0, 0, 0.04)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
minHeight: '100px',
|
||||
},
|
||||
section: {
|
||||
marginBottom: '2rem',
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 0.5rem 0',
|
||||
},
|
||||
sectionDescription: {
|
||||
color: '#666',
|
||||
fontSize: '1rem',
|
||||
margin: 0,
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
formGrid: {
|
||||
display: 'grid',
|
||||
gap: '1.5rem',
|
||||
},
|
||||
formGridCompact: {
|
||||
display: 'grid',
|
||||
gap: '1.5rem',
|
||||
maxWidth: '500px',
|
||||
},
|
||||
infoCard: {
|
||||
padding: '1.5rem',
|
||||
background: 'rgba(26, 19, 37, 0.02)',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
infoCardTitle: {
|
||||
fontSize: '1rem',
|
||||
fontWeight: 600,
|
||||
color: '#1a1325',
|
||||
margin: '0 0 1rem 0',
|
||||
},
|
||||
infoGrid: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '1rem',
|
||||
},
|
||||
field: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
gap: '0.5rem',
|
||||
width: '100%',
|
||||
},
|
||||
fieldLabel: {
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
},
|
||||
fieldInputContainer: {
|
||||
position: 'relative' as const,
|
||||
width: '100%',
|
||||
},
|
||||
fieldInput: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: '#FBFAF6',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldInputWithIcon: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid #e8e8e8',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: '#FBFAF6',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
color: '#161718',
|
||||
width: '100%',
|
||||
paddingRight: '40px',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldInputDisabled: {
|
||||
padding: '0.875rem 1rem',
|
||||
border: '1.5px solid rgba(26, 19, 37, 0.1)',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
background: 'rgba(26, 19, 37, 0.05)',
|
||||
color: '#666',
|
||||
cursor: 'not-allowed',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box' as const,
|
||||
},
|
||||
fieldHint: {
|
||||
fontSize: '0.8rem',
|
||||
color: '#888',
|
||||
marginTop: '0.25rem',
|
||||
width: '100%',
|
||||
},
|
||||
passwordToggleButton: {
|
||||
position: 'absolute' as const,
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '5px',
|
||||
borderRadius: '4px',
|
||||
transition: 'background-color 0.2s',
|
||||
userSelect: 'none' as const,
|
||||
WebkitUserSelect: 'none' as const,
|
||||
touchAction: 'manipulation' as const,
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: '2.5rem',
|
||||
paddingTop: '1.5rem',
|
||||
borderTop: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
},
|
||||
button: {
|
||||
padding: '0.875rem 2rem',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.95rem',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
position: 'relative' as const,
|
||||
overflow: 'hidden' as const,
|
||||
},
|
||||
buttonPrimary: {
|
||||
background: '#1a1325',
|
||||
color: '#FBFAF6',
|
||||
boxShadow: '0 2px 8px rgba(26, 19, 37, 0.2)',
|
||||
},
|
||||
buttonPrimaryHover: {
|
||||
background: '#24163a',
|
||||
transform: 'translateY(-1px)',
|
||||
boxShadow: '0 4px 16px rgba(26, 19, 37, 0.3)',
|
||||
},
|
||||
buttonDisabled: {
|
||||
background: '#ccc',
|
||||
color: '#666',
|
||||
cursor: 'not-allowed',
|
||||
transform: 'none',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
availabilityCard: {
|
||||
padding: '3rem 2rem',
|
||||
textAlign: 'center' as const,
|
||||
background: 'rgba(26, 19, 37, 0.03)',
|
||||
borderRadius: '16px',
|
||||
border: '2px dashed rgba(26, 19, 37, 0.1)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
},
|
||||
availabilityIcon: {
|
||||
fontSize: '3rem',
|
||||
marginBottom: '1.5rem',
|
||||
opacity: 0.8,
|
||||
},
|
||||
availabilityTitle: {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 600,
|
||||
color: '#161718',
|
||||
margin: '0 0 1rem 0',
|
||||
},
|
||||
availabilityDescription: {
|
||||
color: '#666',
|
||||
marginBottom: '2rem',
|
||||
lineHeight: 1.6,
|
||||
maxWidth: '500px',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
infoHint: {
|
||||
padding: '1.25rem',
|
||||
background: 'rgba(26, 19, 37, 0.05)',
|
||||
border: '1px solid rgba(26, 19, 37, 0.1)',
|
||||
borderRadius: '12px',
|
||||
fontSize: '0.9rem',
|
||||
color: '#161718',
|
||||
textAlign: 'left' as const,
|
||||
maxWidth: '400px',
|
||||
margin: '0 auto',
|
||||
},
|
||||
infoList: {
|
||||
margin: '0.75rem 0 0 1rem',
|
||||
padding: 0,
|
||||
listStyle: 'none',
|
||||
},
|
||||
infoListItem: {
|
||||
marginBottom: '0.5rem',
|
||||
position: 'relative' as const,
|
||||
paddingLeft: '1rem',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user