Skip to main content

Website module

The Website tab — the shop's public marketing site, framed inside the Hub rather than reproduced. What edits here IS the page the public gets; the alternative (a separate CMS copy) drifts. The tab also carries the Reserve Your Bike flow — the public-facing rental funnel that lands a real booking back in the Rentals tab.

Tab labelWebsite
Foldersrc/modules/website/
Tab accent#1e3a5f (Kvick mid-blue)
Public URL/website on the Hub Worker — the same Worker serves the store's marketing site
Migrations122 (website_content editable overlay), 124 (screen.website + sys.website.edit permissions), 123 (rental unit description + fit — the copy the site displays), 119 + 120 + 121 (rental rate cards the site quotes from)

Editable site — overlay, not fork (mig 122)​

The site under /website is a build artefact: scripts/ingest-website.mjs wipes the directory and re-copies from the monolith. Anything written back into those HTML files is destroyed the next time the site is ingested. So edits are not written to the files — they're stored in website_content and laid over the page at serve time, the same way the rental rates and fleet already are.

What a block key is. The ingest stamps every editable element with data-wc="<tag>-<hash of its original contents>"; the overlay reads that key back at serve time and swaps the tag's innerHTML for whatever the shop wrote in edit mode. Deleting the row restores the original — the overlay does not destroy the source.

Edit mode — asked for with ?edit=1 and granted by sys.website.edit (mig 122). The save route checks the permission again for itself — a query string is a request, not an authorisation.

Two permissions, not one (mig 124)​

  • screen.website — "may look at the site from in here". The tab is drawn from the screen.* permissions family; without this row the tab is hidden. Mig 124 added it — before that the module was there but had no way to reach it (shipped dark).
  • sys.website.edit — "may change what the public sees". Separate from the tab visibility on purpose: a staffer allowed to preview the site is not automatically allowed to change it.

Rate cards on the site — quotes the shop's numbers (mig 119 + 120 + 121)​

The website's Reserve flow quotes the shop's rate cards, not typed-in prices (f319b78). Rate cards live per rental category (mig 119: category, not per bike — same physical bike could have shipped with three different numbers under the old per-unit model); mig 120 reconciled the vocabulary to what the app already speaks (ht_mtn, fs_mtb, ebike_city, ebike_mtn). Mig 121 flagged the hardtails + full-suspension bikes into the actual rental fleet — the site had always advertised the three categories but before this only the e-bikes carried the flag.

Rate card table on the tile. Each rental bike's card shows its rate ladder as a small grid above the Reserve Your Bike button (cf976c0). The Rates-table-below variant was tried and dropped; per-bike tile grids read better (164cf27). N/A shows for a length the shop does not sell (30212f8).

Reserve Your Bike — the funnel​

The website Reserve flow (05d2a57):

  1. Pick a bike from the rental fleet (each is its own card because each is its own bike — c2ed1c6).
  2. Pick how long — the days ARE the length; the flow doesn't ask twice (0ca3703).
  3. Pick a date on a calendar (multiple days tickable, 120d10d).
  4. Nested tabs at the top choose Rent vs. Demo, each with its own explanation (234411a).
  5. Step 3 — Reserve creates a real booking, and the deposit is quoted (de122bc). The booking lands in Rentals marked as a website booking with nothing paid yet (c882ad6) — the shop takes payment when the rider shows up.

Order-of-questions matters: date first, then length, then show what's free (9b0a99e) — asking length before date meant the calendar didn't know how many days to check availability across.

Unit description + fit (mig 123)​

"Tell about this bike, and say what size it fits; the data belongs in the Rental section of the Inventory item." (Tom.) The site used to list a rental bike as "C3 - SZ2 - Brown - 2026" — the shop's own shorthand, meaningless to a customer. Mig 123 adds a shop-authored description + fit blurb per rental unit, entered on the Inventory Stock Card, rendered on the Website tile ("Tell about a rental bike, and say who it fits" — 34cedcf).

Security headers on the public site (3e23890)​

The Hub itself already carries X-Content-Type-Options: nosniff, HSTS + refuse-plain-HTTP (78d004f), and X-Frame-Options: SAMEORIGIN (1b98339). Mig 3e23890 carries the same headers onto the public website pages too — the same-origin frame constraint means the public site cannot be embedded from another origin, and the plain-HTTP refusal means a rider hitting http:// gets bounced to HTTPS before any content leaves the Worker.

See also​

  • Rentals module — where website bookings land marked, with nothing paid
  • Settings module — where rate cards are edited (site + till read the same numbers)
  • Inventory module — the Stock Card carries the per-unit description + fit the website displays