Files
2026-07-12 10:17:17 -04:00

4.5 KiB

ShopProQuote Critical Pitfalls

Redirect loop: stale PocketBase auth token

Symptom: Browser enters an infinite redirect loop between index.htmldashboard.html (or login.html if browser cache has old code).

Root cause: index.html line 131 auto-redirects to dashboard.html based on a truthy check of localStorage.getItem('pocketbase_auth'). When the PocketBase token is stale (expired/invalid), the PocketBase SDK's onAuthStateChanged fires with null, and the JS redirects back to index.html. The stale token is NOT cleared from localStorage before redirecting, so index.html sees it again → infinite loop.

Fix: In every auth-failure handler (onAuthStateChanged else block), always clear before redirecting:

localStorage.removeItem('pocketbase_auth');
window.location.href = 'index.html';

Affected files: dashboard.js, repair-orders.js, appointments.js, shared/header-functionality.js.

Also check: index.html line 131 only does a truthy check. Never add logic that redirects based solely on localStorage presence without verifying token validity.

PocketBase SDK auto-cancellation

Symptom: "auto-cancelled" error or "request was autocancelled" when creating/updating records, especially on double-click.

Root cause: PocketBase JS SDK auto-cancels requests that share the same requestKey. By default, all create() and update() calls to the same collection share a generated key based on method + URL. A rapid second call cancels the first.

Fix: Always pass { requestKey: null } to disable auto-cancellation in pocketbase.js:

// addDoc
pb.collection(collName).create(pbData, { requestKey: null });

// updateDoc
pb.collection(collName).update(docId, pbData, { requestKey: null });

// setDoc (both paths)
pb.collection(collName).update(records[0].id, pbData, { requestKey: null });
pb.collection(collName).create(pbData, { requestKey: null });

Double-submission guard

Symptom: Form creates duplicate records when user double-clicks submit button.

Fix: Guard all create/update form handlers against re-entry:

function handleSubmit() {
    const submitBtn = document.getElementById('submit-btn');
    if (submitBtn && submitBtn.disabled) return; // guard
    if (submitBtn) {
        submitBtn.disabled = true;
        submitBtn.textContent = 'Creating...';
    }
    try {
        // ... do work ...
    } finally {
        // Always re-enable — success and error paths both need this
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = 'Create Repair Order';
        }
    }
}

Primary file: repair-orders.js handleCreateROSubmit().

scoping bug (ReferenceError silently crashes handler)

Symptom: Button click does nothing — no error, no notification, no action.

Common cause: Variable declared with const/let inside a try block, then referenced outside the block. JavaScript throws a silent ReferenceError that crashes async handlers with no visible feedback.

Example (broken):

try {
    const roData = getROData(roId);
    // ...
} catch (e) { /* ... */ }
// BUG: roData is block-scoped to try, ReferenceError here
if (roData) { /* ... */ }

Fix: Declare the variable before the try block:

const roData = repairOrders.find(ro => ro.id === roId);
try {
    // use roData...
} catch (e) { /* ... */ }
// roData still accessible here

Quote service approved vs customerDecision mismatch (totals show $0 in PDF)

Symptom: On-screen quote summary shows correct totals, but generated PDF or Print view shows $0.00 for all totals even though services are present.

Root cause: The QuoteService type has two fields (approved: boolean and customerDecision: string) that must stay in sync. When they diverge — e.g. a service has approved: true but customerDecision: 'pending' — the UI total (which used s.approved) counts it but the PDF (which uses svc.customerDecision === 'approved') excludes it. If all services are in this broken state, the PDF shows $0.

Fix: All code paths that compute totals must use customerDecision === 'approved' as the single source of truth. See references/quote-service-dual-fields.md for the invariant table and the three-state toggle system.

Verification: Check that computeQuoteTotals (src/lib/totals.ts), store subtotal()/total(), and generateQuotePDF (src/lib/pdf.ts) all filter by customerDecision === 'approved'. The approved boolean should only drive the checkbox UI, never business logic.