"Update login, new job, and settings templates with improved UI elements (feat)"

This commit is contained in:
2025-05-29 16:37:04 +02:00
parent bb0dd812a1
commit 6d3ccb5e26
3 changed files with 1444 additions and 327 deletions

View File

@@ -400,7 +400,24 @@
// Sammle alle Einstellungen und speichere sie
async function saveAllSettings() {
const saveButton = document.getElementById('save-security-settings');
const originalButtonText = saveButton.innerHTML;
try {
// Show loading state
saveButton.disabled = true;
saveButton.innerHTML = `
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Speichern...
`;
// Add loading state to settings cards
const settingsCards = document.querySelectorAll('.glass-card');
settingsCards.forEach(card => card.classList.add('settings-loading'));
// Erscheinungsbild-Einstellungen
const theme = localStorage.getItem(STORAGE_KEY) === 'true' ? 'dark' :
localStorage.getItem(STORAGE_KEY) === 'false' ? 'light' : 'system';
@@ -418,6 +435,11 @@
const twoFactor = document.getElementById('two-factor').checked;
const autoLogout = document.getElementById('auto-logout').value;
// Validate settings
if (!validateSettings({ theme, contrast, autoLogout })) {
throw new Error('Ungültige Einstellungen erkannt');
}
// Einstellungsobjekt erstellen
const settings = {
theme: theme,
@@ -433,11 +455,19 @@
activity_logs: activityLogs,
two_factor: twoFactor,
auto_logout: autoLogout
}
},
timestamp: new Date().toISOString()
};
// Apply reduced motion immediately if changed
if (reducedMotion) {
document.documentElement.style.setProperty('--tw-transition-duration', '0s');
} else {
document.documentElement.style.removeProperty('--tw-transition-duration');
}
// Einstellungen an den Server senden
const response = await fetch('/user/update-settings', {
const response = await fetch('/api/user/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -449,123 +479,484 @@
const result = await response.json();
if (result.success) {
// Success animation
settingsCards.forEach(card => {
card.classList.add('settings-saved');
setTimeout(() => card.classList.remove('settings-saved'), 600);
});
showFlashMessage('Alle Einstellungen wurden erfolgreich gespeichert', 'success');
// Cache settings locally for faster access
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
} else {
throw new Error(result.error || 'Unbekannter Fehler');
}
} catch (error) {
console.error('Fehler beim Speichern der Einstellungen:', error);
showFlashMessage('Fehler beim Speichern der Einstellungen: ' + error.message, 'error');
} finally {
// Restore button and remove loading states
saveButton.disabled = false;
saveButton.innerHTML = originalButtonText;
const settingsCards = document.querySelectorAll('.glass-card');
settingsCards.forEach(card => card.classList.remove('settings-loading'));
}
}
// Einstellungen beim Laden der Seite abrufen
// Validate settings before saving
function validateSettings(settings) {
const validThemes = ['light', 'dark', 'system'];
const validContrast = ['normal', 'high'];
const validLogoutValues = ['never', '30', '60', '120', '480'];
return validThemes.includes(settings.theme) &&
validContrast.includes(settings.contrast) &&
validLogoutValues.includes(settings.autoLogout);
}
// Enhanced settings loading with caching
async function loadUserSettings() {
try {
// Try to load from cache first for better performance
const cachedSettings = localStorage.getItem('myp-settings-cache');
if (cachedSettings) {
try {
const cached = JSON.parse(cachedSettings);
// Use cached settings if they're less than 5 minutes old
if (new Date() - new Date(cached.timestamp) < 5 * 60 * 1000) {
applySettings(cached);
return;
}
} catch (e) {
localStorage.removeItem('myp-settings-cache');
}
}
const response = await fetch('/api/user/settings');
const result = await response.json();
if (result.success) {
const settings = result.settings;
applySettings(settings);
// Theme-Einstellungen anwenden
if (settings.theme === 'dark') {
localStorage.setItem(STORAGE_KEY, 'true');
document.documentElement.classList.add('dark');
setActiveThemeButton(darkThemeBtn);
} else if (settings.theme === 'light') {
localStorage.setItem(STORAGE_KEY, 'false');
document.documentElement.classList.remove('dark');
setActiveThemeButton(lightThemeBtn);
} else {
localStorage.removeItem(STORAGE_KEY);
setActiveThemeButton(systemThemeBtn);
}
// Weitere Einstellungen anwenden
document.getElementById('reduced-motion').checked = settings.reduced_motion;
document.getElementById('notify-new-jobs').checked = settings.notifications.new_jobs;
document.getElementById('notify-job-updates').checked = settings.notifications.job_updates;
document.getElementById('notify-system').checked = settings.notifications.system;
document.getElementById('notify-email').checked = settings.notifications.email;
document.getElementById('activity-logs').checked = settings.privacy.activity_logs;
document.getElementById('two-factor').checked = settings.privacy.two_factor;
document.getElementById('auto-logout').value = settings.privacy.auto_logout;
// Kontrast-Einstellungen
if (settings.contrast === 'high') {
localStorage.setItem('myp-contrast', 'high');
document.documentElement.classList.add('high-contrast');
setActiveContrastButton(highContrastBtn);
} else {
localStorage.setItem('myp-contrast', 'normal');
document.documentElement.classList.remove('high-contrast');
setActiveContrastButton(normalContrastBtn);
}
// Cache the loaded settings
settings.timestamp = new Date().toISOString();
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
}
} catch (error) {
console.error('Fehler beim Laden der Einstellungen:', error);
// Use fallback defaults if loading fails
applyDefaultSettings();
}
}
// Einstellungen beim Laden der Seite abrufen
loadUserSettings();
function applySettings(settings) {
// Theme-Einstellungen anwenden
if (settings.theme === 'dark') {
localStorage.setItem(STORAGE_KEY, 'true');
document.documentElement.classList.add('dark');
setActiveThemeButton(darkThemeBtn);
} else if (settings.theme === 'light') {
localStorage.setItem(STORAGE_KEY, 'false');
document.documentElement.classList.remove('dark');
setActiveThemeButton(lightThemeBtn);
} else {
localStorage.removeItem(STORAGE_KEY);
setActiveThemeButton(systemThemeBtn);
}
// Apply reduced motion setting
if (settings.reduced_motion) {
document.documentElement.style.setProperty('--tw-transition-duration', '0s');
document.getElementById('reduced-motion').checked = true;
} else {
document.documentElement.style.removeProperty('--tw-transition-duration');
document.getElementById('reduced-motion').checked = false;
}
// Weitere Einstellungen anwenden
document.getElementById('notify-new-jobs').checked = settings.notifications?.new_jobs ?? true;
document.getElementById('notify-job-updates').checked = settings.notifications?.job_updates ?? true;
document.getElementById('notify-system').checked = settings.notifications?.system ?? true;
document.getElementById('notify-email').checked = settings.notifications?.email ?? false;
document.getElementById('activity-logs').checked = settings.privacy?.activity_logs ?? true;
document.getElementById('two-factor').checked = settings.privacy?.two_factor ?? false;
document.getElementById('auto-logout').value = settings.privacy?.auto_logout ?? '60';
// Kontrast-Einstellungen
if (settings.contrast === 'high') {
localStorage.setItem('myp-contrast', 'high');
document.documentElement.classList.add('high-contrast');
setActiveContrastButton(highContrastBtn);
} else {
localStorage.setItem('myp-contrast', 'normal');
document.documentElement.classList.remove('high-contrast');
setActiveContrastButton(normalContrastBtn);
}
}
function applyDefaultSettings() {
// Apply safe defaults if loading fails
setActiveThemeButton(systemThemeBtn);
setActiveContrastButton(normalContrastBtn);
document.getElementById('reduced-motion').checked = false;
document.getElementById('notify-new-jobs').checked = true;
document.getElementById('notify-job-updates').checked = true;
document.getElementById('notify-system').checked = true;
document.getElementById('notify-email').checked = false;
document.getElementById('activity-logs').checked = true;
document.getElementById('two-factor').checked = false;
document.getElementById('auto-logout').value = '60';
}
// Auto-logout implementation
let logoutTimer = null;
function setupAutoLogout() {
const autoLogoutSelect = document.getElementById('auto-logout');
function resetLogoutTimer() {
if (logoutTimer) {
clearTimeout(logoutTimer);
}
const minutes = parseInt(autoLogoutSelect.value);
if (minutes && minutes !== 'never') {
logoutTimer = setTimeout(() => {
if (confirm('Sie werden aufgrund von Inaktivität abgemeldet. Möchten Sie angemeldet bleiben?')) {
resetLogoutTimer();
} else {
window.location.href = '/logout';
}
}, minutes * 60 * 1000);
}
}
// Reset timer on any user activity
['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'].forEach(event => {
document.addEventListener(event, resetLogoutTimer, { passive: true });
});
// Initial setup
resetLogoutTimer();
// Update timer when setting changes
autoLogoutSelect.addEventListener('change', resetLogoutTimer);
}
// Enhanced toggle switches with keyboard support
function enhanceToggleSwitches() {
document.querySelectorAll('.toggle-checkbox').forEach(checkbox => {
// Add keyboard support
checkbox.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
// Enhanced change handler with debouncing
let changeTimeout;
checkbox.addEventListener('change', function() {
clearTimeout(changeTimeout);
changeTimeout = setTimeout(() => {
const settingName = this.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
const status = this.checked ? 'aktiviert' : 'deaktiviert';
// Visual feedback
const label = this.nextElementSibling;
if (label) {
label.style.transform = 'scale(1.05)';
setTimeout(() => {
label.style.transform = '';
}, 150);
}
// Auto-save individual settings
saveIndividualSetting(this.name, this.checked);
showFlashMessage(`${settingName} wurde ${status}`, 'info');
}, 300); // Debounce to prevent spam
});
});
}
// Save individual settings for immediate feedback
async function saveIndividualSetting(settingName, value) {
try {
const response = await fetch('/api/user/setting', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
},
body: JSON.stringify({ [settingName]: value })
});
if (!response.ok) {
throw new Error('Fehler beim Speichern der Einstellung');
}
// Update cache
const cached = localStorage.getItem('myp-settings-cache');
if (cached) {
try {
const settings = JSON.parse(cached);
// Update the specific setting in cache
const keys = settingName.split('.');
let current = settings;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
settings.timestamp = new Date().toISOString();
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
} catch (e) {
localStorage.removeItem('myp-settings-cache');
}
}
} catch (error) {
console.error('Fehler beim Speichern der Einzeleinstellung:', error);
}
}
// Performance optimization: Intersection Observer for animations
function setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('in-view');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.glass-card').forEach(card => {
observer.observe(card);
});
}
// Initialize all enhanced features
function initializeEnhancedFeatures() {
setupAutoLogout();
enhanceToggleSwitches();
setupIntersectionObserver();
setupNavigationLinks();
}
// Setup navigation links
function setupNavigationLinks() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function(e) {
// If it's a real link to another page, don't preventDefault
if (this.getAttribute('href').startsWith('#')) {
e.preventDefault();
// Update active state
navItems.forEach(navItem => {
navItem.classList.remove('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
navItem.classList.add('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
});
this.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
this.classList.remove('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
// Scroll to section with offset for header
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.querySelector(`[id="${targetId}"]`) ||
document.querySelector(`h2:contains("${targetId}")`);
if (targetElement) {
const offsetTop = targetElement.offsetTop - 100; // Account for header
window.scrollTo({
top: offsetTop,
behavior: 'smooth'
});
}
}
});
});
}
// Helper function to show flash messages
function showFlashMessage(message, type = 'info') {
// Use the global toast manager if available
if (window.showToast) {
window.showToast(message, type);
} else if (window.MYP && window.MYP.UI && window.MYP.UI.ToastManager) {
const toast = new window.MYP.UI.ToastManager();
toast.show(message, type);
} else {
alert(message);
// Fallback to simple notification
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 p-4 rounded-lg text-white z-50 ${
type === 'success' ? 'bg-green-500' :
type === 'error' ? 'bg-red-500' :
type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'
}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => document.body.removeChild(notification), 300);
}, 3000);
}
}
// Navigation links
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function(e) {
// If it's a real link to another page, don't preventDefault
if (this.getAttribute('href').startsWith('#')) {
e.preventDefault();
// Update active state
navItems.forEach(navItem => {
navItem.classList.remove('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
navItem.classList.add('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
});
this.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
this.classList.remove('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
// Scroll to section
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.querySelector(`[id="${targetId}"]`) ||
document.querySelector(`h2:contains("${targetId}")`);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' });
}
}
});
});
// Initialize everything
loadUserSettings();
initializeEnhancedFeatures();
});
</script>
<style>
/* Glass Card Effect */
.glass-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(229, 231, 235, 0.8);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 1.5rem;
transition: all 0.3s ease;
}
.dark .glass-card {
background: rgba(30, 41, 59, 0.8);
border-color: rgba(100, 116, 139, 0.3);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.glass-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.dark .glass-card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
}
/* Loading State Animations */
.settings-loading {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.settings-loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Enhanced Toggle Switches */
.toggle-checkbox {
transition: all 0.3s ease;
}
/* Toggle Switch Styling */
.toggle-checkbox:checked {
right: 0;
border-color: #60a5fa;
@apply bg-blue-500;
border-color: #3b82f6;
background-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.toggle-checkbox:checked + .toggle-label {
@apply bg-blue-500 dark:bg-blue-600;
background-color: #3b82f6;
}
.dark .toggle-checkbox:checked + .toggle-label {
background-color: #2563eb;
}
.toggle-label {
transition: background-color 0.3s ease;
transition: all 0.3s ease;
}
.toggle-checkbox:focus + .toggle-label {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* Button states */
.theme-btn.active,
.contrast-btn.active {
box-shadow: 0 0 0 2px #3b82f6;
}
/* High contrast mode styles */
.high-contrast {
--tw-text-opacity: 1;
}
.high-contrast * {
outline: 2px solid transparent;
outline-offset: 2px;
}
.high-contrast button:focus,
.high-contrast input:focus,
.high-contrast select:focus {
outline: 3px solid #000 !important;
outline-offset: 2px;
}
.dark.high-contrast button:focus,
.dark.high-contrast input:focus,
.dark.high-contrast select:focus {
outline: 3px solid #fff !important;
}
/* Accessibility improvements */
@media (prefers-reduced-motion: reduce) {
.glass-card,
.toggle-checkbox,
.toggle-label {
transition: none !important;
}
.settings-loading::after {
animation: none !important;
}
}
/* Success feedback animation */
.settings-saved {
animation: settingsSaved 0.6s ease-in-out;
}
@keyframes settingsSaved {
0% { transform: scale(1); }
50% { transform: scale(1.02); background-color: rgba(34, 197, 94, 0.1); }
100% { transform: scale(1); }
}
/* Smooth section transitions */
.settings-section {
scroll-margin-top: 2rem;
}
</style>
{% endblock %}