mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
added password unvewiling and relative boxing
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user