Add user authentication and registration features
- Implement login and registration HTML templates for user authentication. - Create user management functions in user.py for handling user data, including adding, deleting, and updating users. - Introduce password hashing and strength checking for secure user credentials. - Add terms of service and privacy policy pages to the website. - Enhance main page with dynamic content based on user session status.
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* iOS Specific Enhancements for Inventarsystem
|
||||
*
|
||||
* This file contains fixes specific to iOS devices for the Inventarsystem application,
|
||||
* focusing on upload and duplication functionality.
|
||||
*
|
||||
* Copyright 2025 Maximilian Gründinger
|
||||
* Licensed under the Apache License, Version 2.0
|
||||
*/
|
||||
|
||||
// Initialize when document is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (isIOSDevice()) {
|
||||
console.log('iOS device detected, applying iOS-specific enhancements');
|
||||
applyIOSFixes();
|
||||
}
|
||||
});
|
||||
|
||||
// Apply all iOS-specific fixes
|
||||
function applyIOSFixes() {
|
||||
// Fix file input issues
|
||||
fixIOSFileInputs();
|
||||
|
||||
// Fix form submission
|
||||
fixIOSFormSubmission();
|
||||
|
||||
// Fix image handling
|
||||
fixIOSImageHandling();
|
||||
|
||||
// Fix duplication
|
||||
fixIOSDuplication();
|
||||
|
||||
// Apply CSS fixes
|
||||
applyIOSCSSFixes();
|
||||
}
|
||||
|
||||
// Fix iOS file input issues
|
||||
function fixIOSFileInputs() {
|
||||
// Find all file inputs
|
||||
const fileInputs = document.querySelectorAll('input[type="file"]');
|
||||
|
||||
fileInputs.forEach(input => {
|
||||
// Create a touch-friendly wrapper
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'ios-file-input-wrapper';
|
||||
wrapper.style.position = 'relative';
|
||||
|
||||
// Style the original input to be more touch-friendly
|
||||
input.style.padding = '20px 0';
|
||||
|
||||
// Wrap the input
|
||||
if (input.parentNode) {
|
||||
input.parentNode.insertBefore(wrapper, input);
|
||||
wrapper.appendChild(input);
|
||||
|
||||
// Add a visible button for better touch target
|
||||
const fakeButton = document.createElement('div');
|
||||
fakeButton.className = 'ios-file-button';
|
||||
fakeButton.textContent = 'Wählen Sie Bilder/Videos aus';
|
||||
fakeButton.style.display = 'inline-block';
|
||||
fakeButton.style.padding = '10px 15px';
|
||||
fakeButton.style.backgroundColor = '#4CAF50';
|
||||
fakeButton.style.color = 'white';
|
||||
fakeButton.style.borderRadius = '4px';
|
||||
fakeButton.style.textAlign = 'center';
|
||||
fakeButton.style.margin = '10px 0';
|
||||
fakeButton.style.fontWeight = 'bold';
|
||||
wrapper.appendChild(fakeButton);
|
||||
|
||||
// Make sure the real input covers the fake button
|
||||
input.style.position = 'absolute';
|
||||
input.style.top = '0';
|
||||
input.style.left = '0';
|
||||
input.style.width = '100%';
|
||||
input.style.height = '100%';
|
||||
input.style.opacity = '0';
|
||||
input.style.zIndex = '10';
|
||||
}
|
||||
|
||||
// Add custom event handling
|
||||
input.addEventListener('change', function() {
|
||||
// Update visual feedback
|
||||
const fileCount = this.files ? this.files.length : 0;
|
||||
let fileText = fakeButton.textContent;
|
||||
|
||||
if (fileCount > 0) {
|
||||
fileText = `${fileCount} Datei${fileCount !== 1 ? 'en' : ''} ausgewählt`;
|
||||
fakeButton.style.backgroundColor = '#2E7D32';
|
||||
} else {
|
||||
fileText = 'Wählen Sie Bilder/Videos aus';
|
||||
fakeButton.style.backgroundColor = '#4CAF50';
|
||||
}
|
||||
|
||||
fakeButton.textContent = fileText;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fix iOS form submission issues
|
||||
function fixIOSFormSubmission() {
|
||||
// Find upload forms
|
||||
const uploadForms = document.querySelectorAll('form[action*="upload_item"]');
|
||||
|
||||
uploadForms.forEach(form => {
|
||||
// Add an iOS submission handler
|
||||
form.addEventListener('submit', function(e) {
|
||||
// Only intercept on iOS
|
||||
if (!isIOSDevice()) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Show that we're processing
|
||||
const loadingMessage = document.createElement('div');
|
||||
loadingMessage.className = 'ios-loading-message';
|
||||
loadingMessage.textContent = 'Wird verarbeitet...';
|
||||
loadingMessage.style.padding = '10px';
|
||||
loadingMessage.style.backgroundColor = '#f0f0f0';
|
||||
loadingMessage.style.borderRadius = '5px';
|
||||
loadingMessage.style.margin = '10px 0';
|
||||
loadingMessage.style.textAlign = 'center';
|
||||
form.appendChild(loadingMessage);
|
||||
|
||||
// Disable the submit button
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
if (submitButton) {
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
|
||||
// Use timeout to allow UI to update before heavy processing
|
||||
setTimeout(() => {
|
||||
// Gather form data with special handling for iOS
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Add iOS flag
|
||||
formData.append('is_ios', 'true');
|
||||
|
||||
// Submit with fetch API which works better on iOS
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Remove loading message
|
||||
if (loadingMessage.parentNode) {
|
||||
loadingMessage.parentNode.removeChild(loadingMessage);
|
||||
}
|
||||
|
||||
// Re-enable submit button
|
||||
if (submitButton) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
// Show success and redirect
|
||||
alert(data.message || 'Element erfolgreich hinzugefügt');
|
||||
|
||||
// Redirect to the appropriate page
|
||||
if (data.itemId) {
|
||||
window.location.href = `/home_admin?highlight_item=${data.itemId}`;
|
||||
} else {
|
||||
window.location.href = '/home_admin';
|
||||
}
|
||||
} else {
|
||||
// Show error
|
||||
alert(data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('iOS form submission error:', error);
|
||||
|
||||
// Remove loading message
|
||||
if (loadingMessage.parentNode) {
|
||||
loadingMessage.parentNode.removeChild(loadingMessage);
|
||||
}
|
||||
|
||||
// Re-enable submit button
|
||||
if (submitButton) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
|
||||
// Show error
|
||||
alert('Ein Netzwerkfehler ist aufgetreten. Bitte versuchen Sie es erneut.');
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fix iOS image handling issues
|
||||
function fixIOSImageHandling() {
|
||||
// Reduce image preview quality on iOS
|
||||
if (typeof ImagePreviewGenerator !== 'undefined') {
|
||||
// Override the compression quality for iOS
|
||||
const originalCompressImage = ImagePreviewGenerator.prototype._compressImage;
|
||||
|
||||
if (originalCompressImage) {
|
||||
ImagePreviewGenerator.prototype._compressImage = function(dataUrl) {
|
||||
// Lower quality for iOS
|
||||
this.quality = 0.5;
|
||||
this.maxWidth = 600;
|
||||
this.maxHeight = 600;
|
||||
|
||||
return originalCompressImage.call(this, dataUrl);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fix image preview display
|
||||
document.querySelectorAll('.image-preview-container').forEach(container => {
|
||||
// Add iOS-specific styling
|
||||
container.style.webkitOverflowScrolling = 'touch';
|
||||
container.style.maxHeight = '150px';
|
||||
container.style.overflow = 'auto';
|
||||
});
|
||||
}
|
||||
|
||||
// Fix iOS duplication issues
|
||||
function fixIOSDuplication() {
|
||||
// Override duplicateItem function
|
||||
if (typeof duplicateItem !== 'undefined') {
|
||||
const originalDuplicateItem = duplicateItem;
|
||||
|
||||
duplicateItem = function(itemId) {
|
||||
// Use localStorage on iOS instead of sessionStorage
|
||||
const useFunction = originalDuplicateItem;
|
||||
|
||||
useFunction(itemId);
|
||||
|
||||
// Additional iOS-specific handling
|
||||
const checkForSessionData = setInterval(() => {
|
||||
const sessionData = sessionStorage.getItem('duplicateItemData');
|
||||
if (sessionData) {
|
||||
// Copy from sessionStorage to localStorage
|
||||
try {
|
||||
localStorage.setItem('duplicateItemData', sessionData);
|
||||
localStorage.setItem('duplicateItemTimestamp', Date.now().toString());
|
||||
console.log('Copied duplicate data to localStorage for iOS reliability');
|
||||
} catch (e) {
|
||||
console.warn('Failed to copy to localStorage:', e);
|
||||
}
|
||||
|
||||
clearInterval(checkForSessionData);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Clear interval after 3 seconds regardless
|
||||
setTimeout(() => clearInterval(checkForSessionData), 3000);
|
||||
};
|
||||
}
|
||||
|
||||
// Fix prefill function
|
||||
if (typeof prefillFormWithDuplicateData !== 'undefined') {
|
||||
const originalPrefill = prefillFormWithDuplicateData;
|
||||
|
||||
prefillFormWithDuplicateData = function() {
|
||||
// Try to get data from localStorage first
|
||||
let data = null;
|
||||
try {
|
||||
const localData = localStorage.getItem('duplicateItemData');
|
||||
if (localData) {
|
||||
data = JSON.parse(localData);
|
||||
localStorage.removeItem('duplicateItemData');
|
||||
localStorage.removeItem('duplicateItemTimestamp');
|
||||
console.log('Using duplicate data from localStorage');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error accessing localStorage:', e);
|
||||
}
|
||||
|
||||
// If we got data from localStorage, manually populate the form
|
||||
if (data) {
|
||||
console.log('Manually populating form with iOS optimization');
|
||||
|
||||
// Basic fields
|
||||
document.getElementById('name').value = data.name || '';
|
||||
document.getElementById('beschreibung').value = data.description || '';
|
||||
|
||||
// Handle filters with a delay
|
||||
setTimeout(() => {
|
||||
// Filter 1
|
||||
if (data.filter1 && Array.isArray(data.filter1)) {
|
||||
data.filter1.forEach((val, idx) => {
|
||||
const field = document.getElementById(`filter1-${idx+1}`);
|
||||
if (field && val) field.value = val;
|
||||
});
|
||||
}
|
||||
|
||||
// Filter 2
|
||||
if (data.filter2 && Array.isArray(data.filter2)) {
|
||||
data.filter2.forEach((val, idx) => {
|
||||
const field = document.getElementById(`filter2-${idx+1}`);
|
||||
if (field && val) field.value = val;
|
||||
});
|
||||
}
|
||||
|
||||
// Filter 3
|
||||
if (data.filter3 && Array.isArray(data.filter3)) {
|
||||
data.filter3.forEach((val, idx) => {
|
||||
const field = document.getElementById(`filter3-${idx+1}`);
|
||||
if (field && val) field.value = val;
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// Location
|
||||
setTimeout(() => {
|
||||
const location = document.getElementById('ort');
|
||||
if (location && data.location) location.value = data.location;
|
||||
}, 400);
|
||||
|
||||
// Year and cost
|
||||
const year = document.getElementById('anschaffungsjahr');
|
||||
if (year && data.year) year.value = data.year;
|
||||
|
||||
const cost = document.getElementById('anschaffungskosten');
|
||||
if (cost && data.cost) cost.value = data.cost;
|
||||
|
||||
// Handle images - limited for iOS
|
||||
const form = document.querySelector('form');
|
||||
if (form && data.images && Array.isArray(data.images)) {
|
||||
// Add hidden field to indicate duplication
|
||||
const isDuplicatingField = document.createElement('input');
|
||||
isDuplicatingField.type = 'hidden';
|
||||
isDuplicatingField.name = 'is_duplicating';
|
||||
isDuplicatingField.value = 'true';
|
||||
form.appendChild(isDuplicatingField);
|
||||
|
||||
// Only use first 3 images on iOS for performance
|
||||
const limitedImages = data.images.slice(0, 3);
|
||||
|
||||
limitedImages.forEach(imageName => {
|
||||
const imageField = document.createElement('input');
|
||||
imageField.type = 'hidden';
|
||||
imageField.name = 'duplicate_images';
|
||||
imageField.value = imageName;
|
||||
form.appendChild(imageField);
|
||||
});
|
||||
|
||||
// Add a message about limited images
|
||||
if (data.images.length > 3) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.style.margin = '10px 0';
|
||||
messageDiv.style.padding = '10px';
|
||||
messageDiv.style.backgroundColor = '#fff3cd';
|
||||
messageDiv.style.borderRadius = '5px';
|
||||
messageDiv.style.color = '#856404';
|
||||
messageDiv.textContent = `Aus Leistungsgründen werden nur ${limitedImages.length} von ${data.images.length} Bildern verwendet`;
|
||||
|
||||
const fileInput = document.getElementById('images');
|
||||
if (fileInput && fileInput.parentNode) {
|
||||
fileInput.parentNode.appendChild(messageDiv);
|
||||
} else {
|
||||
form.appendChild(messageDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use original function as fallback
|
||||
originalPrefill();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Apply iOS-specific CSS fixes
|
||||
function applyIOSCSSFixes() {
|
||||
// Create a style element
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
/* Improve touch targets for iOS */
|
||||
button, select, input[type="submit"], .ausleihen, .edit-button,
|
||||
.delete-button, .duplicate-button, .details-button {
|
||||
min-height: 44px !important;
|
||||
padding: 10px 15px !important;
|
||||
}
|
||||
|
||||
/* Improve scrolling on iOS */
|
||||
.items-container, .filter-options, .modal-content {
|
||||
-webkit-overflow-scrolling: touch !important;
|
||||
}
|
||||
|
||||
/* Prevent zoom on inputs */
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
/* Fix iOS image display */
|
||||
.item-image {
|
||||
-webkit-transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* Improve form fields on iOS */
|
||||
.form-group {
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
Reference in New Issue
Block a user