From cab561a20e8071d952fef7e04d6539567f62a383 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Sun, 8 Feb 2026 13:06:47 -0800 Subject: [PATCH] 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 --- portal/menu-builder.html | 124 ++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/portal/menu-builder.html b/portal/menu-builder.html index a24ae32..20111c5 100644 --- a/portal/menu-builder.html +++ b/portal/menu-builder.html @@ -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(); },