bison-web-components 1.0.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/LICENSE +21 -0
- package/README.md +749 -0
- package/api.js +659 -0
- package/bison-operator-payments.js +1913 -0
- package/bison_logo.png +0 -0
- package/bison_logo_green.png +0 -0
- package/bison_logo_green.svg +103 -0
- package/bison_logo_white.svg +68 -0
- package/component.js +48 -0
- package/demo-payments.html +275 -0
- package/index.html +199 -0
- package/operator-bank-account.js +1193 -0
- package/operator-management.js +823 -0
- package/operator-onboarding.js +3750 -0
- package/operator-payment.js +2404 -0
- package/operator-underwriting.js +1473 -0
- package/package.json +14 -0
- package/test.js +3 -0
- package/test.mjs +3 -0
- package/theme.css +312 -0
- package/wio-bank-account.js +536 -0
- package/wio-onboarding.js +3981 -0
- package/wio-payment-linking.js +2054 -0
- package/wio-payment.js +579 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperatorManagement Web Component
|
|
3
|
+
*
|
|
4
|
+
* A hybrid web component that dynamically shows either operator-onboarding or
|
|
5
|
+
* operator-underwriting based on whether the operator email exists in the system.
|
|
6
|
+
*
|
|
7
|
+
* @author @kfajardo
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*
|
|
10
|
+
* @requires BisonJibPayAPI - Must be loaded before this component (from api.js)
|
|
11
|
+
* @requires OperatorOnboarding - Must be loaded (from operator-onboarding.js)
|
|
12
|
+
* @requires OperatorUnderwriting - Must be loaded (from operator-underwriting.js)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <script type="module" src="component.js"></script>
|
|
17
|
+
*
|
|
18
|
+
* <operator-management
|
|
19
|
+
* operator-email="operator@example.com"
|
|
20
|
+
* api-base-url="https://api.example.com"
|
|
21
|
+
* embeddable-key="your-key">
|
|
22
|
+
* </operator-management>
|
|
23
|
+
*
|
|
24
|
+
* <script>
|
|
25
|
+
* const management = document.querySelector('operator-management');
|
|
26
|
+
* management.addEventListener('management-mode-determined', (e) => {
|
|
27
|
+
* console.log('Mode:', e.detail.mode); // 'onboarding' or 'underwriting'
|
|
28
|
+
* });
|
|
29
|
+
* </script>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
class OperatorManagement extends HTMLElement {
|
|
34
|
+
constructor() {
|
|
35
|
+
super();
|
|
36
|
+
this.attachShadow({ mode: "open" });
|
|
37
|
+
|
|
38
|
+
// API Configuration
|
|
39
|
+
this.apiBaseURL =
|
|
40
|
+
this.getAttribute("api-base-url") ||
|
|
41
|
+
"https://bison-jib-development.azurewebsites.net";
|
|
42
|
+
this.embeddableKey =
|
|
43
|
+
this.getAttribute("embeddable-key") ||
|
|
44
|
+
"R80WMkbNN8457RofiMYx03DL65P06IaVT30Q2emYJUBQwYCzRC";
|
|
45
|
+
|
|
46
|
+
// Check if BisonJibPayAPI is available
|
|
47
|
+
if (typeof BisonJibPayAPI === "undefined") {
|
|
48
|
+
console.error(
|
|
49
|
+
"OperatorManagement: BisonJibPayAPI is not available. Please ensure api.js is loaded before operator-management.js"
|
|
50
|
+
);
|
|
51
|
+
this.api = null;
|
|
52
|
+
} else {
|
|
53
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Internal state
|
|
57
|
+
this._state = {
|
|
58
|
+
operatorEmail: null,
|
|
59
|
+
moovAccountId: null,
|
|
60
|
+
isLoading: false,
|
|
61
|
+
isError: false,
|
|
62
|
+
mode: null, // 'onboarding' | 'underwriting' | null
|
|
63
|
+
error: null,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Data to pre-populate the onboarding form
|
|
67
|
+
this._onboardingData = null;
|
|
68
|
+
|
|
69
|
+
// Callbacks for onboarding component
|
|
70
|
+
this._onboardingSuccess = null;
|
|
71
|
+
this._onboardingError = null;
|
|
72
|
+
this._onboardingSubmit = null;
|
|
73
|
+
this._onboardingConfirm = null;
|
|
74
|
+
|
|
75
|
+
// Render the component
|
|
76
|
+
this.render();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ==================== STATIC PROPERTIES ====================
|
|
80
|
+
|
|
81
|
+
static get observedAttributes() {
|
|
82
|
+
return ["operator-email", "api-base-url", "embeddable-key"];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ==================== PROPERTY GETTERS/SETTERS ====================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the operator email
|
|
89
|
+
* @returns {string|null}
|
|
90
|
+
*/
|
|
91
|
+
get operatorEmail() {
|
|
92
|
+
return this._state.operatorEmail;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Set the operator email
|
|
97
|
+
* @param {string} value - Operator email address
|
|
98
|
+
*/
|
|
99
|
+
set operatorEmail(value) {
|
|
100
|
+
console.log("OperatorManagement: Setting operator email to:", value);
|
|
101
|
+
|
|
102
|
+
const oldEmail = this._state.operatorEmail;
|
|
103
|
+
|
|
104
|
+
// Update internal state
|
|
105
|
+
this._state.operatorEmail = value;
|
|
106
|
+
|
|
107
|
+
// Update attribute only if different to prevent circular updates
|
|
108
|
+
const currentAttr = this.getAttribute("operator-email");
|
|
109
|
+
if (currentAttr !== value) {
|
|
110
|
+
if (value) {
|
|
111
|
+
this.setAttribute("operator-email", value);
|
|
112
|
+
} else {
|
|
113
|
+
this.removeAttribute("operator-email");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Trigger check if email changed and component is connected
|
|
118
|
+
if (value && value !== oldEmail && this.isConnected) {
|
|
119
|
+
this.checkOperatorStatus();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the moov account ID (if exists)
|
|
125
|
+
* @returns {string|null}
|
|
126
|
+
*/
|
|
127
|
+
get moovAccountId() {
|
|
128
|
+
return this._state.moovAccountId;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get the current mode
|
|
133
|
+
* @returns {string|null} 'onboarding' | 'underwriting' | null
|
|
134
|
+
*/
|
|
135
|
+
get mode() {
|
|
136
|
+
return this._state.mode;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if currently loading
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
get isLoading() {
|
|
144
|
+
return this._state.isLoading;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the onboarding pre-population data
|
|
149
|
+
* @returns {Object|null}
|
|
150
|
+
*/
|
|
151
|
+
get onboardingData() {
|
|
152
|
+
return this._onboardingData;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Set data to pre-populate the onboarding form
|
|
157
|
+
* @param {Object} data - Data object matching operator-onboarding's onLoad format
|
|
158
|
+
* @example
|
|
159
|
+
* management.onboardingData = {
|
|
160
|
+
* businessDetails: {
|
|
161
|
+
* businessName: "Acme Corp",
|
|
162
|
+
* businessEmail: "contact@acme.com",
|
|
163
|
+
* // ... other business fields
|
|
164
|
+
* },
|
|
165
|
+
* representatives: [...],
|
|
166
|
+
* bankDetails: {...},
|
|
167
|
+
* underwriting: {...}
|
|
168
|
+
* };
|
|
169
|
+
*/
|
|
170
|
+
set onboardingData(data) {
|
|
171
|
+
if (data && typeof data === "object") {
|
|
172
|
+
this._onboardingData = data;
|
|
173
|
+
|
|
174
|
+
// If onboarding component already exists, apply data immediately
|
|
175
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
176
|
+
if (onboarding) {
|
|
177
|
+
onboarding.onLoad = data;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
this._onboardingData = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the onboarding success callback
|
|
186
|
+
* @returns {Function|null}
|
|
187
|
+
*/
|
|
188
|
+
get onboardingSuccess() {
|
|
189
|
+
return this._onboardingSuccess;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Set callback for successful onboarding submission
|
|
194
|
+
* @param {Function} callback - Called with submission data on success
|
|
195
|
+
*/
|
|
196
|
+
set onboardingSuccess(callback) {
|
|
197
|
+
if (typeof callback === "function" || callback === null) {
|
|
198
|
+
this._onboardingSuccess = callback;
|
|
199
|
+
|
|
200
|
+
// If onboarding component already exists, apply callback immediately
|
|
201
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
202
|
+
if (onboarding) {
|
|
203
|
+
onboarding.onSuccess = callback;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get the onboarding error callback
|
|
210
|
+
* @returns {Function|null}
|
|
211
|
+
*/
|
|
212
|
+
get onboardingError() {
|
|
213
|
+
return this._onboardingError;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Set callback for onboarding errors
|
|
218
|
+
* @param {Function} callback - Called with error data on failure
|
|
219
|
+
*/
|
|
220
|
+
set onboardingError(callback) {
|
|
221
|
+
if (typeof callback === "function" || callback === null) {
|
|
222
|
+
this._onboardingError = callback;
|
|
223
|
+
|
|
224
|
+
// If onboarding component already exists, apply callback immediately
|
|
225
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
226
|
+
if (onboarding) {
|
|
227
|
+
onboarding.onError = callback;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get the onboarding submit callback
|
|
234
|
+
* @returns {Function|null}
|
|
235
|
+
*/
|
|
236
|
+
get onboardingSubmit() {
|
|
237
|
+
return this._onboardingSubmit;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Set callback for pre-submission handling
|
|
242
|
+
* @param {Function} callback - Called before form submission
|
|
243
|
+
*/
|
|
244
|
+
set onboardingSubmit(callback) {
|
|
245
|
+
if (typeof callback === "function" || callback === null) {
|
|
246
|
+
this._onboardingSubmit = callback;
|
|
247
|
+
|
|
248
|
+
// If onboarding component already exists, apply callback immediately
|
|
249
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
250
|
+
if (onboarding) {
|
|
251
|
+
onboarding.onSubmit = callback;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get the onboarding confirm callback
|
|
258
|
+
* @returns {Function|null}
|
|
259
|
+
*/
|
|
260
|
+
get onboardingConfirm() {
|
|
261
|
+
return this._onboardingConfirm;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Set callback for when user clicks Done on success screen
|
|
266
|
+
* @param {Function} callback - Called when user confirms successful onboarding
|
|
267
|
+
*/
|
|
268
|
+
set onboardingConfirm(callback) {
|
|
269
|
+
if (typeof callback === "function" || callback === null) {
|
|
270
|
+
this._onboardingConfirm = callback;
|
|
271
|
+
|
|
272
|
+
// If onboarding component already exists, apply callback immediately
|
|
273
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
274
|
+
if (onboarding) {
|
|
275
|
+
onboarding.onConfirm = callback;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ==================== LIFECYCLE METHODS ====================
|
|
281
|
+
|
|
282
|
+
connectedCallback() {
|
|
283
|
+
// Initialize email from attribute if present
|
|
284
|
+
const emailAttr = this.getAttribute("operator-email");
|
|
285
|
+
if (emailAttr && !this._state.operatorEmail) {
|
|
286
|
+
this._state.operatorEmail = emailAttr;
|
|
287
|
+
// Trigger API call when email is set
|
|
288
|
+
this.checkOperatorStatus();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
disconnectedCallback() {
|
|
293
|
+
// Cleanup if needed
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
297
|
+
if (oldValue === newValue) return;
|
|
298
|
+
|
|
299
|
+
switch (name) {
|
|
300
|
+
case "operator-email":
|
|
301
|
+
console.log(
|
|
302
|
+
"OperatorManagement: attributeChangedCallback - operator-email:",
|
|
303
|
+
newValue
|
|
304
|
+
);
|
|
305
|
+
this._state.operatorEmail = newValue;
|
|
306
|
+
// Reset state when email changes
|
|
307
|
+
this._state.moovAccountId = null;
|
|
308
|
+
this._state.mode = null;
|
|
309
|
+
this._state.isError = false;
|
|
310
|
+
this._state.error = null;
|
|
311
|
+
// Trigger API call
|
|
312
|
+
if (newValue && this.isConnected) {
|
|
313
|
+
this.checkOperatorStatus();
|
|
314
|
+
} else if (!newValue) {
|
|
315
|
+
this.render();
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
case "api-base-url":
|
|
320
|
+
this.apiBaseURL = newValue;
|
|
321
|
+
if (typeof BisonJibPayAPI !== "undefined") {
|
|
322
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case "embeddable-key":
|
|
327
|
+
this.embeddableKey = newValue;
|
|
328
|
+
if (typeof BisonJibPayAPI !== "undefined") {
|
|
329
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ==================== API INTEGRATION ====================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Check operator status by calling getAccountByEmail API
|
|
339
|
+
* Determines whether to show onboarding or underwriting
|
|
340
|
+
*/
|
|
341
|
+
async checkOperatorStatus() {
|
|
342
|
+
// Validate email is set
|
|
343
|
+
if (!this._state.operatorEmail) {
|
|
344
|
+
console.warn("OperatorManagement: Email is required");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Validate API is available
|
|
349
|
+
if (!this.api) {
|
|
350
|
+
console.error(
|
|
351
|
+
"OperatorManagement: BisonJibPayAPI is not available. Please ensure api.js is loaded first."
|
|
352
|
+
);
|
|
353
|
+
this._state.isError = true;
|
|
354
|
+
this._state.error = "API not available";
|
|
355
|
+
this.render();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Set loading state
|
|
360
|
+
this._state.isLoading = true;
|
|
361
|
+
this._state.isError = false;
|
|
362
|
+
this._state.error = null;
|
|
363
|
+
this._state.mode = null;
|
|
364
|
+
this.render();
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
console.log(
|
|
368
|
+
"OperatorManagement: Checking account status for:",
|
|
369
|
+
this._state.operatorEmail
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const response = await this.api.getAccountByEmail(
|
|
373
|
+
this._state.operatorEmail
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Success: Account exists - show underwriting (keep loading state)
|
|
377
|
+
this._state.moovAccountId =
|
|
378
|
+
response.data?.moovAccountId || response.moovAccountId;
|
|
379
|
+
// Keep isLoading = true, will be set to false when underwriting-ready event fires
|
|
380
|
+
this._state.mode = "underwriting";
|
|
381
|
+
|
|
382
|
+
console.log(
|
|
383
|
+
"OperatorManagement: Account exists, showing underwriting. MoovAccountId:",
|
|
384
|
+
this._state.moovAccountId
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// Emit mode determined event
|
|
388
|
+
this.dispatchEvent(
|
|
389
|
+
new CustomEvent("management-mode-determined", {
|
|
390
|
+
detail: {
|
|
391
|
+
mode: "underwriting",
|
|
392
|
+
moovAccountId: this._state.moovAccountId,
|
|
393
|
+
operatorEmail: this._state.operatorEmail,
|
|
394
|
+
},
|
|
395
|
+
bubbles: true,
|
|
396
|
+
composed: true,
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
// Failure: Account doesn't exist - show onboarding (stop loading immediately)
|
|
401
|
+
this._state.isLoading = false;
|
|
402
|
+
this._state.moovAccountId = null;
|
|
403
|
+
this._state.mode = "onboarding";
|
|
404
|
+
|
|
405
|
+
console.log(
|
|
406
|
+
"OperatorManagement: Account doesn't exist, showing onboarding"
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// Emit mode determined event
|
|
410
|
+
this.dispatchEvent(
|
|
411
|
+
new CustomEvent("management-mode-determined", {
|
|
412
|
+
detail: {
|
|
413
|
+
mode: "onboarding",
|
|
414
|
+
operatorEmail: this._state.operatorEmail,
|
|
415
|
+
error: error.data?.message || error.message,
|
|
416
|
+
},
|
|
417
|
+
bubbles: true,
|
|
418
|
+
composed: true,
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.render();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ==================== RENDERING ====================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Render the component (Shadow DOM)
|
|
430
|
+
*/
|
|
431
|
+
render() {
|
|
432
|
+
this.shadowRoot.innerHTML = `
|
|
433
|
+
<style>
|
|
434
|
+
:host {
|
|
435
|
+
display: block;
|
|
436
|
+
font-family: var(--font-sans, 'Inter', system-ui, sans-serif);
|
|
437
|
+
color: var(--color-secondary, #5f6e78);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.management-container {
|
|
441
|
+
width: 100%;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.loading-button-container {
|
|
445
|
+
display: inline-block;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.unified-loading-button {
|
|
449
|
+
padding: 12px 24px;
|
|
450
|
+
background: var(--color-primary-soft, #678f7a);
|
|
451
|
+
color: var(--color-white, #fff);
|
|
452
|
+
border: none;
|
|
453
|
+
border-radius: var(--radius-xl, 0.75rem);
|
|
454
|
+
font-size: var(--text-sm, 0.875rem);
|
|
455
|
+
font-weight: var(--font-weight-medium, 500);
|
|
456
|
+
cursor: wait;
|
|
457
|
+
transition: all var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
458
|
+
display: inline-flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
height: 40px;
|
|
462
|
+
box-sizing: border-box;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.unified-loading-button:disabled {
|
|
466
|
+
cursor: not-allowed;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.button-spinner {
|
|
470
|
+
width: 16px;
|
|
471
|
+
height: 16px;
|
|
472
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
473
|
+
border-top-color: var(--color-white, #fff);
|
|
474
|
+
border-radius: 50%;
|
|
475
|
+
animation: spin 0.8s linear infinite;
|
|
476
|
+
box-sizing: border-box;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@keyframes spin {
|
|
480
|
+
to {
|
|
481
|
+
transform: rotate(360deg);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.empty-state {
|
|
486
|
+
display: flex;
|
|
487
|
+
flex-direction: column;
|
|
488
|
+
align-items: center;
|
|
489
|
+
justify-content: center;
|
|
490
|
+
padding: 48px 24px;
|
|
491
|
+
text-align: center;
|
|
492
|
+
background: var(--color-gray-50, #f9fafb);
|
|
493
|
+
border: 1px dashed var(--color-gray-200, #d1d5db);
|
|
494
|
+
border-radius: var(--radius-xl, 0.75rem);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.empty-icon {
|
|
498
|
+
width: 64px;
|
|
499
|
+
height: 64px;
|
|
500
|
+
color: var(--color-gray-400, #9ca3af);
|
|
501
|
+
margin-bottom: 16px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.empty-text {
|
|
505
|
+
font-size: 16px;
|
|
506
|
+
color: var(--color-gray-500, #6b7280);
|
|
507
|
+
margin: 0;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.empty-subtext {
|
|
511
|
+
font-size: 14px;
|
|
512
|
+
color: var(--color-gray-400, #9ca3af);
|
|
513
|
+
margin-top: 8px;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.error-container {
|
|
517
|
+
display: flex;
|
|
518
|
+
flex-direction: column;
|
|
519
|
+
align-items: center;
|
|
520
|
+
justify-content: center;
|
|
521
|
+
padding: 48px 24px;
|
|
522
|
+
text-align: center;
|
|
523
|
+
background: var(--color-error-light, #fae5e4);
|
|
524
|
+
border: 1px solid var(--color-error-muted, #eea9a5);
|
|
525
|
+
border-radius: var(--radius-xl, 0.75rem);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.error-icon {
|
|
529
|
+
width: 64px;
|
|
530
|
+
height: 64px;
|
|
531
|
+
color: var(--color-error, #dd524b);
|
|
532
|
+
margin-bottom: 16px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.error-text {
|
|
536
|
+
font-size: 16px;
|
|
537
|
+
color: var(--color-error-dark, #903531);
|
|
538
|
+
margin: 0;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.error-subtext {
|
|
542
|
+
font-size: 14px;
|
|
543
|
+
color: var(--color-error, #dd524b);
|
|
544
|
+
margin-top: 8px;
|
|
545
|
+
}
|
|
546
|
+
</style>
|
|
547
|
+
|
|
548
|
+
<div class="management-container">
|
|
549
|
+
${this.renderContent()}
|
|
550
|
+
</div>
|
|
551
|
+
`;
|
|
552
|
+
|
|
553
|
+
console.log("OperatorManagement: render() called", {
|
|
554
|
+
mode: this._state.mode,
|
|
555
|
+
isLoading: this._state.isLoading,
|
|
556
|
+
operatorEmail: this._state.operatorEmail,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Setup event forwarding after render, with a delay to ensure child components are ready
|
|
560
|
+
setTimeout(() => this.setupEventForwarding(), 0);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Render the appropriate content based on state
|
|
565
|
+
*/
|
|
566
|
+
renderContent() {
|
|
567
|
+
// No email provided
|
|
568
|
+
if (!this._state.operatorEmail) {
|
|
569
|
+
return `
|
|
570
|
+
<div class="empty-state">
|
|
571
|
+
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
572
|
+
<path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
573
|
+
</svg>
|
|
574
|
+
<p class="empty-text">No operator email provided</p>
|
|
575
|
+
<p class="empty-subtext">Set the operator-email attribute to get started</p>
|
|
576
|
+
</div>
|
|
577
|
+
`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Error state (API not available)
|
|
581
|
+
if (this._state.isError) {
|
|
582
|
+
return `
|
|
583
|
+
<div class="error-container">
|
|
584
|
+
<svg class="error-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
585
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
586
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
587
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
588
|
+
</svg>
|
|
589
|
+
<p class="error-text">Unable to check operator status</p>
|
|
590
|
+
<p class="error-subtext">${this._state.error || "Please try again later"
|
|
591
|
+
}</p>
|
|
592
|
+
</div>
|
|
593
|
+
`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Show appropriate component based on mode
|
|
597
|
+
if (this._state.mode === "onboarding") {
|
|
598
|
+
return `
|
|
599
|
+
<operator-onboarding
|
|
600
|
+
api-base-url="${this.apiBaseURL}"
|
|
601
|
+
embeddable-key="${this.embeddableKey}">
|
|
602
|
+
</operator-onboarding>
|
|
603
|
+
`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (this._state.mode === "underwriting") {
|
|
607
|
+
// Show loading button overlay while underwriting is initializing
|
|
608
|
+
const loadingOverlay = this._state.isLoading
|
|
609
|
+
? `
|
|
610
|
+
<div class="loading-button-container">
|
|
611
|
+
<button class="unified-loading-button" disabled>
|
|
612
|
+
<span class="button-spinner"></span>
|
|
613
|
+
Checking operator status...
|
|
614
|
+
</button>
|
|
615
|
+
</div>
|
|
616
|
+
`
|
|
617
|
+
: "";
|
|
618
|
+
|
|
619
|
+
return `
|
|
620
|
+
${loadingOverlay}
|
|
621
|
+
<operator-underwriting
|
|
622
|
+
operator-email="${this._state.operatorEmail}"
|
|
623
|
+
api-base-url="${this.apiBaseURL}"
|
|
624
|
+
embeddable-key="${this.embeddableKey}"
|
|
625
|
+
style="${this._state.isLoading
|
|
626
|
+
? "visibility: hidden; position: absolute;"
|
|
627
|
+
: ""
|
|
628
|
+
}">
|
|
629
|
+
</operator-underwriting>
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Loading state - show unified loading button (only if no mode determined yet)
|
|
634
|
+
if (this._state.isLoading) {
|
|
635
|
+
return `
|
|
636
|
+
<div class="loading-button-container">
|
|
637
|
+
<button class="unified-loading-button" disabled>
|
|
638
|
+
<span class="button-spinner"></span>
|
|
639
|
+
Checking operator status...
|
|
640
|
+
</button>
|
|
641
|
+
</div>
|
|
642
|
+
`;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Default empty state (shouldn't reach here normally)
|
|
646
|
+
return `
|
|
647
|
+
<div class="empty-state">
|
|
648
|
+
<p class="empty-text">Initializing...</p>
|
|
649
|
+
</div>
|
|
650
|
+
`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Setup event forwarding from child components
|
|
655
|
+
*/
|
|
656
|
+
setupEventForwarding() {
|
|
657
|
+
// Forward onboarding events
|
|
658
|
+
const onboarding = this.shadowRoot.querySelector("operator-onboarding");
|
|
659
|
+
|
|
660
|
+
console.log("OperatorManagement: setupEventForwarding called", {
|
|
661
|
+
onboardingFound: !!onboarding,
|
|
662
|
+
hasSuccessCallback: !!this._onboardingSuccess,
|
|
663
|
+
hasErrorCallback: !!this._onboardingError,
|
|
664
|
+
hasSubmitCallback: !!this._onboardingSubmit,
|
|
665
|
+
hasOnboardingData: !!this._onboardingData,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
if (onboarding) {
|
|
669
|
+
// Pre-populate form data if onboardingData was set
|
|
670
|
+
if (this._onboardingData) {
|
|
671
|
+
onboarding.onLoad = this._onboardingData;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Apply callbacks if they were set
|
|
675
|
+
if (this._onboardingSuccess) {
|
|
676
|
+
console.log("OperatorManagement: Applying onSuccess callback to onboarding");
|
|
677
|
+
onboarding.onSuccess = this._onboardingSuccess;
|
|
678
|
+
}
|
|
679
|
+
if (this._onboardingError) {
|
|
680
|
+
onboarding.onError = this._onboardingError;
|
|
681
|
+
}
|
|
682
|
+
if (this._onboardingSubmit) {
|
|
683
|
+
onboarding.onSubmit = this._onboardingSubmit;
|
|
684
|
+
}
|
|
685
|
+
if (this._onboardingConfirm) {
|
|
686
|
+
onboarding.onConfirm = this._onboardingConfirm;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
onboarding.addEventListener("formComplete", (e) => {
|
|
690
|
+
this.dispatchEvent(
|
|
691
|
+
new CustomEvent("onboarding-complete", {
|
|
692
|
+
detail: {
|
|
693
|
+
...e.detail,
|
|
694
|
+
operatorEmail: this._state.operatorEmail,
|
|
695
|
+
},
|
|
696
|
+
bubbles: true,
|
|
697
|
+
composed: true,
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
// Note: checkOperatorStatus is now triggered when user clicks "Done" button
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Listen for confirm button click to re-check status
|
|
704
|
+
onboarding.addEventListener("onboardingConfirmed", (e) => {
|
|
705
|
+
this.dispatchEvent(
|
|
706
|
+
new CustomEvent("onboarding-confirmed", {
|
|
707
|
+
detail: {
|
|
708
|
+
...e.detail,
|
|
709
|
+
operatorEmail: this._state.operatorEmail,
|
|
710
|
+
},
|
|
711
|
+
bubbles: true,
|
|
712
|
+
composed: true,
|
|
713
|
+
})
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
// After user confirms, re-check status to potentially switch to underwriting
|
|
717
|
+
console.log(
|
|
718
|
+
"OperatorManagement: Onboarding confirmed, re-checking status..."
|
|
719
|
+
);
|
|
720
|
+
// Give the backend time to process
|
|
721
|
+
setTimeout(() => this.checkOperatorStatus(), 1000);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
onboarding.addEventListener("submissionFailed", (e) => {
|
|
725
|
+
this.dispatchEvent(
|
|
726
|
+
new CustomEvent("onboarding-failed", {
|
|
727
|
+
detail: e.detail,
|
|
728
|
+
bubbles: true,
|
|
729
|
+
composed: true,
|
|
730
|
+
})
|
|
731
|
+
);
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Forward underwriting events
|
|
736
|
+
const underwriting = this.shadowRoot.querySelector("operator-underwriting");
|
|
737
|
+
if (underwriting) {
|
|
738
|
+
underwriting.addEventListener("underwriting-ready", (e) => {
|
|
739
|
+
// Hide the unified loading button when underwriting is ready
|
|
740
|
+
this._state.isLoading = false;
|
|
741
|
+
|
|
742
|
+
// Just hide the loading overlay and show the underwriting component
|
|
743
|
+
const loadingOverlay = this.shadowRoot.querySelector(
|
|
744
|
+
".loading-button-container"
|
|
745
|
+
);
|
|
746
|
+
if (loadingOverlay) {
|
|
747
|
+
loadingOverlay.style.display = "none";
|
|
748
|
+
}
|
|
749
|
+
underwriting.style.visibility = "visible";
|
|
750
|
+
underwriting.style.position = "static";
|
|
751
|
+
|
|
752
|
+
this.dispatchEvent(
|
|
753
|
+
new CustomEvent("underwriting-ready", {
|
|
754
|
+
detail: e.detail,
|
|
755
|
+
bubbles: true,
|
|
756
|
+
composed: true,
|
|
757
|
+
})
|
|
758
|
+
);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
underwriting.addEventListener("underwriting-error", (e) => {
|
|
762
|
+
// Hide the unified loading button on error too
|
|
763
|
+
this._state.isLoading = false;
|
|
764
|
+
|
|
765
|
+
// Just hide the loading overlay and show the underwriting component
|
|
766
|
+
const loadingOverlay = this.shadowRoot.querySelector(
|
|
767
|
+
".loading-button-container"
|
|
768
|
+
);
|
|
769
|
+
if (loadingOverlay) {
|
|
770
|
+
loadingOverlay.style.display = "none";
|
|
771
|
+
}
|
|
772
|
+
underwriting.style.visibility = "visible";
|
|
773
|
+
underwriting.style.position = "static";
|
|
774
|
+
|
|
775
|
+
this.dispatchEvent(
|
|
776
|
+
new CustomEvent("underwriting-error", {
|
|
777
|
+
detail: e.detail,
|
|
778
|
+
bubbles: true,
|
|
779
|
+
composed: true,
|
|
780
|
+
})
|
|
781
|
+
);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
underwriting.addEventListener("underwriting-modal-open", (e) => {
|
|
785
|
+
this.dispatchEvent(
|
|
786
|
+
new CustomEvent("underwriting-modal-open", {
|
|
787
|
+
detail: e.detail,
|
|
788
|
+
bubbles: true,
|
|
789
|
+
composed: true,
|
|
790
|
+
})
|
|
791
|
+
);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
underwriting.addEventListener("underwriting-modal-close", (e) => {
|
|
795
|
+
this.dispatchEvent(
|
|
796
|
+
new CustomEvent("underwriting-modal-close", {
|
|
797
|
+
detail: e.detail,
|
|
798
|
+
bubbles: true,
|
|
799
|
+
composed: true,
|
|
800
|
+
})
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Register the custom element only if it hasn't been registered yet
|
|
808
|
+
if (!customElements.get("operator-management")) {
|
|
809
|
+
customElements.define("operator-management", OperatorManagement);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Export for module usage
|
|
813
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
814
|
+
module.exports = { OperatorManagement };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Make available globally for script tag usage
|
|
818
|
+
if (typeof window !== "undefined") {
|
|
819
|
+
window.OperatorManagement = OperatorManagement;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Export for ES6 modules
|
|
823
|
+
export { OperatorManagement };
|