TryAITryAITry TryAI

Shared chat

Create Google Sheets PWA

i need to turn this google sheet into pwa app, i will need to upload and display two photos, vin photo and vehicle photo. service column will need a drop down for 5 services. Basic recon, price $30. Full LMC $130. LMC Detail SUV $170. Superior recon $60. Superior recon SUV $85
Claude Fable 5
Great use case — you can do this with **Google Apps Script as the backend** (writes rows to your Sheet, saves photos to Drive) and a simple **PWA frontend** (installable, works on a phone camera). Here's a complete working setup. ## Architecture - **Google Sheet** = your database (columns: Timestamp, VIN, Service, Price, VIN Photo URL, Vehicle Photo URL) - **Apps Script Web App** = API that receives form data + photos, saves photos to a Drive folder, appends a row - **PWA** = HTML form with camera/photo inputs, service dropdown with auto-price, manifest + service worker so it installs like an app ## 1. Apps Script backend (`Code.gs`) In your Sheet: **Extensions → Apps Script**, paste this: ```javascript const SHEET_NAME = 'Sheet1'; const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID'; // create a Drive folder for photos function doPost(e) { const data = JSON.parse(e.postData.contents); const folder = DriveApp.getFolderById(FOLDER_ID); const vinUrl = saveImage(folder, data.vinPhoto, `VIN_${data.vin}`); const vehicleUrl = saveImage(folder, data.vehiclePhoto, `VEHICLE_${data.vin}`); const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME); sheet.appendRow([new Date(), data.vin, data.service, data.price, vinUrl, vehicleUrl]); return ContentService.createTextOutput(JSON.stringify({ ok: true })) .setMimeType(ContentService.MimeType.JSON); } function saveImage(folder, base64, name) { if (!base64) return ''; const bytes = Utilities.base64Decode(base64.split(',')[1]); const blob = Utilities.newBlob(bytes, 'image/jpeg', name + '.jpg'); const file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return `https://drive.google.com/uc?id=${file.getId()}`; } // Optional: fetch rows so the app can display entries function doGet() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME); const rows = sheet.getDataRange().getValues(); return ContentService.createTextOutput(JSON.stringify(rows)) .setMimeType(ContentService.MimeType.JSON); } ``` Then: **Deploy → New deployment → Web app** → Execute as *Me*, access *Anyone*. Copy the web app URL. ## 2. PWA frontend (`index.html`) Host this on GitHub Pages, Netlify, or Vercel (free — PWAs need HTTPS): ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Recon Tracker</title> <link rel="manifest" href="manifest.json"> <meta name="theme-color" content="#1a73e8"> <style> body { font-family: system-ui; max-width: 480px; margin: auto; padding: 16px; } label { display: block; margin-top: 12px; font-weight: 600; } input, select, button { width: 100%; padding: 10px; margin-top: 4px; box-sizing: border-box; } img.preview { max-width: 100%; margin-top: 8px; border-radius: 8px; display: none; } button { background: #1a73e8; color: #fff; border: 0; border-radius: 8px; margin-top: 16px; font-size: 16px; } #price { font-size: 20px; font-weight: bold; margin-top: 8px; } </style> </head> <body> <h2>Vehicle Recon Entry</h2> <label>VIN</label> <input id="vin" placeholder="Enter VIN"> <label>Service</label> <select id="service"> <option value="" data-price="0">Select service…</option> <option data-price="30">Basic Recon — $30</option> <option data-price="130">Full LMC — $130</option> <option data-price="170">LMC Detail SUV — $170</option> <option data-price="60">Superior Recon — $60</option> <option data-price="85">Superior Recon SUV — $85</option> </select> <div id="price"></div> <label>VIN Photo</label> <input type="file" id="vinPhoto" accept="image/*" capture="environment"> <img class="preview" id="vinPreview"> <label>Vehicle Photo</label> <input type="file" id="vehiclePhoto" accept="image/*" capture="environment"> <img class="preview" id="vehiclePreview"> <button id="submit">Save Entry</button> <script> const API_URL = 'YOUR_APPS_SCRIPT_WEB_APP_URL'; const serviceEl = document.getElementById('service'); serviceEl.onchange = () => { const p = serviceEl.selectedOptions[0].dataset.price; document.getElementById('price').textContent = p > 0 ? `Price: $${p}` : ''; }; function readAsCompressedBase64(file, previewId) { return new Promise(resolve => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const scale = Math.min(1, 1280 / img.width); canvas.width = img.width * scale; canvas.height = img.height * scale; canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/jpeg', 0.7); const prev = document.getElementById(previewId); prev.src = dataUrl; prev.style.display = 'block'; resolve(dataUrl); }; img.src = URL.createObjectURL(file); }); } let vinPhotoData = '', vehiclePhotoData = ''; document.getElementById('vinPhoto').onchange = async e => vinPhotoData = await readAsCompressedBase64(e.target.files[0], 'vinPreview'); document.getElementById('vehiclePhoto').onchange = async e => vehiclePhotoData = await readAsCompressedBase64(e.target.files[0], 'vehiclePreview'); document.getElementById('submit').onclick = async () => { const vin = document.getElementById('vin').value.trim(); const opt = serviceEl.selectedOptions[0]; if (!vin || !opt.dataset.price || opt.dataset.price === '0') { alert('Enter VIN and select a service'); return; } const btn = document.getElementById('submit'); btn.disabled = true; btn.textContent = 'Saving…'; await fetch(API_URL, { method: 'POST', body: JSON.stringify({ vin, service: opt.textContent.split('—')[0].trim(), price: opt.dataset.price, vinPhoto: vinPhotoData, vehiclePhoto: vehiclePhotoData }) }); btn.disabled = false; btn.textContent = 'Save Entry'; alert('Saved!'); location.reload(); }; if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js'); </script> </body> </html> ``` ## 3. PWA files `manifest.json`: ```json { "name": "Recon Tracker", "short_name": "Recon", "start_url": ".", "display": "standalone", "background_color": "#ffffff", "theme_color": "#1a73e8", "icons": [ { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ``` `sw.js`: ```javascript const CACHE = 'recon-v1'; self.addEventListener('install', e => { e.waitUntil(caches.open(CACHE).then(c => c.addAll(['.', 'index.html', 'manifest.json']))); }); self.addEventListener('fetch', e => { e.respondWith(caches.match(e.request).then(r => r || fetch(e.request))); }); ``` ## Setup checklist 1. Create a Drive folder for photos → copy its ID into `FOLDER_ID` 2. Paste `Code.gs` into Apps Script → deploy as web app → copy URL into `API_URL` 3. Upload the 3 frontend files + two icon PNGs to GitHub Pages/Netlify 4. Open on your phone → "Add to Home Screen" → it installs as an app **Notes:** - `capture="environment"` opens the rear camera directly on phones — perfect for VIN shots. - Photos are compressed to ~1280px JPEG before upload to keep things fast. - Want offline queueing (save entries with no signal, sync later)? That's an IndexedDB addition to the service worker — let me know and I'll extend it. If you share your sheet's exact column headers, I'll adjust the `appendRow` order to match.
Claude Fable 53.5k tok116 tok/sTTFT 5.2s35.7s
how would I display actual photos, reduced size image instead of links?
Claude Fable 5
You have two places to do this — in the **Google Sheet itself** and in the **app's list view**. Here's both. ## 1. Show thumbnails in the Google Sheet The trick: `drive.google.com/uc?id=...` links often **don't render** in Sheets' `IMAGE()` formula. Use Drive's **thumbnail endpoint** instead — it also gives you the reduced size for free: ``` https://drive.google.com/thumbnail?id=FILE_ID&sz=w200 ``` Update `saveImage()` in your Apps Script to write an `IMAGE()` formula instead of a bare URL: ```javascript function saveImage(folder, base64, name) { if (!base64) return ''; const bytes = Utilities.base64Decode(base64.split(',')[1]); const blob = Utilities.newBlob(bytes, 'image/jpeg', name + '.jpg'); const file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return file.getId(); // return just the ID now } ``` Then in `doPost`, write the formulas with `setFormula` instead of plain `appendRow`: ```javascript function doPost(e) { const data = JSON.parse(e.postData.contents); const folder = DriveApp.getFolderById(FOLDER_ID); const vinId = saveImage(folder, data.vinPhoto, `VIN_${data.vin}`); const vehicleId = saveImage(folder, data.vehiclePhoto, `VEHICLE_${data.vin}`); const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME); const row = sheet.getLastRow() + 1; sheet.getRange(row, 1, 1, 4).setValues([[new Date(), data.vin, data.service, data.price]]); if (vinId) sheet.getRange(row, 5).setFormula( `=IMAGE("https://drive.google.com/thumbnail?id=${vinId}&sz=w200")`); if (vehicleId) sheet.getRange(row, 6).setFormula( `=IMAGE("https://drive.google.com/thumbnail?id=${vehicleId}&sz=w200")`); // optional: store raw IDs in hidden columns G/H so the app can still fetch them sheet.getRange(row, 7, 1, 2).setValues([[vinId, vehicleId]]); // make the row tall enough to see the thumbnails sheet.setRowHeight(row, 80); return ContentService.createTextOutput(JSON.stringify({ ok: true })) .setMimeType(ContentService.MimeType.JSON); } ``` **Tips:** - `sz=w200` = 200px wide. Use `w400` for bigger, `w100` for smaller. - `IMAGE(url)` defaults to "fit to cell," so the row height / column width controls display size. `IMAGE(url, 4, 80, 80)` forces an exact 80×80 px if you prefer. - Keeping the raw file IDs in columns G/H means your app doesn't have to parse formulas. ## 2. Show thumbnails in the PWA (list/gallery view) If you add a list screen that reads from `doGet()`, render the IDs as `<img>` tags using the same thumbnail URL, with a click-through to the full image: ```javascript async function loadEntries() { const rows = await (await fetch(API_URL)).json(); const list = document.getElementById('entries'); list.innerHTML = rows.slice(1).map(r => { const [ts, vin, service, price, , , vinId, vehicleId] = r; const thumb = id => id ? `<a href="https://drive.google.com/file/d/${id}/view" target="_blank"> <img src="https://drive.google.com/thumbnail?id=${id}&sz=w200" loading="lazy"> </a>` : ''; return `<div class="card"> <strong>${vin}</strong> — ${service} ($${price}) <div class="thumbs">${thumb(vinId)}${thumb(vehicleId)}</div> </div>`; }).join(''); } ``` With a little CSS: ```css .card { border: 1px solid #ddd; border-radius: 8px; padding: 10px; margin-top: 10px; } .thumbs img { width: 120px; height: 90px; object-fit: cover; border-radius: 6px; margin-right: 8px; } ``` - `loading="lazy"` keeps the list fast when you have many rows. - Tapping a thumbnail opens the full-resolution photo in Drive. - Photos must be shared "Anyone with link" (your `setSharing` call already does this) or the thumbnails will show as broken images. **One caveat:** if you have existing rows with the old `uc?id=` URLs, you can convert them once with a formula like: ``` =IMAGE(SUBSTITUTE(E2, "uc?id=", "thumbnail?id=") & "&sz=w200") ``` Want me to add the full gallery/list screen to the PWA so you can browse and search past entries by VIN?
Claude Fable 52.0k tok115 tok/sTTFT 6.7s23.9s
ok i have this working, i was able to take and upload pictures, input vin number and select service i do not see the results on the google sheet though. how would i generate the results to the spreadsheet, or what am i missing ?
GPT-5.5
Most likely one of these is missing: 1. **Your Apps Script is saving photos to Drive but not appending the row** 2. **`SHEET_NAME` does not match your actual Google Sheet tab name** 3. **The script is not connected to the correct spreadsheet** 4. **You edited Apps Script but did not redeploy a new Web App version** 5. **The PWA is using the wrong Apps Script URL** 6. **The request is failing silently** The safest fix is to use the **Spreadsheet ID directly** instead of relying on `getActiveSpreadsheet()`. --- ## Use this Apps Script backend In Apps Script, replace your backend with this version. Change these 3 values: ```javascript const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; const SHEET_NAME = 'Sheet1'; const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID'; ``` Full code: ```javascript const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; const SHEET_NAME = 'Sheet1'; const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID'; const SERVICES = { 'Basic Recon': 30, 'Full LMC': 130, 'LMC Detail SUV': 170, 'Superior Recon': 60, 'Superior Recon SUV': 85 }; function doPost(e) { try { const data = JSON.parse(e.postData.contents); const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`Sheet tab not found: ${SHEET_NAME}`); } setupHeadersIfNeeded(sheet); const folder = DriveApp.getFolderById(FOLDER_ID); const vin = data.vin || ''; const service = data.service || ''; const price = SERVICES[service] || data.price || ''; const vinPhotoId = saveImage(folder, data.vinPhoto, `VIN_${vin}_${Date.now()}`); const vehiclePhotoId = saveImage(folder, data.vehiclePhoto, `VEHICLE_${vin}_${Date.now()}`); const row = sheet.getLastRow() + 1; sheet.getRange(row, 1, 1, 8).setValues([[ new Date(), vin, service, price, vinPhotoId, vehiclePhotoId, '', '' ]]); if (vinPhotoId) { sheet.getRange(row, 7).setFormula( `=IMAGE("https://drive.google.com/thumbnail?id=${vinPhotoId}&sz=w200")` ); } if (vehiclePhotoId) { sheet.getRange(row, 8).setFormula( `=IMAGE("https://drive.google.com/thumbnail?id=${vehiclePhotoId}&sz=w200")` ); } sheet.setRowHeight(row, 100); sheet.setColumnWidth(7, 120); sheet.setColumnWidth(8, 120); return jsonResponse({ ok: true, message: 'Saved to sheet', row, vin, service, price }); } catch (err) { return jsonResponse({ ok: false, error: err.message }); } } function saveImage(folder, base64, name) { if (!base64) return ''; const contentType = base64.substring( base64.indexOf(':') + 1, base64.indexOf(';') ); const bytes = Utilities.base64Decode(base64.split(',')[1]); const blob = Utilities.newBlob(bytes, contentType, name + '.jpg'); const file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return file.getId(); } function setupHeadersIfNeeded(sheet) { if (sheet.getLastRow() === 0) { sheet.appendRow([ 'Timestamp', 'VIN', 'Service', 'Price', 'VIN Photo File ID', 'Vehicle Photo File ID', 'VIN Photo', 'Vehicle Photo' ]); } } function doGet() { return jsonResponse({ ok: true, message: 'Apps Script web app is running' }); } function jsonResponse(obj) { return ContentService .createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); } ``` --- ## Important: get the correct Spreadsheet ID Your Google Sheet URL looks like this: ```text https://docs.google.com/spreadsheets/d/SPREADSHEET_ID_HERE/edit ``` Copy only the long ID between `/d/` and `/edit`. Example: ```javascript const SPREADSHEET_ID = '1abcDEFghiJKLmnopQRSTUVWXYZ123456789'; ``` --- ## Important: `SHEET_NAME` means the bottom tab name This is not the file name. If your bottom tab says: ```text Recon ``` Then use: ```javascript const SHEET_NAME = 'Recon'; ``` If it says: ```text Sheet1 ``` Then use: ```javascript const SHEET_NAME = 'Sheet1'; ``` --- ## Your frontend submit code should look like this Make sure your PWA is sending data to the Apps Script URL. ```javascript document.getElementById('submit').onclick = async () => { const vin = document.getElementById('vin').value.trim(); const serviceEl = document.getElementById('service'); const selectedOption = serviceEl.selectedOptions[0]; const service = selectedOption.value; const price = selectedOption.dataset.price; if (!vin) { alert('Enter VIN'); return; } if (!service) { alert('Select a service'); return; } if (!vinPhotoData) { alert('Take VIN photo'); return; } if (!vehiclePhotoData) { alert('Take vehicle photo'); return; } const btn = document.getElementById('submit'); btn.disabled = true; btn.textContent = 'Saving...'; try { const response = await fetch(API_URL, { method: 'POST', body: JSON.stringify({ vin, service, price, vinPhoto: vinPhotoData, vehiclePhoto: vehiclePhotoData }) }); const result = await response.json(); if (!result.ok) { throw new Error(result.error || 'Unknown error'); } alert(`Saved to Google Sheet. Row ${result.row}`); document.getElementById('vin').value = ''; serviceEl.value = ''; document.getElementById('price').textContent = ''; document.getElementById('vinPhoto').value = ''; document.getElementById('vehiclePhoto').value = ''; document.getElementById('vinPreview').style.display = 'none'; document.getElementById('vehiclePreview').style.display = 'none'; vinPhotoData = ''; vehiclePhotoData = ''; } catch (err) { alert('Save failed: ' + err.message); } btn.disabled = false; btn.textContent = 'Save Entry'; }; ``` Your service dropdown should use clean `value` names: ```html <select id="service"> <option value="" data-price="0">Select service...</option> <option value="Basic Recon" data-price="30">Basic Recon — $30</option> <option value="Full LMC" data-price="130">Full LMC — $130</option> <option value="LMC Detail SUV" data-price="170">LMC Detail SUV — $170</option> <option value="Superior Recon" data-price="60">Superior Recon — $60</option> <option value="Superior Recon SUV" data-price="85">Superior Recon SUV — $85</option> </select> ``` --- ## Critical step: redeploy after editing Apps Script After changing the Apps Script code: 1. Click **Deploy** 2. Click **Manage deployments** 3. Click the pencil/edit icon 4. Under **Version**, choose **New version** 5. Click **Deploy** 6. Copy the Web App URL again if needed Your PWA should use the URL ending in: ```text /exec ``` Not: ```text /dev ``` Example in your PWA: ```javascript const API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec'; ``` --- ## How to check what is failing In Apps Script: 1. Open your Apps Script project 2. Click **Executions** on the left 3. Submit a test entry from the PWA 4. Refresh **Executions** 5. Open the latest run If it failed, you will usually see one of these: ### Wrong sheet tab name ```text Sheet tab not found: Sheet1 ``` Fix: ```javascript const SHEET_NAME = 'Your Actual Tab Name'; ``` ### Wrong spreadsheet ID ```text Exception: Unexpected error while getting the method or property openById ``` Fix `SPREADSHEET_ID`. ### Wrong Drive folder ID ```text Exception: Unexpected error while getting the method or property getFolderById ``` Fix `FOLDER_ID`. ### Permission issue Redeploy the Web App as: - **Execute as:** Me - **Who has access:** Anyone --- ## What should appear in the Sheet The Sheet should generate rows like this: | Timestamp | VIN | Service | Price | VIN Photo File ID | Vehicle Photo File ID | VIN Photo | Vehicle Photo | |---|---|---|---:|---|---|---|---| | date/time | 123456789 | Basic Recon | 30 | file ID | file ID | thumbnail | thumbnail | Columns **G** and **H** will show actual reduced-size images, not just links. --- ## Most likely issue in your case If photos are uploading but the Sheet is blank, the most likely problem is this line from the older code: ```javascript SpreadsheetApp.getActiveSpreadsheet() ``` That can fail or point to nothing if the script is not properly bound to the Sheet. Use this instead: ```javascript SpreadsheetApp.openById(SPREADSHEET_ID) ``` That forces Apps Script to write to the exact Google Sheet you want.
GPT-5.52.7k tok106 tok/sTTFT 5.4s30.9s
stuck on saving
GPT-5.5
If it gets stuck on **“Saving…”**, your button is being disabled, but the `fetch()` request is either failing or never reaching your success/reset code. Most common causes: - Apps Script URL is wrong or still using old deployment - Apps Script permissions/deployment not set correctly - JavaScript error after clicking Save - CORS issue with Apps Script - Photo file is too large - Your code does not have a proper `catch/finally` block to reset the button Use this safer submit code. ## Replace your submit button code with this ```javascript document.getElementById('submit').onclick = async () => { const vin = document.getElementById('vin').value.trim(); const serviceEl = document.getElementById('service'); const selectedOption = serviceEl.selectedOptions[0]; const service = selectedOption.value; const price = selectedOption.dataset.price; const btn = document.getElementById('submit'); if (!vin) { alert('Enter VIN'); return; } if (!service) { alert('Select a service'); return; } if (!vinPhotoData) { alert('Take VIN photo'); return; } if (!vehiclePhotoData) { alert('Take vehicle photo'); return; } btn.disabled = true; btn.textContent = 'Saving...'; const payload = { vin, service, price, vinPhoto: vinPhotoData, vehiclePhoto: vehiclePhotoData }; try { console.log('Sending payload:', { vin, service, price, vinPhotoSize: vinPhotoData.length, vehiclePhotoSize: vehiclePhotoData.length }); const response = await fetch(API_URL, { method: 'POST', body: JSON.stringify(payload) }); const text = await response.text(); console.log('Raw response:', text); const result = JSON.parse(text); if (!result.ok) { throw new Error(result.error || 'Save failed'); } alert(`Saved successfully to row ${result.row}`); document.getElementById('vin').value = ''; serviceEl.value = ''; document.getElementById('price').textContent = ''; document.getElementById('vinPhoto').value = ''; document.getElementById('vehiclePhoto').value = ''; document.getElementById('vinPreview').style.display = 'none'; document.getElementById('vehiclePreview').style.display = 'none'; vinPhotoData = ''; vehiclePhotoData = ''; } catch (err) { console.error('Save error:', err); alert('Save failed: ' + err.message); } finally { btn.disabled = false; btn.textContent = 'Save Entry'; } }; ``` This is important because the `finally` section will always reset the button, even if saving fails. --- ## If it still gets stuck, use this Apps Script-friendly version Google Apps Script sometimes causes frontend CORS issues. If you see a CORS error in the browser console, change the fetch to this: ```javascript await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload) }); alert('Submitted. Check the Google Sheet.'); ``` So the middle part becomes: ```javascript try { await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload) }); alert('Submitted. Check the Google Sheet.'); } catch (err) { console.error('Save error:', err); alert('Save failed: ' + err.message); } finally { btn.disabled = false; btn.textContent = 'Save Entry'; } ``` With `mode: 'no-cors'`, the app can send the data, but it cannot read the response from Apps Script. So you will not get `result.row`, but the row should still be added to the Sheet. --- ## Check these 5 things ### 1. Make sure your API URL ends in `/exec` In your PWA: ```javascript const API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec'; ``` Not: ```javascript /dev ``` Use `/exec`. --- ### 2. Open the API URL directly Paste your Apps Script web app URL into your browser. You should see something like: ```json { "ok": true, "message": "Apps Script web app is running" } ``` If you get an authorization error, the deployment settings are wrong. --- ### 3. Redeploy Apps Script after every backend change In Apps Script: 1. Click **Deploy** 2. Click **Manage deployments** 3. Click the pencil/edit icon 4. Choose **New version** 5. Click **Deploy** Make sure deployment is: - **Execute as:** Me - **Who has access:** Anyone --- ### 4. Check Apps Script executions In Apps Script, click **Executions** on the left. Then submit from your app. If the request reached Apps Script, you should see a new execution. If it failed, open it and look for the error. Common errors: ```text Sheet tab not found ``` Fix your `SHEET_NAME`. ```text Cannot call DriveApp.getFolderById ``` Fix your `FOLDER_ID`. ```text Cannot call SpreadsheetApp.openById ``` Fix your `SPREADSHEET_ID`. --- ### 5. Make sure photos are compressed If photos are too large, the upload may freeze or fail. Your compression function should be like this: ```javascript function readAsCompressedBase64(file, previewId) { return new Promise(resolve => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const maxWidth = 1000; const scale = Math.min(1, maxWidth / img.width); canvas.width = img.width * scale; canvas.height = img.height * scale; canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/jpeg', 0.6); const preview = document.getElementById(previewId); preview.src = dataUrl; preview.style.display = 'block'; resolve(dataUrl); }; img.src = URL.createObjectURL(file); }); } ``` This keeps uploads much smaller. --- ## Quick fix I would try first Change your `fetch()` to this: ```javascript await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload) }); alert('Submitted. Check the Google Sheet.'); ``` Then check the Google Sheet and Apps Script **Executions**. If a row appears, the issue was the frontend trying to read the Apps Script response.
GPT-5.52.0k tok121 tok/sTTFT 4.1s20.6s
failed but no errors shown in executions
GPT-5.5
If it **failed but nothing appears in Apps Script → Executions**, then the request is **not reaching your Apps Script web app at all**. So this is probably **not a spreadsheet/write problem yet**. It is one of these: - Wrong `API_URL` - Web app not deployed as `/exec` - PWA is still using old cached JavaScript - Button click code is failing before `fetch()` - CORS/redirect issue - Service worker is serving an old version of your app Do these checks in order. --- ## 1. Make sure your URL is the Web App `/exec` URL In your frontend: ```javascript const API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec'; ``` It should **not** be: ```javascript https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/dev ``` And it should **not** be your Google Sheet URL. --- ## 2. Open the API URL directly in browser Paste the `API_URL` directly into your browser. If your Apps Script has this: ```javascript function doGet() { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON); } ``` You should see: ```json {"ok":true,"message":"Apps Script web app is running"} ``` If you do **not** see that, your deployment is the issue. --- ## 3. Redeploy correctly In Apps Script: 1. Click **Deploy** 2. Click **Manage deployments** 3. Click the pencil/edit icon 4. Choose **New version** 5. Set: - **Execute as:** `Me` - **Who has access:** `Anyone` 6. Click **Deploy** Then copy the Web App URL again. Use that URL in your PWA. --- ## 4. Your PWA may be using an old cached version Because this is a PWA, the service worker may be serving your old JavaScript even after you edit the file. For testing, temporarily unregister the service worker. Add this near the bottom of your `index.html`: ```javascript if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then(registrations => { registrations.forEach(registration => registration.unregister()); }); } ``` Then reload the app completely. On your phone, you may also need to: - Delete the PWA from home screen - Clear browser cache/site data - Reopen the site - Re-add to home screen later This is a very common reason it keeps saying “Saving…” with old code. --- ## 5. Add a visible debug message before `fetch()` Replace your save handler temporarily with this simplified debug version: ```javascript document.getElementById('submit').onclick = async () => { alert('Button clicked'); const btn = document.getElementById('submit'); btn.disabled = true; btn.textContent = 'Saving...'; const payload = { vin: document.getElementById('vin').value.trim(), service: document.getElementById('service').value, price: document.getElementById('service').selectedOptions[0].dataset.price, vinPhoto: vinPhotoData, vehiclePhoto: vehiclePhotoData }; alert('About to send to Apps Script'); try { await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload) }); alert('Request sent. Check Google Sheet and Apps Script Executions.'); } catch (err) { alert('Fetch failed: ' + err.message); } finally { btn.disabled = false; btn.textContent = 'Save Entry'; } }; ``` Now test again. ### What the alerts mean If you do **not** see: ```text Button clicked ``` Then your button handler is not connected. If you see: ```text Button clicked ``` but not: ```text About to send to Apps Script ``` Then your JavaScript is breaking before `fetch()`. If you see: ```text Request sent ``` but still see no execution in Apps Script, then your `API_URL` or deployment is wrong, or the service worker is still serving old code. --- ## 6. Add a basic `doPost` test Temporarily replace your Apps Script with this simple version: ```javascript const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; const SHEET_NAME = 'Sheet1'; function doGet() { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON); } function doPost(e) { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); sheet.appendRow([ new Date(), 'POST RECEIVED', e.postData ? e.postData.contents.substring(0, 100) : 'No post data' ]); return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Post received' })) .setMimeType(ContentService.MimeType.JSON); } ``` Then redeploy as a **new version**. Test from the PWA again. If this works, you should see a row like: ```text Timestamp | POST RECEIVED | {"vin":"..."} ``` Then we know the frontend is reaching Apps Script. --- ## 7. Add this test button to your HTML This removes photos from the equation and tests only the connection. Add this button: ```html <button id="testApi" type="button">Test API</button> ``` Then add this JavaScript: ```javascript document.getElementById('testApi').onclick = async () => { alert('Testing API...'); try { await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify({ test: true, time: new Date().toISOString() }) }); alert('Test sent. Check Google Sheet and Apps Script Executions.'); } catch (err) { alert('Test failed: ' + err.message); } }; ``` Click **Test API**. If this does not create an Apps Script execution, the problem is definitely: - wrong `API_URL` - bad deployment settings - cached frontend - button JavaScript not running --- ## 8. Make sure your button is not submitting a form If your button is inside a `<form>`, the page may be reloading before the upload completes. Change the button to: ```html <button id="submit" type="button">Save Entry</button> ``` Not just: ```html <button id="submit">Save Entry</button> ``` This one small issue can cause “Saving…” problems. --- ## 9. Remove `Content-Type: application/json` if you added it For Apps Script web apps, avoid this during testing: ```javascript headers: { 'Content-Type': 'application/json' } ``` Use this instead: ```javascript await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload) }); ``` Or: ```javascript await fetch(API_URL, { method: 'POST', body: JSON.stringify(payload) }); ``` Do **not** add custom headers yet. --- ## Most likely fix Based on “failed but no errors shown in Executions,” I would first check these: 1. Your PWA is still using an old cached version. 2. Your `API_URL` is not the `/exec` deployment URL. 3. Your Save button is inside a form and needs `type="button"`. 4. You edited Apps Script but did not redeploy as a **new version**. Start by adding this: ```html <button id="submit" type="button">Save Entry</button> ``` Then unregister the service worker, redeploy Apps Script, and test with the simple `doPost` above.
GPT-5.52.3k tok146 tok/sTTFT 8.7s24.3s
Script function not found: doGet
GPT-5.5
That message means your Apps Script Web App URL is working, but Apps Script cannot find a function named: ```javascript doGet() ``` When you open the Web App URL in a browser, Google runs `doGet()`. When your PWA submits data, Google runs `doPost()`. So you need **both** functions in your Apps Script. ## Add this `doGet()` to your Apps Script Paste this anywhere in your Apps Script file, outside of all other functions: ```javascript function doGet(e) { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON); } ``` Then your Web App URL should show: ```json {"ok":true,"message":"Apps Script web app is running"} ``` --- ## Make sure your file has this structure Your Apps Script should look generally like this: ```javascript const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; const SHEET_NAME = 'Sheet1'; const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID'; function doGet(e) { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON); } function doPost(e) { try { const data = JSON.parse(e.postData.contents); const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error('Sheet not found: ' + SHEET_NAME); } sheet.appendRow([ new Date(), data.vin || '', data.service || '', data.price || '' ]); return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Saved' })) .setMimeType(ContentService.MimeType.JSON); } catch (err) { return ContentService .createTextOutput(JSON.stringify({ ok: false, error: err.message })) .setMimeType(ContentService.MimeType.JSON); } } ``` This simplified version only tests writing VIN/service/price to the Sheet. Once this works, you can add the photo logic back in. --- ## Important: redeploy after adding `doGet()` After adding the function: 1. Click **Save** 2. Click **Deploy** 3. Click **Manage deployments** 4. Click the pencil/edit icon 5. Under **Version**, choose **New version** 6. Click **Deploy** 7. Open the Web App URL again Your URL should end in: ```text /exec ``` --- ## If you still see the error Check these things: ### 1. Function name must be exactly lowercase/camelcase Correct: ```javascript function doGet(e) { ``` Wrong: ```javascript function doget(e) { ``` Wrong: ```javascript function doGET(e) { ``` Wrong: ```javascript function DoGet(e) { ``` --- ### 2. `doGet()` cannot be inside another function Wrong: ```javascript function doPost(e) { function doGet(e) { // wrong } } ``` Correct: ```javascript function doGet(e) { // correct } function doPost(e) { // correct } ``` --- ### 3. Make sure you are editing the correct Apps Script project Sometimes there are two script projects: - one attached to the Google Sheet - one standalone project The Web App URL may point to a different project than the one you are editing. To confirm, add this to your `doGet()`: ```javascript function doGet(e) { return ContentService .createTextOutput('THIS IS THE CORRECT SCRIPT') .setMimeType(ContentService.MimeType.TEXT); } ``` Redeploy as a **new version**, then open the URL. If you do not see: ```text THIS IS THE CORRECT SCRIPT ``` then your Web App URL is from a different deployment/project. --- ## Next step First get the Web App URL to show: ```json {"ok":true,"message":"Apps Script web app is running"} ``` Once that works, your PWA should be able to submit to `doPost()`.
GPT-5.51.1k tok132 tok/sTTFT 3.3s11.3s

This is a shared TryAI chat. Sign in to start your own conversation.

Sign in to TryAI