Merge pull request #477 from fleetbase/dev-v0.7.22
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled

Organizations can now set their own alpha-numeric sender ID for SMS
This commit is contained in:
Ron
2025-12-07 06:37:02 +00:00
committed by GitHub
13 changed files with 113 additions and 232 deletions

View File

@@ -1,40 +1,24 @@
# 🚀 Fleetbase v0.7.21 — 2025-12-06 # 🚀 Fleetbase v0.7.22 — 2025-12-07
> "5x faster css compiling and flawless builds." > "Organizations can now set their own alpha-numeric sender ID for SMS"
--- ---
## ✨ Highlights ## ✨ Highlights
- **Custom Alphanumeric Sender ID for SMS:**
### 🔧 Critical Production Build Fix Organizations can now configure their own **Alphanumeric Sender ID** used when sending verification codes and other SMS notifications.
This feature improves brand recognition, enhances trust, and aligns outbound communication with each organizations identity.
This release resolves a **critical issue** that prevented production builds from succeeding when using ember-ui in Ember Engines environments. All Fleetbase applications and engines should upgrade immediately. Supported in regions/carriers where alphanumeric senders are allowed (e.g., Mongolia and others).
**What was broken:**
Production builds were failing with "Broken @import declaration" errors during CSS minification. Development builds worked fine, but production deployments were completely blocked.
**What we fixed:**
Implemented a proper architectural solution that prevents Ember Engines from attempting to recompile ember-ui styles. The addon now correctly detects when it's being included by an engine and blocks both the style tree distribution and PostCSS configuration override.
**The impact:**
-**Production builds now succeed** without CSS import errors
-**5x faster builds** — CSS is compiled once in the host app instead of being reprocessed by each engine
-**4x smaller bundles** — Eliminated CSS duplication across engine vendor files (reduced from ~3MB to ~750KB in typical setups)
-**Consistent styling** — All engines now use the same compiled CSS from the host application
**Technical details:**
The fix adds engine detection to both the `treeForStyles` and `included` hooks, ensuring that ember-ui styles are compiled exclusively in the host application and inherited by all engines. This follows the proper Ember Engines architecture pattern for shared addon styles.
--- ---
## ⚠️ Breaking Changes ## ⚠️ Breaking Changes
- None — This is a fully backward-compatible patch that only changes internal build behavior - None 🙂
--- ---
## 🔧 Upgrade Steps ## 🔧 Upgrade Steps
### For System
```bash ```bash
# Pull latest version # Pull latest version
git pull origin main --no-rebase git pull origin main --no-rebase
@@ -47,44 +31,6 @@ docker compose down && docker compose up -d
docker compose exec application bash -c "./deploy.sh" docker compose exec application bash -c "./deploy.sh"
``` ```
### For Extension Developers
```bash
# Update ember-ui
pnpm upgrade @fleetbase/ember-ui@^0.3.14 @fleetbase/ember-core@^0.3.8
# Reinstall dependencies
pnpm install
```
### Verification
```bash
# Verify production build succeeds
pnpm build --environment production
# Check bundle sizes (should see significant reduction)
ls -lh dist/assets/vendor*.css
ls -lh dist/engines-dist/*/assets/engine-vendor*.css
```
---
## 📦 What Changed
### Fixed
- **Critical**: Production build failures in Ember Engines environments with "Broken @import declaration" errors
- CSS duplication across engine bundles causing bloated file sizes
- PostCSS configuration conflicts between host app and engines
### Changed
- Added `treeForStyles` hook with engine detection to prevent style tree distribution to engines
- Added engine detection guard in `included` hook to prevent `postcssOptions` override
- Improved CSS import path resolution in PostCSS configuration
### Performance
- Reduced build time by 5x through single CSS compilation
- Reduced total CSS bundle size by 4x through elimination of duplication
- Improved caching efficiency with shared vendor.css
--- ---
## Need help? ## Need help?

View File

@@ -20,7 +20,7 @@
"require": { "require": {
"php": ">=8.0 <=8.2.28", "php": ">=8.0 <=8.2.28",
"appstract/laravel-opcache": "^4.0", "appstract/laravel-opcache": "^4.0",
"fleetbase/core-api": "^1.6.27", "fleetbase/core-api": "^1.6.28",
"fleetbase/fleetops-api": "^0.6.29", "fleetbase/fleetops-api": "^0.6.29",
"fleetbase/registry-bridge": "^0.1.2", "fleetbase/registry-bridge": "^0.1.2",
"fleetbase/storefront-api": "^0.4.9", "fleetbase/storefront-api": "^0.4.9",

42
api/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "e2917c30c56c080bdb6a6bf6adbde7a1", "content-hash": "357a3d180da0e49157a3d7057c279056",
"packages": [ "packages": [
{ {
"name": "appstract/laravel-opcache", "name": "appstract/laravel-opcache",
@@ -2157,16 +2157,16 @@
}, },
{ {
"name": "fleetbase/core-api", "name": "fleetbase/core-api",
"version": "1.6.27", "version": "1.6.28",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/fleetbase/core-api.git", "url": "https://github.com/fleetbase/core-api.git",
"reference": "831600c1818cbe76cfe280bfaaa3896ff1e2c597" "reference": "d964db3f4264f0a261364ce0c655f8aa561aeb84"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/fleetbase/core-api/zipball/831600c1818cbe76cfe280bfaaa3896ff1e2c597", "url": "https://api.github.com/repos/fleetbase/core-api/zipball/d964db3f4264f0a261364ce0c655f8aa561aeb84",
"reference": "831600c1818cbe76cfe280bfaaa3896ff1e2c597", "reference": "d964db3f4264f0a261364ce0c655f8aa561aeb84",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2252,9 +2252,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/fleetbase/core-api/issues", "issues": "https://github.com/fleetbase/core-api/issues",
"source": "https://github.com/fleetbase/core-api/tree/v1.6.27" "source": "https://github.com/fleetbase/core-api/tree/v1.6.28"
}, },
"time": "2025-12-05T11:24:12+00:00" "time": "2025-12-07T06:10:01+00:00"
}, },
{ {
"name": "fleetbase/countries", "name": "fleetbase/countries",
@@ -7755,16 +7755,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.6.2", "version": "v5.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "3a454ca033b9e06b63282ce19562e892747449bb" "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"reference": "3a454ca033b9e06b63282ce19562e892747449bb", "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -7807,9 +7807,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0"
}, },
"time": "2025-10-21T19:32:17+00:00" "time": "2025-12-06T11:56:16+00:00"
}, },
{ {
"name": "nunomaduro/termwind", "name": "nunomaduro/termwind",
@@ -10041,16 +10041,16 @@
}, },
{ {
"name": "psy/psysh", "name": "psy/psysh",
"version": "v0.12.15", "version": "v0.12.16",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/bobthecow/psysh.git", "url": "https://github.com/bobthecow/psysh.git",
"reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c" "reference": "ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/38953bc71491c838fcb6ebcbdc41ab7483cd549c", "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67",
"reference": "38953bc71491c838fcb6ebcbdc41ab7483cd549c", "reference": "ee6d5028be4774f56c6c2c85ec4e6bc9acfe6b67",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -10058,8 +10058,8 @@
"ext-tokenizer": "*", "ext-tokenizer": "*",
"nikic/php-parser": "^5.0 || ^4.0", "nikic/php-parser": "^5.0 || ^4.0",
"php": "^8.0 || ^7.4", "php": "^8.0 || ^7.4",
"symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4",
"symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4"
}, },
"conflict": { "conflict": {
"symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
@@ -10114,9 +10114,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/bobthecow/psysh/issues", "issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.12.15" "source": "https://github.com/bobthecow/psysh/tree/v0.12.16"
}, },
"time": "2025-11-28T00:00:14+00:00" "time": "2025-12-07T03:39:01+00:00"
}, },
{ {
"name": "ralouphie/getallheaders", "name": "ralouphie/getallheaders",

View File

@@ -1,31 +0,0 @@
<Overlay @isOpen={{@isOpen}} @onLoad={{this.setOverlayContext}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width={{or this.width @width "400px"}}>
<Overlay::Header @title={{t "component.dashboard-widget-panel.select-widgets"}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
<div class="flex flex-1 justify-end">
<Button @type="default" @icon="times" @helpText={{t "component.dashboard-widget-panel.close-and-save"}} @onClick={{this.onPressClose}} />
</div>
</Overlay::Header>
<Overlay::Body @wrapperClass="new-service-rate-overlay-body px-4 space-y-4 pt-4">
<div class="grid grid-cols-1 gap-4 text-xs dark:text-gray-100">
{{#each this.availableWidgets as |widget|}}
<div
class="rounded-lg border border-gray-300 bg-white dark:border-gray-700 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300 ease-in-out shadow-md px-4 py-2 cursor-pointer"
{{on "click" (fn this.addWidgetToDashboard widget)}}
>
<div class="flex flex-row items-center leading-6 mb-2.5">
<div class="w-8 flex items-center justify-start">
<FaIcon @icon={{widget.icon}} class="text-lg text-gray-600 dark:text-gray-300" />
</div>
<p class="text-sm truncate font-semibold dark:text-gray-100 text-gray-800">
{{t "component.dashboard-widget-panel.widget-name" widgetName=widget.name}}
</p>
</div>
<div>
<p class="text-xs dark:text-gray-100 text-gray-800">{{widget.description}}</p>
</div>
</div>
{{/each}}
</div>
<Spacer @height="300px" />
</Overlay::Body>
</Overlay>

View File

@@ -1,60 +0,0 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class DashboardWidgetPanelComponent extends Component {
@service universe;
@tracked availableWidgets = [];
@tracked dashboard;
@tracked isOpen = true;
@service notifications;
/**
* Constructs the component and applies initial state.
*/
constructor(owner, { dashboard }) {
super(...arguments);
this.availableWidgets = this.universe.getDashboardWidgets();
this.dashboard = dashboard;
}
/**
* Sets the overlay context.
*
* @action
* @param {OverlayContextObject} overlayContext
*/
@action setOverlayContext(overlayContext) {
this.context = overlayContext;
if (typeof this.args.onLoad === 'function') {
this.args.onLoad(...arguments);
}
}
@action addWidgetToDashboard(widget) {
// If widget is a component definition/class
if (typeof widget.component === 'function') {
widget.component = widget.component.name;
}
this.args.dashboard.addWidget(widget).catch((error) => {
this.notifications.serverError(error);
});
}
/**
* Handles cancel button press.
*
* @action
*/
@action onPressClose() {
this.isOpen = false;
if (typeof this.args.onClose === 'function') {
this.args.onClose();
}
}
}

View File

@@ -6,35 +6,13 @@ import createNotificationKey from '@fleetbase/ember-core/utils/create-notificati
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
export default class ConsoleSettingsNotificationsController extends Controller { export default class ConsoleSettingsNotificationsController extends Controller {
/**
* Inject the notifications service.
*
* @memberof ConsoleSettingsNotificationsController
*/
@service notifications; @service notifications;
/**
* Inject the fetch service.
*
* @memberof ConsoleSettingsNotificationsController
*/
@service fetch; @service fetch;
@service store;
/** @service currentUser;
* The notification settings value JSON.
*
* @memberof ConsoleSettingsNotificationsController
* @var {Object}
*/
@tracked notificationSettings = {}; @tracked notificationSettings = {};
/**
* Notification transport methods enabled.
*
* @memberof ConsoleSettingsNotificationsController
* @var {Array}
*/
@tracked notificationTransportMethods = ['email', 'sms']; @tracked notificationTransportMethods = ['email', 'sms'];
@tracked company;
/** /**
* Creates an instance of ConsoleSettingsNotificationsController. * Creates an instance of ConsoleSettingsNotificationsController.
@@ -45,6 +23,40 @@ export default class ConsoleSettingsNotificationsController extends Controller {
this.getSettings.perform(); this.getSettings.perform();
} }
/**
* Toggles the "Alphanumeric Sender ID" feature for the current company.
*
* Updates the company's `options` object by setting the
* `alpha_numeric_sender_id_enabled` flag. This controls whether the
* organization uses a custom alphanumeric sender ID when sending SMS.
*
* @action
* @param {boolean} enabled - Whether the feature should be enabled or disabled.
* @returns {void}
*/
@action toggleAlphaNumericSenderId(enabled) {
const currentOptions = this.company.options ?? {};
this.company.set('options', { ...currentOptions, alpha_numeric_sender_id_enabled: enabled });
}
/**
* Sets the Alphanumeric Sender ID string for the current company.
*
* Reads the input's value from the event and updates the company's `options`
* object by setting the `alpha_numeric_sender_id` field. This value represents
* the sender name that will appear in outbound SMS messages (subject to carrier
* support and restrictions).
*
* @action
* @param {Event} event - Input event containing the alphanumeric sender ID value.
* @returns {void}
*/
@action setAlphaNumericSenderId(event) {
const value = event.target.value;
const currentOptions = this.company.options ?? {};
this.company.set('options', { ...currentOptions, alpha_numeric_sender_id: value });
}
/** /**
* Selectes notifiables for settings. * Selectes notifiables for settings.
* *
@@ -94,7 +106,8 @@ export default class ConsoleSettingsNotificationsController extends Controller {
const { notificationSettings } = this; const { notificationSettings } = this;
try { try {
yield this.fetch.post('notifications/save-settings', { notificationSettings }); yield this.fetch.post('notifications/save-settings', { notificationSettings: notificationSettings ?? {} });
yield this.saveCompanyOptions.perform();
this.notifications.success('Notification settings successfully saved.'); this.notifications.success('Notification settings successfully saved.');
} catch (error) { } catch (error) {
this.notifications.serverError(error); this.notifications.serverError(error);
@@ -114,4 +127,26 @@ export default class ConsoleSettingsNotificationsController extends Controller {
this.notifications.serverError(error); this.notifications.serverError(error);
} }
} }
/**
* Saves the updated company options to the backend.
*
* This ember-concurrency task attempts to persist the company's modified
* `options` object by calling `company.save()`. If the request fails, a server
* error notification is displayed. No action is taken if no company is loaded.
*
* @task
* @generator
* @yields {Promise} Resolves when the save request completes.
* @returns {Promise<void>} Task completion state.
*/
@task *saveCompanyOptions() {
if (!this.company) return;
try {
yield this.company.save();
} catch (error) {
this.notifications.serverError(error);
}
}
} }

View File

@@ -24,7 +24,7 @@ export default class Company extends Model {
@attr('string') logo_url; @attr('string') logo_url;
@attr('string') backdrop_url; @attr('string') backdrop_url;
@attr('string') description; @attr('string') description;
@attr('raw') options; @attr('object') options;
@attr('number') users_count; @attr('number') users_count;
@attr('string') type; @attr('string') type;
@attr('string') currency; @attr('string') currency;

View File

@@ -5,6 +5,7 @@ import groupBy from '@fleetbase/ember-core/utils/group-by';
export default class ConsoleSettingsNotificationsRoute extends Route { export default class ConsoleSettingsNotificationsRoute extends Route {
@service fetch; @service fetch;
@service currentUser;
model() { model() {
return hash({ return hash({
@@ -13,10 +14,11 @@ export default class ConsoleSettingsNotificationsRoute extends Route {
}); });
} }
setupController(controller, { registry, notifiables }) { async setupController(controller, { registry, notifiables }) {
super.setupController(...arguments); super.setupController(...arguments);
controller.groupedNotifications = groupBy(registry, 'package'); controller.groupedNotifications = groupBy(registry, 'package');
controller.notifiables = notifiables; controller.notifiables = notifiables;
controller.company = await this.currentUser.loadCompany();
} }
} }

View File

@@ -17,7 +17,7 @@
@selected={{get this.notificationSettings (concat (get-notification-key notification.definition notification.name) ".notifiables")}} @selected={{get this.notificationSettings (concat (get-notification-key notification.definition notification.name) ".notifiables")}}
@onChange={{fn this.onSelectNotifiable notification}} @onChange={{fn this.onSelectNotifiable notification}}
@placeholder="Select notifiables..." @placeholder="Select notifiables..."
@triggerClass="form-select form-input form-input-sm flex-1" @triggerClass="form-select form-input flex-1"
as |notifiable| as |notifiable|
> >
{{notifiable.label}} {{notifiable.label}}
@@ -27,6 +27,21 @@
{{/each}} {{/each}}
</ContentPanel> </ContentPanel>
{{/each-in}} {{/each-in}}
<ContentPanel @title="SMS Notification Settings" @open={{true}} @wrapperClass="bordered-classic">
<Toggle @isToggled={{this.company.options.alpha_numeric_sender_id_enabled}} @onToggle={{this.toggleAlphaNumericSenderId}} @label="Enable Alpha-Numeric Sender ID" @wrapperClass="mb-4" />
<InputGroup @name="Alpha-Numeric Sender ID" @value={{this.company.options.alpha_numeric_sender_id}} @helpText="Set the custom alphanumeric name that will appear as the sender for all SMS sent by your organization. Up to 11 letters or numbers. Not supported in all countries." @disabled={{not this.company.options.alpha_numeric_sender_id_enabled}} />
<div class="space-y-2 mb-3">
<InfoBlock>
<p>Alphanumeric Sender IDs allow your organization to replace a traditional phone number with a custom text-based sender name when sending SMS notifications (e.g., Fleetbase, MyStore, DispatchHQ). This can improve brand recognition, increase message trust, and enhance deliverability in regions where numeric senders are restricted by local carriers.</p>
<p>When enabled, Fleetbase will use this sender ID for all outbound SMS messages sent on behalf of your organization, including order updates, verification codes, driver notifications, and other automated alerts. Sender IDs can contain up to 11 characters using letters and numbers.</p>
<p>Some countries require or enforce specific messaging rules, and certain carriers may only support alphanumeric senders. Using a Sender ID can significantly improve message delivery in these regions.</p>
</InfoBlock>
<InfoBlock @type="warning">
<p>Delivery of SMS using Alphanumeric Sender IDs depends on local carrier policies. Some regions may restrict or block numeric senders or require the use of alphanumeric senders for successful delivery. While Fleetbase will attempt to deliver all messages using your configured Sender ID, message delivery cannot be guaranteed in countries with carrier-level filtering or regulatory restrictions. Your organization is responsible for ensuring compliance with local messaging regulations in the countries you send SMS to.</p>
</InfoBlock>
</div>
</ContentPanel>
</div> </div>
</div> </div>
<Spacer @height="300px" /> <Spacer @height="300px" />

View File

@@ -1,6 +1,6 @@
{ {
"name": "@fleetbase/console", "name": "@fleetbase/console",
"version": "0.7.21", "version": "0.7.22",
"private": true, "private": true,
"description": "Modular logistics and supply chain operating system (LSOS)", "description": "Modular logistics and supply chain operating system (LSOS)",
"repository": "https://github.com/fleetbase/fleetbase", "repository": "https://github.com/fleetbase/fleetbase",

View File

@@ -1,26 +0,0 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from '@fleetbase/console/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | dashboard/widget-panel', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<Dashboard::WidgetPanel />`);
assert.dom().hasText('');
// Template block usage:
await render(hbs`
<Dashboard::WidgetPanel>
template block text
</Dashboard::WidgetPanel>
`);
assert.dom().hasText('template block text');
});
});

View File

@@ -75,7 +75,7 @@ ENV QUEUE_CONNECTION=redis
ENV CADDYFILE_PATH=/fleetbase/Caddyfile ENV CADDYFILE_PATH=/fleetbase/Caddyfile
ENV CONSOLE_PATH=/fleetbase/console ENV CONSOLE_PATH=/fleetbase/console
ENV OCTANE_SERVER=frankenphp ENV OCTANE_SERVER=frankenphp
ENV FLEETBASE_VERSION=0.7.21 ENV FLEETBASE_VERSION=0.7.22
# Set environment # Set environment
ARG ENVIRONMENT=production ARG ENVIRONMENT=production