Shared chat
Create Google Sheets PWA
This is a shared TryAI chat. Sign in to start your own conversation.
Sign in to TryAIShared chat
This is a shared TryAI chat. Sign in to start your own conversation.
Sign in to TryAIGreat 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.
Code.gs)In your Sheet: Extensions → Apps Script, paste this:
Then: Deploy → New deployment → Web app → Execute as Me, access Anyone. Copy the web app URL.
index.html)Host this on GitHub Pages, Netlify, or Vercel (free — PWAs need HTTPS):
manifest.json:
sw.js:
FOLDER_IDCode.gs into Apps Script → deploy as web app → copy URL into API_URLNotes:
capture="environment" opens the rear camera directly on phones — perfect for VIN shots.If you share your sheet's exact column headers, I'll adjust the appendRow order to match.
You have two places to do this — in the Google Sheet itself and in the app's list view. Here's both.
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:
Update saveImage() in your Apps Script to write an IMAGE() formula instead of a bare URL:
Then in doPost, write the formulas with setFormula instead of plain appendRow:
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.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:
With a little CSS:
loading="lazy" keeps the list fast when you have many rows.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:
Want me to add the full gallery/list screen to the PWA so you can browse and search past entries by VIN?
Most likely one of these is missing:
SHEET_NAME does not match your actual Google Sheet tab nameThe safest fix is to use the Spreadsheet ID directly instead of relying on getActiveSpreadsheet().
In Apps Script, replace your backend with this version.
Change these 3 values:
Full code:
Your Google Sheet URL looks like this:
Copy only the long ID between /d/ and /edit.
Example:
SHEET_NAME means the bottom tab nameThis is not the file name.
If your bottom tab says:
Then use:
If it says:
Then use:
Make sure your PWA is sending data to the Apps Script URL.
Your service dropdown should use clean value names:
After changing the Apps Script code:
Your PWA should use the URL ending in:
Not:
Example in your PWA:
In Apps Script:
If it failed, you will usually see one of these:
Fix:
Fix SPREADSHEET_ID.
Fix FOLDER_ID.
Redeploy the Web App as:
The Sheet should generate rows like this:
Columns G and H will show actual reduced-size images, not just links.
If photos are uploading but the Sheet is blank, the most likely problem is this line from the older code:
That can fail or point to nothing if the script is not properly bound to the Sheet.
Use this instead:
That forces Apps Script to write to the exact Google Sheet you want.
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:
catch/finally block to reset the buttonUse this safer submit code.
This is important because the finally section will always reset the button, even if saving fails.
Google Apps Script sometimes causes frontend CORS issues. If you see a CORS error in the browser console, change the fetch to this:
So the middle part becomes:
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.
/execIn your PWA:
Not:
Use /exec.
Paste your Apps Script web app URL into your browser.
You should see something like:
If you get an authorization error, the deployment settings are wrong.
In Apps Script:
Make sure deployment is:
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:
Fix your SHEET_NAME.
Fix your FOLDER_ID.
Fix your SPREADSHEET_ID.
If photos are too large, the upload may freeze or fail. Your compression function should be like this:
This keeps uploads much smaller.
Change your fetch() to this:
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.
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:
API_URL/execfetch()Do these checks in order.
/exec URLIn your frontend:
It should not be:
And it should not be your Google Sheet URL.
Paste the API_URL directly into your browser.
If your Apps Script has this:
You should see:
If you do not see that, your deployment is the issue.
In Apps Script:
MeAnyoneThen copy the Web App URL again.
Use that URL in your PWA.
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:
Then reload the app completely.
On your phone, you may also need to:
This is a very common reason it keeps saying “Saving…” with old code.
fetch()Replace your save handler temporarily with this simplified debug version:
Now test again.
If you do not see:
Then your button handler is not connected.
If you see:
but not:
Then your JavaScript is breaking before fetch().
If you see:
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.
doPost testTemporarily replace your Apps Script with this simple version:
Then redeploy as a new version.
Test from the PWA again.
If this works, you should see a row like:
Then we know the frontend is reaching Apps Script.
This removes photos from the equation and tests only the connection.
Add this button:
Then add this JavaScript:
Click Test API.
If this does not create an Apps Script execution, the problem is definitely:
API_URLIf your button is inside a <form>, the page may be reloading before the upload completes.
Change the button to:
Not just:
This one small issue can cause “Saving…” problems.
Content-Type: application/json if you added itFor Apps Script web apps, avoid this during testing:
Use this instead:
Or:
Do not add custom headers yet.
Based on “failed but no errors shown in Executions,” I would first check these:
API_URL is not the /exec deployment URL.type="button".Start by adding this:
Then unregister the service worker, redeploy Apps Script, and test with the simple doPost above.
That message means your Apps Script Web App URL is working, but Apps Script cannot find a function named:
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.
doGet() to your Apps ScriptPaste this anywhere in your Apps Script file, outside of all other functions:
Then your Web App URL should show:
Your Apps Script should look generally like this:
This simplified version only tests writing VIN/service/price to the Sheet. Once this works, you can add the photo logic back in.
doGet()After adding the function:
Your URL should end in:
Check these things:
Correct:
Wrong:
Wrong:
Wrong:
doGet() cannot be inside another functionWrong:
Correct:
Sometimes there are two script projects:
The Web App URL may point to a different project than the one you are editing.
To confirm, add this to your doGet():
Redeploy as a new version, then open the URL.
If you do not see:
then your Web App URL is from a different deployment/project.
First get the Web App URL to show:
Once that works, your PWA should be able to submit to doPost().
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 entriesfunction doGet() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME); const rows = sheet.getDataRange().getValues(); return ContentService.createTextOutput(JSON.stringify(rows)) .setMimeType(ContentService.MimeType.JSON);}<!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>{ "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" } ]}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)));});https://drive.google.com/thumbnail?id=FILE_ID&sz=w200function 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}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);}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('');}.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; }=IMAGE(SUBSTITUTE(E2, "uc?id=", "thumbnail?id=") & "&sz=w200")const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';const SHEET_NAME = 'Sheet1';const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID';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);}https://docs.google.com/spreadsheets/d/SPREADSHEET_ID_HERE/editconst SPREADSHEET_ID = '1abcDEFghiJKLmnopQRSTUVWXYZ123456789';Reconconst SHEET_NAME = 'Recon';Sheet1const SHEET_NAME = 'Sheet1';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';};<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>/exec/devconst API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec';Sheet tab not found: Sheet1const SHEET_NAME = 'Your Actual Tab Name';Exception: Unexpected error while getting the method or property openByIdException: Unexpected error while getting the method or property getFolderByIdSpreadsheetApp.getActiveSpreadsheet()SpreadsheetApp.openById(SPREADSHEET_ID)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'; }};await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload)});
alert('Submitted. Check the Google Sheet.');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';}const API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec';/dev{ "ok": true, "message": "Apps Script web app is running"}Sheet tab not foundCannot call DriveApp.getFolderByIdCannot call SpreadsheetApp.openByIdfunction 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); });}await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload)});
alert('Submitted. Check the Google Sheet.');const API_URL = 'https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec';https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/devfunction doGet() { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON);}{"ok":true,"message":"Apps Script web app is running"}if ('serviceWorker' in navigator) { navigator.serviceWorker.getRegistrations().then(registrations => { registrations.forEach(registration => registration.unregister()); });}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'; }};Button clickedButton clickedAbout to send to Apps ScriptRequest sentconst 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);}Timestamp | POST RECEIVED | {"vin":"..."}<button id="testApi" type="button">Test API</button>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); }};<button id="submit" type="button">Save Entry</button><button id="submit">Save Entry</button>headers: { 'Content-Type': 'application/json'}await fetch(API_URL, { method: 'POST', mode: 'no-cors', body: JSON.stringify(payload)});await fetch(API_URL, { method: 'POST', body: JSON.stringify(payload)});<button id="submit" type="button">Save Entry</button>doGet()function doGet(e) { return ContentService .createTextOutput(JSON.stringify({ ok: true, message: 'Apps Script web app is running' })) .setMimeType(ContentService.MimeType.JSON);}{"ok":true,"message":"Apps Script web app is running"}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); }}/execfunction doGet(e) {function doget(e) {function doGET(e) {function DoGet(e) {function doPost(e) { function doGet(e) { // wrong }}function doGet(e) { // correct}
function doPost(e) { // correct}function doGet(e) { return ContentService .createTextOutput('THIS IS THE CORRECT SCRIPT') .setMimeType(ContentService.MimeType.TEXT);}THIS IS THE CORRECT SCRIPT{"ok":true,"message":"Apps Script web app is running"}