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

7.7 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>

Response — n8n may return a JSON array, a single object, or NDJSON. All three formats are handled by parseJson() (see below).

[
  { "Name": "Oliver", "id": 1, "createdAt": "...", "updatedAt": "..." },
  { "Name": "Luka",   "id": 2, ... },
  { "Name": "Benni",  "id": 3, ... }
]

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 → POSTs to webhook, prepends result row, clears + re-focuses input
  • 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 in the live table before every POST; if found, shows a warning toast and skips the webhook call entirely

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