Fix photo upload on mobile - prevent page reload issue

- Use persistent file input instead of dynamically created one
- Store pending upload info in sessionStorage for mobile camera flow
- Use simpler accept="image/*" for better mobile compatibility
- Handle case where page context is lost after camera returns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-08 13:06:47 -08:00
parent 9fc984bea3
commit cab561a20e

View file

@ -2804,7 +2804,10 @@
}
},
// Upload photo
// Pending photo upload info (for mobile camera flow)
_pendingPhotoUpload: null,
// Upload photo - mobile-friendly version
uploadPhoto(itemId) {
// Find the item and check it has a database ID
let targetItem = null;
@ -2817,52 +2820,81 @@
return;
}
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/jpeg,image/png,image/gif,image/webp,image/heic,image/heif,.heic,.heif';
input.capture = 'environment'; // Prefer rear camera on mobile
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
// Be permissive with file types - server will validate
// Mobile cameras may report inconsistent MIME types
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/heic', 'image/heif', ''];
if (file.type && !validTypes.includes(file.type) && !file.type.startsWith('image/')) {
this.toast('Please select a valid image file', 'error');
return;
}
if (file.size > 5 * 1024 * 1024) {
this.toast('Image must be under 5MB', 'error');
return;
}
this.toast('Uploading photo...', 'info');
try {
const formData = new FormData();
formData.append('photo', file);
formData.append('ItemID', targetItem.dbId);
const response = await fetch(`${this.config.apiBaseUrl}/menu/uploadItemPhoto.cfm`, {
method: 'POST',
body: formData,
credentials: 'include'
});
const data = await response.json();
if (data.OK) {
targetItem.imageUrl = data.IMAGEURL + '?t=' + Date.now();
this.render();
this.toast('Photo uploaded!', 'success');
} else {
this.toast(data.MESSAGE || 'Failed to upload photo', 'error');
}
} catch (err) {
console.error('Photo upload error:', err);
this.toast('Failed to upload photo', 'error');
}
// Store pending upload info (survives page context issues on mobile)
this._pendingPhotoUpload = {
itemId: itemId,
dbId: targetItem.dbId
};
sessionStorage.setItem('pendingPhotoUpload', JSON.stringify(this._pendingPhotoUpload));
// Get or create persistent file input (mobile browsers handle these better)
let input = document.getElementById('photoUploadInput');
if (!input) {
input = document.createElement('input');
input.type = 'file';
input.id = 'photoUploadInput';
input.accept = 'image/*'; // Simpler accept for better mobile compatibility
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
// Get pending upload info
const pending = this._pendingPhotoUpload || JSON.parse(sessionStorage.getItem('pendingPhotoUpload') || 'null');
if (!pending) {
this.toast('Upload context lost - please try again', 'error');
return;
}
if (file.size > 5 * 1024 * 1024) {
this.toast('Image must be under 5MB', 'error');
return;
}
this.toast('Uploading photo...', 'info');
try {
const formData = new FormData();
formData.append('photo', file);
formData.append('ItemID', pending.dbId);
const response = await fetch(`${this.config.apiBaseUrl}/menu/uploadItemPhoto.cfm`, {
method: 'POST',
body: formData,
credentials: 'include'
});
const data = await response.json();
if (data.OK) {
// Find and update the item
for (const cat of this.menu.categories) {
const item = cat.items.find(i => i.id === pending.itemId);
if (item) {
item.imageUrl = data.IMAGEURL + '?t=' + Date.now();
break;
}
}
this.render();
this.toast('Photo uploaded!', 'success');
} else {
this.toast(data.MESSAGE || 'Failed to upload photo', 'error');
}
} catch (err) {
console.error('Photo upload error:', err);
this.toast('Failed to upload photo', 'error');
}
// Clean up
sessionStorage.removeItem('pendingPhotoUpload');
this._pendingPhotoUpload = null;
input.value = ''; // Reset for next upload
});
}
// Reset and trigger
input.value = '';
input.click();
},