Compare commits

...

6 Commits

Author SHA1 Message Date
Ron
dccd1b6883 Merge pull request #500 from fleetbase/fix/onboarding-context-quota-error
fix: Add localStorage quota error handling to onboarding-context service
2026-02-12 15:49:48 +08:00
Ronald A. Richardson
5e69a7d443 v0.7.29 2026-02-12 15:48:02 +08:00
Ron
989fa9a777 Merge pull request #501 from fleetbase/dev-v0.7.28
Some checks failed
Fleetbase CI / Build and Start Docker Services (push) Has been cancelled
Upgrades fleetops to v0.6.35
2026-02-06 10:17:13 +08:00
Ronald A. Richardson
edec9d02a4 Sync submodule pointers to origin/main 2026-02-06 10:16:16 +08:00
Ronald A. Richardson
a79ebda392 Upgrades fleetops to v0.6.35 2026-02-06 10:04:32 +08:00
Ronald A Richardson
3dc5b9b015 fix: Add localStorage quota error handling to onboarding-context service
- Add _safeSet() and _safeGet() wrappers around appCache operations
- Implement in-memory fallback when localStorage quota is exceeded
- Add user notification when storage issues occur
- Track quota exceeded status to avoid duplicate notifications
- Add getStorageStatus() method for debugging
- Ensure onboarding can complete even with full localStorage
- Data persists in memory for current session when storage is full
2026-02-05 03:54:10 -05:00
11 changed files with 152 additions and 46 deletions

View File

@@ -210,6 +210,9 @@ jobs:
- name: Set Env Variables
run: |
# ensure file ends with a newline
printf '\n' >> ./environments/.env.production
echo "EXTENSIONS=${{ secrets.EXTENSIONS }}" >> ./environments/.env.production
echo "LOGROCKET_APP_ID=${{ secrets.LOGROCKET_APP_ID }}" >> ./environments/.env.production
echo "STRIPE_KEY=${{ secrets.STRIPE_KEY }}" >> ./environments/.env.production

View File

@@ -1,19 +1,11 @@
# 🚀 Fleetbase v0.7.27 — 2026-02-05
# 🚀 Fleetbase v0.7.28 — 2026-02-05
> "Improvements and patches"
> "Critical patch: eager load driver and vehicle in consumable orders query API"
---
## ✨ Highlights
- Core now supports disabling cache in runtime for `HasApiModelCache` x `HasApiModelBehavior`
- Added new `FileResolverService` to support file resolution from file resources, URL's, base64, and file uploads [190c03d](https://github.com/fleetbase/core-api/pull/187/commits/190c03d484648319f3d890439f74e45820f352fc)
- `VerificationCode` model in core always throws SMS exceptions
- FleetOps: Patched proof of delivery component in order details
- FleetOps: Improved and patched service rate `getServicableForPlaces` which improved service quote performance
- FleetOps: Fix location GeoJSON Point casting for `location` properties - using the new `Utils::castPoint` utility [208151f](https://github.com/fleetbase/fleetops/pull/202/commits/208151f37ece54bb23cfeeebdbb6fde1142908f7)
- Storefront: Critical patch for QPay checkout workflow [storefront#66](https://github.com/fleetbase/storefront/pull/66)
- Storefront: Added new phone number verification endpoints for customers (`request-phone-verification` and `verify-phone-number`)
- Storefront: Fixed cart based service quotes
- Critical patch which fixes eager loading relationship when querying orders via the consumable API, this is done to handle the new `whenLoaded` resource conditional methods. [fleetbase/fleetops#203](https://github.com/fleetbase/fleetops/pull/203)
---

View File

@@ -21,7 +21,7 @@
"php": ">=8.0 <=8.2.30",
"appstract/laravel-opcache": "^4.0",
"fleetbase/core-api": "^1.6.35",
"fleetbase/fleetops-api": "^0.6.34",
"fleetbase/fleetops-api": "^0.6.35",
"fleetbase/registry-bridge": "^0.1.5",
"fleetbase/storefront-api": "^0.4.13",
"guzzlehttp/guzzle": "^7.0.1",

14
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",
"This file is @generated automatically"
],
"content-hash": "e1a0396811a349ab03f8782119c89e5b",
"content-hash": "240f5916db1171e37d4f4548cd9f1373",
"packages": [
{
"name": "appstract/laravel-opcache",
@@ -2323,16 +2323,16 @@
},
{
"name": "fleetbase/fleetops-api",
"version": "0.6.34",
"version": "0.6.35",
"source": {
"type": "git",
"url": "https://github.com/fleetbase/fleetops.git",
"reference": "05503cee9f55f5e4e1bad2210615e4f67bb165ab"
"reference": "c89d1f3e81564cb5009293001a67cd4e59051a69"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/05503cee9f55f5e4e1bad2210615e4f67bb165ab",
"reference": "05503cee9f55f5e4e1bad2210615e4f67bb165ab",
"url": "https://api.github.com/repos/fleetbase/fleetops/zipball/c89d1f3e81564cb5009293001a67cd4e59051a69",
"reference": "c89d1f3e81564cb5009293001a67cd4e59051a69",
"shasum": ""
},
"require": {
@@ -2407,9 +2407,9 @@
],
"support": {
"issues": "https://github.com/fleetbase/fleetops/issues",
"source": "https://github.com/fleetbase/fleetops/tree/v0.6.34"
"source": "https://github.com/fleetbase/fleetops/tree/v0.6.35"
},
"time": "2026-02-05T00:08:14+00:00"
"time": "2026-02-06T00:58:53+00:00"
},
{
"name": "fleetbase/laravel-mysql-spatial",

View File

@@ -2,6 +2,7 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { isArray } from '@ember/array';
import { debug } from '@ember/debug';
import { storageFor } from 'ember-local-storage';
import { add, isPast } from 'date-fns';
import { task } from 'ember-concurrency';

View File

@@ -6,20 +6,110 @@ const KEYS_INDEX = `${CONTEXT_PREFIX}__keys__`;
export default class OnboardingContextService extends Service {
@service appCache;
@service notifications;
@tracked data = {};
@tracked quotaExceeded = false;
@tracked usingMemoryFallback = false;
// In-memory fallback storage for when localStorage is full
_memoryCache = new Map();
/**
* Safe wrapper for appCache.set with quota error handling
*
* @param {string} key - The key to set
* @param {*} value - The value to store
* @returns {Object} Result object with success status and storage type
*/
_safeSet(key, value) {
try {
this.appCache.set(key, value);
return { success: true, storage: 'localStorage' };
} catch (error) {
if (this._isQuotaError(error)) {
console.warn(`[OnboardingContext] localStorage quota exceeded, using memory fallback for key: ${key}`);
// Store in memory as fallback
this._memoryCache.set(key, value);
// Mark that we're using fallback and notify user (only once)
if (!this.quotaExceeded) {
this.quotaExceeded = true;
this.usingMemoryFallback = true;
this._notifyUser();
}
return { success: true, storage: 'memory', warning: 'Using memory fallback' };
}
// Re-throw non-quota errors
throw error;
}
}
/**
* Safe wrapper for appCache.get with memory fallback
*
* @param {string} key - The key to retrieve
* @returns {*} The stored value or undefined
*/
_safeGet(key) {
try {
const value = this.appCache.get(key);
if (value !== undefined) {
return value;
}
} catch (error) {
console.warn(`[OnboardingContext] Error reading from appCache: ${error.message}`);
}
// Fallback to memory cache
return this._memoryCache.get(key);
}
/**
* Check if error is a quota exceeded error
*
* @param {Error} error - The error to check
* @returns {boolean} True if it's a quota error
*/
_isQuotaError(error) {
return (
error instanceof DOMException &&
(error.code === 22 ||
error.code === 1014 ||
error.name === 'QuotaExceededError' ||
error.name === 'NS_ERROR_DOM_QUOTA_REACHED')
);
}
/**
* Notify user about storage issues (only called once)
*/
_notifyUser() {
if (this.notifications) {
this.notifications.warning(
'Your browser storage is full. Your onboarding progress will be saved temporarily but may be lost if you close this tab. Please complete the onboarding process in this session.',
{
timeout: 10000,
clearDuration: 300
}
);
}
}
/**
* Get a value from in-memory state first, then fallback to cache
*/
get(key) {
return this.data[key] ?? this.appCache.get(`${CONTEXT_PREFIX}${key}`);
return this.data[key] ?? this._safeGet(`${CONTEXT_PREFIX}${key}`);
}
/**
* Get a value directly from cache
*/
getFromCache(key) {
return this.appCache.get(`${CONTEXT_PREFIX}${key}`);
return this._safeGet(`${CONTEXT_PREFIX}${key}`);
}
/**
@@ -28,11 +118,11 @@ export default class OnboardingContextService extends Service {
* @returns {Object}
*/
restore() {
const keys = this.appCache.get(KEYS_INDEX) ?? [];
const keys = this._safeGet(KEYS_INDEX) ?? [];
const persisted = {};
for (const key of keys) {
const value = this.appCache.get(`${CONTEXT_PREFIX}${key}`);
const value = this._safeGet(`${CONTEXT_PREFIX}${key}`);
if (value !== undefined) {
persisted[key] = value;
}
@@ -63,14 +153,14 @@ export default class OnboardingContextService extends Service {
this.data = { ...this.data, ...filteredData };
if (options.persist === true) {
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
const keys = new Set(this._safeGet(KEYS_INDEX) ?? []);
for (const key of Object.keys(filteredData)) {
keys.add(key);
this.appCache.set(`${CONTEXT_PREFIX}${key}`, this.data[key]);
this._safeSet(`${CONTEXT_PREFIX}${key}`, this.data[key]);
}
this.appCache.set(KEYS_INDEX, [...keys]);
this._safeSet(KEYS_INDEX, [...keys]);
}
}
@@ -88,11 +178,11 @@ export default class OnboardingContextService extends Service {
this.data = { ...this.data, [key]: value };
if (options.persist === true) {
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
const keys = new Set(this._safeGet(KEYS_INDEX) ?? []);
keys.add(key);
this.appCache.set(`${CONTEXT_PREFIX}${key}`, value);
this.appCache.set(KEYS_INDEX, [...keys]);
this._safeSet(`${CONTEXT_PREFIX}${key}`, value);
this._safeSet(KEYS_INDEX, [...keys]);
}
}
@@ -110,24 +200,44 @@ export default class OnboardingContextService extends Service {
const { [key]: _removed, ...rest } = this.data; // eslint-disable-line no-unused-vars
this.data = rest;
const keys = new Set(this.appCache.get(KEYS_INDEX) ?? []);
const keys = new Set(this._safeGet(KEYS_INDEX) ?? []);
keys.delete(key);
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
this.appCache.set(KEYS_INDEX, [...keys]);
this._safeSet(`${CONTEXT_PREFIX}${key}`, undefined);
this._safeSet(KEYS_INDEX, [...keys]);
// Also remove from memory cache
this._memoryCache.delete(`${CONTEXT_PREFIX}${key}`);
}
/**
* Fully reset onboarding context (memory + persistence)
*/
reset() {
const keys = this.appCache.get(KEYS_INDEX) ?? [];
const keys = this._safeGet(KEYS_INDEX) ?? [];
for (const key of keys) {
this.appCache.set(`${CONTEXT_PREFIX}${key}`, undefined);
this._safeSet(`${CONTEXT_PREFIX}${key}`, undefined);
this._memoryCache.delete(`${CONTEXT_PREFIX}${key}`);
}
this.appCache.set(KEYS_INDEX, []);
this._safeSet(KEYS_INDEX, []);
this._memoryCache.clear();
this.data = {};
this.quotaExceeded = false;
this.usingMemoryFallback = false;
}
}
/**
* Get storage status for debugging
*
* @returns {Object} Storage status information
*/
getStorageStatus() {
return {
quotaExceeded: this.quotaExceeded,
usingMemoryFallback: this.usingMemoryFallback,
memoryItemCount: this._memoryCache.size
};
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@fleetbase/console",
"version": "0.7.27",
"version": "0.7.28",
"private": true,
"description": "Modular logistics and supply chain operating system (LSOS)",
"repository": "https://github.com/fleetbase/fleetbase",
@@ -37,7 +37,7 @@
"@fleetbase/ember-core": "^0.3.10",
"@fleetbase/ember-ui": "^0.3.18",
"@fleetbase/fleetops-data": "^0.1.25",
"@fleetbase/fleetops-engine": "^0.6.34",
"@fleetbase/fleetops-engine": "^0.6.35",
"@fleetbase/iam-engine": "^0.1.6",
"@fleetbase/leaflet-routing-machine": "^3.2.17",
"@fleetbase/registry-bridge-engine": "^0.1.5",

10
console/pnpm-lock.yaml generated
View File

@@ -29,8 +29,8 @@ importers:
specifier: ^0.1.25
version: 0.1.25(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(webpack@5.105.0)
'@fleetbase/fleetops-engine':
specifier: ^0.6.34
version: 0.6.34(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
specifier: ^0.6.35
version: 0.6.35(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
'@fleetbase/iam-engine':
specifier: ^0.1.6
version: 0.1.6(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)
@@ -1544,8 +1544,8 @@ packages:
resolution: {integrity: sha512-uCX/qB4ANDGNN+EM1vdsVc4inprGEwj1dT0G5OTYKsFaHL3CWOeXsOg8qSa5EDClqxIodadx6stB+dSwrhYowg==}
engines: {node: '>= 18'}
'@fleetbase/fleetops-engine@0.6.34':
resolution: {integrity: sha512-1xDoJl8fUldGC0VorxcfAfxw6nG1BQrGL9AY1y8go4rosvhoufuZ/F+Ve5tDvZuXNhN5UzsdtoKBgt32ZwJKwQ==}
'@fleetbase/fleetops-engine@0.6.35':
resolution: {integrity: sha512-UYSLnnoV3we4dn/FXjnpmsS+b5/BhnzpIz/3Kg8y9tUAdz6FHRT/PnhOi9Iav99BGFMEiVOyQTJwDs4VDnDgfg==}
engines: {node: '>= 18'}
peerDependencies:
ember-engines: ^0.9.0
@@ -10757,7 +10757,7 @@ snapshots:
- utf-8-validate
- webpack
'@fleetbase/fleetops-engine@0.6.34(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
'@fleetbase/fleetops-engine@0.6.35(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(@glimmer/component@1.1.2(@babel/core@7.29.0))(@glimmer/tracking@1.1.2)(ember-engines@0.9.0(@ember/legacy-built-in-components@0.4.2(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(postcss@8.5.6)(rollup@2.79.2)(tracked-built-ins@3.4.0(@babel/core@7.29.0))(webpack@5.105.0)':
dependencies:
'@babel/core': 7.29.0
'@fleetbase/ember-core': 0.3.10(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.29.0)(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(webpack@5.105.0))(ember-resolver@11.0.1(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0)))(ember-source@5.4.1(@babel/core@7.29.0)(@glimmer/component@1.1.2(@babel/core@7.29.0))(rsvp@4.8.5)(webpack@5.105.0))(eslint@8.57.1)(webpack@5.105.0)

View File

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