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) { uploadPhoto(itemId) {
// Find the item and check it has a database ID // Find the item and check it has a database ID
let targetItem = null; let targetItem = null;
@ -2817,21 +2820,34 @@
return; return;
} }
const input = document.createElement('input'); // 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.type = 'file';
input.accept = 'image/jpeg,image/png,image/gif,image/webp,image/heic,image/heif,.heic,.heif'; input.id = 'photoUploadInput';
input.capture = 'environment'; // Prefer rear camera on mobile input.accept = 'image/*'; // Simpler accept for better mobile compatibility
input.onchange = async (e) => { input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file) return; if (!file) return;
// Be permissive with file types - server will validate // Get pending upload info
// Mobile cameras may report inconsistent MIME types const pending = this._pendingPhotoUpload || JSON.parse(sessionStorage.getItem('pendingPhotoUpload') || 'null');
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/heic', 'image/heif', '']; if (!pending) {
if (file.type && !validTypes.includes(file.type) && !file.type.startsWith('image/')) { this.toast('Upload context lost - please try again', 'error');
this.toast('Please select a valid image file', 'error');
return; return;
} }
if (file.size > 5 * 1024 * 1024) { if (file.size > 5 * 1024 * 1024) {
this.toast('Image must be under 5MB', 'error'); this.toast('Image must be under 5MB', 'error');
return; return;
@ -2842,7 +2858,7 @@
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append('photo', file); formData.append('photo', file);
formData.append('ItemID', targetItem.dbId); formData.append('ItemID', pending.dbId);
const response = await fetch(`${this.config.apiBaseUrl}/menu/uploadItemPhoto.cfm`, { const response = await fetch(`${this.config.apiBaseUrl}/menu/uploadItemPhoto.cfm`, {
method: 'POST', method: 'POST',
@ -2852,7 +2868,14 @@
const data = await response.json(); const data = await response.json();
if (data.OK) { if (data.OK) {
targetItem.imageUrl = data.IMAGEURL + '?t=' + Date.now(); // 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.render();
this.toast('Photo uploaded!', 'success'); this.toast('Photo uploaded!', 'success');
} else { } else {
@ -2862,7 +2885,16 @@
console.error('Photo upload error:', err); console.error('Photo upload error:', err);
this.toast('Failed to upload photo', 'error'); 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(); input.click();
}, },