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,1473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperatorUnderwriting Web Component
|
|
3
|
+
*
|
|
4
|
+
* A web component for operator underwriting with a button that opens a modal.
|
|
5
|
+
* Validates operator email via API and manages button state based on response.
|
|
6
|
+
*
|
|
7
|
+
* @author @kfajardo
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*
|
|
10
|
+
* @requires BisonJibPayAPI - Must be loaded before this component (from api.js)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <script type="module" src="component.js"></script>
|
|
15
|
+
*
|
|
16
|
+
* <operator-underwriting
|
|
17
|
+
* operator-email="operator@example.com"
|
|
18
|
+
* api-base-url="https://api.example.com"
|
|
19
|
+
* embeddable-key="your-key">
|
|
20
|
+
* </operator-underwriting>
|
|
21
|
+
*
|
|
22
|
+
* <script>
|
|
23
|
+
* const underwriting = document.querySelector('operator-underwriting');
|
|
24
|
+
* underwriting.addEventListener('underwriting-ready', (e) => {
|
|
25
|
+
* console.log('Account validated:', e.detail.moovAccountId);
|
|
26
|
+
* });
|
|
27
|
+
* underwriting.addEventListener('underwriting-error', (e) => {
|
|
28
|
+
* console.error('Validation failed:', e.detail.error);
|
|
29
|
+
* });
|
|
30
|
+
* </script>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
class OperatorUnderwriting extends HTMLElement {
|
|
35
|
+
constructor() {
|
|
36
|
+
super();
|
|
37
|
+
this.attachShadow({ mode: "open" });
|
|
38
|
+
|
|
39
|
+
// API Configuration
|
|
40
|
+
this.apiBaseURL =
|
|
41
|
+
this.getAttribute("api-base-url") ||
|
|
42
|
+
"https://bison-jib-development.azurewebsites.net";
|
|
43
|
+
this.embeddableKey =
|
|
44
|
+
this.getAttribute("embeddable-key") ||
|
|
45
|
+
"R80WMkbNN8457RofiMYx03DL65P06IaVT30Q2emYJUBQwYCzRC";
|
|
46
|
+
|
|
47
|
+
// Check if BisonJibPayAPI is available
|
|
48
|
+
if (typeof BisonJibPayAPI === "undefined") {
|
|
49
|
+
console.error(
|
|
50
|
+
"OperatorUnderwriting: BisonJibPayAPI is not available. Please ensure api.js is loaded before operator-underwriting.js"
|
|
51
|
+
);
|
|
52
|
+
this.api = null;
|
|
53
|
+
} else {
|
|
54
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Internal state
|
|
58
|
+
this._state = {
|
|
59
|
+
operatorEmail: null,
|
|
60
|
+
moovAccountId: null,
|
|
61
|
+
isLoading: false,
|
|
62
|
+
isError: false,
|
|
63
|
+
isModalOpen: false,
|
|
64
|
+
error: null,
|
|
65
|
+
underwritingHistory: null,
|
|
66
|
+
isLoadingUnderwritingHistory: false,
|
|
67
|
+
underwritingHistoryError: null,
|
|
68
|
+
hasInitialized: false, // Guard to prevent multiple initializations
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Render the component
|
|
72
|
+
this.render();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ==================== STATIC PROPERTIES ====================
|
|
76
|
+
|
|
77
|
+
static get observedAttributes() {
|
|
78
|
+
return ["operator-email", "api-base-url", "embeddable-key"];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ==================== PROPERTY GETTERS/SETTERS ====================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the operator email
|
|
85
|
+
* @returns {string|null}
|
|
86
|
+
*/
|
|
87
|
+
get operatorEmail() {
|
|
88
|
+
return this._state.operatorEmail;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set the operator email
|
|
93
|
+
* @param {string} value - Operator email address
|
|
94
|
+
*/
|
|
95
|
+
set operatorEmail(value) {
|
|
96
|
+
console.log("OperatorUnderwriting: Setting operator email to:", value);
|
|
97
|
+
|
|
98
|
+
const oldEmail = this._state.operatorEmail;
|
|
99
|
+
|
|
100
|
+
// Update internal state
|
|
101
|
+
this._state.operatorEmail = value;
|
|
102
|
+
|
|
103
|
+
// Update attribute only if different to prevent circular updates
|
|
104
|
+
const currentAttr = this.getAttribute("operator-email");
|
|
105
|
+
if (currentAttr !== value) {
|
|
106
|
+
if (value) {
|
|
107
|
+
this.setAttribute("operator-email", value);
|
|
108
|
+
} else {
|
|
109
|
+
this.removeAttribute("operator-email");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Trigger initialization if email changed and component is connected
|
|
114
|
+
if (value && value !== oldEmail && this.isConnected) {
|
|
115
|
+
this.initializeAccount();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get the moov account ID
|
|
121
|
+
* @returns {string|null}
|
|
122
|
+
*/
|
|
123
|
+
get moovAccountId() {
|
|
124
|
+
return this._state.moovAccountId;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if the component is ready (account validated)
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
get isReady() {
|
|
132
|
+
return (
|
|
133
|
+
!this._state.isLoading &&
|
|
134
|
+
!this._state.isError &&
|
|
135
|
+
!!this._state.moovAccountId
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the open state
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
get isOpen() {
|
|
144
|
+
return this._state.isModalOpen;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ==================== LIFECYCLE METHODS ====================
|
|
148
|
+
|
|
149
|
+
connectedCallback() {
|
|
150
|
+
// Initialize email from attribute if present
|
|
151
|
+
const emailAttr = this.getAttribute("operator-email");
|
|
152
|
+
if (emailAttr && !this._state.operatorEmail) {
|
|
153
|
+
this._state.operatorEmail = emailAttr;
|
|
154
|
+
// Trigger API call when email is set
|
|
155
|
+
this.initializeAccount();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.setupEventListeners();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
disconnectedCallback() {
|
|
162
|
+
this.removeEventListeners();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
166
|
+
if (oldValue === newValue) return;
|
|
167
|
+
|
|
168
|
+
switch (name) {
|
|
169
|
+
case "operator-email":
|
|
170
|
+
console.log(
|
|
171
|
+
"OperatorUnderwriting: attributeChangedCallback - operator-email:",
|
|
172
|
+
newValue
|
|
173
|
+
);
|
|
174
|
+
this._state.operatorEmail = newValue;
|
|
175
|
+
// Reset state when email changes
|
|
176
|
+
this._state.moovAccountId = null;
|
|
177
|
+
this._state.isError = false;
|
|
178
|
+
this._state.error = null;
|
|
179
|
+
this._state.hasInitialized = false; // Allow re-initialization for new email
|
|
180
|
+
// Trigger API call
|
|
181
|
+
if (newValue && this.isConnected) {
|
|
182
|
+
this.initializeAccount();
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case "api-base-url":
|
|
187
|
+
this.apiBaseURL = newValue;
|
|
188
|
+
if (typeof BisonJibPayAPI !== "undefined") {
|
|
189
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
|
|
193
|
+
case "embeddable-key":
|
|
194
|
+
this.embeddableKey = newValue;
|
|
195
|
+
if (typeof BisonJibPayAPI !== "undefined") {
|
|
196
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ==================== API INTEGRATION ====================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Initialize account by calling getAccountByEmail API
|
|
206
|
+
* Called when operator-email attribute is set
|
|
207
|
+
*/
|
|
208
|
+
async initializeAccount() {
|
|
209
|
+
// Validate email is set
|
|
210
|
+
if (!this._state.operatorEmail) {
|
|
211
|
+
console.warn(
|
|
212
|
+
"OperatorUnderwriting: Email is required for initialization"
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Prevent multiple simultaneous initializations
|
|
218
|
+
if (this._state.isLoading || this._state.hasInitialized) {
|
|
219
|
+
console.log(
|
|
220
|
+
"OperatorUnderwriting: Already initializing or initialized, skipping"
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Validate API is available
|
|
226
|
+
if (!this.api) {
|
|
227
|
+
console.error(
|
|
228
|
+
"OperatorUnderwriting: BisonJibPayAPI is not available. Please ensure api.js is loaded first."
|
|
229
|
+
);
|
|
230
|
+
this._state.isError = true;
|
|
231
|
+
this._state.error = "API not available";
|
|
232
|
+
this._state.hasInitialized = true;
|
|
233
|
+
this.updateButtonState();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Set loading state
|
|
238
|
+
this._state.isLoading = true;
|
|
239
|
+
this._state.isError = false;
|
|
240
|
+
this._state.error = null;
|
|
241
|
+
this.updateButtonState();
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
console.log(
|
|
245
|
+
"OperatorUnderwriting: Calling getAccountByEmail for:",
|
|
246
|
+
this._state.operatorEmail
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const response = await this.api.getAccountByEmail(
|
|
250
|
+
this._state.operatorEmail
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Success: Store moov account ID
|
|
254
|
+
this._state.moovAccountId =
|
|
255
|
+
response.data?.moovAccountId || response.moovAccountId;
|
|
256
|
+
this._state.isLoading = false;
|
|
257
|
+
this._state.isError = false;
|
|
258
|
+
this._state.hasInitialized = true;
|
|
259
|
+
|
|
260
|
+
console.log(
|
|
261
|
+
"OperatorUnderwriting: Account validated, moovAccountId:",
|
|
262
|
+
this._state.moovAccountId
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// Emit success event
|
|
266
|
+
this.dispatchEvent(
|
|
267
|
+
new CustomEvent("underwriting-ready", {
|
|
268
|
+
detail: {
|
|
269
|
+
moovAccountId: this._state.moovAccountId,
|
|
270
|
+
operatorEmail: this._state.operatorEmail,
|
|
271
|
+
},
|
|
272
|
+
bubbles: true,
|
|
273
|
+
composed: true,
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// Failure: Set error state
|
|
278
|
+
this._state.isError = true;
|
|
279
|
+
this._state.isLoading = false;
|
|
280
|
+
this._state.moovAccountId = null;
|
|
281
|
+
this._state.hasInitialized = true;
|
|
282
|
+
this._state.error =
|
|
283
|
+
error.data?.message || error.message || "Failed to validate operator";
|
|
284
|
+
|
|
285
|
+
console.error("OperatorUnderwriting: API call failed:", error);
|
|
286
|
+
|
|
287
|
+
// Emit error event
|
|
288
|
+
this.dispatchEvent(
|
|
289
|
+
new CustomEvent("underwriting-error", {
|
|
290
|
+
detail: {
|
|
291
|
+
error: this._state.error,
|
|
292
|
+
operatorEmail: this._state.operatorEmail,
|
|
293
|
+
originalError: error,
|
|
294
|
+
},
|
|
295
|
+
bubbles: true,
|
|
296
|
+
composed: true,
|
|
297
|
+
})
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this.updateButtonState();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ==================== EVENT HANDLING ====================
|
|
305
|
+
|
|
306
|
+
setupEventListeners() {
|
|
307
|
+
const button = this.shadowRoot.querySelector(".underwriting-btn");
|
|
308
|
+
const closeBtn = this.shadowRoot.querySelector(".close-btn");
|
|
309
|
+
const overlay = this.shadowRoot.querySelector(".modal-overlay");
|
|
310
|
+
|
|
311
|
+
if (button) {
|
|
312
|
+
button.addEventListener("click", this.handleButtonClick.bind(this));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (closeBtn) {
|
|
316
|
+
closeBtn.addEventListener("click", this.closeModal.bind(this));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (overlay) {
|
|
320
|
+
overlay.addEventListener("click", this.closeModal.bind(this));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ESC key to close modal
|
|
324
|
+
this._escHandler = (e) => {
|
|
325
|
+
if (e.key === "Escape" && this._state.isModalOpen) {
|
|
326
|
+
this.closeModal();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
document.addEventListener("keydown", this._escHandler);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
removeEventListeners() {
|
|
333
|
+
if (this._escHandler) {
|
|
334
|
+
document.removeEventListener("keydown", this._escHandler);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Handle button click - open modal if ready
|
|
340
|
+
*/
|
|
341
|
+
handleButtonClick() {
|
|
342
|
+
console.log("OperatorUnderwriting: Button clicked");
|
|
343
|
+
|
|
344
|
+
// Only open modal if not loading and not in error state
|
|
345
|
+
if (this._state.isLoading || this._state.isError) {
|
|
346
|
+
console.warn(
|
|
347
|
+
"OperatorUnderwriting: Cannot open modal - button is disabled"
|
|
348
|
+
);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Validate we have an account ID
|
|
353
|
+
if (!this._state.moovAccountId) {
|
|
354
|
+
console.warn("OperatorUnderwriting: Cannot open modal - no account ID");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
this.openModal();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Open the modal
|
|
363
|
+
*/
|
|
364
|
+
async openModal() {
|
|
365
|
+
this._state.isModalOpen = true;
|
|
366
|
+
const modal = this.shadowRoot.querySelector(".modal");
|
|
367
|
+
if (modal) {
|
|
368
|
+
// Show modal and start animation
|
|
369
|
+
modal.classList.add("show", "animating-in");
|
|
370
|
+
|
|
371
|
+
// Remove animating-in class after animation completes
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
modal.classList.remove("animating-in");
|
|
374
|
+
}, 200);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Prevent background scrolling when modal is open
|
|
378
|
+
document.body.style.overflow = "hidden";
|
|
379
|
+
|
|
380
|
+
// Fetch underwriting history when modal opens
|
|
381
|
+
await this.fetchUnderwritingHistory();
|
|
382
|
+
|
|
383
|
+
// Emit modal open event
|
|
384
|
+
this.dispatchEvent(
|
|
385
|
+
new CustomEvent("underwriting-modal-open", {
|
|
386
|
+
detail: {
|
|
387
|
+
moovAccountId: this._state.moovAccountId,
|
|
388
|
+
operatorEmail: this._state.operatorEmail,
|
|
389
|
+
},
|
|
390
|
+
bubbles: true,
|
|
391
|
+
composed: true,
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Close the modal
|
|
398
|
+
*/
|
|
399
|
+
closeModal() {
|
|
400
|
+
this._state.isModalOpen = false;
|
|
401
|
+
const modal = this.shadowRoot.querySelector(".modal");
|
|
402
|
+
if (modal) {
|
|
403
|
+
// Start close animation
|
|
404
|
+
modal.classList.add("animating-out");
|
|
405
|
+
|
|
406
|
+
// Hide modal after animation completes
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
modal.classList.remove("show", "animating-out");
|
|
409
|
+
}, 150);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Restore background scrolling when modal is closed
|
|
413
|
+
document.body.style.overflow = "";
|
|
414
|
+
|
|
415
|
+
// Emit modal close event
|
|
416
|
+
this.dispatchEvent(
|
|
417
|
+
new CustomEvent("underwriting-modal-close", {
|
|
418
|
+
detail: {
|
|
419
|
+
moovAccountId: this._state.moovAccountId,
|
|
420
|
+
operatorEmail: this._state.operatorEmail,
|
|
421
|
+
},
|
|
422
|
+
bubbles: true,
|
|
423
|
+
composed: true,
|
|
424
|
+
})
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Fetch underwriting history using the saved moovAccountId
|
|
430
|
+
*/
|
|
431
|
+
async fetchUnderwritingHistory() {
|
|
432
|
+
// Validate we have a moovAccountId
|
|
433
|
+
if (!this._state.moovAccountId) {
|
|
434
|
+
console.warn(
|
|
435
|
+
"OperatorUnderwriting: Cannot fetch underwriting history - no moovAccountId"
|
|
436
|
+
);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Validate API is available
|
|
441
|
+
if (!this.api) {
|
|
442
|
+
console.error(
|
|
443
|
+
"OperatorUnderwriting: BisonJibPayAPI is not available for fetching underwriting history"
|
|
444
|
+
);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Set loading state
|
|
449
|
+
this._state.isLoadingUnderwritingHistory = true;
|
|
450
|
+
this._state.underwritingHistoryError = null;
|
|
451
|
+
this.updateModalContent();
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
console.log(
|
|
455
|
+
"OperatorUnderwriting: Fetching underwriting history for moovAccountId:",
|
|
456
|
+
this._state.moovAccountId
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const response = await this.api.fetchUnderwritingByAccountId(
|
|
460
|
+
this._state.moovAccountId
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
// Success: Store underwriting history
|
|
464
|
+
this._state.underwritingHistory = response.data || [];
|
|
465
|
+
this._state.isLoadingUnderwritingHistory = false;
|
|
466
|
+
|
|
467
|
+
console.log(
|
|
468
|
+
"OperatorUnderwriting: Underwriting history fetched successfully:",
|
|
469
|
+
this._state.underwritingHistory
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
// Emit success event
|
|
473
|
+
this.dispatchEvent(
|
|
474
|
+
new CustomEvent("underwriting-history-loaded", {
|
|
475
|
+
detail: {
|
|
476
|
+
moovAccountId: this._state.moovAccountId,
|
|
477
|
+
history: this._state.underwritingHistory,
|
|
478
|
+
},
|
|
479
|
+
bubbles: true,
|
|
480
|
+
composed: true,
|
|
481
|
+
})
|
|
482
|
+
);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
// Failure: Set error state
|
|
485
|
+
this._state.isLoadingUnderwritingHistory = false;
|
|
486
|
+
this._state.underwritingHistoryError =
|
|
487
|
+
error.data?.message ||
|
|
488
|
+
error.message ||
|
|
489
|
+
"Failed to load underwriting history";
|
|
490
|
+
|
|
491
|
+
console.error(
|
|
492
|
+
"OperatorUnderwriting: Failed to fetch underwriting history:",
|
|
493
|
+
error
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
// Emit error event
|
|
497
|
+
this.dispatchEvent(
|
|
498
|
+
new CustomEvent("underwriting-history-error", {
|
|
499
|
+
detail: {
|
|
500
|
+
error: this._state.underwritingHistoryError,
|
|
501
|
+
moovAccountId: this._state.moovAccountId,
|
|
502
|
+
originalError: error,
|
|
503
|
+
},
|
|
504
|
+
bubbles: true,
|
|
505
|
+
composed: true,
|
|
506
|
+
})
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this.updateModalContent();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Update modal content based on underwriting history state
|
|
515
|
+
*/
|
|
516
|
+
updateModalContent() {
|
|
517
|
+
const modalBody = this.shadowRoot.querySelector(".modal-body");
|
|
518
|
+
if (!modalBody) return;
|
|
519
|
+
|
|
520
|
+
if (this._state.isLoadingUnderwritingHistory) {
|
|
521
|
+
modalBody.innerHTML = `
|
|
522
|
+
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center;">
|
|
523
|
+
<div style="width: 48px; height: 48px; border: 4px solid var(--color-border, #e8e8e8); border-top-color: var(--color-primary, #4c7b63); border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 16px;"></div>
|
|
524
|
+
<p style="font-size: 16px; color: var(--color-gray-500, #6b7280); margin: 0;">Loading underwriting history...</p>
|
|
525
|
+
</div>
|
|
526
|
+
`;
|
|
527
|
+
} else if (this._state.underwritingHistoryError) {
|
|
528
|
+
modalBody.innerHTML = `
|
|
529
|
+
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center; background: var(--color-error-light, #fae5e4); border: 1px solid var(--color-error-muted, #eea9a5); border-radius: var(--radius-xl, 0.75rem);">
|
|
530
|
+
<svg style="width: 64px; height: 64px; color: var(--color-error, #dd524b); margin-bottom: 16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
531
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
532
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
533
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
534
|
+
</svg>
|
|
535
|
+
<p style="font-size: 16px; color: var(--color-error-dark, #903531); margin: 0;">Failed to load underwriting history</p>
|
|
536
|
+
<p style="font-size: 14px; color: var(--color-error, #dd524b); margin-top: 8px;">${this._state.underwritingHistoryError}</p>
|
|
537
|
+
</div>
|
|
538
|
+
`;
|
|
539
|
+
} else if (
|
|
540
|
+
this._state.underwritingHistory &&
|
|
541
|
+
this._state.underwritingHistory.length > 0
|
|
542
|
+
) {
|
|
543
|
+
// Log underwriting data when available
|
|
544
|
+
console.log(
|
|
545
|
+
"OperatorUnderwriting: Underwriting data:",
|
|
546
|
+
this._state.underwritingHistory
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
modalBody.innerHTML = this.renderUnderwritingTimeline(
|
|
550
|
+
this._state.underwritingHistory
|
|
551
|
+
);
|
|
552
|
+
} else {
|
|
553
|
+
modalBody.innerHTML = `
|
|
554
|
+
<div style="display: flex; margin: 24px 0; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center; background: var(--color-gray-50, #f9fafb); border: 1px dashed var(--color-gray-200, #d1d5db); border-radius: var(--radius-xl, 0.75rem);">
|
|
555
|
+
<svg style="width: 64px; height: 64px; color: var(--color-gray-400, #9ca3af); margin-bottom: 16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
556
|
+
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
557
|
+
</svg>
|
|
558
|
+
<p style="font-size: 16px; color: var(--color-gray-500, #6b7280); margin: 0;">No underwriting history found</p>
|
|
559
|
+
<p style="font-size: 14px; color: var(--color-gray-400, #9ca3af); margin-top: 8px;">This operator has no underwriting records yet</p>
|
|
560
|
+
</div>
|
|
561
|
+
`;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Update button state based on current state
|
|
567
|
+
*/
|
|
568
|
+
updateButtonState() {
|
|
569
|
+
const button = this.shadowRoot.querySelector(".underwriting-btn");
|
|
570
|
+
const wrapper = this.shadowRoot.querySelector(".btn-wrapper");
|
|
571
|
+
if (!button) return;
|
|
572
|
+
|
|
573
|
+
// Remove all state classes
|
|
574
|
+
button.classList.remove("loading", "error");
|
|
575
|
+
|
|
576
|
+
if (this._state.isLoading) {
|
|
577
|
+
button.classList.add("loading");
|
|
578
|
+
button.disabled = true;
|
|
579
|
+
if (wrapper) wrapper.classList.remove("has-error");
|
|
580
|
+
} else if (this._state.isError) {
|
|
581
|
+
button.classList.add("error");
|
|
582
|
+
button.disabled = true;
|
|
583
|
+
if (wrapper) wrapper.classList.add("has-error");
|
|
584
|
+
} else {
|
|
585
|
+
button.disabled = false;
|
|
586
|
+
if (wrapper) wrapper.classList.remove("has-error");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ==================== RENDERING ====================
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Render the component (Shadow DOM)
|
|
594
|
+
*/
|
|
595
|
+
render() {
|
|
596
|
+
this.shadowRoot.innerHTML = `
|
|
597
|
+
<style>
|
|
598
|
+
:host {
|
|
599
|
+
display: inline-block;
|
|
600
|
+
font-family: var(--font-sans, 'Inter', system-ui, sans-serif);
|
|
601
|
+
color: var(--color-secondary, #5f6e78);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.underwriting-btn {
|
|
605
|
+
padding: 12px 24px;
|
|
606
|
+
background: var(--color-primary, #4c7b63);
|
|
607
|
+
color: var(--color-white, #fff);
|
|
608
|
+
border: none;
|
|
609
|
+
border-radius: var(--radius-xl, 0.75rem);
|
|
610
|
+
font-size: var(--text-sm, 0.875rem);
|
|
611
|
+
font-weight: var(--font-weight-medium, 500);
|
|
612
|
+
cursor: pointer;
|
|
613
|
+
transition: all var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
614
|
+
display: inline-flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
gap: 8px;
|
|
617
|
+
height: 40px;
|
|
618
|
+
box-sizing: border-box;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.underwriting-btn:hover:not(.error):not(.loading):not(:disabled) {
|
|
622
|
+
background: var(--color-primary-hover, #436c57);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.underwriting-btn:active:not(.error):not(.loading):not(:disabled) {
|
|
626
|
+
background: var(--color-primary-active, #3d624f);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.underwriting-btn.error {
|
|
630
|
+
background: var(--color-gray-400, #9ca3af);
|
|
631
|
+
cursor: not-allowed;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.underwriting-btn.loading {
|
|
635
|
+
background: var(--color-primary-soft, #678f7a);
|
|
636
|
+
cursor: wait;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.underwriting-btn .broken-link-icon {
|
|
640
|
+
display: none;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.underwriting-btn.error .broken-link-icon {
|
|
644
|
+
display: inline-block;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.underwriting-btn .loading-spinner {
|
|
648
|
+
display: none;
|
|
649
|
+
width: 16px;
|
|
650
|
+
height: 16px;
|
|
651
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
652
|
+
border-top-color: var(--color-white, #fff);
|
|
653
|
+
border-radius: 50%;
|
|
654
|
+
animation: spin 0.8s linear infinite;
|
|
655
|
+
box-sizing: border-box;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.underwriting-btn.loading .loading-spinner {
|
|
659
|
+
display: inline-block;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
@keyframes spin {
|
|
663
|
+
to {
|
|
664
|
+
transform: rotate(360deg);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.btn-wrapper {
|
|
669
|
+
position: relative;
|
|
670
|
+
display: inline-block;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.tooltip {
|
|
674
|
+
visibility: hidden;
|
|
675
|
+
opacity: 0;
|
|
676
|
+
position: absolute;
|
|
677
|
+
bottom: 100%;
|
|
678
|
+
left: 50%;
|
|
679
|
+
transform: translateX(-50%);
|
|
680
|
+
background: var(--color-gray-700, #374151);
|
|
681
|
+
color: var(--color-white, #fff);
|
|
682
|
+
padding: 8px 12px;
|
|
683
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
684
|
+
font-size: 13px;
|
|
685
|
+
white-space: nowrap;
|
|
686
|
+
margin-bottom: 8px;
|
|
687
|
+
transition: opacity var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1)),
|
|
688
|
+
visibility var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
689
|
+
z-index: 10002;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.tooltip::after {
|
|
693
|
+
content: '';
|
|
694
|
+
position: absolute;
|
|
695
|
+
top: 100%;
|
|
696
|
+
left: 50%;
|
|
697
|
+
transform: translateX(-50%);
|
|
698
|
+
border: 6px solid transparent;
|
|
699
|
+
border-top-color: var(--color-gray-700, #374151);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.btn-wrapper:hover .tooltip {
|
|
703
|
+
visibility: visible;
|
|
704
|
+
opacity: 1;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.btn-wrapper:not(.has-error) .tooltip {
|
|
708
|
+
display: none;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.modal {
|
|
712
|
+
display: none;
|
|
713
|
+
position: fixed;
|
|
714
|
+
top: 0;
|
|
715
|
+
left: 0;
|
|
716
|
+
right: 0;
|
|
717
|
+
bottom: 0;
|
|
718
|
+
z-index: 10000;
|
|
719
|
+
align-items: center;
|
|
720
|
+
justify-content: center;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.modal.show {
|
|
724
|
+
display: flex;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.modal.animating-in .modal-overlay {
|
|
728
|
+
animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.modal.animating-in .modal-content {
|
|
732
|
+
animation: slideInScale 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.modal.animating-out .modal-overlay {
|
|
736
|
+
animation: fadeOut 0.15s cubic-bezier(0.4, 0, 1, 1);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.modal.animating-out .modal-content {
|
|
740
|
+
animation: slideOutScale 0.15s cubic-bezier(0.4, 0, 1, 1);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
@keyframes fadeIn {
|
|
744
|
+
from {
|
|
745
|
+
opacity: 0;
|
|
746
|
+
}
|
|
747
|
+
to {
|
|
748
|
+
opacity: 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
@keyframes fadeOut {
|
|
753
|
+
from {
|
|
754
|
+
opacity: 1;
|
|
755
|
+
}
|
|
756
|
+
to {
|
|
757
|
+
opacity: 0;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
@keyframes slideInScale {
|
|
762
|
+
from {
|
|
763
|
+
opacity: 0;
|
|
764
|
+
transform: scale(0.95) translateY(-10px);
|
|
765
|
+
}
|
|
766
|
+
to {
|
|
767
|
+
opacity: 1;
|
|
768
|
+
transform: scale(1) translateY(0);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@keyframes slideOutScale {
|
|
773
|
+
from {
|
|
774
|
+
opacity: 1;
|
|
775
|
+
transform: scale(1) translateY(0);
|
|
776
|
+
}
|
|
777
|
+
to {
|
|
778
|
+
opacity: 0;
|
|
779
|
+
transform: scale(0.98) translateY(-8px);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.modal-overlay {
|
|
784
|
+
position: absolute;
|
|
785
|
+
top: 0;
|
|
786
|
+
left: 0;
|
|
787
|
+
right: 0;
|
|
788
|
+
bottom: 0;
|
|
789
|
+
background: rgba(0, 0, 0, 0.5);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.modal-content {
|
|
793
|
+
position: relative;
|
|
794
|
+
background: var(--color-white, #fff);
|
|
795
|
+
border-radius: var(--radius-xl, 0.75rem);
|
|
796
|
+
width: 90%;
|
|
797
|
+
max-width: 600px;
|
|
798
|
+
height: 80vh;
|
|
799
|
+
max-height: 600px;
|
|
800
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
801
|
+
z-index: 10001;
|
|
802
|
+
display: flex;
|
|
803
|
+
flex-direction: column;
|
|
804
|
+
padding: 40px;
|
|
805
|
+
overflow: hidden;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.close-btn {
|
|
809
|
+
position: absolute;
|
|
810
|
+
top: 16px;
|
|
811
|
+
right: 16px;
|
|
812
|
+
background: transparent;
|
|
813
|
+
border: none;
|
|
814
|
+
font-size: 28px;
|
|
815
|
+
color: var(--color-gray-400, #9ca3af);
|
|
816
|
+
cursor: pointer;
|
|
817
|
+
width: 32px;
|
|
818
|
+
height: 32px;
|
|
819
|
+
display: flex;
|
|
820
|
+
align-items: center;
|
|
821
|
+
justify-content: center;
|
|
822
|
+
border-radius: 4px;
|
|
823
|
+
transition: all var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
.close-btn:hover {
|
|
827
|
+
background: var(--color-gray-100, #f3f4f6);
|
|
828
|
+
color: var(--color-headline, #0f2a39);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/* Modal Header - Static */
|
|
832
|
+
.modal-header {
|
|
833
|
+
text-align: center;
|
|
834
|
+
padding-bottom: 16px;
|
|
835
|
+
border-bottom: 1px solid var(--color-border, #e8e8e8);
|
|
836
|
+
flex-shrink: 0;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.modal-header h2 {
|
|
840
|
+
font-size: 20px;
|
|
841
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
842
|
+
color: var(--color-headline, #0f2a39);
|
|
843
|
+
margin: 0 0 4px 0;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.modal-header p {
|
|
847
|
+
font-size: 14px;
|
|
848
|
+
color: var(--color-gray-500, #6b7280);
|
|
849
|
+
margin: 0;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/* Modal Body */
|
|
853
|
+
.modal-body {
|
|
854
|
+
width: 100%;
|
|
855
|
+
flex: 1;
|
|
856
|
+
overflow-y: auto;
|
|
857
|
+
min-height: 0;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
/* Powered By Footer - Static */
|
|
862
|
+
.powered-by {
|
|
863
|
+
display: flex;
|
|
864
|
+
align-items: center;
|
|
865
|
+
justify-content: center;
|
|
866
|
+
gap: 6px;
|
|
867
|
+
padding-top: 16px;
|
|
868
|
+
border-top: 1px solid var(--color-border, #e8e8e8);
|
|
869
|
+
font-size: 11px;
|
|
870
|
+
color: var(--color-gray-400, #9ca3af);
|
|
871
|
+
flex-shrink: 0;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.powered-by svg {
|
|
875
|
+
width: 16px;
|
|
876
|
+
height: 16px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.powered-by span {
|
|
880
|
+
font-weight: 500;
|
|
881
|
+
color: var(--color-gray-500, #6b7280);
|
|
882
|
+
}
|
|
883
|
+
</style>
|
|
884
|
+
|
|
885
|
+
<!-- Main Button -->
|
|
886
|
+
<div class="btn-wrapper">
|
|
887
|
+
<span class="tooltip">Operator is not onboarded to the Bison system</span>
|
|
888
|
+
<button class="underwriting-btn">
|
|
889
|
+
<span class="loading-spinner"></span>
|
|
890
|
+
<svg class="broken-link-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
891
|
+
<path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
|
|
892
|
+
<line x1="1" y1="1" x2="23" y2="23"></line>
|
|
893
|
+
</svg>
|
|
894
|
+
View Underwriting Status
|
|
895
|
+
</button>
|
|
896
|
+
</div>
|
|
897
|
+
|
|
898
|
+
<!-- Modal -->
|
|
899
|
+
<div class="modal">
|
|
900
|
+
<div class="modal-overlay"></div>
|
|
901
|
+
<div class="modal-content">
|
|
902
|
+
<button class="close-btn">×</button>
|
|
903
|
+
|
|
904
|
+
<!-- Modal Header -->
|
|
905
|
+
<div class="modal-header">
|
|
906
|
+
<h2>Underwriting Status</h2>
|
|
907
|
+
<p>Track your underwriting application progress</p>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
910
|
+
<!-- Content Area -->
|
|
911
|
+
<div class="modal-body">
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
<!-- Powered By Footer -->
|
|
915
|
+
<div class="powered-by">
|
|
916
|
+
Powered by
|
|
917
|
+
<img src="./bison_logo.png" alt="Bison" style="height: 16px; margin-left: 4px;" onerror="this.onerror=null; this.src='https://bisonpaywell.com/lovable-uploads/28831244-e8b3-4e7b-8dbb-c016f9f9d54f.png';">
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
920
|
+
</div>
|
|
921
|
+
`;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Render the timeline with payment methods
|
|
926
|
+
* @returns {string} HTML string for timeline
|
|
927
|
+
*/
|
|
928
|
+
renderTimeline() {
|
|
929
|
+
// Show loading state
|
|
930
|
+
if (this._state.isLoadingPaymentMethods) {
|
|
931
|
+
return `
|
|
932
|
+
<div class="timeline-loading">
|
|
933
|
+
<div class="loading-spinner-large"></div>
|
|
934
|
+
<p>Loading payment methods...</p>
|
|
935
|
+
</div>
|
|
936
|
+
`;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Show error state
|
|
940
|
+
if (this._state.paymentMethodsError) {
|
|
941
|
+
return `
|
|
942
|
+
<div class="timeline-error">
|
|
943
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
944
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
945
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
946
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
947
|
+
</svg>
|
|
948
|
+
<p>${this._state.paymentMethodsError}</p>
|
|
949
|
+
<button class="retry-btn">Retry</button>
|
|
950
|
+
</div>
|
|
951
|
+
`;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Show empty state
|
|
955
|
+
if (
|
|
956
|
+
!this._state.paymentMethods ||
|
|
957
|
+
this._state.paymentMethods.length === 0
|
|
958
|
+
) {
|
|
959
|
+
return `
|
|
960
|
+
<div class="timeline-empty">
|
|
961
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
962
|
+
<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
|
|
963
|
+
<line x1="1" y1="10" x2="23" y2="10"></line>
|
|
964
|
+
</svg>
|
|
965
|
+
<p>No payment methods linked yet</p>
|
|
966
|
+
</div>
|
|
967
|
+
`;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Render payment methods as timeline items
|
|
971
|
+
return this._state.paymentMethods
|
|
972
|
+
.map((method) => this.renderPaymentMethodItem(method))
|
|
973
|
+
.join("");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Render a single payment method as timeline item
|
|
978
|
+
* @param {Object} method - Payment method data
|
|
979
|
+
* @returns {string} HTML string for timeline item
|
|
980
|
+
*/
|
|
981
|
+
renderPaymentMethodItem(method) {
|
|
982
|
+
const icon = this.getPaymentMethodIcon(method.paymentMethodType);
|
|
983
|
+
const title = this.formatPaymentMethodTitle(method);
|
|
984
|
+
const description = this.formatPaymentMethodDescription(method);
|
|
985
|
+
const paymentMethodId = method.paymentMethodID;
|
|
986
|
+
|
|
987
|
+
return `
|
|
988
|
+
<div class="timeline-item">
|
|
989
|
+
<div class="timeline-icon completed">
|
|
990
|
+
${this.getPaymentTypeIconSvg(method.paymentMethodType)}
|
|
991
|
+
</div>
|
|
992
|
+
<div class="timeline-card">
|
|
993
|
+
<div class="card-header">
|
|
994
|
+
<h4 class="card-title">
|
|
995
|
+
<span class="payment-type-icon">${icon}</span>
|
|
996
|
+
${title}
|
|
997
|
+
</h4>
|
|
998
|
+
<button class="delete-btn" data-payment-method-id="${paymentMethodId}" title="Delete payment method">
|
|
999
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1000
|
+
<polyline points="3 6 5 6 21 6"></polyline>
|
|
1001
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
1002
|
+
<line x1="10" y1="11" x2="10" y2="17"></line>
|
|
1003
|
+
<line x1="14" y1="11" x2="14" y2="17"></line>
|
|
1004
|
+
</svg>
|
|
1005
|
+
</button>
|
|
1006
|
+
</div>
|
|
1007
|
+
<p class="card-description">${description}</p>
|
|
1008
|
+
<div class="card-timestamp">
|
|
1009
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1010
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
1011
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
1012
|
+
</svg>
|
|
1013
|
+
${this.formatPaymentMethodTimestamp(method)}
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Get emoji icon based on payment method type
|
|
1022
|
+
* @param {string} type - Payment method type
|
|
1023
|
+
* @returns {string} Emoji icon
|
|
1024
|
+
*/
|
|
1025
|
+
getPaymentMethodIcon(type) {
|
|
1026
|
+
const icons = {
|
|
1027
|
+
card: "💳",
|
|
1028
|
+
bankAccount: "🏦",
|
|
1029
|
+
wallet: "💰",
|
|
1030
|
+
applePay: "🍎",
|
|
1031
|
+
moovWallet: "💰",
|
|
1032
|
+
};
|
|
1033
|
+
return icons[type] || "💳";
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Get SVG icon for timeline based on payment type
|
|
1038
|
+
* @param {string} type - Payment method type
|
|
1039
|
+
* @returns {string} SVG HTML string
|
|
1040
|
+
*/
|
|
1041
|
+
getPaymentTypeIconSvg(type) {
|
|
1042
|
+
switch (type) {
|
|
1043
|
+
case "card":
|
|
1044
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line></svg>`;
|
|
1045
|
+
case "bankAccount":
|
|
1046
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><path d="M3 21h18M3 10h18M5 6l7-3 7 3M4 10v11M20 10v11M8 14v3M12 14v3M16 14v3"></path></svg>`;
|
|
1047
|
+
case "wallet":
|
|
1048
|
+
case "moovWallet":
|
|
1049
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><path d="M21 12V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5z"></path><path d="M16 12h.01"></path></svg>`;
|
|
1050
|
+
case "applePay":
|
|
1051
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2z"></path><path d="M12 6v2M12 16v2M6 12h2M16 12h2"></path></svg>`;
|
|
1052
|
+
default:
|
|
1053
|
+
return `<svg viewBox="0 0 24 24" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Format payment method title based on type
|
|
1059
|
+
* @param {Object} method - Payment method data
|
|
1060
|
+
* @returns {string} Formatted title
|
|
1061
|
+
*/
|
|
1062
|
+
formatPaymentMethodTitle(method) {
|
|
1063
|
+
switch (method.paymentMethodType) {
|
|
1064
|
+
case "card":
|
|
1065
|
+
if (method.card) {
|
|
1066
|
+
const brand = method.card.brand || method.card.cardType || "Card";
|
|
1067
|
+
return `${this.capitalizeFirst(brand)}`;
|
|
1068
|
+
}
|
|
1069
|
+
return "Credit/Debit Card";
|
|
1070
|
+
case "bankAccount":
|
|
1071
|
+
if (method.bankAccount) {
|
|
1072
|
+
return method.bankAccount.bankName || "Bank Account";
|
|
1073
|
+
}
|
|
1074
|
+
return "Bank Account";
|
|
1075
|
+
case "wallet":
|
|
1076
|
+
case "moovWallet":
|
|
1077
|
+
return "Moov Wallet";
|
|
1078
|
+
case "applePay":
|
|
1079
|
+
return "Apple Pay";
|
|
1080
|
+
default:
|
|
1081
|
+
return "Payment Method";
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Format payment method description with details
|
|
1087
|
+
* @param {Object} method - Payment method data
|
|
1088
|
+
* @returns {string} Formatted description
|
|
1089
|
+
*/
|
|
1090
|
+
formatPaymentMethodDescription(method) {
|
|
1091
|
+
switch (method.paymentMethodType) {
|
|
1092
|
+
case "card":
|
|
1093
|
+
if (method.card) {
|
|
1094
|
+
const lastFour = method.card.lastFourCardNumber || "****";
|
|
1095
|
+
const expiry = method.card.expiration
|
|
1096
|
+
? `Expires ${method.card.expiration.month}/${method.card.expiration.year}`
|
|
1097
|
+
: "";
|
|
1098
|
+
return `Ending in ****${lastFour}${expiry ? ` • ${expiry}` : ""}`;
|
|
1099
|
+
}
|
|
1100
|
+
return "Card details unavailable";
|
|
1101
|
+
case "bankAccount":
|
|
1102
|
+
if (method.bankAccount) {
|
|
1103
|
+
const lastFour = method.bankAccount.lastFourAccountNumber || "****";
|
|
1104
|
+
const type = method.bankAccount.bankAccountType || "";
|
|
1105
|
+
return `${this.capitalizeFirst(
|
|
1106
|
+
type
|
|
1107
|
+
)} account ending in ****${lastFour}`;
|
|
1108
|
+
}
|
|
1109
|
+
return "Bank account details unavailable";
|
|
1110
|
+
case "wallet":
|
|
1111
|
+
case "moovWallet":
|
|
1112
|
+
if (method.wallet) {
|
|
1113
|
+
return `Available balance: $${(method.wallet.availableBalance?.value || 0) / 100
|
|
1114
|
+
}`;
|
|
1115
|
+
}
|
|
1116
|
+
return "Digital wallet for payments";
|
|
1117
|
+
case "applePay":
|
|
1118
|
+
if (method.applePay) {
|
|
1119
|
+
return `${method.applePay.brand || "Card"} via Apple Pay`;
|
|
1120
|
+
}
|
|
1121
|
+
return "Apple Pay enabled device";
|
|
1122
|
+
default:
|
|
1123
|
+
return "Payment method linked to your account";
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Format timestamp for payment method
|
|
1129
|
+
* @param {Object} method - Payment method data
|
|
1130
|
+
* @returns {string} Formatted timestamp
|
|
1131
|
+
*/
|
|
1132
|
+
formatPaymentMethodTimestamp(method) {
|
|
1133
|
+
// Try to get creation date from various possible fields
|
|
1134
|
+
const dateStr = method.createdOn || method.createdAt || method.addedAt;
|
|
1135
|
+
|
|
1136
|
+
if (dateStr) {
|
|
1137
|
+
try {
|
|
1138
|
+
const date = new Date(dateStr);
|
|
1139
|
+
return `Added ${date.toLocaleDateString("en-US", {
|
|
1140
|
+
month: "short",
|
|
1141
|
+
day: "numeric",
|
|
1142
|
+
year: "numeric",
|
|
1143
|
+
})}`;
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
return "Date unavailable";
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return "Recently added";
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Capitalize first letter of string
|
|
1154
|
+
* @param {string} str - String to capitalize
|
|
1155
|
+
* @returns {string} Capitalized string
|
|
1156
|
+
*/
|
|
1157
|
+
capitalizeFirst(str) {
|
|
1158
|
+
if (!str) return "";
|
|
1159
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Get SVG icon based on status (kept for backwards compatibility)
|
|
1164
|
+
* @param {string} status - Status type
|
|
1165
|
+
* @returns {string} SVG HTML string
|
|
1166
|
+
*/
|
|
1167
|
+
getStatusIcon(status) {
|
|
1168
|
+
switch (status) {
|
|
1169
|
+
case "completed":
|
|
1170
|
+
return `<svg viewBox="0 0 24 24" stroke-width="3"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
1171
|
+
case "in-progress":
|
|
1172
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><circle cx="12" cy="12" r="3"></circle></svg>`;
|
|
1173
|
+
case "error":
|
|
1174
|
+
return `<svg viewBox="0 0 24 24" stroke-width="3"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
|
|
1175
|
+
case "pending":
|
|
1176
|
+
default:
|
|
1177
|
+
return `<svg viewBox="0 0 24 24" stroke-width="2"><circle cx="12" cy="12" r="1"></circle></svg>`;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Format status for display (kept for backwards compatibility)
|
|
1183
|
+
* @param {string} status - Status type
|
|
1184
|
+
* @returns {string} Formatted status text
|
|
1185
|
+
*/
|
|
1186
|
+
formatStatus(status) {
|
|
1187
|
+
const statusMap = {
|
|
1188
|
+
completed: "Completed",
|
|
1189
|
+
"in-progress": "In Progress",
|
|
1190
|
+
pending: "Pending",
|
|
1191
|
+
error: "Error",
|
|
1192
|
+
};
|
|
1193
|
+
return statusMap[status] || status;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Render underwriting history as a timeline
|
|
1198
|
+
* @param {Array} history - Array of underwriting status records
|
|
1199
|
+
* @returns {string} HTML string for timeline
|
|
1200
|
+
*/
|
|
1201
|
+
renderUnderwritingTimeline(history) {
|
|
1202
|
+
if (!history || history.length === 0) {
|
|
1203
|
+
return `
|
|
1204
|
+
<div style="display: flex; margin: 24px 0; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center; background: var(--color-gray-50, #f9fafb); border: 1px dashed var(--color-gray-200, #d1d5db); border-radius: var(--radius-xl, 0.75rem);">
|
|
1205
|
+
<svg style="width: 64px; height: 64px; color: var(--color-gray-400, #9ca3af); margin-bottom: 16px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
1206
|
+
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
1207
|
+
</svg>
|
|
1208
|
+
<p style="font-size: 16px; color: var(--color-gray-500, #6b7280); margin: 0;">No underwriting history found</p>
|
|
1209
|
+
</div>
|
|
1210
|
+
`;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Sort by changedAt (oldest first, will be reversed in display)
|
|
1214
|
+
const sortedHistory = [...history].sort((a, b) => {
|
|
1215
|
+
const dateA = new Date(a.changedAt);
|
|
1216
|
+
const dateB = new Date(b.changedAt);
|
|
1217
|
+
return dateA - dateB;
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Reverse to show newest at top
|
|
1221
|
+
const reversedHistory = [...sortedHistory].reverse();
|
|
1222
|
+
|
|
1223
|
+
const timelineItems = reversedHistory
|
|
1224
|
+
.map((item, index) => {
|
|
1225
|
+
const isNewest = index === 0;
|
|
1226
|
+
const isOldest = index === reversedHistory.length - 1;
|
|
1227
|
+
return this.renderUnderwritingTimelineItem(item, isNewest, isOldest);
|
|
1228
|
+
})
|
|
1229
|
+
.join("");
|
|
1230
|
+
|
|
1231
|
+
return `
|
|
1232
|
+
<style>
|
|
1233
|
+
.underwriting-timeline {
|
|
1234
|
+
padding: 16px;
|
|
1235
|
+
position: relative;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.timeline-item {
|
|
1239
|
+
display: flex;
|
|
1240
|
+
gap: 12px;
|
|
1241
|
+
position: relative;
|
|
1242
|
+
padding-bottom: 24px;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.timeline-item:last-child {
|
|
1246
|
+
padding-bottom: 0;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.timeline-icon-wrapper {
|
|
1250
|
+
position: relative;
|
|
1251
|
+
display: flex;
|
|
1252
|
+
flex-direction: column;
|
|
1253
|
+
align-items: center;
|
|
1254
|
+
flex-shrink: 0;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.timeline-icon {
|
|
1258
|
+
width: 32px;
|
|
1259
|
+
height: 32px;
|
|
1260
|
+
border-radius: 50%;
|
|
1261
|
+
display: flex;
|
|
1262
|
+
align-items: center;
|
|
1263
|
+
justify-content: center;
|
|
1264
|
+
z-index: 2;
|
|
1265
|
+
position: relative;
|
|
1266
|
+
background: var(--color-border, #e8e8e8);
|
|
1267
|
+
color: var(--color-gray-500, #6b7280);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
.timeline-icon.approved {
|
|
1271
|
+
background: var(--color-success-light, #d3f3df);
|
|
1272
|
+
color: var(--color-success, #22c55e);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.timeline-icon.pending {
|
|
1276
|
+
background: var(--color-orange-100, #fdecce);
|
|
1277
|
+
color: var(--color-warning, #f59e0b);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
.timeline-icon.latest::before {
|
|
1281
|
+
content: '';
|
|
1282
|
+
position: absolute;
|
|
1283
|
+
width: 100%;
|
|
1284
|
+
height: 100%;
|
|
1285
|
+
border-radius: 50%;
|
|
1286
|
+
border: 2px solid currentColor;
|
|
1287
|
+
opacity: 0.6;
|
|
1288
|
+
animation: ping 1.2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
@keyframes ping {
|
|
1292
|
+
0% {
|
|
1293
|
+
transform: scale(1);
|
|
1294
|
+
opacity: 0.6;
|
|
1295
|
+
}
|
|
1296
|
+
75%, 100% {
|
|
1297
|
+
transform: scale(1.4);
|
|
1298
|
+
opacity: 0;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.timeline-line {
|
|
1303
|
+
position: absolute;
|
|
1304
|
+
top: 32px;
|
|
1305
|
+
left: 50%;
|
|
1306
|
+
transform: translateX(-50%);
|
|
1307
|
+
width: 2px;
|
|
1308
|
+
height: calc(100% - 0px);
|
|
1309
|
+
background: var(--color-border, #e8e8e8);
|
|
1310
|
+
z-index: 1;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.timeline-content {
|
|
1314
|
+
flex: 1;
|
|
1315
|
+
background: var(--color-white, #fff);
|
|
1316
|
+
border: 1px solid var(--color-border, #e8e8e8);
|
|
1317
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
1318
|
+
padding: 12px 16px;
|
|
1319
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.timeline-status {
|
|
1323
|
+
font-size: 13px;
|
|
1324
|
+
font-weight: 600;
|
|
1325
|
+
color: var(--color-headline, #0f2a39);
|
|
1326
|
+
margin: 0 0 4px 0;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
.timeline-date {
|
|
1330
|
+
font-size: 12px;
|
|
1331
|
+
color: var(--color-gray-400, #9ca3af);
|
|
1332
|
+
margin: 0;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
.timeline-badge {
|
|
1336
|
+
display: inline-block;
|
|
1337
|
+
padding: 2px 8px;
|
|
1338
|
+
border-radius: 10px;
|
|
1339
|
+
font-size: 10px;
|
|
1340
|
+
font-weight: 500;
|
|
1341
|
+
margin-top: 6px;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
.timeline-badge.newest {
|
|
1345
|
+
background: var(--color-primary-light, #e8f0eb);
|
|
1346
|
+
color: var(--color-primary, #4c7b63);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.timeline-badge.oldest {
|
|
1350
|
+
background: var(--color-gray-100, #f3f4f6);
|
|
1351
|
+
color: var(--color-gray-500, #6b7280);
|
|
1352
|
+
}
|
|
1353
|
+
</style>
|
|
1354
|
+
|
|
1355
|
+
<div class="underwriting-timeline">
|
|
1356
|
+
${timelineItems}
|
|
1357
|
+
</div>
|
|
1358
|
+
`;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Render a single underwriting timeline item
|
|
1363
|
+
* @param {Object} item - Underwriting status record
|
|
1364
|
+
* @param {boolean} isNewest - Whether this is the newest status
|
|
1365
|
+
* @param {boolean} isOldest - Whether this is the oldest status
|
|
1366
|
+
* @returns {string} HTML string for timeline item
|
|
1367
|
+
*/
|
|
1368
|
+
renderUnderwritingTimelineItem(item, isNewest, isOldest) {
|
|
1369
|
+
const status = item.status || "unknown";
|
|
1370
|
+
const statusDisplay = this.formatUnderwritingStatus(status);
|
|
1371
|
+
const icon = this.getUnderwritingStatusIcon(status);
|
|
1372
|
+
const formattedDate = this.formatUnderwritingDate(item.changedAt);
|
|
1373
|
+
|
|
1374
|
+
return `
|
|
1375
|
+
<div class="timeline-item">
|
|
1376
|
+
<div class="timeline-icon-wrapper">
|
|
1377
|
+
<div class="timeline-icon ${status} ${isNewest ? "latest" : ""}">
|
|
1378
|
+
${icon}
|
|
1379
|
+
</div>
|
|
1380
|
+
${!isOldest ? '<div class="timeline-line"></div>' : ""}
|
|
1381
|
+
</div>
|
|
1382
|
+
<div class="timeline-content">
|
|
1383
|
+
<h4 class="timeline-status">${statusDisplay}</h4>
|
|
1384
|
+
<p class="timeline-date">${formattedDate}</p>
|
|
1385
|
+
${isNewest ? '<span class="timeline-badge newest">Latest</span>' : ""}
|
|
1386
|
+
${isOldest ? '<span class="timeline-badge oldest">Initial</span>' : ""
|
|
1387
|
+
}
|
|
1388
|
+
</div>
|
|
1389
|
+
</div>
|
|
1390
|
+
`;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Format underwriting status for display
|
|
1395
|
+
* @param {string} status - Status value
|
|
1396
|
+
* @returns {string} Formatted status text
|
|
1397
|
+
*/
|
|
1398
|
+
formatUnderwritingStatus(status) {
|
|
1399
|
+
const statusMap = {
|
|
1400
|
+
pending: "Pending Review",
|
|
1401
|
+
approved: "Approved",
|
|
1402
|
+
rejected: "Rejected",
|
|
1403
|
+
under_review: "Under Review",
|
|
1404
|
+
submitted: "Submitted",
|
|
1405
|
+
};
|
|
1406
|
+
return (
|
|
1407
|
+
statusMap[status] || status.charAt(0).toUpperCase() + status.slice(1)
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Get icon SVG for underwriting status
|
|
1413
|
+
* @param {string} status - Status value
|
|
1414
|
+
* @returns {string} SVG HTML string
|
|
1415
|
+
*/
|
|
1416
|
+
getUnderwritingStatusIcon(status) {
|
|
1417
|
+
switch (status) {
|
|
1418
|
+
case "approved":
|
|
1419
|
+
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
1420
|
+
case "rejected":
|
|
1421
|
+
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
|
|
1422
|
+
case "under_review":
|
|
1423
|
+
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
|
|
1424
|
+
case "pending":
|
|
1425
|
+
default:
|
|
1426
|
+
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Format date for underwriting timeline
|
|
1432
|
+
* @param {string} dateStr - ISO date string
|
|
1433
|
+
* @returns {string} Formatted date string
|
|
1434
|
+
*/
|
|
1435
|
+
formatUnderwritingDate(dateStr) {
|
|
1436
|
+
if (!dateStr) return "Date unavailable";
|
|
1437
|
+
|
|
1438
|
+
try {
|
|
1439
|
+
const date = new Date(dateStr);
|
|
1440
|
+
const dateFormatted = date.toLocaleDateString("en-US", {
|
|
1441
|
+
month: "short",
|
|
1442
|
+
day: "numeric",
|
|
1443
|
+
year: "numeric",
|
|
1444
|
+
});
|
|
1445
|
+
const timeFormatted = date.toLocaleTimeString("en-US", {
|
|
1446
|
+
hour: "numeric",
|
|
1447
|
+
minute: "2-digit",
|
|
1448
|
+
hour12: true,
|
|
1449
|
+
});
|
|
1450
|
+
return `${dateFormatted} ${timeFormatted}`;
|
|
1451
|
+
} catch (e) {
|
|
1452
|
+
return dateStr;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// Register the custom element only if it hasn't been registered yet
|
|
1458
|
+
if (!customElements.get("operator-underwriting")) {
|
|
1459
|
+
customElements.define("operator-underwriting", OperatorUnderwriting);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// Export for module usage
|
|
1463
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
1464
|
+
module.exports = { OperatorUnderwriting };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// Make available globally for script tag usage
|
|
1468
|
+
if (typeof window !== "undefined") {
|
|
1469
|
+
window.OperatorUnderwriting = OperatorUnderwriting;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Export for ES6 modules
|
|
1473
|
+
export { OperatorUnderwriting };
|