Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5518e67
fix(reviews): upload review photos without a public ACL
roncodes Oct 6, 2026
b5a436e
feat(socket-auth): storefront customer and checkout socket tokens, ch…
roncodes Oct 6, 2026
a38e234
perf: speed up network and console store listings
roncodes Oct 6, 2026
6b02d96
feat(reviews): verified-purchase reviews, eligibility endpoint and ow…
roncodes Oct 6, 2026
a406dd2
feat(promotions): store attribution, shareable code and availability …
roncodes Oct 6, 2026
fb6ecf7
feat(chat): customer chat with the driver delivering their order
roncodes Oct 6, 2026
4f5ba15
feat(products): expose add-on category is_required and max_selectable…
roncodes Oct 6, 2026
2aeffa6
style: align test fixture keys
roncodes Oct 6, 2026
868dfa2
fix(reviews): show reviewers as first name and last initial, without …
roncodes Oct 6, 2026
da400f2
fix(chat): advertise the chat.{id} socket channel core broadcasts mes…
roncodes Oct 6, 2026
919c310
chore(release): v0.4.25
roncodes Oct 6, 2026
a4ec476
Merge pull request #112 from fleetbase/fix/private-media-bucket
roncodes Oct 7, 2026
9181534
Merge pull request #114 from fleetbase/fix/network-stores-performance
roncodes Oct 7, 2026
58d7276
Merge pull request #115 from fleetbase/feat/verified-reviews
roncodes Oct 7, 2026
ca4a2f6
Merge pull request #116 from fleetbase/feat/public-promotion-details
roncodes Oct 7, 2026
224fe4c
Merge remote-tracking branch 'origin/release/v0.4.25' into feat/order…
roncodes Oct 7, 2026
7d71b2a
Merge pull request #117 from fleetbase/feat/order-chat
roncodes Oct 7, 2026
574e120
Merge pull request #118 from fleetbase/feat/product-option-limits
roncodes Oct 7, 2026
af47459
feat(seeders): service stores, promotions, segments and campaigns in …
roncodes Oct 8, 2026
aa0dda6
fix(reviews, stripe): review sort direction, Stripe key errors
roncodes Oct 8, 2026
986843a
fix(seeders): keep store and network keys when re-seeding
roncodes Oct 8, 2026
78f4017
fix(seeders): seed a storefront delivery rate and keep real Stripe keys
roncodes Oct 8, 2026
7df89a9
fix(networks): save network payment gateway changes through the gatew…
roncodes Oct 8, 2026
174589e
fix(seeders): seed the Singapore test stores and network in SGD
roncodes Oct 8, 2026
2443648
fix(checkout): keep the checkout linked to its Stripe PaymentIntent w…
roncodes Oct 8, 2026
eb267af
fix(seeders): write Singapore dollar amounts as S$ in promotion and c…
roncodes Oct 8, 2026
17184b4
feat(orders): customer order activity flow from the order's own config
roncodes Oct 8, 2026
e00f4ea
fix(seeders): write store hour days capitalised, as the console does
roncodes Oct 8, 2026
14fea8a
fix(orders): console order view loads items, barcode and the promotio…
roncodes Oct 9, 2026
bfcfa7f
fix(orders): order data leaves out the customer's saved places; one d…
roncodes Oct 9, 2026
1b2a657
revert(orders): order data keeps the customer's places as the API ret…
roncodes Oct 9, 2026
c4b66dc
feat(notifications): customers are notified of each order step withou…
roncodes Oct 9, 2026
7c9bab3
feat(bookings): service bookings follow their own order flow, with pr…
roncodes Oct 9, 2026
b9165c8
fix(orders): console can move orders on a custom flow to their next a…
roncodes Oct 9, 2026
6c5e71c
fix(network): multi-store delivery quotes measure the real route; net…
roncodes Oct 9, 2026
c8af413
feat(network): networks can split store tips across the stores of a m…
roncodes Oct 9, 2026
02f9ff8
fix(i18n): keep the Russian multi-cart label on its own lines so the …
roncodes Oct 9, 2026
c833b5c
feat(identity): drivers, customers, vehicles and places render throug…
roncodes Oct 9, 2026
341d1b5
Merge pull request #120 from fleetbase/feat/shared-identity-components
roncodes Oct 9, 2026
e96ed4b
fix(checkout): multi-store orders can be placed; stores are found bef…
roncodes Oct 9, 2026
babef37
perf(notifications): order, new-order and chat notifications are sent…
roncodes Oct 9, 2026
7aaf8e4
feat(orders): a multi-store order is listed once, with its breakdown …
roncodes Oct 9, 2026
8c5d6e8
feat(checkout): a multi-store order records the names of its stores
roncodes Oct 9, 2026
d72c44b
feat(food-trucks): networks show their member stores' trucks; a truck…
roncodes Oct 10, 2026
8911aba
feat(seeders): Mongolian testing network with QPay sandbox gateways, …
roncodes Oct 10, 2026
72c0b5c
feat(seeders): Mongolian network and store promotions, segments and a…
roncodes Oct 10, 2026
1c1e0b9
fix(seeders): re-seeding keeps product, vehicle and food truck ids, s…
roncodes Oct 10, 2026
99aa860
fix(quotes): carts with lines that can no longer be ordered get a car…
roncodes Oct 10, 2026
ea4d524
fix(qpay): retry an invoice without the receiver email when QPay reje…
roncodes Oct 10, 2026
e234ca1
revert(qpay): drop the retry-without-email added in ea4d524
roncodes Oct 10, 2026
59b7a6d
fix(qpay): follow QPay API v2: paid status and amount before ordering…
roncodes Oct 10, 2026
7145ab0
fix(seeders): Mongolian amounts in whole tögrög, a 1,000₮ test item, …
roncodes Oct 10, 2026
a49efed
fix(qpay): exact VAT amounts; floating point truncated e.g. 3190 → 28…
roncodes Oct 10, 2026
02ea732
fix(checkout): QPay's callback creates the order; the app hears about…
roncodes Oct 10, 2026
10836c5
fix(order-panel): wire the Items/Route/Payment/Activity/Customer/Data…
roncodes Oct 10, 2026
fa38fd2
feat(carts): carts keep what they held: open, checked out and cleared…
roncodes Oct 10, 2026
ace3e57
feat(qpay): payment recorded on the checkout the moment it's verified…
roncodes Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
> v0.4.24 ~ "Extensions can add columns, actions and buttons to storefront tables and panels"
> v0.4.25 ~ "Verified reviews, order chat with the driver, richer public promotions and faster store listings"

---
## Highlights

- **Resource view registries.** Extensions can add the following through `storefront:<resource>:table:<slot>` and `storefront:<resource>:details:<slot>`:
- columns, row actions, bulk actions and toolbar buttons on the orders, customers, promotions, campaigns, segments and network tables;
- buttons and menu items on the order and customer panels.
- **Orders use the standard table layout.**
- **Verified-purchase reviews.** Reviews can be tied to a delivered order and are marked verified. A new eligibility endpoint tells the app whether a customer can review a store, and customers can delete their own reviews. Reviewer email and phone are no longer exposed publicly; public reviews show a short name such as "Ada B.".
- **Chat with the driver.** Customers can chat with the driver delivering their order: get the chat, page through messages, send text and photos, and mark messages read. New messages are broadcast on the chat's `chat.{id}` channel.
- **Public promotions.** Public promotion payloads now include the owning store, the shareable code, availability and the next start time.
- **Product options.** Public product payloads now include each add-on category's `is_required` and `max_selectable`.
- **Faster store listings.** Network and Console store listings are quicker.
- **Private review photos.** Review photos upload without a public ACL, so private buckets work.

---
## Upgrading
Needs fleetbase/ember-core v0.3.25 and fleetbase/ember-ui v0.4.5.
- Run migrations. This release adds an index on `reviews.subject_uuid` and an `order_uuid` column on `reviews`.
- Order chat needs storefront socket authentication and fleetbase/core-api v1.6.69 or later.

---
## Need help?
Expand Down
3 changes: 2 additions & 1 deletion addon/components/modals/assign-driver.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<InputGroup class="mt-4" @name={{t "storefront.component.modals.assign-driver.message-driver"}}>
<ModelSelect
@modelName="driver"
@selectedItemComponent={{component "select-option/driver" compact=true}}
@selectedModel={{@options.order.driver_assigned}}
@placeholder={{t "storefront.component.modals.assign-driver.placeholder-driver"}}
@triggerClass="form-select form-input w-full"
Expand All @@ -24,7 +25,7 @@
@onChange={{fn (mut @options.order.driver_assigned)}}
as |model|
>
{{model.name}}
<SelectOption::Driver @option={{model}} />
</ModelSelect>
</InputGroup>
</div>
Expand Down
11 changes: 2 additions & 9 deletions addon/components/modals/create-food-truck.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<InputGroup @name="Assign Food Truck Vehicle">
<ModelSelect
@modelName="vehicle"
@selectedItemComponent={{component "select-option/vehicle" compact=true}}
@selectedModel={{@options.foodTruck.vehicle}}
@placeholder="Assign Food Truck Vehicle"
@triggerClass="form-select form-input"
Expand All @@ -11,15 +12,7 @@
@onChange={{fn (mut @options.foodTruck.vehicle)}}
as |model|
>
<div class="flex flex-row">
<div class="mr-2">
<Image src={{model.photo_url}} class="w-10 h-10 rounded-md shadow-sm" />
</div>
<div class="flex flex-col flex-1">
<div class="font-semibold">{{model.display_name}}</div>
<div>{{model.plate_number}}</div>
</div>
</div>
<SelectOption::Vehicle @option={{model}} />
</ModelSelect>
</InputGroup>
<InputGroup @name="Service Area">
Expand Down
2 changes: 1 addition & 1 deletion addon/components/modals/create-network-category.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@onChange={{@options.setParentCategory}}
as |model|
>
{{model.name}}
<SelectOption::Category @option={{model}} />
</ModelSelect>
</InputGroup>
<InputGroup
Expand Down
2 changes: 1 addition & 1 deletion addon/components/modals/import-products.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
@onChange={{fn (mut @options.selectedCategory)}}
as |model|
>
{{model.name}}
<SelectOption::Category @option={{model}} />
</ModelSelect>
</InputGroup>
</div>
Expand Down
24 changes: 4 additions & 20 deletions addon/components/modals/incoming-order.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,7 @@
<h5 class="dark:text-gray-100 font-semibold">{{t "storefront.component.modals.incoming-order.assigned"}}</h5>
<div class="flex flex-col space-y-4">
{{#if @options.order.driver_assigned.id}}
<div class="flex items-center">
<Image src={{@options.order.driver_assigned.photoUrl}} class="w-12 h-12 rounded-md shadow-sm mr-4" alt={{@options.order.driver_assigned.name}} />
<div>
<h5 class="font-semibold dark:text-white text-xs">{{n-a @options.order.driver_assigned.displayName}}</h5>
<div class="font-semibold dark:text-gray-100 text-xs">{{n-a
@options.order.driver_assigned.phone
(t "storefront.component.modals.incoming-order.no-phone")
}}</div>
</div>
</div>
<Driver::Pill @driver={{@options.order.driver_assigned}} />
{{else}}
<div>
<h5 class="text-red-500 text-sm">{{t "storefront.component.modals.incoming-order.not-assigned"}}</h5>
Expand Down Expand Up @@ -72,16 +63,9 @@
<div class="bg-gray-100 border border-gray-200 dark:bg-gray-800 dark:border-gray-700 rounded-md px-4 py-2 space-y-2 h-full">
<h5 class="dark:text-gray-100 font-semibold">{{t "storefront.common.customer"}}</h5>
<div class="">
<div class="flex flex-row">
<div>
<Image src={{avatar-url @options.order.customer.photo_url}} class="w-12 h-12 rounded-md shadow-sm mr-4" alt={{@options.order.customer.name}} />
</div>
<div>
<div class="text-xs font-bold dark:text-gray-100">{{@options.order.customer.name}}</div>
<div class="text-xs dark:text-gray-100">{{@options.order.customer.email}}</div>
<div class="text-xs dark:text-gray-100">{{@options.order.customer.phone}}</div>
</div>
</div>
<Customer::Pill @customer={{@options.order.customer}} @static={{not @options.order.customer.id}} />
<div class="text-xs dark:text-gray-100 mt-1">{{n-a @options.order.customer.email}}</div>
<div class="text-xs dark:text-gray-100">{{n-a @options.order.customer.phone}}</div>
{{#unless @options.order.meta.is_pickup}}
<div class="mt-2">
<h5 class="dark:text-gray-100 font-semibold text-xs truncate">{{t "storefront.component.modals.incoming-order.address"}}</h5>
Expand Down
3 changes: 2 additions & 1 deletion addon/components/modals/order-ready-assign-driver.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<InputGroup class="mt-4" @name={{t "storefront.component.modals.order-ready-assign-driver.select-driver"}}>
<ModelSelect
@modelName="driver"
@selectedItemComponent={{component "select-option/driver" compact=true}}
@selectedModel={{@options.driver}}
@placeholder={{t "storefront.component.modals.order-ready-assign-driver.select-driver-to-assign"}}
@triggerClass="form-select form-input"
Expand All @@ -37,7 +38,7 @@
@onChange={{fn (mut @options.driver)}}
as |model|
>
{{model.name}}
<SelectOption::Driver @option={{model}} />
</ModelSelect>
</InputGroup>
{{/unless}}
Expand Down
11 changes: 11 additions & 0 deletions addon/components/storefront/order/details/commerce-summary.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@
{{#unless @resource.meta.is_pickup}}
<div><span>Delivery fee</span><strong>{{format-currency @resource.meta.delivery_fee @resource.meta.currency}}</strong></div>
{{/unless}}
{{#each this.promotions as |promotion|}}
<div class="text-green-600 dark:text-green-400">
<span>
<FaIcon @icon={{if promotion.isFreeDelivery "truck-fast" "tag"}} @size="xs" class="mr-1" />{{promotion.name}}{{#if promotion.code}} · <code>{{promotion.code}}</code>{{/if}}
</span>
<strong>-{{format-currency promotion.amount @resource.meta.currency}}</strong>
</div>
{{/each}}
{{#if this.unattributedDiscount}}
<div class="text-green-600 dark:text-green-400"><span>Discount</span><strong>-{{format-currency this.unattributedDiscount @resource.meta.currency}}</strong></div>
{{/if}}
{{#if @resource.meta.tip}}
<div><span>Tip</span><strong>{{get-tip-amount @resource.meta.tip @resource.meta.subtotal @resource.meta.currency}}</strong></div>
{{/if}}
Expand Down
24 changes: 23 additions & 1 deletion addon/components/storefront/order/details/commerce-summary.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
import Component from '@glimmer/component';

export default class StorefrontOrderDetailsCommerceSummaryComponent extends Component {}
export default class StorefrontOrderDetailsCommerceSummaryComponent extends Component {
/** Each promotion applied at checkout, with what it took off the order (items and delivery). */
get promotions() {
const promotions = this.args.resource?.meta?.promotions;
if (!Array.isArray(promotions)) {
return [];
}

return promotions.map((promotion) => ({
name: promotion.name || promotion.code || 'Promotion',
code: promotion.code,
isFreeDelivery: promotion.type === 'free_delivery',
amount: (Number(promotion.amount) || 0) + (Number(promotion.delivery_amount) || 0),
}));
}

/** The order's discount when no promotion breakdown was recorded. */
get unattributedDiscount() {
const discount = Number(this.args.resource?.meta?.discount) || 0;

return this.promotions.length === 0 && discount > 0 ? discount : 0;
}
}
16 changes: 4 additions & 12 deletions addon/components/storefront/order/details/customer.hbs
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
<ContentPanel @title="Customer" @open={{true}} @isLoading={{@isLoading}} @wrapperClass="bordered-top" ...attributes>
<div class="storefront-order-customer">
<div class="storefront-order-person">
<Image
src={{avatar-url @resource.customer.photo_url}}
@fallbackSrc={{config "defaultValues.contactImage"}}
class="storefront-order-person__avatar"
alt={{@resource.customer.name}}
/>
<div class="min-w-0">
<div class="storefront-order-person__name">{{n-a @resource.customer.name "No customer"}}</div>
<div class="storefront-order-person__meta">{{n-a @resource.customer.email "No email"}}</div>
<div class="storefront-order-person__meta">{{n-a @resource.customer.phone "No phone"}}</div>
</div>
<Customer::Pill @customer={{@resource.customer}} @titleFallback="No customer" @static={{not @resource.customer.id}} data-test-order-customer-pill />
<div class="storefront-order-facts mt-2">
<div><span>Email</span><strong>{{n-a @resource.customer.email "No email"}}</strong></div>
<div><span>Phone</span><strong>{{n-a @resource.customer.phone "No phone"}}</strong></div>
</div>

{{#unless @resource.meta.is_pickup}}
Expand Down
18 changes: 2 additions & 16 deletions addon/components/storefront/order/details/detail.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,13 @@
<div class="field-info-container">
<div class="field-name">Customer</div>
<div class="field-value">
<div class="storefront-order-person storefront-order-person--compact">
<Image src={{avatar-url @resource.customer.photo_url}} @fallbackSrc={{config "defaultValues.contactImage"}} class="storefront-order-person__avatar" alt={{@resource.customer.name}} />
<div class="min-w-0">
<div class="storefront-order-person__name {{unless @resource.customer "text-gray-300"}}">{{n-a @resource.customer.name "No customer"}}</div>
<div class="storefront-order-person__meta">{{n-a (or @resource.customer.phone @resource.customer.email) "No contact details"}}</div>
</div>
</div>
<Customer::Pill @customer={{@resource.customer}} @titleFallback="No customer" @static={{not @resource.customer.id}} data-test-order-customer-pill />
</div>
</div>
<div class="field-info-container">
<div class="field-name">Driver</div>
<div class="field-value">
<div class="storefront-order-person storefront-order-person--compact">
<Image src={{avatar-url @resource.driver_assigned.photo_url}} @fallbackSrc={{config "defaultValues.driverImage"}} class="storefront-order-person__avatar" alt={{@resource.driver_assigned.name}} />
<div class="min-w-0">
<div class="storefront-order-person__name {{unless @resource.driver_assigned "text-gray-300"}}">
{{n-a (or @resource.driver_assigned.displayName @resource.driver_assigned.name) "No driver assigned"}}
</div>
<div class="storefront-order-person__meta">{{n-a @resource.driver_assigned.phone "No phone"}}</div>
</div>
</div>
<Driver::Pill @driver={{@resource.driver_assigned}} @titleFallback="No driver assigned" @static={{not @resource.driver_assigned.id}} data-test-order-driver-pill />
</div>
</div>
</div>
Expand Down
13 changes: 1 addition & 12 deletions addon/components/storefront/order/details/fulfillment.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,7 @@
<div class="storefront-order-card-grid">
<div class="storefront-order-detail-card">
<div class="storefront-order-detail-card__label">Driver</div>
<div class="storefront-order-person storefront-order-person--compact">
<Image
src={{avatar-url @resource.driver_assigned.photo_url}}
@fallbackSrc={{config "defaultValues.driverImage"}}
class="storefront-order-person__avatar"
alt={{@resource.driver_assigned.name}}
/>
<div class="min-w-0">
<div class="storefront-order-person__name">{{n-a (or @resource.driver_assigned.displayName @resource.driver_assigned.name) "No driver assigned"}}</div>
<div class="storefront-order-person__meta">{{n-a @resource.driver_assigned.phone "No phone"}}</div>
</div>
</div>
<Driver::Pill @driver={{@resource.driver_assigned}} @titleFallback="No driver assigned" @static={{not @resource.driver_assigned.id}} data-test-order-driver-pill />
</div>
<div class="storefront-order-detail-card">
<div class="storefront-order-detail-card__label">Schedule</div>
Expand Down
11 changes: 3 additions & 8 deletions addon/components/widget/customers.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
{{#each this.customers as |customer|}}
<article class="storefront-customer-card storefront-animate-card">
<div class="flex items-center gap-3">
<Image src={{avatar-url customer.photo_url}} class="storefront-customer-avatar" alt={{customer.name}} />
<div class="min-w-0 flex-1">
<a href="javascript:;" {{on "click" (fn this.viewCustomer customer)}} class="storefront-customer-name">{{n-a customer.name}}</a>
<div class="storefront-table-muted">{{customer.public_id}}</div>
<Customer::Pill @customer={{customer}} @subtitle={{customer.public_id}} @onClick={{fn this.viewCustomer customer}} />
</div>
<div class="storefront-customer-order-count">{{n-a customer.orders 0}}</div>
</div>
Expand All @@ -32,7 +30,7 @@
{{/each}}
</div>
{{else}}
<div class="storefront-customers-table-wrap {{if (not this.customer) 'empty-state'}}">
<div class="storefront-customers-table-wrap {{unless this.customer 'empty-state'}}">
<table class="storefront-customers-table">
<thead>
<tr>
Expand All @@ -48,10 +46,7 @@
<tr class="storefront-animate-row">
<td><a href="javascript:;" {{on "click" (fn this.viewCustomer customer)}} class="storefront-customer-id">{{customer.public_id}}</a></td>
<td>
<div class="storefront-customer-cell">
<Image src={{avatar-url customer.photo_url}} class="storefront-customer-avatar" alt={{customer.name}} />
<span class="storefront-table-text">{{n-a customer.name}}</span>
</div>
<Customer::Pill @customer={{customer}} @onClick={{fn this.viewCustomer customer}} />
</td>
<td><span class="storefront-table-text">{{n-a customer.phone}}</span></td>
<td><span class="storefront-table-text">{{n-a customer.email}}</span></td>
Expand Down
Loading
Loading