Files
brandize-barcode/agent.md
T
2026-05-25 12:43:21 -03:00

9.2 KiB
Raw Blame History

Health Routine Package Scanner

ALWAYS UPDATE THIS FILE AFTER MAKING CHANGES

What this is

A single-page web app (index.html) used internally by Health Routine staff to scan package barcodes. A user selects their name, scans/types a code, and the result is looked up via a webhook and appended to a table. Runs in any browser — designed for phones, tablets, and desktop.

Files

index.html   — the entire app (HTML + CSS + JS, no build step)
logo.png     — Health Routine brand logo (used in the header)
agent.md     — this file
start        — bash script that launches live-server on localhost:8080

Design

Colors are pulled directly from healthroutine.de's CSS variables:

Token Hex Usage
--primary #005f5a Controls bar, table header, buttons
--primary-dark #004845 Hover states
--primary-light #e0f0ef Row hover, badge backgrounds
--bg #f7f7fa Page background
--card #eaeaf2 Alternating table rows
--sage #82917d (brand footer green, available)
--error #c71b1b Error states
--success #15803d Success toasts

Font: system font stack (-apple-system, Segoe UI, etc.)

Webhook

Base URL: https://brandize.app.n8n.cloud/webhook/e0268b0f-4935-49ba-bfdf-1c0ee01d3b9d

GET — load users

Called once on page load to populate the user dropdown.

GET <base_url>
Authorization: Basic <base64>

Actual response shape (confirmed via console):

{ "Name": ["Oliver", "Luka", "Benni"] }

buildDropdown() handles all three shapes n8n may return:

  1. { Name: ["Oliver", "Luka", "Benni"] } — single object, Name is an array (current)
  2. [ { Name: "Oliver" }, ... ] — array of objects
  3. { Name: "Oliver" } — single object, Name is a string

POST — submit scan

Called when the user presses Enter in the scan input.

POST <base_url>
Authorization: Basic <base64>
Content-Type: application/json

{ "user": "Oliver", "code": "SCANNED_BARCODE_STRING" }

Success response:

{
  "name":       "Christa Gierdahl, Christa Gierdahl",
  "producttag": "0-HR-Nail-Care-5",
  "batch":      "BATCH/25-11-14/06173",
  "country":    "DE"
}

Error response (non-2xx or 200 with error field):

{ "error": "Human-readable error description" }

Fields mapped to table columns:

  • countryCountry (teal pill badge)
  • nameName
  • producttagTag (monospace pill, truncated with tooltip on hover)
  • batch → received but not displayed in the table

Authentication

The webhook requires HTTP Basic Auth.

  • On 401 (GET or POST) a modal appears asking for username + password.
  • Credentials are saved to a cookie (hr_auth, 30-day expiry, SameSite=Strict) so the user is not prompted again.
  • On page load, the cookie is read and credentials restored before the first network call.
  • If the modal is triggered again (e.g. wrong password), the fields are pre-filled from the cookie and focus lands on the password field for quick confirmation.
  • saveCreds(user, pass) — JSON-stringifies, URI-encodes, base64-encodes, writes cookie.
  • loadCredsFromCookie() — reverses that; returns { user, pass } or null.
  • clearCredsCookie() — available but not exposed in UI (call from browser console if needed).

Error handling (POST)

Two cases:

  1. Non-2xx response — body is parsed; if errData.error exists it is shown verbatim; otherwise a generic HTTP <status> message is shown.
  2. 200 response with error field — treated as failure: item.error is displayed, row is not added to the table.

Errors appear in the persistent status banner (red, no auto-dismiss) and the scan input is selected so the user can re-scan or correct the code.

NDJSON / response format

n8n sometimes returns multiple items as NDJSON (one JSON object per line) instead of a proper JSON array. parseJson(res) handles all three formats:

  1. Standard JSON array [...]
  2. Single JSON object {...} — passed through as-is
  3. NDJSON — split on \n, each line parsed individually, returned as an array

UI layout

┌─────────────────────────────────────────────────────┐
│  [HR logo]  Health Routine  |  Package Scanner       │  ← white brand bar
├─────────────────────────────────────────────────────┤
│  [▥ SCAN HERE_____________]  [User ▾]  [✕ Clear]    │  ← teal controls bar
├─────────────────────────────────────────────────────┤
│  SCAN LOG                               3 scans ●   │
│ ┌───────────────────────────────────────────────┐   │
│ │ COUNTRY  │ NAME                  │ TAG         │   │  ← sticky header
│ ├──────────┼───────────────────────┼─────────────┤   │
│ │ DE       │ Christa Gierdahl      │ 0-HR-Nail…  │   │  ← newest row on top
│ │ AT       │ ...                   │ ...         │   │
│ │          scrollable area          │             │   │
│ └───────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

On mobile (< 620 px): scan input goes full-width on its own row; user dropdown and clear button share the row below.

Key behaviours

  • On load: cookie credentials restored → GET webhook fires → dropdown populated → scan input auto-focused
  • Enter key: trims input → duplicate check → clears input immediately → adds pending row → pushed onto FIFO queue → drainQueue() starts if idle
  • Clear button: empties the table, clears the scan input, resets the scan counter
  • No user selected: red toast + highlighted dropdown; does not submit
  • 401 on any request: modal prompts for credentials (pre-filled from cookie), saves to cookie, retries original call
  • Webhook error: error field shown verbatim in persistent red banner; input selected for re-scan
  • Network error: dismissing error banner; input selected so user can retry
  • Dropdown error state: tapping/focusing the dropdown while it shows an error re-triggers loadUsers()
  • NDJSON/array/object: parseJson() normalises all response formats from n8n
  • Duplicate scan: isDuplicate(code) queries .col-code cells (present on pending, resolved, and error rows) before enqueuing
  • Scan queue: scanQueue[] is a FIFO array of { code, user, rowEl }. drainQueue() runs sequentially in the background — one processItem() at a time. Scans can be added faster than the webhook responds; each gets its own pending row immediately
  • Pending row: shows a spinner + Processing… <code> while the item is in-flight; replaced in-place by the resolved row or an error row
  • Error row: red background, shows the error message and the raw code; code is still in .col-code so it cannot be re-scanned
  • Queue pill: teal badge next to the scan counter showing N pending; hidden when queue is empty
  • Auth mid-queue: if processItem gets 401, it returns "auth" to drainQueue, which pauses (queueBusy = false), shows the auth modal with drainQueue as the retry callback — queue resumes from the same item after login
  • Clear: also drains scanQueue and resets queueBusy

Scan flow (sequence)

Enter key
  └─ enqueue(code)
       ├─ validation (user selected? duplicate?)
       ├─ scanInput.value = ""  ← immediate clear
       ├─ addPendingRow(code)  ← spinner row in table
       └─ scanQueue.push(...)  ← add to FIFO
            └─ drainQueue() if idle
                 └─ processItem() loop
                      ├─ fetch POST webhook
                      ├─ resolveRow()  ← replace spinner with result
                      └─ rowError()    ← replace spinner with error

Table columns (DOM)

Visible Header CSS class Content
Country item.country wrapped in teal badge
Name name-cell item.name
Tag item.producttag in monospace pill
Code col-code Raw scanned code (display:none), used for duplicate detection

How to run

Open index.html directly in a browser (file://) — no server needed for local use. Or run the start script for live-reload during development:

./start   # requires Node.js; installs live-server globally if missing