initial commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# ShopProQuote Critical Pitfalls
|
||||
|
||||
## Redirect loop: stale PocketBase auth token
|
||||
|
||||
**Symptom:** Browser enters an infinite redirect loop between `index.html` ↔ `dashboard.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:
|
||||
```javascript
|
||||
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`:
|
||||
```javascript
|
||||
// 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:
|
||||
```javascript
|
||||
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):**
|
||||
```javascript
|
||||
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:
|
||||
```javascript
|
||||
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.
|
||||
Reference in New Issue
Block a user