Skip to content

sh4t4d33p/per-diem

Repository files navigation

Per Diem Menu (Square Sandbox)

1. What this is

A multi-location menu browser for Square sandbox merchants. It answers the same three questions the requirements describe: what the brand sells (catalog), where it can be sold (locations), and whether a guest can buy it right now at the selected storefront (availability by location, day, and local time).

The UI lets guests switch locations, browse items grouped by category, filter by category, and open an item detail view (name, description, image, correctly formatted price). All Square access stays on the server; the browser talks only to Next.js route handlers (/api/*).


2. Setup: run locally, env, and seed

Prerequisites

Install

npm install

Environment variables

Secrets must never be committed. Copy the example file and fill in values:

cp .env.example .env.local
Variable Required Description
SQUARE_ENVIRONMENT Yes Must be sandbox as required (do not point at production merchants).
SQUARE_ACCESS_TOKEN Yes Sandbox access token from your Square application (Dashboard → Credentials).
MENU_HIDE_OUTSIDE_AVAILABILITY No If true, /api/menu omits items outside their schedule window; default keeps them with orderableNow: false.

Optional: SQUARE_APPLICATION_ID is noted in .env.example for dashboard reference; the app does not require it for Catalog/Locations calls used here.

Seed data (Square sandbox)

The requirements call for roughly two locations, several categories, several items, at least one item only at one location, and time windows for the availability bonus. This repo ships a seed script that creates “PD Challenge — Brooklyn” and “PD Challenge — Los Angeles” if missing, then upserts a catalog (breakfast windows, Brooklyn-only item, etc.).

From the project root:

npm run seed

Notes:

  • Re-running seed adds another catalog graph (temporary #… ids in one batchUpsert). Locations are idempotent by exact name. To fully reset catalog objects, delete them in the Square Sandbox Dashboard or use a fresh sandbox app (per comments in scripts/seed/index.ts).
  • After seeding, you can sanity-check API access:
npm run square:smoke

Run the app

npm run dev

Open http://localhost:3000. Use the location selector and optional ?locationId= in the URL to deep-link.

Quality checks (optional)

npm test
npm run lint
npm run build

3. Architecture and code flow

High-level

  • Next.js App Router (src/app): UI routes and Route Handlers as a thin BFF (backend-for-frontend).
  • src/lib/square: Square Node SDK usage (singleton client, env), listing locations and catalog.
  • src/lib/menu: Domain logic — map Catalog objects to Api* JSON, location presence, availability windows, money formatting.
  • src/lib/api-types.ts: Shared wire types for JSON responses (browser and server stay aligned).
  • src/lib/client: Browser-only helpers (fetchJson, api.ts) calling /api/*no Square token in the bundle.
  • scripts/: CLI (seed, square:smoke) loads .env / .env.local via dotenv and imports server libs from src/lib.

Request flow (happy path)

  1. Browser loads /src/app/page.tsx renders MenuApp (src/components/menu-app.tsx).
  2. Client calls fetchLocations()GET /api/locationslistLocations() → Square Locations API → JSON array of { id, name, timezone }.
  3. User picks a location → URL updates ?locationId=GET /api/menu?locationId= loads Square location + full catalog → buildApiMenuResponse composes categories + items (presentAtLocation, orderableNow, prices, images).
  4. User opens an item → /item/[id]?locationId=GET /api/items/[id]?locationId=buildApiItemDetail (adds descriptionHtml when present); client sanitizes HTML with DOMPurify before render.
flowchart LR
  subgraph browser [Browser]
    UI[React components]
    Client[src/lib/client]
  end
  subgraph next [Next.js server]
    Routes[src/app/api]
    Menu[src/lib/menu]
    Sq[src/lib/square]
  end
  Square[(Square sandbox APIs)]
  UI --> Client
  Client -->|same-origin /api| Routes
  Routes --> Menu
  Routes --> Sq
  Sq --> Square
Loading

4. HTTP API (BFF endpoints)

All routes return JSON. Errors use shape { "error": { "code": string, "message": string } } unless noted.

GET /api/locations

Purpose List merchant locations for the location switcher.
Query None
Success 200ApiLocation[] (id, name, timezone).
Failure Square/SDK/network errors → 4xx/5xx with normalized code (e.g. rate limit, unauthorized).

GET /api/menu

Purpose Menu for one location: categories and item summaries (price, image, presence, schedule flag).
Query locationId (required) — Square catalog/location resource id.
Success 200ApiMenuResponse: { location, categories[] } with items sorted within categories.
Client errors 400 — missing or malformed locationId.
Not found 404 — location id not found for this merchant token.

GET /api/items/[id]

Purpose Single item detail for the menu item id at a location.
Path id — Square ITEM object id.
Query locationId (required).
Success 200ApiMenuItemDetail (summary fields + optional descriptionHtml).
Client errors 400 — missing/malformed path or query ids.
Not found 404 — location missing, or item not sold at location / missing priced variation.

5. Key architectural decisions and trade-offs

Decision Why Trade-off
Next Route Handlers as BFF Keeps SQUARE_ACCESS_TOKEN server-only; single deployment; TypeScript end-to-end. Extra hop vs calling Square from a separate service; fine for this scope.
Thin handlers, fat src/lib/menu Easier to unit test catalog composition without HTTP; reuse from scripts/tests. Handlers stay repetitive unless further factored.
fetch + cache: 'no-store' on client Menu and prices must reflect current catalog and clock. No HTTP caching of menu responses in the browser.
Primary variation only Square allows many variations per item; menu shows one row using ordinal/name ordering. Multi-variation choice UI not implemented.
orderableNow + optional hide flag Matches requirements: off-window items visibly disabled by default; optional env to hide them for a “current menu only” demo. Two behaviors to explain; product choice, not API ambiguity.
DOMPurify on item HTML Square descriptionHtml treated as untrusted for XSS defense in depth. Small client bundle cost; server still returns raw HTML in JSON.
HTTPS-only image URLs Avoid mixed content and odd schemes from catalog metadata. Rare legitimate http image URLs would be dropped.
Structured console logging Observable flows without adding a log vendor for this exercise. Not structured JSON logs for production aggregation.

6. Bonus implemented: time-of-day and day-of-week availability

The requirements note that scheduled menu availability APIs have evolved, that engineers should read Square’s docs and choose a sensible approach, and that out-of-window items should be hidden or visibly disabled using the selected location’s local timezone.

What we picked

  • Square Catalog objects: categories reference AVAILABILITY_PERIOD rows (dayOfWeek, startLocalTime, endLocalTime). We resolve “now” in the Location.timezone IANA zone (with fallback if invalid), evaluate windows per category, and compute orderableNow on each item row.
  • Default UX: Items outside the window remain visible but not orderable (orderableNow: false), with copy in the UI — aligns with “visibly disabled.”
  • Optional UX: Set MENU_HIDE_OUTSIDE_AVAILABILITY=true to omit off-window items from GET /api/menu — aligns with “hidden” when you want a stricter “current menu only” list.

Why this approach

  • It maps directly to category-linked availability periods in Catalog v2-style modeling and avoids pretending unsupported Inventory-time semantics exist for menu hours.
  • Fail-safe behavior: timezone / evaluation errors do not claim an item is orderable when uncertain (orderableNow conservative).

Details live in src/lib/menu/availability.ts, src/lib/menu/build-api-menu.ts, and seed data in scripts/seed/build-catalog.ts (e.g. breakfast category with weekday windows).


7. Given another week — what we’d add next

Concrete product/engineering next steps (not implementation detail):

  • Search — Client-side filter over the loaded menu JSON, or server-side query param with normalized search terms; debounce; empty state when no matches.
  • Modifiers — Surface Square modifier lists on item detail; enforce selection rules (min/max); reflect price deltas using variation/modifier pricing rules.
  • Cart — Line items with quantity, variation/modifier snapshot, subtotal using minor-unit math (BigInt / explicit rounding policy); persist in memory or sessionStorage before payments.
  • Checkout / payments — Square Payments API or Orders API in sandbox with clear separation from catalog browsing; idempotency keys; never expose secrets client-side.
  • Inventory / out-of-stock — Pull inventory counts or availability flags per location; merge with catalog rows; show “sold out” vs schedule-off.
  • Geolocation — Browser geolocation prompt (with HTTPS + Permissions-Policy updates), suggest nearest seeded location; optional distance sort; privacy copy and graceful denial handling.
  • Offline / resilience — Cache last successful menu JSON (e.g. Cache Storage or localStorage with TTL); stale-while-revalidate messaging; retry/backoff on failed BFF calls already partially aligned with SDK retries server-side.
  • Observability — Request IDs, structured JSON logs, basic metrics for /api/* latency and Square error rates.

Useful commands

Command Purpose
npm run dev Development server
npm run build / npm run start Production build and run
npm run seed Seed sandbox locations + catalog
npm run square:smoke List locations + catalog count
npm test Unit tests

About

Multi-location Square sandbox menu browser — Next.js BFF hides tokens, composes catalog JSON with location filtering, category grouping, availability windows in store timezone, seed & tests.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors