@koloseum/utils 0.2.27 → 0.3.0
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/dist/client.d.ts +125 -0
- package/dist/client.js +680 -0
- package/dist/formatting.d.ts +125 -0
- package/dist/formatting.js +369 -0
- package/dist/general.d.ts +54 -0
- package/dist/general.js +88 -0
- package/dist/platform.d.ts +66 -0
- package/dist/platform.js +764 -0
- package/dist/server.d.ts +77 -0
- package/dist/server.js +300 -0
- package/package.json +25 -3
- package/dist/utils.d.ts +0 -394
- package/dist/utils.js +0 -2015
package/dist/client.js
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import { Config } from "./platform.js";
|
|
2
|
+
import Bowser from "bowser";
|
|
3
|
+
/* BROWSER HELPERS */
|
|
4
|
+
export const Browser = {
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a specific feature is supported by the browser.
|
|
7
|
+
* @param feature - The feature to check
|
|
8
|
+
* @param browserName - The name of the browser
|
|
9
|
+
* @param browserVersion - The version of the browser
|
|
10
|
+
* @returns `true` if the feature is supported, `false` otherwise
|
|
11
|
+
*/
|
|
12
|
+
checkFeatureSupport: (feature, browserName, browserVersion) => {
|
|
13
|
+
// Feature support matrix based on browser versions
|
|
14
|
+
const featureSupport = {
|
|
15
|
+
optionalChaining: {
|
|
16
|
+
Chrome: 80,
|
|
17
|
+
Firefox: 74,
|
|
18
|
+
Safari: 13.4,
|
|
19
|
+
Edge: 80,
|
|
20
|
+
Opera: 80
|
|
21
|
+
},
|
|
22
|
+
nullishCoalescing: {
|
|
23
|
+
Chrome: 80,
|
|
24
|
+
Firefox: 72,
|
|
25
|
+
Safari: 13.4,
|
|
26
|
+
Edge: 80,
|
|
27
|
+
Opera: 80
|
|
28
|
+
},
|
|
29
|
+
fetch: {
|
|
30
|
+
Chrome: 42,
|
|
31
|
+
Firefox: 39,
|
|
32
|
+
Safari: 10.1,
|
|
33
|
+
Edge: 14,
|
|
34
|
+
Opera: 29
|
|
35
|
+
},
|
|
36
|
+
es6Modules: {
|
|
37
|
+
Chrome: 61,
|
|
38
|
+
Firefox: 60,
|
|
39
|
+
Safari: 10.1,
|
|
40
|
+
Edge: 16,
|
|
41
|
+
Opera: 48
|
|
42
|
+
},
|
|
43
|
+
asyncAwait: {
|
|
44
|
+
Chrome: 55,
|
|
45
|
+
Firefox: 52,
|
|
46
|
+
Safari: 10.1,
|
|
47
|
+
Edge: 14,
|
|
48
|
+
Opera: 42
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const featureMatrix = featureSupport[feature];
|
|
52
|
+
if (!featureMatrix)
|
|
53
|
+
return true; // Unknown feature, assume supported
|
|
54
|
+
const minVersion = featureMatrix[browserName];
|
|
55
|
+
if (!minVersion)
|
|
56
|
+
return true; // Unknown browser, assume supported
|
|
57
|
+
return browserVersion >= minVersion;
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Gets detailed browser information for debugging.
|
|
61
|
+
*/
|
|
62
|
+
getDetailedInfo: () => {
|
|
63
|
+
if (typeof window === "undefined") {
|
|
64
|
+
return {
|
|
65
|
+
browser: "Server-side rendering",
|
|
66
|
+
os: "Unknown",
|
|
67
|
+
platform: "Unknown",
|
|
68
|
+
engine: "Unknown",
|
|
69
|
+
userAgent: "Unknown"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
73
|
+
return {
|
|
74
|
+
browser: `${browser.getBrowserName()} ${browser.getBrowserVersion()}`,
|
|
75
|
+
os: `${browser.getOSName()} ${browser.getOSVersion()}`,
|
|
76
|
+
platform: browser.getPlatformType(),
|
|
77
|
+
engine: browser.getEngineName(),
|
|
78
|
+
userAgent: window.navigator.userAgent
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Gets comprehensive browser information using Bowser.
|
|
83
|
+
*/
|
|
84
|
+
getInfo: () => {
|
|
85
|
+
if (typeof window === "undefined") {
|
|
86
|
+
return {
|
|
87
|
+
name: "server",
|
|
88
|
+
version: 0,
|
|
89
|
+
isOldSafari: false,
|
|
90
|
+
isIOS: false,
|
|
91
|
+
supportsOptionalChaining: true,
|
|
92
|
+
supportsNullishCoalescing: true,
|
|
93
|
+
supportsFetch: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
97
|
+
const browserName = browser.getBrowserName();
|
|
98
|
+
const browserVersion = parseFloat(browser.getBrowserVersion() || "0");
|
|
99
|
+
const osName = browser.getOSName();
|
|
100
|
+
const isIOS = osName === "iOS";
|
|
101
|
+
const isSafari = browserName === "Safari";
|
|
102
|
+
const isOldSafari = isSafari && browserVersion < 13.4;
|
|
103
|
+
return {
|
|
104
|
+
name: browserName,
|
|
105
|
+
version: browserVersion,
|
|
106
|
+
isOldSafari,
|
|
107
|
+
isIOS,
|
|
108
|
+
supportsOptionalChaining: Browser.checkFeatureSupport("optionalChaining", browserName, browserVersion),
|
|
109
|
+
supportsNullishCoalescing: Browser.checkFeatureSupport("nullishCoalescing", browserName, browserVersion),
|
|
110
|
+
supportsFetch: Browser.checkFeatureSupport("fetch", browserName, browserVersion)
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* Gets specific browser recommendations based on the detected browser.
|
|
115
|
+
*/
|
|
116
|
+
getRecommendations: () => {
|
|
117
|
+
if (typeof window === "undefined")
|
|
118
|
+
return {
|
|
119
|
+
title: "Browser compatibility issue",
|
|
120
|
+
message: "Browser detection is not available on theserver.",
|
|
121
|
+
recommendations: [],
|
|
122
|
+
critical: false
|
|
123
|
+
};
|
|
124
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
125
|
+
const browserName = browser.getBrowserName();
|
|
126
|
+
const browserVersion = browser.getBrowserVersion();
|
|
127
|
+
// Internet Explorer
|
|
128
|
+
if (browserName === "Internet Explorer") {
|
|
129
|
+
return {
|
|
130
|
+
title: "Unsupported browser",
|
|
131
|
+
message: "Internet Explorer is not supported. Please use a modern browser.",
|
|
132
|
+
recommendations: [
|
|
133
|
+
"Download and install Microsoft Edge",
|
|
134
|
+
"Use an alternative such as Chrome or Firefox (or Safari on Mac)"
|
|
135
|
+
],
|
|
136
|
+
critical: true
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Apple Safari
|
|
140
|
+
if (browserName === "Safari" && parseFloat(browserVersion || "0") < 13.4) {
|
|
141
|
+
return {
|
|
142
|
+
title: "Browser compatibility issue",
|
|
143
|
+
message: `You're using Safari ${browserVersion || "Unknown"}, which doesn't support modern web features.`,
|
|
144
|
+
recommendations: [
|
|
145
|
+
"Update to Safari 13.4 or later",
|
|
146
|
+
"Use Chrome or Firefox as an alternative",
|
|
147
|
+
"Update your iOS device to the latest version"
|
|
148
|
+
],
|
|
149
|
+
critical: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// Google Chrome
|
|
153
|
+
if (browserName === "Chrome" && parseFloat(browserVersion || "0") < 80) {
|
|
154
|
+
return {
|
|
155
|
+
title: "Outdated browser",
|
|
156
|
+
message: `You're using Chrome ${browserVersion || "Unknown"}, which may not support all features.`,
|
|
157
|
+
recommendations: ["Update Chrome to version 80 or later", "Enable automatic updates in Chrome settings"],
|
|
158
|
+
critical: false
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Mozilla Firefox
|
|
162
|
+
if (browserName === "Firefox" && parseFloat(browserVersion || "0") < 80) {
|
|
163
|
+
return {
|
|
164
|
+
title: "Outdated browser",
|
|
165
|
+
message: `You're using Firefox ${browserVersion || "Unknown"}, which may not support all features.`,
|
|
166
|
+
recommendations: ["Update Firefox to version 80 or later", "Enable automatic updates in Firefox settings"],
|
|
167
|
+
critical: false
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Microsoft Edge
|
|
171
|
+
if (browserName === "Edge" && parseFloat(browserVersion || "0") < 80) {
|
|
172
|
+
return {
|
|
173
|
+
title: "Outdated browser",
|
|
174
|
+
message: `You're using Edge ${browserVersion || "Unknown"}, which may not support all features.`,
|
|
175
|
+
recommendations: ["Update Edge to version 80 or later", "Enable automatic updates in Edge settings"],
|
|
176
|
+
critical: false
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// For unknown or very old browsers
|
|
180
|
+
if (browserName === "Unknown" || parseFloat(browserVersion || "0") < 50) {
|
|
181
|
+
return {
|
|
182
|
+
title: "Unrecognised browser",
|
|
183
|
+
message: "Your browser may not be fully supported. For the best experience, please use a modern browser.",
|
|
184
|
+
recommendations: ["Use Chrome, Firefox or Edge (latest version)", "Use Safari 13.4+ (on Mac/iOS)"],
|
|
185
|
+
critical: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// Default for modern browsers
|
|
189
|
+
return {
|
|
190
|
+
title: "Browser compatibility issue",
|
|
191
|
+
message: "Your browser may not fully support this application.",
|
|
192
|
+
recommendations: [
|
|
193
|
+
"Update your browser to the latest version",
|
|
194
|
+
"Clear your browser cache and cookies",
|
|
195
|
+
"Disable browser extensions temporarily",
|
|
196
|
+
"Try a different modern browser"
|
|
197
|
+
],
|
|
198
|
+
critical: false
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* Checks if the current browser is compatible with the application.
|
|
203
|
+
*/
|
|
204
|
+
isBrowserCompatible: () => {
|
|
205
|
+
if (typeof window === "undefined")
|
|
206
|
+
return true;
|
|
207
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
208
|
+
const browserName = browser.getBrowserName();
|
|
209
|
+
const browserVersion = parseFloat(browser.getBrowserVersion() || "0");
|
|
210
|
+
// Define minimum supported versions
|
|
211
|
+
const minVersions = {
|
|
212
|
+
"Internet Explorer": 0, // Not supported at all
|
|
213
|
+
Safari: 13.4,
|
|
214
|
+
Chrome: 80,
|
|
215
|
+
Firefox: 80,
|
|
216
|
+
Edge: 80,
|
|
217
|
+
Opera: 80
|
|
218
|
+
};
|
|
219
|
+
// Internet Explorer is never compatible
|
|
220
|
+
if (browserName === "Internet Explorer") {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
// Check if browser version meets minimum requirements
|
|
224
|
+
const minVersion = minVersions[browserName];
|
|
225
|
+
if (minVersion && browserVersion < minVersion) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
// Check for critical modern features
|
|
229
|
+
return (Browser.checkFeatureSupport("optionalChaining", browserName, browserVersion) &&
|
|
230
|
+
Browser.checkFeatureSupport("nullishCoalescing", browserName, browserVersion) &&
|
|
231
|
+
Browser.checkFeatureSupport("fetch", browserName, browserVersion) &&
|
|
232
|
+
Browser.checkFeatureSupport("es6Modules", browserName, browserVersion) &&
|
|
233
|
+
Browser.checkFeatureSupport("asyncAwait", browserName, browserVersion));
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* Checks if the browser is Internet Explorer.
|
|
237
|
+
*/
|
|
238
|
+
isInternetExplorer: () => {
|
|
239
|
+
if (typeof window === "undefined")
|
|
240
|
+
return false;
|
|
241
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
242
|
+
return browser.getBrowserName() === "Internet Explorer";
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* Checks if the browser is iOS Safari.
|
|
246
|
+
*/
|
|
247
|
+
isIOSSafari: () => {
|
|
248
|
+
if (typeof window === "undefined")
|
|
249
|
+
return false;
|
|
250
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
251
|
+
return browser.getOSName() === "iOS" && browser.getBrowserName() === "Safari";
|
|
252
|
+
},
|
|
253
|
+
/**
|
|
254
|
+
* Checks if the browser is a legacy version that needs updating.
|
|
255
|
+
*/
|
|
256
|
+
isLegacyBrowser: () => {
|
|
257
|
+
if (typeof window === "undefined")
|
|
258
|
+
return false;
|
|
259
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
260
|
+
const browserName = browser.getBrowserName();
|
|
261
|
+
const browserVersion = parseFloat(browser.getBrowserVersion() || "0");
|
|
262
|
+
// Legacy browser thresholds
|
|
263
|
+
const legacyThresholds = {
|
|
264
|
+
"Internet Explorer": 0, // All versions are legacy
|
|
265
|
+
Safari: 13.4,
|
|
266
|
+
Chrome: 80,
|
|
267
|
+
Firefox: 80,
|
|
268
|
+
Edge: 80,
|
|
269
|
+
Opera: 80
|
|
270
|
+
};
|
|
271
|
+
const threshold = legacyThresholds[browserName];
|
|
272
|
+
return threshold ? browserVersion < threshold : false;
|
|
273
|
+
},
|
|
274
|
+
/**
|
|
275
|
+
* Checks if the browser is an old version of Safari.
|
|
276
|
+
*/
|
|
277
|
+
isOldSafari: () => {
|
|
278
|
+
if (typeof window === "undefined")
|
|
279
|
+
return false;
|
|
280
|
+
const browser = Bowser.getParser(window.navigator.userAgent);
|
|
281
|
+
const browserName = browser.getBrowserName();
|
|
282
|
+
const browserVersion = parseFloat(browser.getBrowserVersion() || "0");
|
|
283
|
+
return browserName === "Safari" && browserVersion < 13.4;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/* ACCESS HELPERS */
|
|
287
|
+
export const Access = {
|
|
288
|
+
/**
|
|
289
|
+
* Get the current role of the user.
|
|
290
|
+
* @param user - The user object
|
|
291
|
+
* @param type - The type of role to get, i.e. "backroom" or "lounges"
|
|
292
|
+
* @returns The current role of the user.
|
|
293
|
+
*/
|
|
294
|
+
getCurrentRole: (user, type) => {
|
|
295
|
+
// Return null if no user is provided
|
|
296
|
+
if (!user)
|
|
297
|
+
return null;
|
|
298
|
+
// Get the role from the user's app metadata
|
|
299
|
+
const role = user.app_metadata.roles.find((role) => role.startsWith(type + "_")).split("_")[1];
|
|
300
|
+
// Check for Superuser
|
|
301
|
+
if (role === "superuser")
|
|
302
|
+
return {
|
|
303
|
+
name: "Superuser",
|
|
304
|
+
slug: "superuser",
|
|
305
|
+
superuser: true
|
|
306
|
+
};
|
|
307
|
+
// Define roles and slugs array
|
|
308
|
+
const roles = (type === "backroom" ? Config.microservices.backroom : Config.microservices.lounges)
|
|
309
|
+
.map(({ roles }) => roles)
|
|
310
|
+
.flat();
|
|
311
|
+
let slugs;
|
|
312
|
+
// Check if the user's role is a top-level microservice role
|
|
313
|
+
const topLevelRoles = roles
|
|
314
|
+
.filter((role) => role !== null && "root" in role && !("superuser" in role) && role.root === true)
|
|
315
|
+
.filter((role) => role !== null);
|
|
316
|
+
slugs = topLevelRoles.map((role) => role.slug);
|
|
317
|
+
if (slugs.includes(role))
|
|
318
|
+
return topLevelRoles.find(({ slug }) => slug === role);
|
|
319
|
+
// Check if the user's role is a sub-role of a microservice
|
|
320
|
+
const subRoles = roles.filter((role) => role !== null && !("root" in role)).filter((role) => role !== null);
|
|
321
|
+
slugs = subRoles.map((role) => role.slug);
|
|
322
|
+
if (slugs.includes(role))
|
|
323
|
+
return subRoles.find(({ slug }) => slug === role);
|
|
324
|
+
// Return null
|
|
325
|
+
return null;
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* Get a microservice feature by slug.
|
|
329
|
+
* @param featureSlug - The slug of the feature to get.
|
|
330
|
+
* @param type - The type of microservice to get the feature for, i.e. "backroom" or "lounges"
|
|
331
|
+
* @returns The feature object or `null` if the feature does not exist.
|
|
332
|
+
*/
|
|
333
|
+
getMicroserviceFeature: (featureSlug, type) => (type === "backroom" ? Config.microservices.backroom : Config.microservices.lounges)
|
|
334
|
+
.flatMap(({ features }) => features)
|
|
335
|
+
.find(({ slug }) => slug === featureSlug) ?? null,
|
|
336
|
+
/**
|
|
337
|
+
* Get the microservices that a role has access to.
|
|
338
|
+
* @param role - The role to check.
|
|
339
|
+
* @param type - The type of microservice to get the features for, i.e. "backroom" or "lounges"
|
|
340
|
+
* @returns The microservices that the role has access to.
|
|
341
|
+
*/
|
|
342
|
+
getMicroservicesForRole: (role, type) => (type === "backroom" ? Config.microservices.backroom : Config.microservices.lounges)
|
|
343
|
+
.filter(({ slug }) => slug !== "account")
|
|
344
|
+
.filter(({ features, roles }) => {
|
|
345
|
+
// Superusers have access to everything
|
|
346
|
+
if ("superuser" in role && role.superuser === true)
|
|
347
|
+
return true;
|
|
348
|
+
// Check if the user's role is a root role for this microservice
|
|
349
|
+
if (roles?.some((r) => "root" in r && r.root === true && r.slug === role.slug))
|
|
350
|
+
return true;
|
|
351
|
+
// Check if the user's role has access to any features in this microservice
|
|
352
|
+
return features.some((feature) => Access.roleHasAccessToFeature(role, feature, type));
|
|
353
|
+
}),
|
|
354
|
+
/**
|
|
355
|
+
* Renders the microservice features for the current user.
|
|
356
|
+
* @param user - The user to render the features for.
|
|
357
|
+
* @returns The microservice features for the current user, or `null` if no user is provided.
|
|
358
|
+
*/
|
|
359
|
+
renderMicroserviceFeatures: (user) => {
|
|
360
|
+
// Return null if no user is provided
|
|
361
|
+
if (!user)
|
|
362
|
+
return null;
|
|
363
|
+
// Initialise variables
|
|
364
|
+
let features = Config.microservices.players.filter(({ slug }) => slug === "account").flatMap(({ features }) => features);
|
|
365
|
+
let loungesCount = 0;
|
|
366
|
+
let backroomCount = 0;
|
|
367
|
+
// Count number of Lounges and Backroom roles
|
|
368
|
+
for (const role of user.app_metadata.roles) {
|
|
369
|
+
if (role.startsWith("lounges_"))
|
|
370
|
+
loungesCount++;
|
|
371
|
+
if (role.startsWith("backroom_"))
|
|
372
|
+
backroomCount++;
|
|
373
|
+
}
|
|
374
|
+
// Filter features based on roles
|
|
375
|
+
if (loungesCount === 0)
|
|
376
|
+
features = features.filter(({ slug }) => slug !== "lounges");
|
|
377
|
+
if (backroomCount === 0)
|
|
378
|
+
features = features.filter(({ slug }) => slug !== "backroom");
|
|
379
|
+
// Return filtered features
|
|
380
|
+
return features;
|
|
381
|
+
},
|
|
382
|
+
/**
|
|
383
|
+
* Check if a role has access to a microservice's feature.
|
|
384
|
+
* @param role - The role to check.
|
|
385
|
+
* @param feature - The feature to check.
|
|
386
|
+
* @param type - The type of microservice to check the feature for, i.e. "backroom" or "lounges"
|
|
387
|
+
* @returns `true` if the role has access to the feature, `false` otherwise.
|
|
388
|
+
*/
|
|
389
|
+
roleHasAccessToFeature: (role, feature, type) => {
|
|
390
|
+
// Return false if no role or feature is provided
|
|
391
|
+
if (!role || !feature)
|
|
392
|
+
return false;
|
|
393
|
+
// Return true if the role is superuser
|
|
394
|
+
if ("superuser" in role && role.superuser === true)
|
|
395
|
+
return true;
|
|
396
|
+
/* LOUNGES */
|
|
397
|
+
if (type === "lounges") {
|
|
398
|
+
// Branches
|
|
399
|
+
const { features: branchesFeatures, roles: branchesRoles } = Config.microservices.lounges.find(({ slug }) => slug === "branches");
|
|
400
|
+
if (branchesFeatures.some((f) => f.slug === feature.slug) && branchesRoles.some((r) => r.slug === role.slug)) {
|
|
401
|
+
if ("root" in role && role.root === true)
|
|
402
|
+
return true;
|
|
403
|
+
if ("featureSlugs" in role)
|
|
404
|
+
return role.featureSlugs.includes(feature.slug);
|
|
405
|
+
}
|
|
406
|
+
// Staff
|
|
407
|
+
const { features: staffFeatures, roles: staffRoles } = Config.microservices.lounges.find(({ slug }) => slug === "staff");
|
|
408
|
+
if (staffFeatures.some((f) => f.slug === feature.slug) && staffRoles.some((r) => r.slug === role.slug)) {
|
|
409
|
+
if ("root" in role && role.root === true)
|
|
410
|
+
return true;
|
|
411
|
+
if ("featureSlugs" in role)
|
|
412
|
+
return role.featureSlugs.includes(feature.slug);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/* BACKROOM */
|
|
416
|
+
if (type === "backroom") {
|
|
417
|
+
// Compliance
|
|
418
|
+
const { features: complianceFeatures, roles: complianceRoles } = Config.microservices.backroom.find(({ slug }) => slug === "compliance");
|
|
419
|
+
if (complianceFeatures.some((f) => f.slug === feature.slug) && complianceRoles.some((r) => r.slug === role.slug)) {
|
|
420
|
+
if ("root" in role && role.root === true)
|
|
421
|
+
return true;
|
|
422
|
+
if ("featureSlugs" in role)
|
|
423
|
+
return role.featureSlugs.includes(feature.slug);
|
|
424
|
+
}
|
|
425
|
+
// Commerce
|
|
426
|
+
const { features: commerceFeatures, roles: commerceRoles } = Config.microservices.backroom.find(({ slug }) => slug === "commerce");
|
|
427
|
+
if (commerceFeatures.some((f) => f.slug === feature.slug) && commerceRoles.some((r) => r.slug === role.slug)) {
|
|
428
|
+
if ("root" in role && role.root === true)
|
|
429
|
+
return true;
|
|
430
|
+
if ("featureSlugs" in role)
|
|
431
|
+
return role.featureSlugs.includes(feature.slug);
|
|
432
|
+
}
|
|
433
|
+
// Staff
|
|
434
|
+
const { features: staffFeatures, roles: staffRoles } = Config.microservices.backroom.find(({ slug }) => slug === "staff");
|
|
435
|
+
if (staffFeatures.some((f) => f.slug === feature.slug) && staffRoles.some((r) => r.slug === role.slug)) {
|
|
436
|
+
if ("root" in role && role.root === true)
|
|
437
|
+
return true;
|
|
438
|
+
if ("featureSlugs" in role)
|
|
439
|
+
return role.featureSlugs.includes(feature.slug);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Return false
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
/* INTERFACE HELPERS */
|
|
447
|
+
export const Interface = {
|
|
448
|
+
/**
|
|
449
|
+
* Returns the URL for a menu item based on the slug.
|
|
450
|
+
* @param {string} base - The base URL
|
|
451
|
+
* @param {string} slug - The slug of the menu item
|
|
452
|
+
* @returns {string} The URL for the menu item
|
|
453
|
+
*/
|
|
454
|
+
getMenuItemUrl: (base, slug) => {
|
|
455
|
+
// Validate base URL
|
|
456
|
+
if (typeof base !== "string")
|
|
457
|
+
return "";
|
|
458
|
+
// Format base URL
|
|
459
|
+
if (base === "/")
|
|
460
|
+
base = "";
|
|
461
|
+
if (base.charAt(base.length - 1) === "/")
|
|
462
|
+
base = base.slice(0, -1);
|
|
463
|
+
// Return URL
|
|
464
|
+
return `${base}${slug === "/" ? slug : typeof slug === "string" ? `/${slug}` : ""}`;
|
|
465
|
+
},
|
|
466
|
+
/**
|
|
467
|
+
* Generate a SuprSend notification inbox configuration object for a user.
|
|
468
|
+
* @param {string} userId - The user ID to generate the configuration for.
|
|
469
|
+
* @param {string} publicApiKey - The public API key to use for SuprSend.
|
|
470
|
+
* @returns The SuprSend notification inbox configuration object.
|
|
471
|
+
*/
|
|
472
|
+
getSuprSendInboxConfig: (userId, publicApiKey) => ({
|
|
473
|
+
distinctId: userId,
|
|
474
|
+
publicApiKey,
|
|
475
|
+
inbox: {
|
|
476
|
+
stores: [
|
|
477
|
+
{ storeId: "all", label: "Inbox", query: { archived: false } },
|
|
478
|
+
{ storeId: "archived", label: "Archived", query: { archived: true } }
|
|
479
|
+
],
|
|
480
|
+
theme: {
|
|
481
|
+
bell: {
|
|
482
|
+
color: "var(--color-accent)"
|
|
483
|
+
},
|
|
484
|
+
badge: {
|
|
485
|
+
backgroundColor: "var(--color-primary)",
|
|
486
|
+
color: "var(--color-primary-content)"
|
|
487
|
+
},
|
|
488
|
+
header: {
|
|
489
|
+
container: {
|
|
490
|
+
backgroundColor: "var(--color-base-100)",
|
|
491
|
+
borderColor: "var(--color-base-200)"
|
|
492
|
+
},
|
|
493
|
+
headerText: {
|
|
494
|
+
color: "var(--color-neutral)"
|
|
495
|
+
},
|
|
496
|
+
markAllReadText: {
|
|
497
|
+
color: "var(--color-accent)"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
tabs: {
|
|
501
|
+
color: "var(--color-primary)",
|
|
502
|
+
unselectedColor: "var(--color-accent)",
|
|
503
|
+
bottomColor: "var(--color-primary)",
|
|
504
|
+
badgeColor: "var(--color-base-200)",
|
|
505
|
+
badgeText: "var(--color-accent)"
|
|
506
|
+
},
|
|
507
|
+
notificationsContainer: {
|
|
508
|
+
container: {
|
|
509
|
+
backgroundColor: "var(--color-base-100)",
|
|
510
|
+
borderColor: "var(--color-base-200)",
|
|
511
|
+
height: "75vh",
|
|
512
|
+
marginTop: "0.75rem"
|
|
513
|
+
},
|
|
514
|
+
noNotificationsText: {
|
|
515
|
+
color: "var(--color-warning)"
|
|
516
|
+
},
|
|
517
|
+
noNotificationsSubtext: {
|
|
518
|
+
color: "var(--color-neutral)"
|
|
519
|
+
},
|
|
520
|
+
loader: {
|
|
521
|
+
color: "var(--color-primary)"
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
notification: {
|
|
525
|
+
container: {
|
|
526
|
+
borderColor: "var(--color-base-200)",
|
|
527
|
+
readBackgroundColor: "var(--color-base-100)",
|
|
528
|
+
unreadBackgroundColor: "var(--color-base-150)",
|
|
529
|
+
hoverBackgroundColor: "var(--color-base-200)"
|
|
530
|
+
},
|
|
531
|
+
headerText: {
|
|
532
|
+
color: "var(--color-secondary)"
|
|
533
|
+
},
|
|
534
|
+
bodyText: {
|
|
535
|
+
color: "var(--color-neutral)",
|
|
536
|
+
linkColor: "var(--color-secondary)"
|
|
537
|
+
},
|
|
538
|
+
unseenDot: {
|
|
539
|
+
backgroundColor: "var(--color-warning)"
|
|
540
|
+
},
|
|
541
|
+
createdOnText: {
|
|
542
|
+
color: "var(--color-accent)"
|
|
543
|
+
},
|
|
544
|
+
subtext: {
|
|
545
|
+
color: "var(--color-neutral)"
|
|
546
|
+
},
|
|
547
|
+
expiresText: {
|
|
548
|
+
color: "var(--color-warning)",
|
|
549
|
+
expiringBackgroundColor: "var(--color-warning)",
|
|
550
|
+
expiringColor: "var(--color-neutral)"
|
|
551
|
+
},
|
|
552
|
+
actions: [
|
|
553
|
+
{
|
|
554
|
+
text: {
|
|
555
|
+
color: "var(--color-primary-content)"
|
|
556
|
+
},
|
|
557
|
+
container: {
|
|
558
|
+
backgroundColor: "var(--color-primary)",
|
|
559
|
+
hoverBackgroundColor: "var(--color-secondary)",
|
|
560
|
+
border: "unset"
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
text: {
|
|
565
|
+
color: "var(--color-primary-content)"
|
|
566
|
+
},
|
|
567
|
+
container: {
|
|
568
|
+
backgroundColor: "var(--color-primary)",
|
|
569
|
+
hoverBackgroundColor: "var(--color-secondary)",
|
|
570
|
+
border: "unset"
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
actionsMenuIcon: {
|
|
575
|
+
hoverBackgroundColor: "var(--color-base-200)",
|
|
576
|
+
color: "var(--color-neutral)"
|
|
577
|
+
},
|
|
578
|
+
actionsMenu: {
|
|
579
|
+
backgroundColor: "var(--color-base-100)",
|
|
580
|
+
borderColor: "var(--color-base-200)"
|
|
581
|
+
},
|
|
582
|
+
actionsMenuItem: {
|
|
583
|
+
hoverBackgroundColor: "var(--color-base-200)"
|
|
584
|
+
},
|
|
585
|
+
actionsMenuItemIcon: {
|
|
586
|
+
color: "var(--color-accent)"
|
|
587
|
+
},
|
|
588
|
+
actionsMenuItemText: {
|
|
589
|
+
color: "var(--color-neutral)"
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
toast: {
|
|
595
|
+
theme: {
|
|
596
|
+
container: {
|
|
597
|
+
backgroundColor: "var(--color-base-100)",
|
|
598
|
+
borderColor: "var(--color-base-200)"
|
|
599
|
+
},
|
|
600
|
+
headerText: {
|
|
601
|
+
color: "var(--color-primary)"
|
|
602
|
+
},
|
|
603
|
+
bodyText: {
|
|
604
|
+
color: "var(--color-neutral)"
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}),
|
|
609
|
+
/**
|
|
610
|
+
* Handles the click event for pronouns checkboxes.
|
|
611
|
+
* @param {MouseEvent} e - The click event
|
|
612
|
+
* @param {PronounsCheckboxes} checkboxes - The pronouns checkboxes
|
|
613
|
+
*/
|
|
614
|
+
handlePronounsCheckboxClick: (e, checkboxes) => {
|
|
615
|
+
const target = e.target;
|
|
616
|
+
if (target.value === "none") {
|
|
617
|
+
for (let checkbox of Object.values(checkboxes))
|
|
618
|
+
if (checkbox && checkbox !== target && checkbox.checked) {
|
|
619
|
+
e.preventDefault();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
for (let checkbox of Object.values(checkboxes))
|
|
623
|
+
if (checkbox && checkbox !== target) {
|
|
624
|
+
checkbox.checked = false;
|
|
625
|
+
checkbox.disabled = !checkbox.disabled;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
if (checkboxes.none?.checked) {
|
|
630
|
+
e.preventDefault();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (target.value === "male" || target.value === "female") {
|
|
634
|
+
const oppositeIndex = target.value === "male" ? "female" : "male";
|
|
635
|
+
const oppositeCheckbox = checkboxes[oppositeIndex];
|
|
636
|
+
if (oppositeCheckbox?.checked) {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (oppositeCheckbox) {
|
|
641
|
+
oppositeCheckbox.checked = false;
|
|
642
|
+
oppositeCheckbox.disabled = !oppositeCheckbox.disabled;
|
|
643
|
+
}
|
|
644
|
+
if (checkboxes.none) {
|
|
645
|
+
checkboxes.none.checked = false;
|
|
646
|
+
if (!checkboxes.neutral?.checked)
|
|
647
|
+
checkboxes.none.disabled = !checkboxes.none.disabled;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
if (checkboxes.none) {
|
|
652
|
+
checkboxes.none.checked = false;
|
|
653
|
+
if (checkboxes.neutral && !checkboxes.male?.checked && !checkboxes.female?.checked)
|
|
654
|
+
checkboxes.none.disabled = checkboxes.neutral.checked;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
/**
|
|
660
|
+
* Check if a page is active based on the slug and microservice.
|
|
661
|
+
* @param page - The current page object.
|
|
662
|
+
* @param slug - The slug of the page to check.
|
|
663
|
+
* @param microservice - The microservice to check against.
|
|
664
|
+
* @param base - The base path of the application; defaults to an empty string.
|
|
665
|
+
* @returns `true` if the page is active, `false` otherwise.
|
|
666
|
+
*/
|
|
667
|
+
pageIsActive: (page, slug, microservice, base = "") => {
|
|
668
|
+
// Return true if microservice is provided and matches slug
|
|
669
|
+
if (microservice)
|
|
670
|
+
return slug === microservice;
|
|
671
|
+
// Remove trailing slash from base for consistency
|
|
672
|
+
const cleanBase = base.replace(/\/$/, "");
|
|
673
|
+
// Match exactly the base path if slug is falsy (i.e. root microservice feature)
|
|
674
|
+
if (!slug)
|
|
675
|
+
return page.url.pathname === cleanBase || page.url.pathname === `${cleanBase}/`;
|
|
676
|
+
// Match exact or subpath otherwise
|
|
677
|
+
const target = `${cleanBase}/${slug}`;
|
|
678
|
+
return page.url.pathname === target || page.url.pathname.startsWith(`${target}/`);
|
|
679
|
+
}
|
|
680
|
+
};
|