@fleetbase/ember-core 0.3.18 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/authenticators/fleetbase.js +2 -2
- package/addon/library/subject-custom-fields.js +3 -3
- package/addon/services/chat.js +23 -1
- package/addon/services/current-user.js +33 -7
- package/addon/services/fetch.js +6 -1
- package/addon/services/theme.js +28 -7
- package/addon/utils/load-installed-extensions.js +2 -0
- package/addon/utils/lookup-user-ip.js +29 -6
- package/package.json +1 -1
- package/pnpm-workspace.yaml +2 -0
|
@@ -61,8 +61,8 @@ export default class FleetbaseAuthenticator extends Base {
|
|
|
61
61
|
* @param {boolean} remember
|
|
62
62
|
* @param {string} path
|
|
63
63
|
*/
|
|
64
|
-
authenticate(credentials = {}, remember = false, path = 'auth/login') {
|
|
65
|
-
return this.fetch.post(path, { ...credentials, remember }).then((response) => {
|
|
64
|
+
authenticate(credentials = {}, remember = false, path = 'auth/login', options = {}) {
|
|
65
|
+
return this.fetch.post(path, { ...credentials, remember }, options).then((response) => {
|
|
66
66
|
if (response.errors) {
|
|
67
67
|
const errorMessage = getWithDefault(response.errors, '0', 'Authentication failed!');
|
|
68
68
|
const errorCode = getWithDefault(response, 'code');
|
|
@@ -64,7 +64,7 @@ export default class SubjectCustomFields {
|
|
|
64
64
|
this.setFieldValue(value, customField);
|
|
65
65
|
|
|
66
66
|
const fieldId = typeof customField === 'string' ? customField : customField?.id;
|
|
67
|
-
const valueType = typeof customField === 'string' ? null : customField?.valueType ?? customField?.value_type ?? null;
|
|
67
|
+
const valueType = typeof customField === 'string' ? null : (customField?.valueType ?? customField?.value_type ?? null);
|
|
68
68
|
if (!fieldId || !resource) return;
|
|
69
69
|
|
|
70
70
|
let rec = this.#getLocalValueRecord(resource, fieldId);
|
|
@@ -91,7 +91,7 @@ export default class SubjectCustomFields {
|
|
|
91
91
|
let { value_type } = entry;
|
|
92
92
|
if (!value_type) {
|
|
93
93
|
const cf = this.store.peekRecord?.('custom-field', fieldId);
|
|
94
|
-
value_type = cf ? cf.valueType ?? cf.value_type ?? null : null;
|
|
94
|
+
value_type = cf ? (cf.valueType ?? cf.value_type ?? null) : null;
|
|
95
95
|
}
|
|
96
96
|
next[fieldId] = { value, value_type };
|
|
97
97
|
}
|
|
@@ -253,7 +253,7 @@ export default class SubjectCustomFields {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Normalize to array
|
|
256
|
-
const existing = isArray(existingMany) ? existingMany : existingMany?.toArray?.() ?? [];
|
|
256
|
+
const existing = isArray(existingMany) ? existingMany : (existingMany?.toArray?.() ?? []);
|
|
257
257
|
|
|
258
258
|
const byFieldId = new Map();
|
|
259
259
|
for (let i = 0; i < existing.length; i++) {
|
package/addon/services/chat.js
CHANGED
|
@@ -9,6 +9,7 @@ export default class ChatService extends Service.extend(Evented) {
|
|
|
9
9
|
@service store;
|
|
10
10
|
@service currentUser;
|
|
11
11
|
@service appCache;
|
|
12
|
+
@service fetch;
|
|
12
13
|
@tracked channels = [];
|
|
13
14
|
@tracked openChannels = [];
|
|
14
15
|
|
|
@@ -72,7 +73,28 @@ export default class ChatService extends Service.extend(Evented) {
|
|
|
72
73
|
return this.openChannels;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
createChatChannel(name) {
|
|
76
|
+
createChatChannel(name, participants = []) {
|
|
77
|
+
return this.fetch
|
|
78
|
+
.post(
|
|
79
|
+
'chat-channels',
|
|
80
|
+
{
|
|
81
|
+
chatChannel: {
|
|
82
|
+
name,
|
|
83
|
+
participants,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
normalizeToEmberData: true,
|
|
88
|
+
normalizeModelType: 'chatChannel',
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
.then((chatChannelRecord) => {
|
|
92
|
+
this.trigger('chat.created', chatChannelRecord);
|
|
93
|
+
return chatChannelRecord;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
createEmptyChatChannel(name) {
|
|
76
98
|
const chatChannelRecord = this.store.createRecord('chat-channel', { name });
|
|
77
99
|
return chatChannelRecord.save().finally(() => {
|
|
78
100
|
this.trigger('chat.created', chatChannelRecord);
|
|
@@ -3,12 +3,12 @@ import Evented from '@ember/object/evented';
|
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { tracked } from '@glimmer/tracking';
|
|
5
5
|
import { dasherize } from '@ember/string';
|
|
6
|
-
import {
|
|
6
|
+
import { get } from '@ember/object';
|
|
7
7
|
import { isBlank } from '@ember/utils';
|
|
8
8
|
import { alias } from '@ember/object/computed';
|
|
9
9
|
import { storageFor } from 'ember-local-storage';
|
|
10
10
|
import { debug } from '@ember/debug';
|
|
11
|
-
import lookupUserIp from '../utils/lookup-user-ip';
|
|
11
|
+
import lookupUserIp, { getBrowserTimezone } from '../utils/lookup-user-ip';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* CurrentUserService
|
|
@@ -60,8 +60,29 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
60
60
|
@alias('userSnapshot.role_name') roleName;
|
|
61
61
|
@alias('userSnapshot.role') role;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
get authenticatedOptionOwnerId() {
|
|
64
|
+
const authenticatedUserId = this.session?.data?.authenticated?.user;
|
|
65
|
+
|
|
66
|
+
if (this.session?.isAuthenticated && authenticatedUserId) {
|
|
67
|
+
return authenticatedUserId;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const localStorageSession = JSON.parse(window.localStorage.getItem('ember_simple_auth-session'));
|
|
72
|
+
const authenticatedSession = localStorageSession?.authenticated;
|
|
73
|
+
|
|
74
|
+
if (authenticatedSession?.token && authenticatedSession?.user) {
|
|
75
|
+
return authenticatedSession.user;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// Ignore malformed session storage and fall back to the current snapshot.
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get optionsPrefix() {
|
|
85
|
+
return `${this.authenticatedOptionOwnerId || this.id || 'anon'}:`;
|
|
65
86
|
}
|
|
66
87
|
|
|
67
88
|
get latitude() {
|
|
@@ -84,12 +105,16 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
84
105
|
return this.whois('country_code');
|
|
85
106
|
}
|
|
86
107
|
|
|
108
|
+
get timezone() {
|
|
109
|
+
return this.whois('timezone') || getBrowserTimezone();
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
async load() {
|
|
88
113
|
if (this.session.isAuthenticated) {
|
|
89
114
|
const user = await this.store.findRecord('user', 'me');
|
|
90
115
|
|
|
91
116
|
// set user
|
|
92
|
-
this.setUser(user);
|
|
117
|
+
await this.setUser(user);
|
|
93
118
|
|
|
94
119
|
// Load preferences
|
|
95
120
|
await this.loadPreferences();
|
|
@@ -110,7 +135,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
110
135
|
const user = await this.store.queryRecord('user', { me: true });
|
|
111
136
|
|
|
112
137
|
// set user
|
|
113
|
-
this.setUser(user);
|
|
138
|
+
await this.setUser(user);
|
|
114
139
|
|
|
115
140
|
// Load user whois data
|
|
116
141
|
await this.loadWhois();
|
|
@@ -180,7 +205,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
180
205
|
const fallback = {
|
|
181
206
|
city: null,
|
|
182
207
|
country_code: null,
|
|
183
|
-
timezone:
|
|
208
|
+
timezone: getBrowserTimezone(),
|
|
184
209
|
_source: 'fallback',
|
|
185
210
|
};
|
|
186
211
|
|
|
@@ -344,6 +369,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
344
369
|
// Set current user
|
|
345
370
|
this.set('user', user);
|
|
346
371
|
this.set('userSnapshot', snapshot);
|
|
372
|
+
this.theme.syncThemeFromCurrentUser();
|
|
347
373
|
|
|
348
374
|
// Resolve the organization for event payload
|
|
349
375
|
const organization = this.store.peekRecord('company', user.get('company_uuid'));
|
package/addon/services/fetch.js
CHANGED
|
@@ -3,7 +3,7 @@ import { tracked } from '@glimmer/tracking';
|
|
|
3
3
|
import { inject as service } from '@ember/service';
|
|
4
4
|
import { get, set } from '@ember/object';
|
|
5
5
|
import { isBlank } from '@ember/utils';
|
|
6
|
-
import { dasherize } from '@ember/string';
|
|
6
|
+
import { dasherize, underscore } from '@ember/string';
|
|
7
7
|
import { isArray } from '@ember/array';
|
|
8
8
|
import { singularize, pluralize } from 'ember-inflector';
|
|
9
9
|
import { task } from 'ember-concurrency';
|
|
@@ -186,11 +186,16 @@ export default class FetchService extends Service {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
const type = dasherize(singularize(modelType));
|
|
189
|
+
const pluralizedModelType = pluralize(underscore(modelType));
|
|
189
190
|
|
|
190
191
|
if (isArray(payload)) {
|
|
191
192
|
return payload.map((instance) => this.store.push(this.store.normalize(type, instance)));
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
if (isArray(payload[pluralizedModelType])) {
|
|
196
|
+
return payload[pluralizedModelType].map((instance) => this.store.push(this.store.normalize(type, instance)));
|
|
197
|
+
}
|
|
198
|
+
|
|
194
199
|
if (isArray(payload[modelType])) {
|
|
195
200
|
return payload[modelType].map((instance) => this.store.push(this.store.normalize(type, instance)));
|
|
196
201
|
}
|
package/addon/services/theme.js
CHANGED
|
@@ -2,7 +2,6 @@ import Service from '@ember/service';
|
|
|
2
2
|
import Evented from '@ember/object/evented';
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
4
|
import { inject as service } from '@ember/service';
|
|
5
|
-
import { computed } from '@ember/object';
|
|
6
5
|
import { dasherize } from '@ember/string';
|
|
7
6
|
import { isArray } from '@ember/array';
|
|
8
7
|
import { getOwner } from '@ember/application';
|
|
@@ -35,8 +34,8 @@ export default class ThemeService extends Service.extend(Evented) {
|
|
|
35
34
|
*
|
|
36
35
|
* @var {String}
|
|
37
36
|
*/
|
|
38
|
-
|
|
39
|
-
const userSetTheme = this.currentUser.getOption(
|
|
37
|
+
get activeTheme() {
|
|
38
|
+
const userSetTheme = this.currentUser.getOption('theme');
|
|
40
39
|
|
|
41
40
|
if (userSetTheme) {
|
|
42
41
|
return userSetTheme;
|
|
@@ -67,7 +66,7 @@ export default class ThemeService extends Service.extend(Evented) {
|
|
|
67
66
|
*
|
|
68
67
|
* @var {String}
|
|
69
68
|
*/
|
|
70
|
-
@tracked currentTheme = 'dark';
|
|
69
|
+
@tracked currentTheme = this.currentUser.getOption('theme', 'dark');
|
|
71
70
|
|
|
72
71
|
/**
|
|
73
72
|
* The initially set theme
|
|
@@ -131,7 +130,7 @@ export default class ThemeService extends Service.extend(Evented) {
|
|
|
131
130
|
*/
|
|
132
131
|
initialize(options = {}) {
|
|
133
132
|
this.initialTheme = options?.theme;
|
|
134
|
-
this.
|
|
133
|
+
this.applyTheme(this.activeTheme, { persist: false });
|
|
135
134
|
this.setEnvironment();
|
|
136
135
|
this.resetScroll();
|
|
137
136
|
this.setRoutebodyClassNames(options.bodyClassNames && isArray(options.bodyClassNames) ? options.bodyClassNames : []);
|
|
@@ -203,10 +202,32 @@ export default class ThemeService extends Service.extend(Evented) {
|
|
|
203
202
|
* @void
|
|
204
203
|
*/
|
|
205
204
|
setTheme(theme = 'light') {
|
|
205
|
+
this.applyTheme(theme);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Synchronize the active theme from the current user-scoped preference.
|
|
210
|
+
*
|
|
211
|
+
* @void
|
|
212
|
+
*/
|
|
213
|
+
syncThemeFromCurrentUser() {
|
|
214
|
+
this.applyTheme(this.activeTheme, { persist: false });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Apply a theme consistently to service state and document body.
|
|
219
|
+
*
|
|
220
|
+
* @void
|
|
221
|
+
*/
|
|
222
|
+
applyTheme(theme = 'light', options = {}) {
|
|
223
|
+
const persist = options.persist !== false;
|
|
224
|
+
|
|
206
225
|
debug(`Theme was changed to: ${theme}`);
|
|
207
|
-
document.body.classList.remove(
|
|
226
|
+
document.body.classList.remove('light-theme', 'dark-theme');
|
|
208
227
|
document.body.classList.add(`${theme}-theme`);
|
|
209
|
-
|
|
228
|
+
if (persist) {
|
|
229
|
+
this.currentUser.setOption('theme', theme);
|
|
230
|
+
}
|
|
210
231
|
this.activeTheme = theme;
|
|
211
232
|
this.trigger('theme.changed', theme);
|
|
212
233
|
}
|
|
@@ -11,6 +11,8 @@ export default async function loadInstalledExtensions(additionalCoreEngines = []
|
|
|
11
11
|
'@fleetbase/iam-engine',
|
|
12
12
|
'@fleetbase/ledger-engine',
|
|
13
13
|
'@fleetbase/pallet-engine',
|
|
14
|
+
'@fleetbase/vroom-engine',
|
|
15
|
+
'@fleetbase/valhalla-engine',
|
|
14
16
|
...additionalCoreEngines,
|
|
15
17
|
];
|
|
16
18
|
const INDEXED_ENGINES = await loadExtensions();
|
|
@@ -59,13 +59,14 @@ export default async function lookupUserIp(options = {}) {
|
|
|
59
59
|
|
|
60
60
|
const data = await response.json();
|
|
61
61
|
const normalized = api.normalize(data);
|
|
62
|
+
const normalizedWithTimezone = ensureWhoisTimezone(normalized);
|
|
62
63
|
|
|
63
64
|
// Cache the result if enabled
|
|
64
65
|
if (cache) {
|
|
65
|
-
cacheWhois(
|
|
66
|
+
cacheWhois(normalizedWithTimezone);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
return
|
|
69
|
+
return normalizedWithTimezone;
|
|
69
70
|
} catch (error) {
|
|
70
71
|
console.warn(`[lookupUserIp] ${api.url} failed:`, error.message);
|
|
71
72
|
// Continue to next API
|
|
@@ -92,7 +93,7 @@ function normalizeGeoIPLookup(data) {
|
|
|
92
93
|
latitude: data.latitude,
|
|
93
94
|
longitude: data.longitude,
|
|
94
95
|
postal_code: data.postal_code,
|
|
95
|
-
timezone: data.timezone_name,
|
|
96
|
+
timezone: data.timezone_name || getBrowserTimezone(),
|
|
96
97
|
currency: {
|
|
97
98
|
code: data.currency_code,
|
|
98
99
|
name: data.currency_name,
|
|
@@ -128,7 +129,7 @@ function normalizeIPApi(data) {
|
|
|
128
129
|
latitude: data.latitude,
|
|
129
130
|
longitude: data.longitude,
|
|
130
131
|
postal_code: data.postal,
|
|
131
|
-
timezone: data.timezone,
|
|
132
|
+
timezone: data.timezone || getBrowserTimezone(),
|
|
132
133
|
currency: {
|
|
133
134
|
code: data.currency,
|
|
134
135
|
name: data.currency_name,
|
|
@@ -163,7 +164,12 @@ function getCachedWhois() {
|
|
|
163
164
|
return null;
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
const normalized = ensureWhoisTimezone(data);
|
|
168
|
+
if (normalized.timezone !== data.timezone) {
|
|
169
|
+
cacheWhois(normalized);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return normalized;
|
|
167
173
|
} catch (error) {
|
|
168
174
|
console.error('[getCachedWhois] Error reading cache:', error);
|
|
169
175
|
return null;
|
|
@@ -187,7 +193,7 @@ function cacheWhois(data) {
|
|
|
187
193
|
function getFallbackWhois() {
|
|
188
194
|
// Try to get browser language and timezone as fallback
|
|
189
195
|
const browserLang = navigator.language || 'en-US';
|
|
190
|
-
const timezone =
|
|
196
|
+
const timezone = getBrowserTimezone();
|
|
191
197
|
|
|
192
198
|
return {
|
|
193
199
|
ip: null,
|
|
@@ -219,3 +225,20 @@ function getFallbackWhois() {
|
|
|
219
225
|
_timestamp: Date.now(),
|
|
220
226
|
};
|
|
221
227
|
}
|
|
228
|
+
|
|
229
|
+
export function getBrowserTimezone() {
|
|
230
|
+
try {
|
|
231
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || null;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function ensureWhoisTimezone(whois = {}) {
|
|
238
|
+
whois = whois || {};
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...whois,
|
|
242
|
+
timezone: whois.timezone || getBrowserTimezone(),
|
|
243
|
+
};
|
|
244
|
+
}
|
package/package.json
CHANGED