Repository navigation
feat(storefront): UX overhaul — context switcher, network screens, cards, panels, promotions hub, create order - #137
Merged
Conversation
The dummy app never ran: ember-classic-decorator (unused) crashes on Ember 5, lazy loading kept the engine's modules out of the test bundle, and the host services, config and intl data the panels need were missing. Mirrors what fleetops already does: eager loading when `ember test` runs from this package, an inert stand-in for the console's extensions module, the API config the core adapter reads at module scope, and the legacy built-in components the engine's templates still use. The dummy intl service ignores missing locale data, since headless Chrome has no number formats for every bundled locale, and the order details and network controller tests stub the host router, fetch and app cache the way the widget tests already do.
… is only found by its own storefront
…rce card Catalog, truck and network index pages use Layout::Resource::CardsGrid with one card each on Layout::Resource::Card, following the Fleet-Ops anatomy: identity and subtitle in the header, media or facts in the body, status, icon actions and meta in the footer, with registry slots at each end. A catalog card shows its categories with product counts, its hours as a seven-day strip (the new Storefront::HoursStrip) and its product total; a draft is dashed. A truck card is a Vehicle::Pill with the online dot, the vehicle photo, where it serves, and a presence badge that says serving, offline, inactive, or online with no catalog. A network card shows the logo on its backdrop, the online state, the currency and member count. "Food trucks" reads "Trucks" in the UI; the food-truck model, routes and permissions keep their names. The catalogs and trucks pages gain the search the header already offered.
…ut a vehicle renders its placeholder
… and crashed createRecord; the store summary is store_summary
…s own sidebar for each - storefront service tracks a network context derived from the network route - StoreSelector becomes the context switcher with a Networks group and create-network - application route loads networks; sidebar rewrites for the network context - network page is a full page with an identity strip; the list hides while one is open - new Trucks section for a network backed by FoodTruckFilter::network - network orders and customers are scoped to the network being viewed, not the active store - createNewNetwork lives on the storefront service
…enu without Networks - trigger: context icon, name, STORE/NETWORK label and caret; blue for a store, indigo for a network; invitation badge - menu: Stores and Networks groups with tiles, currency or member count, a check on the active one, readable in dark mode; footer actions read New store / New network - store navigation follows the design order (Dashboard, Orders, Products, Catalogs, Customers, Trucks, Promotions, Settings); networks live in the switcher, not the menu - network page loses the All networks button; leaving a network is the switcher's job
…e sidebar re-renders on a switch; networks report their store count
…d an invite panel - network index is now an Overview (stat tiles, members with 7-day share, invitations, recent orders); settings move to networks.index.network.settings - NetworkController gains overview, invitations, resend and revoke; one Invite per recipient carrying category, expiry, message and approval in meta - OrderFilter/CustomerFilter gain a network filter on meta.storefront_network_id; the network orders and customers routes use it - Storefront::Network::InvitePanel (by email, shareable link, add a store you manage) replaces the share-network and add-stores modals - stores page: store identity column via a new store descriptor, All/Online/Offline/Invited tabs, bulk assign category and remove, invitation rows with resend/revoke/invite again - store and network resource descriptors registered with the shared identity registry; SelectOption::Store wrapper; width-style helper
…ost re-export broke console boot before the lazy engine loaded
…ore stacks and next actions - a multi-store checkout is one expandable row (customer, stores, fulfillment, total with tip split, shared driver, least-advanced status) that opens into each store's order - status tabs (needs action, preparing, ready for pickup, out for delivery, completed, canceled) drive the status query param; grouped/flat toggle - new cells: reference, stores (identity stack of store pills), fulfillment, total, driver (inline assign), status, placed, actions (next action promoted, rest behind an ellipsis) - order index resource keeps meta.checkout_id so the console can group
…lide with the networks list's page, sort and query
…ive questions first - header: id, status and store/network badges, the next workflow action promoted to a button, Edit, the rest behind a menu - Storefront::Order::FlowSteps renders the activity flow as steps (custom order configs contribute their own) - Storefront::Order::IdentityStrip: customer, driver with inline assign, fulfillment and timing, total with tip and promotion - tabs: Items (with store block and the other orders of the checkout), Route (with tracking), Payment, Activity (with comments), Customer, Data (metadata and documents); registered tabs still appended - panel widened to 600px
…out rules and a save bar - settings shell: store identity header with copy-store-key, a section rail (General, Branding and media, Locations, Checkout rules, Payment gateways, Notification channels, Alerts, API keys); anchors on the General page, routes for the rest - General page restructured into always-open sections: general, branding and media, checkout rules, alerts - every rule the server honours is a Storefront::SettingRule row with its consequence written under it, detail inputs appear when it is on - sticky save bar with unsaved-change count and the sections touched; Discard rolls back attributes, options and alerts
…f categories, products, hours and who serves it
- new full-page route catalogs.index.edit: categories on the left (add, rename, reorder, remove), the selected category's products in the middle with an in-place picker over the store's products that hides what is already in the catalog, health on the right; Hours (ScheduleManager), Served by (stores and trucks) and Details sections; sticky save bar with a summary of unsaved changes
- a store can now serve a catalog like a truck: Store gains catalog relations, PUT catalogs/{id}/subjects syncs stores and trucks, the catalog resource exposes hours and subjects
- category order persists; the catalog list opens the editor, the create modal only asks for name, description and status, and the card shows who serves the catalog
… issue badges and a save bar - Storefront::Product::Form gains tabs: Details (basics, pricing, flags, a folded More for translations and metadata), Variants and add-ons, Availability, Media (images and videos) - inline validation: missing name or price, sale price not below price, published without category or image; the tab holding the field carries a badge and the save bar links to it - status select moves into the panel header; the save bar counts unsaved changes across the product, its variants, add-ons and queued uploads, with Discard
…Ulaanbaatar zones and food trucks
…ns on one screen
- header rebuilt: photo, name, type and preference badges, copyable id, since-date, and a lifetime strip (orders, spend, average, last order) from a new customers/{id}/insights endpoint scoped to the store or network
- actions: New order for this customer, Edit, copy id/email
- tabs: Overview (contact facts, saved places with the default marked, stores ordered from in network context, recent orders), Orders, Places (set default, open in Fleet-Ops from the pill), Activity and notes (first/last order, cancellations, internal notes via CommentThread); registered tabs keep working
- panel widened to 600px
…builder, a redemptions screen and promotion cards - hub shell: title, subtitle, live counts (active, scheduled, redemptions, discount given) from promotions/hub, one tab strip across promotions, campaigns, segments, pushes and the new Redemptions tab - promotions list: status tabs (all, active, scheduled, paused, ended, draft), cards or table, Storefront::Promotion::Card with value, trigger, window, targets and redemptions, row actions gain duplicate and view redemptions - Storefront::Promotion::Builder replaces the modal: What, When it applies (with codes once saved), Targets (products, categories, exclusions, segment), Schedule and limits, Review and announce; done steps collapse to one line; save draft at any step - Redemptions: PromotionRedemption controller, filter, resource and relations, frontend model, route and table - Storefront::Promotion::Codes extracted from the codes modal so the builder and the modal share it
… can run their own trucks - Storefront::Truck::Panel (Details: vehicle, service area, zone, status; Catalogs: assign with serving-now badges) replaces the create/edit and assign-catalogs modals - trucks page gains All/Online/Offline/Inactive tabs; the network trucks page gets New truck, owned by the network - food_trucks.network_uuid (nullable) with model relation, resource fields, filter (network: member stores' trucks plus network-owned; ownedByNetwork) and public API ownership rules - card shows the owning network
… status tabs, bulk actions and draft hints - category sidebar nests by parent, shows per-category counts from a new products/summary endpoint, and offers smart groups (out of stock, drafts, recommended, on sale, uncategorised) - status tabs (all, published, draft, on sale) and smart groups drive new product filters: status, category, available, on_sale, recommended, uncategorized - cards and rows are selectable; a bulk bar publishes, unpublishes, moves to a category, marks on sale or deletes the selection - a draft card says what it is missing (category, image, price); an out-of-stock card that is still published says so
… sidebar and render the status tabs and bulk bar; the earlier edit never applied and the sidebar crashed on an undefined handler
…n today and deep-links into it - analytics/attention returns orders to confirm (with the oldest wait), pickups ready, deliveries without a driver, published products out of stock, trucks offline and pending network invitations for the active store - Widget::AttentionStrip renders them as clickable tiles and is registered as a default storefront dashboard widget
…twork invitation - the invitation email now lands in the engine at storefront/networks/join/:uri, which showed nothing before - join page reviews the network (identity, currency, category, approval, message, sender, expiry), picks which store joins, and accepts or declines; closed invitations say why - endpoints: lookup, accept (creates the membership in the invited category; awaiting_approval when the network asked to review), decline, and pending invitations for a store by its email - the store dashboard shows a banner per open invitation and the context switcher carries a badge
…own checkout - orders.index.new is a real form: customer, fulfillment (delivery or pickup, ASAP or scheduled, store location, the customer's saved places), items from the store's published products with inline variant and add-on choices, notes, promotion code, payment (cash on delivery or mark as paid), tip, and a live summary - prices, delivery fee, discounts, minimum order and totals come from the API: POST orders/console/quote builds a cart, quotes delivery through the service-quote logic and evaluates promotions; POST orders/console/place runs the cash checkout and the same capture that creates app orders - placing opens the order panel; the customer panel's New order lands here with the customer preselected
…o carts left open in an app keep working
…t_items_unavailable error naming them; seeders keep customer ids and their app accounts
…cts only that; Mongolian test customers use emails QPay accepts
… rendered now do New truck: an icon tile instead of a placeholder photo, a one-line subtitle, no tabs until the truck exists, uppercase section titles on one gutter, and the save bar pinned flush to the bottom with no second rule above it. The console ships only the solid and brand icon sets, so every regular-style icon (truck catalog checkboxes, the catalog picker's selection, the create-order option checkboxes and radios) rendered as nothing; they are a drawn .storefront-check now.
… place when a status is empty The hub loads the owner's promotions once (up to 100) and the tabs filter them in the controller instead of re-querying behind the loading skeleton; Scheduled is an active promotion that has not started. An empty status shows one line under the tabs instead of replacing the page with a no-results state.
…elds, one field rhythm, flush sticky footer
…e; the save bar spans the content flush The ContentPanel heading in settings, customer and order panels drops the darker band and the rule under it, reads 14px uppercase, and sits on a compact padding. The settings form fills the content area (sections keep their reading width) so its sticky save bar runs flush edge to edge instead of floating with the page padding around it.
…order with identity cells, row click and aligned totals
…here, paged and sorted by Storefront; row Edit opens the details form
…s, cards and table, as the server applies it
…ace and hover reaches the sticky ID and action cells
…th a Placed column and the row rhythm of the lists
… its header, tabs, columns, checkbox, cards and rails; the dashboard orders widget gives Placed and Next their width
… list includes the optional KPIs; network customers route reads Storefront's endpoint
…show without a refresh
…each the categories query
…e it; the status steps get room, the customer shows their phone, actions share one compact height; the store section becomes a store profile; section headings compact and aligned with their content, settings sections single cards; Delivery badges in their own colour; breakdown strings translated
…; nested panels align their fields with their heading
…stomer's Places tab and from the new order form; Places has a real empty state; a chosen address reads as a place card with Change address; 20% and 30% tips; order notes full width with room below
…er column, panels, widgets, incoming order) with the id beside it; dashboard widgets use the orders page's identity cells; table row buttons share one 26px height
…card gets a 4:3 image (or a quiet placeholder), a two-line title so prices align, hover and selection on the image, one-height footer buttons; the list pins the product and actions columns; skeletons follow the grid and the bare list
… with thumbnails; variants and add-on groups share one group anatomy (options table, offered count, hidden add-ons); availability as a compact week for products, catalogs and store locations; translations as language chips over the name and description with the originals as hints; the save bar counts every change
… move; card buttons share one height; the catalog editor's actions header no longer overflows
…s widget identity cell
…rebalanced; the products grid fits below its toolbar with room to scroll the last row clear; card footers sit on the card's bottom edge; card buttons share one line
…ut shows round store logos with the summary on hover (the network itself included); the order and actions columns stay put while scrolling; a reference shows its item count instead of the checkout id
…rs, reached from the stores table, the overview cards and a customer's stores; overview cards four across with the store backdrop and logo; switching to a network lands on its overview without loading the networks list, which loads when shown; the overview skeleton also covers returning to the overview; new order store cards match the promotion choice cards; recent order rows show their hover; the category caret sits at the right
… (order id on hover), store and driver as identity cells with a 20px image and the name only, the total without its tip line (tip on hover); a checkout's stores are evenly spaced 20px logos; the child branch follows the view, so Flat shows none
…elation so the save answers with the new catalogs, and clears the cached truck lists since only the pivot changed; the card title opens the truck instead of the Fleet-Ops vehicle panel
…ayment up top, the tracking number with status, fulfillment and timing, the route as stops, customer and driver side by side, the customer's note, the items and totals the order panel uses (now shared as line items, zero tips left out), and the label codes for handover; Accept is green; the truck card opens the truck without a page handler crashing it
Conflicts resolved: - v1/FoodTruckController: keep network-owned trucks alongside member stores' trucks (superset of release's member-store listing), for both the list and the single-truck lookup - Resources/FoodTruck: keep the internal 'today' stats next to release's store block - orders/index/view.hbs: keep the storefront-overlay-body body class - storefront-engine.css: keep the tuned 0.5rem order panel heading gap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Storefront UX overhaul (single PR)
Replaces the stacked PRs #121–#136 with one branch targeting
release/v0.4.25. Implements the Storefront redesign from the Claude Design canvas on@fleetbase/ember-uiprimitives: store/network context switcher and context-scoped navigation, network overview / stores / orders / trucks / customers / categories / settings, resource cards onLayout::Resource::Card, order details panel, create order form (console orders through the app's checkout), products page, catalog editor, product editor, customer panel, promotions hub and builder, trucks page and panel, settings shell with a section rail, dashboard attention strip, network join flow.Every screen was exercised in a running console in both store and network context (dark and light) before being marked done.
What changed since the last review
storefront-engine.css); oneStorefront::ViewToggle; product grid card on the shared card anatomy.SettingsContainershell is gone), Hours summary per location, Danger zone (delete store), rail counts; network settings rebuilt on the same shell.network_stores.status); catalog card actions menu; trucks Map button and panel Location tab.applies_to.segmentonce the customer is known.coupons,broadcastandpagesroutes removed; muted light-mode text darkened to gray-500 for 4.5:1 at 12px.Server / API
2026_10_11_000000_add_status_to_network_stores_table(statusdefaultactive, alsopendingfor memberships awaiting approval) and2026_10_11_000001_add_overrides_to_catalog_category_products_table(price,is_available, both nullable).POST networks/{id}/suspend-stores,reinstate-stores,approve-stores,reject-stores({ stores: [uuid] }); membership changes invalidate the cached store listings (ApiModelCache).product_overrides({ <product uuid>: { price, is_available } }, an explicit map is authoritative) and returns it internally; the public catalog product carriesprice(effective),store_price,catalog_price,is_available(effective) andcatalog_available.POST networks/{id}/shareable-link(enabled,regenerate,expires_in_days,category_uuid,requires_approval) manages the link;GET/POST networks/join/{uri}now also resolve a link token and create a link invitation + membership on accept; invitations carryvia(email|link) andstore_name; the overview's members carrycategoryandmembership_status; customer insights carrysegments.POST carts/{id}/add/{product}acceptscatalog(catalog id); the line keepscatalog_idand is priced at the catalog price on add and update; a product hidden in that catalog is refused. Checkout itself is unchanged: it prices from the cart lines. Needs human review (needs-human-review).network_status(internal, whennetworkis given);networkswithwith[]=networks.OrderFilter:customer/customer_uuid;ConsoleOrderControllerquote/place for console orders.store-locations?store_uuid=was ignored by the API (filter method isstore); the console now queries withstore=.Screen audit after the 11 Oct review
Storefront::PageHeader(avatar or icon, name, status badge, meta line, search, actions) andStorefront::SectionCardreplaceContentPanelon the network overview, stores, orders and trucks pages; KPI tiles are compact (uppercase 11px label, single-line value).overflow-y-scroll-iclass is now defined).networks/:id/promotions(promotions, campaigns, segments, redemptions) reuse the hub templates and controllers with the network as owner; the builder gains a Stores target for network-owned promotions; one Promotions entry in each sidebar as the board specifies. The networks list query params are prefixed (n_page,n_query, …) so nested pages keep the plain keys.Server / API (11 Oct additions)
FoodTruckresource carriestoday(internal only):{ orders, revenue, currency, avg_prep_minutes }for orders stampedmeta.food_truck_idcreated today in the owner's timezone; the truck list eager-loadsvehicle.driver.OrderFilter::foodTruck(?food_truck=<public id>),ProductFilter::network(?network=<uuid>, products of the network's member stores).12 Oct review (store context, network context, UI and skeletons)
Server / API (12 Oct)
recent_orderslists each order, not one per checkout. Each entry carriescustomer_id,customer_photo_urlandstore_logo_url(internal endpoint).GET storefront/int/v1/customers?storefront=|network=(existing) for its customer lists.12 Oct, second review
Comment::publishleft every model unguarded in the Octane worker after a failed note, so request parameters becamewhereclauses. The fix is fix(comments): a failed comment publish no longer leaves every model unguarded core-api#298. Separately, the products page no longer sends its view mode to the categories query.12 Oct, third review
Store context:
Network overview:
Store details (new,
networks/:id/stores/:store_id):Network orders:
New order: store, location and address cards use the promotion builder's choice-card styling in both themes.
Hover: recent order rows (customer panel and network overview) have a visible hover in light.
Stores table: the category caret sits at the far right.
Switching to a network:
Server: the network overview's members include
backdrop_url.Network orders, final polish:
Trucks:
FoodTruck::setCatalogsloaded the relation before syncing and never reloaded it, so the save answered with the old catalogs, and the next save deleted the new row. It now reloads the relation and clears the cached truck lists, because a catalogs-only change fires noupdatedevent. New Pest test, which fails without the fix.vehicle/panel-headernot found). The general fix for opening Fleet-Ops records from other engines is fix(descriptors): open Fleet-Ops records on their page outside Fleet-Ops fleetops-data#88.Incoming order modal, redesigned as a ticket:
Storefront::Order::LineItems(zero tips are left out).Validation
npx eslint addon app tests,npx ember-template-lint .,stylelinton the stylesheet: clean.ember test(12 Oct, third review, customer-panel integration test excluded): 275 tests, 28 skipped, 246 pass, 1 failure in a new route test's stub, fixed and rerun green with the affected modules (20/20). New tests: network order cells (single-line store, avatar stack, reference without the checkout), the networks list skip and reload, Stores staying active on store details.ember test(12 Oct, second review, customer-panel integration test excluded): 271 tests, 28 skipped; three tests written against replaced markup were updated and pass. Earlier run: 271 tests, 28 skipped, the rest pass after the 12 Oct fixes. That run's 7 failures were stale tests: the error controller resolving outside the engine, the widget list and the network customers query. Each was fixed and rerun green. The customer-panel integration render still hangs in this environment for reasons unrelated to this branch, as before.php scripts/pest-runner.php --log-junit … server/testson this branch: 657 tests, 17 failures + 27 errors, all remaining failures pre-existing on the base commit (missingSocketClusterclasses in the local vendor, invitesmetacolumn, etc.; the base commit has 18 failures + 43 errors). No new failures. New tests: promotion segments, hidden memberships in the public store listing, catalog overrides (category sync, resource, cart pricing). The Pest console printer prints nothing under PHP 8.4 locally, so results were read from the JUnit log.php-cs-fixerclean on the touched server files.phpstanis not run in CI; its pre-existing findings onPromotionEngineare unchanged.Not in this PR (follow-ups)
Console dependency
console/app/routes/application.jsloadingaction returnstrue. That change is on fleetbase/fleetbaserelease/v0.7.69(e50764aa1).Docs / API spec
User-facing behaviour, navigation and internal endpoints change:
fleetbase.iostorefront docs (networks, settings, promotions, create order) and thefleetbase/postmancollections (network overviewmembers[].backdrop_url, suspend/reinstate/approve/reject, console order quote/place, order customer and food_truck filters, product network filter, food trucktoday, storenetwork_status, catalogproduct_overrides, cart addcatalog, public catalog product price fields) need updates. Labels:needs-docs,needs-api-spec,module:storefront.