added password unvewiling and relative boxing

This commit is contained in:
2025-10-23 20:01:21 +02:00
parent b9a88bce1c
commit ce1c6b08b1
2 changed files with 236 additions and 60 deletions

View File

@@ -1,5 +1,5 @@
// frontend/src/pages/Settings/Settings.tsx // frontend/src/pages/Settings/Settings.tsx - UPDATED WITH NEW STYLES
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
import { employeeService } from '../../services/employeeService'; import { employeeService } from '../../services/employeeService';
import { useNotification } from '../../contexts/NotificationContext'; import { useNotification } from '../../contexts/NotificationContext';
@@ -27,6 +27,16 @@ const Settings: React.FC = () => {
confirmPassword: '' confirmPassword: ''
}); });
// Password visibility states
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
// Refs for timeout management
const currentPasswordTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const newPasswordTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const confirmPasswordTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => { useEffect(() => {
if (currentUser) { if (currentUser) {
setProfileForm({ setProfileForm({
@@ -36,6 +46,17 @@ const Settings: React.FC = () => {
} }
}, [currentUser]); }, [currentUser]);
// Cleanup timeouts on unmount
useEffect(() => {
return () => {
[currentPasswordTimeoutRef, newPasswordTimeoutRef, confirmPasswordTimeoutRef].forEach(ref => {
if (ref.current) {
clearTimeout(ref.current);
}
});
};
}, []);
const handleProfileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleProfileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; const { name, value } = e.target;
setProfileForm(prev => ({ setProfileForm(prev => ({
@@ -52,6 +73,67 @@ const Settings: React.FC = () => {
})); }));
}; };
// Password visibility handlers for current password
const handleCurrentPasswordMouseDown = () => {
currentPasswordTimeoutRef.current = setTimeout(() => {
setShowCurrentPassword(true);
}, 300);
};
const handleCurrentPasswordMouseUp = () => {
if (currentPasswordTimeoutRef.current) {
clearTimeout(currentPasswordTimeoutRef.current);
currentPasswordTimeoutRef.current = null;
}
setShowCurrentPassword(false);
};
// Password visibility handlers for new password
const handleNewPasswordMouseDown = () => {
newPasswordTimeoutRef.current = setTimeout(() => {
setShowNewPassword(true);
}, 300);
};
const handleNewPasswordMouseUp = () => {
if (newPasswordTimeoutRef.current) {
clearTimeout(newPasswordTimeoutRef.current);
newPasswordTimeoutRef.current = null;
}
setShowNewPassword(false);
};
// Password visibility handlers for confirm password
const handleConfirmPasswordMouseDown = () => {
confirmPasswordTimeoutRef.current = setTimeout(() => {
setShowConfirmPassword(true);
}, 300);
};
const handleConfirmPasswordMouseUp = () => {
if (confirmPasswordTimeoutRef.current) {
clearTimeout(confirmPasswordTimeoutRef.current);
confirmPasswordTimeoutRef.current = null;
}
setShowConfirmPassword(false);
};
// Touch event handlers
const handleTouchStart = (setter: () => void) => (e: React.TouchEvent) => {
e.preventDefault();
setter();
};
const handleTouchEnd = (cleanup: () => void) => (e: React.TouchEvent) => {
e.preventDefault();
cleanup();
};
// Prevent context menu
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
};
const handleProfileUpdate = async (e: React.FormEvent) => { const handleProfileUpdate = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!currentUser) return; if (!currentUser) return;
@@ -180,11 +262,6 @@ const Settings: React.FC = () => {
); );
} }
// Get full name for display
const getFullName = () => {
return `${currentUser.firstname || ''} ${currentUser.lastname || ''}`.trim();
};
return ( return (
<div style={styles.container}> <div style={styles.container}>
{/* Left Sidebar with Tabs */} {/* Left Sidebar with Tabs */}
@@ -443,77 +520,137 @@ const Settings: React.FC = () => {
<form onSubmit={handlePasswordUpdate} style={{ marginTop: '2rem' }}> <form onSubmit={handlePasswordUpdate} style={{ marginTop: '2rem' }}>
<div style={styles.formGridCompact}> <div style={styles.formGridCompact}>
{/* Current Password Field */}
<div style={styles.field}> <div style={styles.field}>
<label style={styles.fieldLabel}> <label style={styles.fieldLabel}>
Aktuelles Passwort * Aktuelles Passwort *
</label> </label>
<input <div style={styles.fieldInputContainer}>
type="password" <input
name="currentPassword" type={showCurrentPassword ? 'text' : 'password'}
value={passwordForm.currentPassword} name="currentPassword"
onChange={handlePasswordChange} value={passwordForm.currentPassword}
required onChange={handlePasswordChange}
style={styles.fieldInput} required
placeholder="Aktuelles Passwort" style={styles.fieldInputWithIcon}
onFocus={(e) => { placeholder="Aktuelles Passwort"
e.target.style.borderColor = '#1a1325'; onFocus={(e) => {
e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)'; e.target.style.borderColor = '#1a1325';
}} e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)';
onBlur={(e) => { }}
e.target.style.borderColor = '#e8e8e8'; onBlur={(e) => {
e.target.style.boxShadow = 'none'; e.target.style.borderColor = '#e8e8e8';
}} e.target.style.boxShadow = 'none';
/> }}
/>
<button
type="button"
onMouseDown={handleCurrentPasswordMouseDown}
onMouseUp={handleCurrentPasswordMouseUp}
onMouseLeave={handleCurrentPasswordMouseUp}
onTouchStart={handleTouchStart(handleCurrentPasswordMouseDown)}
onTouchEnd={handleTouchEnd(handleCurrentPasswordMouseUp)}
onTouchCancel={handleTouchEnd(handleCurrentPasswordMouseUp)}
onContextMenu={handleContextMenu}
style={{
...styles.passwordToggleButton,
backgroundColor: showCurrentPassword ? 'rgba(26, 19, 37, 0.1)' : 'transparent'
}}
title="Gedrückt halten zum Anzeigen des Passworts"
>
{showCurrentPassword ? '👁' : '👁'}
</button>
</div>
</div> </div>
{/* New Password Field */}
<div style={styles.field}> <div style={styles.field}>
<label style={styles.fieldLabel}> <label style={styles.fieldLabel}>
Neues Passwort * Neues Passwort *
</label> </label>
<input <div style={styles.fieldInputContainer}>
type="password" <input
name="newPassword" type={showNewPassword ? 'text' : 'password'}
value={passwordForm.newPassword} name="newPassword"
onChange={handlePasswordChange} value={passwordForm.newPassword}
required onChange={handlePasswordChange}
minLength={6} required
style={styles.fieldInput} minLength={6}
placeholder="Mindestens 6 Zeichen" style={styles.fieldInputWithIcon}
onFocus={(e) => { placeholder="Mindestens 6 Zeichen"
e.target.style.borderColor = '#1a1325'; onFocus={(e) => {
e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)'; e.target.style.borderColor = '#1a1325';
}} e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)';
onBlur={(e) => { }}
e.target.style.borderColor = '#e8e8e8'; onBlur={(e) => {
e.target.style.boxShadow = 'none'; e.target.style.borderColor = '#e8e8e8';
}} e.target.style.boxShadow = 'none';
/> }}
/>
<button
type="button"
onMouseDown={handleNewPasswordMouseDown}
onMouseUp={handleNewPasswordMouseUp}
onMouseLeave={handleNewPasswordMouseUp}
onTouchStart={handleTouchStart(handleNewPasswordMouseDown)}
onTouchEnd={handleTouchEnd(handleNewPasswordMouseUp)}
onTouchCancel={handleTouchEnd(handleNewPasswordMouseUp)}
onContextMenu={handleContextMenu}
style={{
...styles.passwordToggleButton,
backgroundColor: showNewPassword ? 'rgba(26, 19, 37, 0.1)' : 'transparent'
}}
title="Gedrückt halten zum Anzeigen des Passworts"
>
{showNewPassword ? '👁' : '👁'}
</button>
</div>
<div style={styles.fieldHint}> <div style={styles.fieldHint}>
Das Passwort muss mindestens 6 Zeichen lang sein. Das Passwort muss mindestens 6 Zeichen lang sein.
</div> </div>
</div> </div>
{/* Confirm Password Field */}
<div style={styles.field}> <div style={styles.field}>
<label style={styles.fieldLabel}> <label style={styles.fieldLabel}>
Neues Passwort bestätigen * Neues Passwort bestätigen *
</label> </label>
<input <div style={styles.fieldInputContainer}>
type="password" <input
name="confirmPassword" type={showConfirmPassword ? 'text' : 'password'}
value={passwordForm.confirmPassword} name="confirmPassword"
onChange={handlePasswordChange} value={passwordForm.confirmPassword}
required onChange={handlePasswordChange}
style={styles.fieldInput} required
placeholder="Passwort wiederholen" style={styles.fieldInputWithIcon}
onFocus={(e) => { placeholder="Passwort wiederholen"
e.target.style.borderColor = '#1a1325'; onFocus={(e) => {
e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)'; e.target.style.borderColor = '#1a1325';
}} e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)';
onBlur={(e) => { }}
e.target.style.borderColor = '#e8e8e8'; onBlur={(e) => {
e.target.style.boxShadow = 'none'; e.target.style.borderColor = '#e8e8e8';
}} e.target.style.boxShadow = 'none';
/> }}
/>
<button
type="button"
onMouseDown={handleConfirmPasswordMouseDown}
onMouseUp={handleConfirmPasswordMouseUp}
onMouseLeave={handleConfirmPasswordMouseUp}
onTouchStart={handleTouchStart(handleConfirmPasswordMouseDown)}
onTouchEnd={handleTouchEnd(handleConfirmPasswordMouseUp)}
onTouchCancel={handleTouchEnd(handleConfirmPasswordMouseUp)}
onContextMenu={handleContextMenu}
style={{
...styles.passwordToggleButton,
backgroundColor: showConfirmPassword ? 'rgba(26, 19, 37, 0.1)' : 'transparent'
}}
title="Gedrückt halten zum Anzeigen des Passworts"
>
{showConfirmPassword ? '👁' : '👁'}
</button>
</div>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
export const styles = { // frontend/src/pages/Settings/type/SettingsType.tsx - CORRECTED
export const styles = {
container: { container: {
display: 'flex', display: 'flex',
minHeight: 'calc(100vh - 120px)', minHeight: 'calc(100vh - 120px)',
@@ -121,11 +122,17 @@
display: 'flex', display: 'flex',
flexDirection: 'column' as const, flexDirection: 'column' as const,
gap: '0.5rem', gap: '0.5rem',
width: '100%',
}, },
fieldLabel: { fieldLabel: {
fontSize: '0.9rem', fontSize: '0.9rem',
fontWeight: 600, fontWeight: 600,
color: '#161718', color: '#161718',
width: '100%',
},
fieldInputContainer: {
position: 'relative' as const,
width: '100%',
}, },
fieldInput: { fieldInput: {
padding: '0.875rem 1rem', padding: '0.875rem 1rem',
@@ -135,6 +142,20 @@
background: '#FBFAF6', background: '#FBFAF6',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
color: '#161718', 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: { fieldInputDisabled: {
padding: '0.875rem 1rem', padding: '0.875rem 1rem',
@@ -144,11 +165,29 @@
background: 'rgba(26, 19, 37, 0.05)', background: 'rgba(26, 19, 37, 0.05)',
color: '#666', color: '#666',
cursor: 'not-allowed', cursor: 'not-allowed',
width: '100%',
boxSizing: 'border-box' as const,
}, },
fieldHint: { fieldHint: {
fontSize: '0.8rem', fontSize: '0.8rem',
color: '#888', color: '#888',
marginTop: '0.25rem', 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: { actions: {
display: 'flex', display: 'flex',