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,3750 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Onboarding Web Component
|
|
3
|
+
*
|
|
4
|
+
* A web component that captures operator information via stepper form
|
|
5
|
+
* with necessary field validations. This serves as the simplified approach
|
|
6
|
+
* in comparison to the Moov Onboarding Drop.
|
|
7
|
+
*
|
|
8
|
+
* @requires BisonJibPayAPI - Must be loaded before this component (from api.js)
|
|
9
|
+
*
|
|
10
|
+
* @author @kfajardo
|
|
11
|
+
* @version 1.0.0
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```html
|
|
15
|
+
* <script src="api.js"></script>
|
|
16
|
+
* <script src="operator-onboarding.js"></script>
|
|
17
|
+
*
|
|
18
|
+
* <operator-onboarding id="onboarding"></operator-onboarding>
|
|
19
|
+
* <script>
|
|
20
|
+
* const onboarding = document.getElementById('onboarding');
|
|
21
|
+
* onboarding.onSuccess = (data) => console.log('Success!', data);
|
|
22
|
+
* onboarding.onError = (error) => console.error('Error:', error);
|
|
23
|
+
* </script>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
class OperatorOnboarding extends HTMLElement {
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.attachShadow({ mode: "open" });
|
|
31
|
+
|
|
32
|
+
// API Configuration
|
|
33
|
+
this.apiBaseURL =
|
|
34
|
+
this.getAttribute("api-base-url") ||
|
|
35
|
+
"https://bison-jib-development.azurewebsites.net";
|
|
36
|
+
this.embeddableKey =
|
|
37
|
+
this.getAttribute("embeddable-key") ||
|
|
38
|
+
"R80WMkbNN8457RofiMYx03DL65P06IaVT30Q2emYJUBQwYCzRC";
|
|
39
|
+
|
|
40
|
+
// Check if BisonJibPayAPI is available
|
|
41
|
+
if (typeof BisonJibPayAPI === "undefined") {
|
|
42
|
+
console.error(
|
|
43
|
+
"OperatorOnboarding: BisonJibPayAPI is not available. Please ensure api.js is loaded before operator-onboarding.js"
|
|
44
|
+
);
|
|
45
|
+
this.api = null;
|
|
46
|
+
} else {
|
|
47
|
+
this.api = new BisonJibPayAPI(this.apiBaseURL, this.embeddableKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize state
|
|
51
|
+
this.state = {
|
|
52
|
+
isModalOpen: false,
|
|
53
|
+
currentStep: 0,
|
|
54
|
+
totalSteps: 4, // Business, Representatives, Bank, Underwriting
|
|
55
|
+
isSubmitted: false,
|
|
56
|
+
isFailed: false,
|
|
57
|
+
isSubmissionFailed: false,
|
|
58
|
+
formData: {
|
|
59
|
+
businessDetails: {
|
|
60
|
+
businessName: "",
|
|
61
|
+
doingBusinessAs: "",
|
|
62
|
+
ein: "",
|
|
63
|
+
businessWebsite: "",
|
|
64
|
+
businessPhoneNumber: "",
|
|
65
|
+
businessEmail: "",
|
|
66
|
+
BusinessAddress1: "",
|
|
67
|
+
businessCity: "",
|
|
68
|
+
businessState: "",
|
|
69
|
+
businessPostalCode: "",
|
|
70
|
+
},
|
|
71
|
+
representatives: [],
|
|
72
|
+
underwriting: {
|
|
73
|
+
underwritingDocuments: [], // File upload support for underwriting documents
|
|
74
|
+
},
|
|
75
|
+
bankDetails: {
|
|
76
|
+
bankAccountHolderName: "",
|
|
77
|
+
bankAccountType: "checking",
|
|
78
|
+
bankRoutingNumber: "",
|
|
79
|
+
bankAccountNumber: "",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
validationState: {
|
|
83
|
+
step0: { isValid: false, errors: {} }, // Business Details
|
|
84
|
+
step1: { isValid: true, errors: {} }, // Representatives (optional)
|
|
85
|
+
step2: { isValid: false, errors: {} }, // Bank Details
|
|
86
|
+
step3: { isValid: false, errors: {} }, // Underwriting (required)
|
|
87
|
+
},
|
|
88
|
+
completedSteps: new Set(),
|
|
89
|
+
uiState: {
|
|
90
|
+
isLoading: false,
|
|
91
|
+
showErrors: false,
|
|
92
|
+
errorMessage: null,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Step configuration (Verification is now pre-stepper)
|
|
97
|
+
this.STEPS = [
|
|
98
|
+
{
|
|
99
|
+
id: "business-details",
|
|
100
|
+
title: "Business",
|
|
101
|
+
description: "Provide your business details",
|
|
102
|
+
canSkip: false,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "representatives",
|
|
106
|
+
title: "Representatives",
|
|
107
|
+
description: "Add business representatives (optional)",
|
|
108
|
+
canSkip: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "bank-details",
|
|
112
|
+
title: "Bank Account",
|
|
113
|
+
description: "Link your bank account",
|
|
114
|
+
canSkip: false,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "underwriting",
|
|
118
|
+
title: "Underwriting",
|
|
119
|
+
description: "Upload required documents",
|
|
120
|
+
canSkip: false,
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
// US States for dropdown
|
|
125
|
+
this.US_STATES = [
|
|
126
|
+
"AL",
|
|
127
|
+
"AK",
|
|
128
|
+
"AZ",
|
|
129
|
+
"AR",
|
|
130
|
+
"CA",
|
|
131
|
+
"CO",
|
|
132
|
+
"CT",
|
|
133
|
+
"DE",
|
|
134
|
+
"FL",
|
|
135
|
+
"GA",
|
|
136
|
+
"HI",
|
|
137
|
+
"ID",
|
|
138
|
+
"IL",
|
|
139
|
+
"IN",
|
|
140
|
+
"IA",
|
|
141
|
+
"KS",
|
|
142
|
+
"KY",
|
|
143
|
+
"LA",
|
|
144
|
+
"ME",
|
|
145
|
+
"MD",
|
|
146
|
+
"MA",
|
|
147
|
+
"MI",
|
|
148
|
+
"MN",
|
|
149
|
+
"MS",
|
|
150
|
+
"MO",
|
|
151
|
+
"MT",
|
|
152
|
+
"NE",
|
|
153
|
+
"NV",
|
|
154
|
+
"NH",
|
|
155
|
+
"NJ",
|
|
156
|
+
"NM",
|
|
157
|
+
"NY",
|
|
158
|
+
"NC",
|
|
159
|
+
"ND",
|
|
160
|
+
"OH",
|
|
161
|
+
"OK",
|
|
162
|
+
"OR",
|
|
163
|
+
"PA",
|
|
164
|
+
"RI",
|
|
165
|
+
"SC",
|
|
166
|
+
"SD",
|
|
167
|
+
"TN",
|
|
168
|
+
"TX",
|
|
169
|
+
"UT",
|
|
170
|
+
"VT",
|
|
171
|
+
"VA",
|
|
172
|
+
"WA",
|
|
173
|
+
"WV",
|
|
174
|
+
"WI",
|
|
175
|
+
"WY",
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// Internal callback storage
|
|
179
|
+
this._onSuccessCallback = null;
|
|
180
|
+
this._onErrorCallback = null;
|
|
181
|
+
this._onSubmitCallback = null;
|
|
182
|
+
this._onConfirmCallback = null;
|
|
183
|
+
this._initialData = null;
|
|
184
|
+
|
|
185
|
+
this.render();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Getter and setter for onSuccess property (for easy framework integration)
|
|
189
|
+
get onSuccess() {
|
|
190
|
+
return this._onSuccessCallback;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
set onSuccess(callback) {
|
|
194
|
+
console.log("OperatorOnboarding: onSuccess setter called", {
|
|
195
|
+
callbackType: typeof callback,
|
|
196
|
+
isFunction: typeof callback === "function",
|
|
197
|
+
});
|
|
198
|
+
if (typeof callback === "function" || callback === null) {
|
|
199
|
+
this._onSuccessCallback = callback;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Getter and setter for onError property (for error handling)
|
|
204
|
+
get onError() {
|
|
205
|
+
return this._onErrorCallback;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
set onError(callback) {
|
|
209
|
+
if (typeof callback === "function" || callback === null) {
|
|
210
|
+
this._onErrorCallback = callback;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Getter and setter for onSubmit property (for pre-submission handling)
|
|
215
|
+
get onSubmit() {
|
|
216
|
+
return this._onSubmitCallback;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
set onSubmit(callback) {
|
|
220
|
+
if (typeof callback === "function" || callback === null) {
|
|
221
|
+
this._onSubmitCallback = callback;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Getter and setter for onConfirm property (for success confirmation button)
|
|
226
|
+
get onConfirm() {
|
|
227
|
+
return this._onConfirmCallback;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
set onConfirm(callback) {
|
|
231
|
+
if (typeof callback === "function" || callback === null) {
|
|
232
|
+
this._onConfirmCallback = callback;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Getter and setter for onLoad property (for pre-populating form data)
|
|
237
|
+
get onLoad() {
|
|
238
|
+
return this._initialData;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
set onLoad(data) {
|
|
242
|
+
if (data && typeof data === "object") {
|
|
243
|
+
this._initialData = data;
|
|
244
|
+
this.loadInitialData(data);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Static getter for observed attributes
|
|
249
|
+
static get observedAttributes() {
|
|
250
|
+
return ["on-success", "on-error", "on-submit", "on-load"];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ==================== VALIDATORS ====================
|
|
254
|
+
|
|
255
|
+
validators = {
|
|
256
|
+
required: (value, fieldName) => ({
|
|
257
|
+
isValid: value && value.trim().length > 0,
|
|
258
|
+
error: `${fieldName} is required`,
|
|
259
|
+
}),
|
|
260
|
+
|
|
261
|
+
email: (value) => ({
|
|
262
|
+
isValid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
|
|
263
|
+
error: "Please enter a valid email address",
|
|
264
|
+
}),
|
|
265
|
+
|
|
266
|
+
usPhone: (value) => {
|
|
267
|
+
const cleaned = value.replace(/\D/g, "");
|
|
268
|
+
return {
|
|
269
|
+
isValid: cleaned.length === 10,
|
|
270
|
+
error: "Please enter a valid 10-digit U.S. phone number",
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
routingNumber: (value) => {
|
|
275
|
+
const cleaned = value.replace(/\D/g, "");
|
|
276
|
+
return {
|
|
277
|
+
isValid: cleaned.length === 9,
|
|
278
|
+
error: "Routing number must be 9 digits",
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
accountNumber: (value) => {
|
|
283
|
+
const cleaned = value.replace(/\D/g, "");
|
|
284
|
+
return {
|
|
285
|
+
isValid: cleaned.length >= 4 && cleaned.length <= 17,
|
|
286
|
+
error: "Account number must be 4-17 digits",
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
ein: (value) => {
|
|
291
|
+
const cleaned = value.replace(/\D/g, "");
|
|
292
|
+
return {
|
|
293
|
+
isValid: cleaned.length === 9,
|
|
294
|
+
error: "EIN must be 9 digits",
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
url: (value) => {
|
|
299
|
+
if (!value) return { isValid: true, error: "" }; // Optional
|
|
300
|
+
|
|
301
|
+
// Trim whitespace
|
|
302
|
+
const trimmed = value.trim();
|
|
303
|
+
if (!trimmed) return { isValid: true, error: "" };
|
|
304
|
+
|
|
305
|
+
// Pattern for basic domain validation
|
|
306
|
+
// Accepts: domain.com, www.domain.com, subdomain.domain.com
|
|
307
|
+
const domainPattern = /^(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$/;
|
|
308
|
+
|
|
309
|
+
// Check if it's already a full URL
|
|
310
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
311
|
+
try {
|
|
312
|
+
new URL(trimmed);
|
|
313
|
+
return { isValid: true, error: "", normalizedValue: trimmed };
|
|
314
|
+
} catch {
|
|
315
|
+
return { isValid: false, error: "Please enter a valid URL" };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check if it matches domain pattern (without protocol)
|
|
320
|
+
if (domainPattern.test(trimmed)) {
|
|
321
|
+
// Auto-normalize by adding https://
|
|
322
|
+
const normalized = `https://${trimmed}`;
|
|
323
|
+
try {
|
|
324
|
+
new URL(normalized); // Validate the normalized URL
|
|
325
|
+
return { isValid: true, error: "", normalizedValue: normalized };
|
|
326
|
+
} catch {
|
|
327
|
+
return { isValid: false, error: "Please enter a valid URL" };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
isValid: false,
|
|
333
|
+
error:
|
|
334
|
+
"Please enter a valid URL (e.g., example.com, www.example.com, or https://example.com)",
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
postalCode: (value) => {
|
|
339
|
+
const cleaned = value.replace(/\D/g, "");
|
|
340
|
+
return {
|
|
341
|
+
isValid: cleaned.length === 5,
|
|
342
|
+
error: "Please enter a valid 5-digit ZIP code",
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// ==================== STATE MANAGEMENT ====================
|
|
348
|
+
|
|
349
|
+
setState(newState) {
|
|
350
|
+
const wasModalOpen = this.state.isModalOpen;
|
|
351
|
+
|
|
352
|
+
this.state = {
|
|
353
|
+
...this.state,
|
|
354
|
+
...newState,
|
|
355
|
+
formData: {
|
|
356
|
+
...this.state.formData,
|
|
357
|
+
...(newState.formData || {}),
|
|
358
|
+
},
|
|
359
|
+
validationState: {
|
|
360
|
+
...this.state.validationState,
|
|
361
|
+
...(newState.validationState || {}),
|
|
362
|
+
},
|
|
363
|
+
uiState: {
|
|
364
|
+
...this.state.uiState,
|
|
365
|
+
...(newState.uiState || {}),
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Track if modal was already open to skip animations on content updates
|
|
370
|
+
this._skipModalAnimation = wasModalOpen && this.state.isModalOpen;
|
|
371
|
+
this.render();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ==================== VALIDATION ====================
|
|
375
|
+
|
|
376
|
+
validateStep(stepIdentifier) {
|
|
377
|
+
// Handle both step index (number) and step id (string)
|
|
378
|
+
let step;
|
|
379
|
+
let stepKey;
|
|
380
|
+
|
|
381
|
+
if (typeof stepIdentifier === "number") {
|
|
382
|
+
step = this.STEPS[stepIdentifier];
|
|
383
|
+
stepKey = `step${stepIdentifier}`;
|
|
384
|
+
} else {
|
|
385
|
+
step = this.STEPS.find((s) => s.id === stepIdentifier);
|
|
386
|
+
stepKey = stepIdentifier;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!step) return false;
|
|
390
|
+
|
|
391
|
+
let isValid = true;
|
|
392
|
+
const errors = {};
|
|
393
|
+
|
|
394
|
+
// Update validation state
|
|
395
|
+
this.setState({
|
|
396
|
+
validationState: {
|
|
397
|
+
[stepKey]: { isValid, errors },
|
|
398
|
+
},
|
|
399
|
+
uiState: { showErrors: !isValid },
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
return isValid;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
validateField(value, validators, fieldName) {
|
|
406
|
+
for (const validatorName of validators) {
|
|
407
|
+
const validator = this.validators[validatorName];
|
|
408
|
+
if (validator) {
|
|
409
|
+
const result = validator(value, fieldName);
|
|
410
|
+
if (!result.isValid) {
|
|
411
|
+
return result.error;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return "";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
validateCurrentStep() {
|
|
419
|
+
const stepId = this.STEPS[this.state.currentStep].id;
|
|
420
|
+
let isValid = true;
|
|
421
|
+
const errors = {};
|
|
422
|
+
|
|
423
|
+
if (stepId === "business-details") {
|
|
424
|
+
const data = this.state.formData.businessDetails;
|
|
425
|
+
const fields = [
|
|
426
|
+
{
|
|
427
|
+
name: "businessName",
|
|
428
|
+
validators: ["required"],
|
|
429
|
+
label: "Business Name",
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "doingBusinessAs",
|
|
433
|
+
validators: ["required"],
|
|
434
|
+
label: "Doing Business As (DBA)",
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "ein",
|
|
438
|
+
validators: ["required", "ein"],
|
|
439
|
+
label: "EIN",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "businessWebsite",
|
|
443
|
+
validators: ["required", "url"],
|
|
444
|
+
label: "Business Website",
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "businessPhoneNumber",
|
|
448
|
+
validators: ["required", "usPhone"],
|
|
449
|
+
label: "Business Phone",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "businessEmail",
|
|
453
|
+
validators: ["required", "email"],
|
|
454
|
+
label: "Business Email",
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "BusinessAddress1",
|
|
458
|
+
validators: ["required"],
|
|
459
|
+
label: "Street Address",
|
|
460
|
+
},
|
|
461
|
+
{ name: "businessCity", validators: ["required"], label: "City" },
|
|
462
|
+
{ name: "businessState", validators: ["required"], label: "State" },
|
|
463
|
+
{
|
|
464
|
+
name: "businessPostalCode",
|
|
465
|
+
validators: ["required", "postalCode"],
|
|
466
|
+
label: "ZIP Code",
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
fields.forEach((field) => {
|
|
471
|
+
const error = this.validateField(
|
|
472
|
+
data[field.name],
|
|
473
|
+
field.validators,
|
|
474
|
+
field.label
|
|
475
|
+
);
|
|
476
|
+
if (error) {
|
|
477
|
+
errors[field.name] = error;
|
|
478
|
+
isValid = false;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
} else if (stepId === "representatives") {
|
|
482
|
+
// Validate each representative if any field is filled
|
|
483
|
+
this.state.formData.representatives.forEach((rep, index) => {
|
|
484
|
+
const hasAnyValue = Object.values(rep).some(
|
|
485
|
+
(v) =>
|
|
486
|
+
(typeof v === "string" && v.trim()) ||
|
|
487
|
+
(typeof v === "object" &&
|
|
488
|
+
Object.values(v).some((av) => av && av.trim()))
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (hasAnyValue) {
|
|
492
|
+
const requiredFields = [
|
|
493
|
+
{
|
|
494
|
+
name: "representativeFirstName",
|
|
495
|
+
validators: ["required"],
|
|
496
|
+
label: "First Name",
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
name: "representativeLastName",
|
|
500
|
+
validators: ["required"],
|
|
501
|
+
label: "Last Name",
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "representativeJobTitle",
|
|
505
|
+
validators: ["required"],
|
|
506
|
+
label: "Job Title",
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "representativePhone",
|
|
510
|
+
validators: ["required", "usPhone"],
|
|
511
|
+
label: "Phone",
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "representativeEmail",
|
|
515
|
+
validators: ["required", "email"],
|
|
516
|
+
label: "Email",
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
name: "representativeDateOfBirth",
|
|
520
|
+
validators: ["required"],
|
|
521
|
+
label: "Date of Birth",
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "representativeAddress",
|
|
525
|
+
validators: ["required"],
|
|
526
|
+
label: "Address",
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: "representativeCity",
|
|
530
|
+
validators: ["required"],
|
|
531
|
+
label: "City",
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "representativeState",
|
|
535
|
+
validators: ["required"],
|
|
536
|
+
label: "State",
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "representativeZip",
|
|
540
|
+
validators: ["required", "postalCode"],
|
|
541
|
+
label: "ZIP Code",
|
|
542
|
+
},
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
requiredFields.forEach((field) => {
|
|
546
|
+
const error = this.validateField(
|
|
547
|
+
rep[field.name],
|
|
548
|
+
field.validators,
|
|
549
|
+
field.label
|
|
550
|
+
);
|
|
551
|
+
if (error) {
|
|
552
|
+
if (!errors[`rep${index}`]) errors[`rep${index}`] = {};
|
|
553
|
+
errors[`rep${index}`][field.name] = error;
|
|
554
|
+
isValid = false;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
} else if (stepId === "underwriting") {
|
|
560
|
+
// Validate that at least one document is uploaded
|
|
561
|
+
const data = this.state.formData.underwriting;
|
|
562
|
+
if (
|
|
563
|
+
!data.underwritingDocuments ||
|
|
564
|
+
data.underwritingDocuments.length === 0
|
|
565
|
+
) {
|
|
566
|
+
errors.underwritingDocuments = "At least one document is required";
|
|
567
|
+
isValid = false;
|
|
568
|
+
}
|
|
569
|
+
} else if (stepId === "bank-details") {
|
|
570
|
+
const data = this.state.formData.bankDetails;
|
|
571
|
+
const fields = [
|
|
572
|
+
{
|
|
573
|
+
name: "bankAccountHolderName",
|
|
574
|
+
validators: ["required"],
|
|
575
|
+
label: "Account Holder Name",
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "bankAccountType",
|
|
579
|
+
validators: ["required"],
|
|
580
|
+
label: "Account Type",
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "bankRoutingNumber",
|
|
584
|
+
validators: ["required", "bankRoutingNumber"],
|
|
585
|
+
label: "Routing Number",
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: "bankAccountNumber",
|
|
589
|
+
validators: ["required", "bankAccountNumber"],
|
|
590
|
+
label: "Account Number",
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
fields.forEach((field) => {
|
|
595
|
+
const error = this.validateField(
|
|
596
|
+
data[field.name],
|
|
597
|
+
field.validators,
|
|
598
|
+
field.label
|
|
599
|
+
);
|
|
600
|
+
if (error) {
|
|
601
|
+
errors[field.name] = error;
|
|
602
|
+
isValid = false;
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
this.setState({
|
|
608
|
+
validationState: {
|
|
609
|
+
[`step${this.state.currentStep}`]: { isValid, errors },
|
|
610
|
+
},
|
|
611
|
+
uiState: { showErrors: !isValid },
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
return isValid;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ==================== NAVIGATION ====================
|
|
618
|
+
|
|
619
|
+
async goToNextStep() {
|
|
620
|
+
// Validate current step
|
|
621
|
+
const isValid = this.validateCurrentStep();
|
|
622
|
+
|
|
623
|
+
console.log("🔍 Validation Result:", {
|
|
624
|
+
currentStep: this.state.currentStep,
|
|
625
|
+
stepId: this.STEPS[this.state.currentStep].id,
|
|
626
|
+
isValid,
|
|
627
|
+
errors:
|
|
628
|
+
this.state.validationState[`step${this.state.currentStep}`]?.errors,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
if (!isValid) {
|
|
632
|
+
console.warn("❌ Validation failed - cannot proceed to next step");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Mark step complete
|
|
637
|
+
const completedSteps = new Set(this.state.completedSteps);
|
|
638
|
+
completedSteps.add(this.state.currentStep);
|
|
639
|
+
|
|
640
|
+
// Progress to next step
|
|
641
|
+
if (this.state.currentStep < this.state.totalSteps - 1) {
|
|
642
|
+
console.log("✅ Moving to next step:", this.state.currentStep + 1);
|
|
643
|
+
this.setState({
|
|
644
|
+
currentStep: this.state.currentStep + 1,
|
|
645
|
+
completedSteps,
|
|
646
|
+
uiState: { showErrors: false },
|
|
647
|
+
});
|
|
648
|
+
} else {
|
|
649
|
+
console.log("✅ Final step - submitting form");
|
|
650
|
+
this.handleFormCompletion();
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
goToPreviousStep() {
|
|
655
|
+
if (this.state.currentStep > 0) {
|
|
656
|
+
this.setState({
|
|
657
|
+
currentStep: this.state.currentStep - 1,
|
|
658
|
+
uiState: { showErrors: false },
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
goToStep(stepIndex) {
|
|
664
|
+
if (
|
|
665
|
+
this.state.completedSteps.has(stepIndex) ||
|
|
666
|
+
stepIndex < this.state.currentStep
|
|
667
|
+
) {
|
|
668
|
+
this.setState({
|
|
669
|
+
currentStep: stepIndex,
|
|
670
|
+
uiState: { showErrors: false },
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
skipStep() {
|
|
676
|
+
if (this.STEPS[this.state.currentStep].canSkip) {
|
|
677
|
+
const completedSteps = new Set(this.state.completedSteps);
|
|
678
|
+
completedSteps.add(this.state.currentStep);
|
|
679
|
+
|
|
680
|
+
this.setState({
|
|
681
|
+
currentStep: this.state.currentStep + 1,
|
|
682
|
+
completedSteps,
|
|
683
|
+
uiState: { showErrors: false },
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// ==================== REPRESENTATIVES CRUD ====================
|
|
689
|
+
|
|
690
|
+
addRepresentative() {
|
|
691
|
+
const newRep = {
|
|
692
|
+
id: crypto.randomUUID(),
|
|
693
|
+
representativeFirstName: "",
|
|
694
|
+
representativeLastName: "",
|
|
695
|
+
representativeJobTitle: "",
|
|
696
|
+
representativePhone: "",
|
|
697
|
+
representativeEmail: "",
|
|
698
|
+
representativeDateOfBirth: "",
|
|
699
|
+
representativeAddress: "",
|
|
700
|
+
representativeCity: "",
|
|
701
|
+
representativeState: "",
|
|
702
|
+
representativeZip: "",
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
this.setState({
|
|
706
|
+
formData: {
|
|
707
|
+
representatives: [...this.state.formData.representatives, newRep],
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
removeRepresentative(index) {
|
|
713
|
+
const representatives = this.state.formData.representatives.filter(
|
|
714
|
+
(_, i) => i !== index
|
|
715
|
+
);
|
|
716
|
+
this.setState({
|
|
717
|
+
formData: { representatives },
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
updateRepresentative(index, field, value) {
|
|
722
|
+
const representatives = [...this.state.formData.representatives];
|
|
723
|
+
representatives[index] = {
|
|
724
|
+
...representatives[index],
|
|
725
|
+
[field]: value,
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
this.setState({
|
|
729
|
+
formData: { representatives },
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ==================== INITIAL DATA LOADING ====================
|
|
734
|
+
|
|
735
|
+
loadInitialData(data) {
|
|
736
|
+
const newFormData = { ...this.state.formData };
|
|
737
|
+
|
|
738
|
+
// Load business details
|
|
739
|
+
if (data.businessDetails) {
|
|
740
|
+
newFormData.businessDetails = {
|
|
741
|
+
...newFormData.businessDetails,
|
|
742
|
+
...data.businessDetails,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Load representatives
|
|
747
|
+
if (data.representatives && Array.isArray(data.representatives)) {
|
|
748
|
+
newFormData.representatives = data.representatives.map((rep) => ({
|
|
749
|
+
id: rep.id || crypto.randomUUID(),
|
|
750
|
+
representativeFirstName: rep.representativeFirstName || "",
|
|
751
|
+
representativeLastName: rep.representativeLastName || "",
|
|
752
|
+
representativeJobTitle: rep.representativeJobTitle || "",
|
|
753
|
+
representativePhone: rep.representativePhone || "",
|
|
754
|
+
representativeEmail: rep.representativeEmail || "",
|
|
755
|
+
representativeDateOfBirth: rep.representativeDateOfBirth || "",
|
|
756
|
+
representativeAddress: rep.representativeAddress || "",
|
|
757
|
+
representativeCity: rep.representativeCity || "",
|
|
758
|
+
representativeState: rep.representativeState || "",
|
|
759
|
+
representativeZip: rep.representativeZip || "",
|
|
760
|
+
}));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Load underwriting
|
|
764
|
+
if (data.underwriting) {
|
|
765
|
+
newFormData.underwriting = {
|
|
766
|
+
...newFormData.underwriting,
|
|
767
|
+
...data.underwriting,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Load bank details
|
|
772
|
+
if (data.bankDetails) {
|
|
773
|
+
newFormData.bankDetails = {
|
|
774
|
+
...newFormData.bankDetails,
|
|
775
|
+
...data.bankDetails,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Update state with loaded data and initial step if provided
|
|
780
|
+
const newState = {
|
|
781
|
+
formData: newFormData,
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// Set initial step if provided (0-indexed)
|
|
785
|
+
if (typeof data.initialStep === "number" && data.initialStep >= 0 && data.initialStep < this.state.totalSteps) {
|
|
786
|
+
newState.currentStep = data.initialStep;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
this.setState(newState);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Reset form to initial state or to onLoad values if provided
|
|
794
|
+
*/
|
|
795
|
+
resetForm() {
|
|
796
|
+
// Default empty form data
|
|
797
|
+
const defaultFormData = {
|
|
798
|
+
businessDetails: {
|
|
799
|
+
businessName: "",
|
|
800
|
+
doingBusinessAs: "",
|
|
801
|
+
ein: "",
|
|
802
|
+
businessWebsite: "",
|
|
803
|
+
businessPhoneNumber: "",
|
|
804
|
+
businessEmail: "",
|
|
805
|
+
BusinessAddress1: "",
|
|
806
|
+
businessCity: "",
|
|
807
|
+
businessState: "",
|
|
808
|
+
businessPostalCode: "",
|
|
809
|
+
},
|
|
810
|
+
representatives: [],
|
|
811
|
+
underwriting: {
|
|
812
|
+
underwritingDocuments: [],
|
|
813
|
+
},
|
|
814
|
+
bankDetails: {
|
|
815
|
+
bankAccountHolderName: "",
|
|
816
|
+
bankAccountType: "checking",
|
|
817
|
+
bankRoutingNumber: "",
|
|
818
|
+
bankAccountNumber: "",
|
|
819
|
+
},
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
// Default validation state
|
|
823
|
+
const defaultValidationState = {
|
|
824
|
+
step0: { isValid: false, errors: {} },
|
|
825
|
+
step1: { isValid: true, errors: {} },
|
|
826
|
+
step2: { isValid: false, errors: {} },
|
|
827
|
+
step3: { isValid: false, errors: {} },
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// Reset to defaults
|
|
831
|
+
this.state = {
|
|
832
|
+
...this.state,
|
|
833
|
+
currentStep: 0,
|
|
834
|
+
isSubmitted: false,
|
|
835
|
+
isFailed: false,
|
|
836
|
+
isSubmissionFailed: false,
|
|
837
|
+
formData: defaultFormData,
|
|
838
|
+
validationState: defaultValidationState,
|
|
839
|
+
completedSteps: new Set(),
|
|
840
|
+
uiState: {
|
|
841
|
+
isLoading: false,
|
|
842
|
+
showErrors: false,
|
|
843
|
+
errorMessage: null,
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
// If we have initial data from onLoad, re-apply it
|
|
848
|
+
if (this._initialData) {
|
|
849
|
+
this.loadInitialData(this._initialData);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// ==================== UTILITIES ====================
|
|
854
|
+
|
|
855
|
+
formatPhoneNumber(value) {
|
|
856
|
+
// Remove all non-digits
|
|
857
|
+
const cleaned = value.replace(/\D/g, "");
|
|
858
|
+
|
|
859
|
+
// Limit to 10 digits
|
|
860
|
+
const limited = cleaned.slice(0, 10);
|
|
861
|
+
|
|
862
|
+
// Format progressively as (XXX) XXX-XXXX
|
|
863
|
+
if (limited.length === 0) {
|
|
864
|
+
return "";
|
|
865
|
+
} else if (limited.length <= 3) {
|
|
866
|
+
return limited;
|
|
867
|
+
} else if (limited.length <= 6) {
|
|
868
|
+
return `(${limited.slice(0, 3)}) ${limited.slice(3)}`;
|
|
869
|
+
} else {
|
|
870
|
+
return `(${limited.slice(0, 3)}) ${limited.slice(3, 6)}-${limited.slice(
|
|
871
|
+
6
|
|
872
|
+
)}`;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
formatEIN(value) {
|
|
877
|
+
// Remove all non-digits
|
|
878
|
+
const cleaned = value.replace(/\D/g, "");
|
|
879
|
+
|
|
880
|
+
// Limit to 9 digits
|
|
881
|
+
const limited = cleaned.slice(0, 9);
|
|
882
|
+
|
|
883
|
+
// Format as XX-XXXXXXX
|
|
884
|
+
if (limited.length <= 2) {
|
|
885
|
+
return limited;
|
|
886
|
+
} else {
|
|
887
|
+
return `${limited.slice(0, 2)}-${limited.slice(2)}`;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
getFieldError(fieldName, repIndex = null) {
|
|
892
|
+
if (!this.state.uiState.showErrors) return "";
|
|
893
|
+
|
|
894
|
+
// For stepper steps
|
|
895
|
+
const errors =
|
|
896
|
+
this.state.validationState[`step${this.state.currentStep}`]?.errors || {};
|
|
897
|
+
|
|
898
|
+
if (repIndex !== null) {
|
|
899
|
+
return errors[`rep${repIndex}`]?.[fieldName] || "";
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return errors[fieldName] || "";
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// ==================== FORM COMPLETION ====================
|
|
906
|
+
|
|
907
|
+
async handleFormCompletion(shouldFail = false) {
|
|
908
|
+
console.log("OperatorOnboarding: handleFormCompletion STARTED");
|
|
909
|
+
|
|
910
|
+
const completedSteps = new Set(this.state.completedSteps);
|
|
911
|
+
completedSteps.add(this.state.currentStep);
|
|
912
|
+
|
|
913
|
+
// Prepare form data
|
|
914
|
+
const formData = {
|
|
915
|
+
businessDetails: this.state.formData.businessDetails,
|
|
916
|
+
representatives: this.state.formData.representatives,
|
|
917
|
+
underwriting: this.state.formData.underwriting,
|
|
918
|
+
bankDetails: this.state.formData.bankDetails,
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
// Call onSubmit callback if provided (before submission)
|
|
922
|
+
let processedData = formData;
|
|
923
|
+
if (this.onSubmit && typeof this.onSubmit === "function") {
|
|
924
|
+
try {
|
|
925
|
+
const result = await this.onSubmit(formData);
|
|
926
|
+
|
|
927
|
+
// If callback returns false, cancel submission
|
|
928
|
+
if (result === false) {
|
|
929
|
+
console.log("Form submission cancelled by onSubmit callback");
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// If callback returns modified data, use it
|
|
934
|
+
if (result && typeof result === "object") {
|
|
935
|
+
processedData = result;
|
|
936
|
+
}
|
|
937
|
+
} catch (error) {
|
|
938
|
+
console.error("Error in onSubmit callback:", error);
|
|
939
|
+
this.handleSubmissionFailure(formData);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Show loading state
|
|
945
|
+
this.setState({
|
|
946
|
+
completedSteps,
|
|
947
|
+
uiState: { isLoading: true },
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
console.log("Form Submission - Complete Data:", processedData);
|
|
951
|
+
|
|
952
|
+
// Check if API is available
|
|
953
|
+
if (!this.api) {
|
|
954
|
+
console.error("OperatorOnboarding: API not available for registration");
|
|
955
|
+
this.handleSubmissionFailure(processedData);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
try {
|
|
960
|
+
// Build FormData for API submission
|
|
961
|
+
const apiFormData = new FormData();
|
|
962
|
+
|
|
963
|
+
// Add business details
|
|
964
|
+
const businessDetails = processedData.businessDetails;
|
|
965
|
+
apiFormData.append("businessName", businessDetails.businessName || "");
|
|
966
|
+
apiFormData.append("doingBusinessAs", businessDetails.doingBusinessAs || "");
|
|
967
|
+
apiFormData.append("ein", businessDetails.ein || "");
|
|
968
|
+
apiFormData.append("businessWebsite", businessDetails.businessWebsite || "");
|
|
969
|
+
apiFormData.append("businessPhoneNumber", businessDetails.businessPhoneNumber || "");
|
|
970
|
+
apiFormData.append("businessEmail", businessDetails.businessEmail || "");
|
|
971
|
+
apiFormData.append("businessAddress1", businessDetails.BusinessAddress1 || "");
|
|
972
|
+
apiFormData.append("businessCity", businessDetails.businessCity || "");
|
|
973
|
+
apiFormData.append("businessState", businessDetails.businessState || "");
|
|
974
|
+
apiFormData.append("businessPostalCode", businessDetails.businessPostalCode || "");
|
|
975
|
+
|
|
976
|
+
// Add representatives as JSON string
|
|
977
|
+
if (processedData.representatives && processedData.representatives.length > 0) {
|
|
978
|
+
apiFormData.append("representatives", JSON.stringify(processedData.representatives));
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Add bank details
|
|
982
|
+
const bankDetails = processedData.bankDetails;
|
|
983
|
+
apiFormData.append("bankAccountHolderName", bankDetails.bankAccountHolderName || "");
|
|
984
|
+
apiFormData.append("bankAccountType", bankDetails.bankAccountType || "checking");
|
|
985
|
+
apiFormData.append("bankRoutingNumber", bankDetails.bankRoutingNumber || "");
|
|
986
|
+
apiFormData.append("bankAccountNumber", bankDetails.bankAccountNumber || "");
|
|
987
|
+
|
|
988
|
+
// Add underwriting documents (files)
|
|
989
|
+
const underwritingDocs = processedData.underwriting?.underwritingDocuments || [];
|
|
990
|
+
underwritingDocs.forEach((file) => {
|
|
991
|
+
if (file instanceof File) {
|
|
992
|
+
apiFormData.append("underwritingDocuments", file);
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
console.log("OperatorOnboarding: Calling registerOperator API");
|
|
997
|
+
console.log("OperatorOnboarding: FormData entries:");
|
|
998
|
+
for (const [key, value] of apiFormData.entries()) {
|
|
999
|
+
console.log(` ${key}:`, value instanceof File ? `File(${value.name})` : value);
|
|
1000
|
+
}
|
|
1001
|
+
const response = await this.api.registerOperator(apiFormData);
|
|
1002
|
+
console.log("OperatorOnboarding: registerOperator API response", response);
|
|
1003
|
+
|
|
1004
|
+
if (shouldFail || !response.success) {
|
|
1005
|
+
// Handle submission failure
|
|
1006
|
+
this.handleSubmissionFailure(processedData);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Update state to show success page
|
|
1011
|
+
this.setState({
|
|
1012
|
+
isSubmitted: true,
|
|
1013
|
+
uiState: { isLoading: false },
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// Emit custom event
|
|
1017
|
+
this.dispatchEvent(
|
|
1018
|
+
new CustomEvent("formComplete", {
|
|
1019
|
+
detail: { ...processedData, apiResponse: response },
|
|
1020
|
+
bubbles: true,
|
|
1021
|
+
composed: true,
|
|
1022
|
+
})
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
// Call onSuccess callback if provided
|
|
1026
|
+
console.log("OperatorOnboarding: Checking onSuccess callback", {
|
|
1027
|
+
hasCallback: !!this.onSuccess,
|
|
1028
|
+
callbackType: typeof this.onSuccess,
|
|
1029
|
+
});
|
|
1030
|
+
if (this.onSuccess && typeof this.onSuccess === "function") {
|
|
1031
|
+
console.log("OperatorOnboarding: Calling onSuccess callback");
|
|
1032
|
+
this.onSuccess({ ...processedData, apiResponse: response });
|
|
1033
|
+
}
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
console.error("OperatorOnboarding: registerOperator API error", error);
|
|
1036
|
+
this.handleSubmissionFailure(processedData);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
handleSubmissionFailure(formData) {
|
|
1041
|
+
const errorData = {
|
|
1042
|
+
formData,
|
|
1043
|
+
message: "Form submission failed. Please try again.",
|
|
1044
|
+
timestamp: new Date().toISOString(),
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
// Log error to console
|
|
1048
|
+
console.error("Submission Failed:", errorData);
|
|
1049
|
+
|
|
1050
|
+
// Update state to show failure page
|
|
1051
|
+
this.setState({
|
|
1052
|
+
isSubmissionFailed: true,
|
|
1053
|
+
uiState: {
|
|
1054
|
+
...this.state.uiState,
|
|
1055
|
+
isLoading: false,
|
|
1056
|
+
errorMessage: errorData.message,
|
|
1057
|
+
showErrors: false,
|
|
1058
|
+
},
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// Emit custom error event
|
|
1062
|
+
this.dispatchEvent(
|
|
1063
|
+
new CustomEvent("submissionFailed", {
|
|
1064
|
+
detail: errorData,
|
|
1065
|
+
bubbles: true,
|
|
1066
|
+
composed: true,
|
|
1067
|
+
})
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
// Call onError callback if provided
|
|
1071
|
+
if (this.onError && typeof this.onError === "function") {
|
|
1072
|
+
this.onError(errorData);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Handle success confirmation button click
|
|
1078
|
+
* Dispatches event and calls onConfirm callback, then closes modal
|
|
1079
|
+
*/
|
|
1080
|
+
handleSuccessConfirm() {
|
|
1081
|
+
const confirmData = {
|
|
1082
|
+
formData: this.state.formData,
|
|
1083
|
+
timestamp: new Date().toISOString(),
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// Dispatch custom event
|
|
1087
|
+
this.dispatchEvent(
|
|
1088
|
+
new CustomEvent("onboardingConfirmed", {
|
|
1089
|
+
detail: confirmData,
|
|
1090
|
+
bubbles: true,
|
|
1091
|
+
composed: true,
|
|
1092
|
+
})
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
// Call onConfirm callback if provided
|
|
1096
|
+
if (this.onConfirm && typeof this.onConfirm === "function") {
|
|
1097
|
+
this.onConfirm(confirmData);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Close the modal
|
|
1101
|
+
this.closeModal();
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// ==================== MODAL METHODS ====================
|
|
1105
|
+
|
|
1106
|
+
openModal() {
|
|
1107
|
+
this.setState({ isModalOpen: true });
|
|
1108
|
+
|
|
1109
|
+
// Apply animation classes after modal is rendered
|
|
1110
|
+
requestAnimationFrame(() => {
|
|
1111
|
+
const modal = this.shadowRoot.querySelector(".modal-overlay");
|
|
1112
|
+
if (modal) {
|
|
1113
|
+
modal.classList.add("show", "animating-in");
|
|
1114
|
+
setTimeout(() => {
|
|
1115
|
+
modal.classList.remove("animating-in");
|
|
1116
|
+
}, 200);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
this.dispatchEvent(
|
|
1121
|
+
new CustomEvent("onboarding-modal-open", {
|
|
1122
|
+
bubbles: true,
|
|
1123
|
+
composed: true,
|
|
1124
|
+
})
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
closeModal() {
|
|
1129
|
+
// Clean up escape key handler
|
|
1130
|
+
if (this._escapeHandler) {
|
|
1131
|
+
document.removeEventListener("keydown", this._escapeHandler);
|
|
1132
|
+
this._escapeHandler = null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Get the modal overlay for animation
|
|
1136
|
+
const overlay = this.shadowRoot.querySelector(".modal-overlay");
|
|
1137
|
+
|
|
1138
|
+
if (overlay) {
|
|
1139
|
+
// Add animating-out class to trigger exit animation
|
|
1140
|
+
overlay.classList.add("animating-out");
|
|
1141
|
+
overlay.classList.remove("show");
|
|
1142
|
+
|
|
1143
|
+
// Wait for animation to complete before removing from DOM
|
|
1144
|
+
setTimeout(() => {
|
|
1145
|
+
// Reset form to initial state (or onLoad values if provided)
|
|
1146
|
+
this.resetForm();
|
|
1147
|
+
|
|
1148
|
+
// Update state to remove modal from DOM
|
|
1149
|
+
this.setState({ isModalOpen: false });
|
|
1150
|
+
|
|
1151
|
+
// Restore body scroll
|
|
1152
|
+
document.body.style.overflow = "";
|
|
1153
|
+
|
|
1154
|
+
// Dispatch close event
|
|
1155
|
+
this.dispatchEvent(
|
|
1156
|
+
new CustomEvent("onboarding-modal-close", {
|
|
1157
|
+
bubbles: true,
|
|
1158
|
+
composed: true,
|
|
1159
|
+
})
|
|
1160
|
+
);
|
|
1161
|
+
}, 150); // Match the fadeOut animation duration (150ms)
|
|
1162
|
+
} else {
|
|
1163
|
+
// Reset form to initial state (or onLoad values if provided)
|
|
1164
|
+
this.resetForm();
|
|
1165
|
+
|
|
1166
|
+
// Fallback if overlay not found - close immediately
|
|
1167
|
+
this.setState({ isModalOpen: false });
|
|
1168
|
+
document.body.style.overflow = "";
|
|
1169
|
+
this.dispatchEvent(
|
|
1170
|
+
new CustomEvent("onboarding-modal-close", {
|
|
1171
|
+
bubbles: true,
|
|
1172
|
+
composed: true,
|
|
1173
|
+
})
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// ==================== RENDERING ====================
|
|
1179
|
+
|
|
1180
|
+
render() {
|
|
1181
|
+
// Always render the button + modal structure
|
|
1182
|
+
this.shadowRoot.innerHTML = `
|
|
1183
|
+
${this.renderStyles()}
|
|
1184
|
+
${this.renderButton()}
|
|
1185
|
+
${this.state.isModalOpen ? this.renderModal() : ""}
|
|
1186
|
+
`;
|
|
1187
|
+
this.attachEventListeners();
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
renderButton() {
|
|
1191
|
+
return `
|
|
1192
|
+
<button class="onboarding-trigger-btn" type="button">
|
|
1193
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1194
|
+
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
1195
|
+
<circle cx="8.5" cy="7" r="4"></circle>
|
|
1196
|
+
<line x1="20" y1="8" x2="20" y2="14"></line>
|
|
1197
|
+
<line x1="23" y1="11" x2="17" y2="11"></line>
|
|
1198
|
+
</svg>
|
|
1199
|
+
Start Onboarding
|
|
1200
|
+
</button>
|
|
1201
|
+
`;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
renderModal() {
|
|
1205
|
+
// Add 'show' class if modal was already open to prevent flash during re-renders
|
|
1206
|
+
const showClass = this._skipModalAnimation ? "show" : "";
|
|
1207
|
+
// Hide close button during submission and on success screen (require confirm button)
|
|
1208
|
+
const hideCloseButton = this.state.uiState.isLoading || this.state.isSubmitted;
|
|
1209
|
+
|
|
1210
|
+
return `
|
|
1211
|
+
<div class="modal-overlay ${showClass}">
|
|
1212
|
+
<div class="modal-container">
|
|
1213
|
+
${!hideCloseButton ? `
|
|
1214
|
+
<button class="modal-close-btn" type="button" aria-label="Close modal">
|
|
1215
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1216
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
1217
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
1218
|
+
</svg>
|
|
1219
|
+
</button>
|
|
1220
|
+
` : ''}
|
|
1221
|
+
${this.renderModalContent()}
|
|
1222
|
+
</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
`;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
renderModalContent() {
|
|
1228
|
+
// Show submission failure page
|
|
1229
|
+
if (this.state.isSubmissionFailed) {
|
|
1230
|
+
return `
|
|
1231
|
+
<div class="modal-body-full">
|
|
1232
|
+
${this.renderSubmissionFailurePage()}
|
|
1233
|
+
</div>
|
|
1234
|
+
`;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Show success page if form is submitted
|
|
1238
|
+
if (this.state.isSubmitted) {
|
|
1239
|
+
return `
|
|
1240
|
+
<div class="modal-body-full">
|
|
1241
|
+
${this.renderSuccessPage()}
|
|
1242
|
+
</div>
|
|
1243
|
+
`;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Show loading during submission
|
|
1247
|
+
if (this.state.uiState.isLoading) {
|
|
1248
|
+
return `
|
|
1249
|
+
<div class="modal-body-full">
|
|
1250
|
+
<div class="loading-content">
|
|
1251
|
+
<h2>Submitting Your Application...</h2>
|
|
1252
|
+
<p style="color: var(--gray-medium); margin-bottom: var(--spacing-lg);">
|
|
1253
|
+
Please wait while we process your information.
|
|
1254
|
+
</p>
|
|
1255
|
+
<div class="loading-spinner"></div>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Show main stepper form with fixed header/footer layout
|
|
1262
|
+
return `
|
|
1263
|
+
<div class="modal-layout">
|
|
1264
|
+
<div class="modal-header">
|
|
1265
|
+
<div class="form-logo">
|
|
1266
|
+
<img src="https://bisonpaywell.com/lovable-uploads/28831244-e8b3-4e7b-8dbb-c016f9f9d54f.png" alt="Logo" />
|
|
1267
|
+
</div>
|
|
1268
|
+
${this.renderStepperHeader()}
|
|
1269
|
+
</div>
|
|
1270
|
+
<div class="modal-body">
|
|
1271
|
+
${this.renderFormContent()}
|
|
1272
|
+
</div>
|
|
1273
|
+
<div class="modal-footer">
|
|
1274
|
+
${this.renderNavigationFooter()}
|
|
1275
|
+
</div>
|
|
1276
|
+
</div>
|
|
1277
|
+
`;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
renderFormContent() {
|
|
1281
|
+
const stepId = this.STEPS[this.state.currentStep].id;
|
|
1282
|
+
|
|
1283
|
+
switch (stepId) {
|
|
1284
|
+
case "business-details":
|
|
1285
|
+
return this.renderBusinessDetailsForm();
|
|
1286
|
+
case "representatives":
|
|
1287
|
+
return this.renderRepresentativesForm();
|
|
1288
|
+
case "bank-details":
|
|
1289
|
+
return this.renderBankDetailsForm();
|
|
1290
|
+
case "underwriting":
|
|
1291
|
+
return this.renderUnderwritingForm();
|
|
1292
|
+
default:
|
|
1293
|
+
return "";
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
renderStyles() {
|
|
1298
|
+
return `
|
|
1299
|
+
<style>
|
|
1300
|
+
* {
|
|
1301
|
+
box-sizing: border-box;
|
|
1302
|
+
margin: 0;
|
|
1303
|
+
padding: 0;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
:host {
|
|
1307
|
+
--primary-color: var(--color-primary, #4c7b63);
|
|
1308
|
+
--success-color: var(--color-success, #22c55e);
|
|
1309
|
+
--error-color: var(--color-error, #dd524b);
|
|
1310
|
+
--border-color: var(--color-border, #e8e8e8);
|
|
1311
|
+
--gray-light: var(--color-gray-50, #f9fafb);
|
|
1312
|
+
--gray-medium: var(--color-gray-500, #6b7280);
|
|
1313
|
+
--border-radius: var(--radius-xl, 0.75rem);
|
|
1314
|
+
--border-radius-sm: var(--radius-lg, 0.5rem);
|
|
1315
|
+
--border-radius-lg: var(--radius-2xl, 1rem);
|
|
1316
|
+
--spacing-sm: var(--spacing-sm, 0.5rem);
|
|
1317
|
+
--spacing-md: var(--spacing-md, 1rem);
|
|
1318
|
+
--spacing-lg: var(--spacing-lg, 1.5rem);
|
|
1319
|
+
font-family: var(--font-sans, 'Inter', system-ui, sans-serif);
|
|
1320
|
+
color: var(--color-secondary, #5f6e78);
|
|
1321
|
+
display: inline-block;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* Trigger Button */
|
|
1325
|
+
.onboarding-trigger-btn {
|
|
1326
|
+
display: inline-flex;
|
|
1327
|
+
align-items: center;
|
|
1328
|
+
gap: 8px;
|
|
1329
|
+
padding: 12px 24px;
|
|
1330
|
+
background: var(--primary-color);
|
|
1331
|
+
color: var(--color-white, #fff);
|
|
1332
|
+
border: none;
|
|
1333
|
+
border-radius: var(--border-radius);
|
|
1334
|
+
font-size: var(--text-sm, 0.875rem);
|
|
1335
|
+
font-weight: var(--font-weight-medium, 500);
|
|
1336
|
+
cursor: pointer;
|
|
1337
|
+
transition: all var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
1338
|
+
height: 40px;
|
|
1339
|
+
box-sizing: border-box;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
.onboarding-trigger-btn:hover {
|
|
1343
|
+
background: var(--color-primary-hover, #436c57);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.onboarding-trigger-btn:active {
|
|
1347
|
+
transform: translateY(0);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.onboarding-trigger-btn svg {
|
|
1351
|
+
flex-shrink: 0;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/* Modal Overlay */
|
|
1355
|
+
.modal-overlay {
|
|
1356
|
+
position: fixed;
|
|
1357
|
+
top: 0;
|
|
1358
|
+
left: 0;
|
|
1359
|
+
right: 0;
|
|
1360
|
+
bottom: 0;
|
|
1361
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1362
|
+
display: flex;
|
|
1363
|
+
align-items: center;
|
|
1364
|
+
justify-content: center;
|
|
1365
|
+
z-index: 10000;
|
|
1366
|
+
padding: 20px;
|
|
1367
|
+
opacity: 0;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.modal-overlay.show {
|
|
1371
|
+
opacity: 1;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.modal-overlay.animating-in {
|
|
1375
|
+
animation: fadeIn 200ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.modal-overlay.animating-out {
|
|
1379
|
+
animation: fadeOut 150ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
@keyframes fadeIn {
|
|
1383
|
+
from { opacity: 0; }
|
|
1384
|
+
to { opacity: 1; }
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
@keyframes fadeOut {
|
|
1388
|
+
from { opacity: 1; }
|
|
1389
|
+
to { opacity: 0; }
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/* Modal Container */
|
|
1393
|
+
.modal-container {
|
|
1394
|
+
background: var(--color-white, #fff);
|
|
1395
|
+
border-radius: var(--border-radius-lg);
|
|
1396
|
+
max-width: 900px;
|
|
1397
|
+
width: 100%;
|
|
1398
|
+
max-height: 90vh;
|
|
1399
|
+
overflow: hidden;
|
|
1400
|
+
position: relative;
|
|
1401
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
1402
|
+
display: flex;
|
|
1403
|
+
flex-direction: column;
|
|
1404
|
+
opacity: 0;
|
|
1405
|
+
transform: scale(0.95) translateY(-10px);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
.modal-overlay.show .modal-container {
|
|
1409
|
+
opacity: 1;
|
|
1410
|
+
transform: scale(1) translateY(0);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
.modal-overlay.animating-in .modal-container {
|
|
1414
|
+
animation: slideInScale 200ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.modal-overlay.animating-out .modal-container {
|
|
1418
|
+
animation: slideOutScale 150ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
@keyframes slideInScale {
|
|
1422
|
+
from {
|
|
1423
|
+
opacity: 0;
|
|
1424
|
+
transform: scale(0.95) translateY(-10px);
|
|
1425
|
+
}
|
|
1426
|
+
to {
|
|
1427
|
+
opacity: 1;
|
|
1428
|
+
transform: scale(1) translateY(0);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
@keyframes slideOutScale {
|
|
1433
|
+
from {
|
|
1434
|
+
opacity: 1;
|
|
1435
|
+
transform: scale(1) translateY(0);
|
|
1436
|
+
}
|
|
1437
|
+
to {
|
|
1438
|
+
opacity: 0;
|
|
1439
|
+
transform: scale(0.95) translateY(-10px);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/* Modal Layout - Fixed Header/Footer with Scrollable Body */
|
|
1444
|
+
.modal-layout {
|
|
1445
|
+
display: flex;
|
|
1446
|
+
flex-direction: column;
|
|
1447
|
+
height: 100%;
|
|
1448
|
+
max-height: 90vh;
|
|
1449
|
+
overflow: hidden;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
.modal-header {
|
|
1453
|
+
flex-shrink: 0;
|
|
1454
|
+
padding: var(--spacing-lg);
|
|
1455
|
+
border-bottom: 1px solid var(--border-color);
|
|
1456
|
+
background: var(--color-white, #fff);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
.modal-body {
|
|
1460
|
+
flex: 1;
|
|
1461
|
+
overflow-y: auto;
|
|
1462
|
+
padding: var(--spacing-lg);
|
|
1463
|
+
min-height: 0;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.modal-footer {
|
|
1467
|
+
flex-shrink: 0;
|
|
1468
|
+
padding: var(--spacing-lg);
|
|
1469
|
+
border-top: 1px solid var(--border-color);
|
|
1470
|
+
background: var(--color-white, #fff);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
.modal-body-full {
|
|
1474
|
+
padding: var(--spacing-lg);
|
|
1475
|
+
overflow-y: auto;
|
|
1476
|
+
max-height: calc(90vh - 60px);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
.loading-content {
|
|
1480
|
+
text-align: center;
|
|
1481
|
+
padding: calc(var(--spacing-lg) * 2);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
/* Modal Close Button */
|
|
1486
|
+
.modal-close-btn {
|
|
1487
|
+
position: absolute;
|
|
1488
|
+
top: 16px;
|
|
1489
|
+
right: 16px;
|
|
1490
|
+
background: none;
|
|
1491
|
+
border: none;
|
|
1492
|
+
cursor: pointer;
|
|
1493
|
+
padding: 8px;
|
|
1494
|
+
border-radius: 50%;
|
|
1495
|
+
color: var(--gray-medium);
|
|
1496
|
+
transition: all 0.2s ease;
|
|
1497
|
+
z-index: 10;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.modal-close-btn:hover {
|
|
1501
|
+
background: var(--gray-light);
|
|
1502
|
+
color: var(--color-headline, #0f2a39);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
.onboarding-container {
|
|
1506
|
+
max-width: 900px;
|
|
1507
|
+
margin: 0 auto;
|
|
1508
|
+
padding: var(--spacing-lg);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/* Logo inside modal header */
|
|
1512
|
+
.form-logo {
|
|
1513
|
+
text-align: center;
|
|
1514
|
+
margin-bottom: var(--spacing-md);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.form-logo img {
|
|
1518
|
+
max-width: 140px;
|
|
1519
|
+
height: auto;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/* Stepper Header */
|
|
1523
|
+
.stepper-header {
|
|
1524
|
+
display: flex;
|
|
1525
|
+
justify-content: space-between;
|
|
1526
|
+
position: relative;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
.stepper-header::before {
|
|
1530
|
+
content: '';
|
|
1531
|
+
position: absolute;
|
|
1532
|
+
top: 20px;
|
|
1533
|
+
left: 0;
|
|
1534
|
+
right: 0;
|
|
1535
|
+
height: 2px;
|
|
1536
|
+
background: var(--border-color);
|
|
1537
|
+
z-index: 0;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
.step-indicator {
|
|
1541
|
+
flex: 1;
|
|
1542
|
+
text-align: center;
|
|
1543
|
+
position: relative;
|
|
1544
|
+
z-index: 1;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
.step-indicator.clickable {
|
|
1548
|
+
cursor: pointer;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
.step-circle {
|
|
1552
|
+
width: 40px;
|
|
1553
|
+
height: 40px;
|
|
1554
|
+
border-radius: 50%;
|
|
1555
|
+
background: var(--color-white, #fff);
|
|
1556
|
+
border: 2px solid var(--border-color);
|
|
1557
|
+
display: flex;
|
|
1558
|
+
align-items: center;
|
|
1559
|
+
justify-content: center;
|
|
1560
|
+
margin: 0 auto var(--spacing-sm);
|
|
1561
|
+
font-weight: bold;
|
|
1562
|
+
color: var(--gray-medium);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.step-indicator.active .step-circle {
|
|
1566
|
+
border-color: var(--primary-color);
|
|
1567
|
+
color: var(--primary-color);
|
|
1568
|
+
background: var(--primary-color);
|
|
1569
|
+
color: var(--color-white, #fff);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.step-indicator.complete .step-circle {
|
|
1573
|
+
border-color: var(--success-color);
|
|
1574
|
+
background: var(--success-color);
|
|
1575
|
+
color: var(--color-white, #fff);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
.step-label {
|
|
1579
|
+
font-size: 12px;
|
|
1580
|
+
color: var(--gray-medium);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.step-indicator.active .step-label {
|
|
1584
|
+
color: var(--primary-color);
|
|
1585
|
+
font-weight: 600;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/* Step Content */
|
|
1589
|
+
.step-content {
|
|
1590
|
+
background: var(--color-white, #fff);
|
|
1591
|
+
padding: calc(var(--spacing-lg) * 1.5);
|
|
1592
|
+
border: 1px solid var(--border-color);
|
|
1593
|
+
border-radius: var(--border-radius-lg);
|
|
1594
|
+
margin-bottom: var(--spacing-lg);
|
|
1595
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
.step-content h2 {
|
|
1599
|
+
margin-bottom: var(--spacing-sm);
|
|
1600
|
+
color: var(--color-headline, #0f2a39);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
.step-content > p {
|
|
1604
|
+
color: var(--gray-medium);
|
|
1605
|
+
margin-bottom: var(--spacing-lg);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/* Form Section - for scrollable content in modal */
|
|
1609
|
+
.form-section {
|
|
1610
|
+
background: var(--color-white, #fff);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
.form-section h2 {
|
|
1614
|
+
margin-bottom: var(--spacing-sm);
|
|
1615
|
+
color: var(--color-headline, #0f2a39);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
.form-section > p {
|
|
1619
|
+
color: var(--gray-medium);
|
|
1620
|
+
margin-bottom: var(--spacing-lg);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/* Form Fields */
|
|
1624
|
+
.form-field {
|
|
1625
|
+
margin-bottom: var(--spacing-md);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.form-field label {
|
|
1629
|
+
display: block;
|
|
1630
|
+
margin-bottom: var(--spacing-sm);
|
|
1631
|
+
font-weight: 500;
|
|
1632
|
+
color: var(--color-headline, #0f2a39);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/* Red asterisk for required fields */
|
|
1636
|
+
.required-asterisk {
|
|
1637
|
+
color: var(--error-color);
|
|
1638
|
+
font-weight: bold;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
.form-field input,
|
|
1642
|
+
.form-field select {
|
|
1643
|
+
width: 100%;
|
|
1644
|
+
padding: 10px;
|
|
1645
|
+
border: 1px solid var(--border-color);
|
|
1646
|
+
border-radius: var(--border-radius-sm);
|
|
1647
|
+
font-size: 14px;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
.form-field input:focus,
|
|
1651
|
+
.form-field select:focus {
|
|
1652
|
+
outline: none;
|
|
1653
|
+
border-color: var(--primary-color);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.form-field input[readonly] {
|
|
1657
|
+
background: var(--gray-light);
|
|
1658
|
+
cursor: not-allowed;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
.form-field.has-error input,
|
|
1662
|
+
.form-field.has-error select {
|
|
1663
|
+
border-color: var(--error-color);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
.error-message {
|
|
1667
|
+
display: block;
|
|
1668
|
+
color: var(--error-color);
|
|
1669
|
+
font-size: 12px;
|
|
1670
|
+
margin-top: var(--spacing-sm);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.form-grid {
|
|
1674
|
+
display: grid;
|
|
1675
|
+
grid-template-columns: 1fr 1fr;
|
|
1676
|
+
gap: var(--spacing-md);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.form-grid .full-width {
|
|
1680
|
+
grid-column: 1 / -1;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/* Radio Buttons */
|
|
1684
|
+
.radio-group {
|
|
1685
|
+
display: flex;
|
|
1686
|
+
gap: var(--spacing-md);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
.radio-option {
|
|
1690
|
+
display: flex;
|
|
1691
|
+
align-items: center;
|
|
1692
|
+
gap: var(--spacing-sm);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.radio-option input[type="radio"] {
|
|
1696
|
+
width: auto;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/* Representatives */
|
|
1700
|
+
.representative-card {
|
|
1701
|
+
border: 1px solid var(--border-color);
|
|
1702
|
+
border-radius: var(--border-radius-lg);
|
|
1703
|
+
padding: var(--spacing-md);
|
|
1704
|
+
margin-bottom: var(--spacing-md);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
.card-header {
|
|
1708
|
+
display: flex;
|
|
1709
|
+
justify-content: space-between;
|
|
1710
|
+
align-items: center;
|
|
1711
|
+
margin-bottom: var(--spacing-md);
|
|
1712
|
+
padding-bottom: var(--spacing-sm);
|
|
1713
|
+
border-bottom: 1px solid var(--border-color);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.card-header h3 {
|
|
1717
|
+
font-size: 16px;
|
|
1718
|
+
color: var(--color-headline, #0f2a39);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
.remove-btn {
|
|
1722
|
+
background: none;
|
|
1723
|
+
border: none;
|
|
1724
|
+
color: var(--error-color);
|
|
1725
|
+
cursor: pointer;
|
|
1726
|
+
font-size: 14px;
|
|
1727
|
+
padding: var(--spacing-sm);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.remove-btn:hover {
|
|
1731
|
+
text-decoration: underline;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
.add-representative-btn {
|
|
1735
|
+
width: 100%;
|
|
1736
|
+
padding: 12px;
|
|
1737
|
+
background: var(--color-white, #fff);
|
|
1738
|
+
border: 2px dashed var(--border-color);
|
|
1739
|
+
border-radius: var(--border-radius);
|
|
1740
|
+
color: var(--primary-color);
|
|
1741
|
+
cursor: pointer;
|
|
1742
|
+
font-size: 14px;
|
|
1743
|
+
font-weight: 500;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.add-representative-btn:hover {
|
|
1747
|
+
border-color: var(--primary-color);
|
|
1748
|
+
background: var(--gray-light);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/* Navigation Footer */
|
|
1752
|
+
.navigation-footer {
|
|
1753
|
+
display: flex;
|
|
1754
|
+
justify-content: space-between;
|
|
1755
|
+
gap: var(--spacing-md);
|
|
1756
|
+
margin: 0;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.navigation-footer button {
|
|
1760
|
+
padding: 12px 24px;
|
|
1761
|
+
border: none;
|
|
1762
|
+
border-radius: var(--border-radius);
|
|
1763
|
+
font-size: 14px;
|
|
1764
|
+
font-weight: 500;
|
|
1765
|
+
cursor: pointer;
|
|
1766
|
+
height: 40px;
|
|
1767
|
+
box-sizing: border-box;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
.btn-back {
|
|
1771
|
+
background: var(--color-white, #fff);
|
|
1772
|
+
border: 1px solid var(--border-color);
|
|
1773
|
+
color: var(--color-headline, #0f2a39);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
.btn-back:hover {
|
|
1777
|
+
background: var(--gray-light);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
.btn-skip {
|
|
1781
|
+
background: var(--color-white, #fff);
|
|
1782
|
+
border: 1px solid var(--border-color);
|
|
1783
|
+
color: var(--gray-medium);
|
|
1784
|
+
margin-left: auto;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
.btn-skip:hover {
|
|
1788
|
+
background: var(--gray-light);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
.btn-next {
|
|
1792
|
+
background: var(--primary-color);
|
|
1793
|
+
color: var(--color-white, #fff);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
.btn-next:hover {
|
|
1797
|
+
background: var(--color-primary-hover, #436c57);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
/* Success Confirmation Button */
|
|
1801
|
+
.btn-confirm-success {
|
|
1802
|
+
margin-top: var(--spacing-lg);
|
|
1803
|
+
padding: 14px 32px;
|
|
1804
|
+
background: var(--primary-color);
|
|
1805
|
+
color: var(--color-white, #fff);
|
|
1806
|
+
border: none;
|
|
1807
|
+
border-radius: var(--border-radius);
|
|
1808
|
+
font-size: 16px;
|
|
1809
|
+
font-weight: var(--font-weight-medium, 500);
|
|
1810
|
+
cursor: pointer;
|
|
1811
|
+
transition: all var(--duration-normal, 200ms) var(--ease-in-out, cubic-bezier(0.4, 0, 0.2, 1));
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
.btn-confirm-success:hover {
|
|
1815
|
+
background: var(--color-primary-hover, #436c57);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
.btn-confirm-success:active {
|
|
1819
|
+
transform: translateY(0);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* Loading & Messages */
|
|
1823
|
+
.loading-spinner {
|
|
1824
|
+
border: 3px solid var(--gray-light);
|
|
1825
|
+
border-top: 3px solid var(--primary-color);
|
|
1826
|
+
border-radius: 50%;
|
|
1827
|
+
width: 40px;
|
|
1828
|
+
height: 40px;
|
|
1829
|
+
animation: spin 1s linear infinite;
|
|
1830
|
+
margin: var(--spacing-md) auto;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
@keyframes spin {
|
|
1834
|
+
0% { transform: rotate(0deg); }
|
|
1835
|
+
100% { transform: rotate(360deg); }
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
.success-message {
|
|
1839
|
+
background: var(--color-success-light, #d3f3df);
|
|
1840
|
+
color: var(--color-success-dark, #136c34);
|
|
1841
|
+
padding: var(--spacing-md);
|
|
1842
|
+
border-radius: var(--border-radius);
|
|
1843
|
+
margin-top: var(--spacing-md);
|
|
1844
|
+
border: 1px solid var(--color-success-light, #d3f3df);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
.empty-state {
|
|
1848
|
+
text-align: center;
|
|
1849
|
+
padding: var(--spacing-lg);
|
|
1850
|
+
color: var(--gray-medium);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/* Drag and Drop Styles */
|
|
1854
|
+
.drag-drop-area {
|
|
1855
|
+
border: 2px dashed var(--border-color);
|
|
1856
|
+
border-radius: var(--border-radius-lg);
|
|
1857
|
+
padding: calc(var(--spacing-lg) * 2);
|
|
1858
|
+
text-align: center;
|
|
1859
|
+
background: var(--gray-light);
|
|
1860
|
+
transition: all 0.3s ease;
|
|
1861
|
+
cursor: pointer;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
.drag-drop-area:hover {
|
|
1865
|
+
border-color: var(--primary-color);
|
|
1866
|
+
background: var(--color-primary-light, #e8f0eb);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
.drag-drop-area.drag-over {
|
|
1870
|
+
border-color: var(--primary-color);
|
|
1871
|
+
background: var(--color-primary-light, #e8f0eb);
|
|
1872
|
+
border-style: solid;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
.drag-drop-content {
|
|
1876
|
+
pointer-events: none;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
.btn-browse:hover {
|
|
1880
|
+
background: var(--color-primary-hover, #436c57);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
.uploaded-files {
|
|
1884
|
+
margin-top: var(--spacing-md);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.file-item:hover {
|
|
1888
|
+
background: var(--color-gray-100, #f3f4f6);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
.btn-remove-file:hover {
|
|
1892
|
+
text-decoration: underline;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
/* Error/Failure Styles */
|
|
1896
|
+
.error-container {
|
|
1897
|
+
text-align: center;
|
|
1898
|
+
padding: var(--spacing-lg) 0;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
.error-icon {
|
|
1902
|
+
width: 120px;
|
|
1903
|
+
height: 120px;
|
|
1904
|
+
margin: 0 auto var(--spacing-lg);
|
|
1905
|
+
background: linear-gradient(135deg, var(--error-color) 0%, var(--color-error-dark, #903531) 100%);
|
|
1906
|
+
border-radius: 50%;
|
|
1907
|
+
display: flex;
|
|
1908
|
+
align-items: center;
|
|
1909
|
+
justify-content: center;
|
|
1910
|
+
animation: errorPulse 0.6s ease-out;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
@keyframes errorPulse {
|
|
1914
|
+
0% {
|
|
1915
|
+
transform: scale(0);
|
|
1916
|
+
opacity: 0;
|
|
1917
|
+
}
|
|
1918
|
+
50% {
|
|
1919
|
+
transform: scale(1.1);
|
|
1920
|
+
}
|
|
1921
|
+
100% {
|
|
1922
|
+
transform: scale(1);
|
|
1923
|
+
opacity: 1;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
.error-icon svg {
|
|
1928
|
+
width: 70px;
|
|
1929
|
+
height: 70px;
|
|
1930
|
+
stroke: var(--color-white, #fff);
|
|
1931
|
+
stroke-width: 3;
|
|
1932
|
+
stroke-linecap: round;
|
|
1933
|
+
stroke-linejoin: round;
|
|
1934
|
+
fill: none;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.error-container h2 {
|
|
1938
|
+
color: var(--error-color);
|
|
1939
|
+
margin-bottom: var(--spacing-md);
|
|
1940
|
+
font-size: 32px;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
.error-container p {
|
|
1944
|
+
color: var(--gray-medium);
|
|
1945
|
+
font-size: 16px;
|
|
1946
|
+
line-height: 1.6;
|
|
1947
|
+
margin-bottom: var(--spacing-sm);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.error-details {
|
|
1951
|
+
background: var(--color-error-light, #fae5e4);
|
|
1952
|
+
border: 1px solid var(--color-error-muted, #eea9a5);
|
|
1953
|
+
border-radius: var(--border-radius-lg);
|
|
1954
|
+
padding: var(--spacing-lg);
|
|
1955
|
+
margin: var(--spacing-lg) 0;
|
|
1956
|
+
text-align: left;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
.error-details h3 {
|
|
1960
|
+
color: var(--error-color);
|
|
1961
|
+
margin-bottom: var(--spacing-md);
|
|
1962
|
+
font-size: 18px;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
.error-details p {
|
|
1966
|
+
color: var(--color-error-dark, #903531);
|
|
1967
|
+
margin-bottom: var(--spacing-sm);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
.btn-fail {
|
|
1971
|
+
background: var(--error-color);
|
|
1972
|
+
color: var(--color-white, #fff);
|
|
1973
|
+
padding: 12px 24px;
|
|
1974
|
+
border: none;
|
|
1975
|
+
border-radius: var(--border-radius);
|
|
1976
|
+
font-size: 14px;
|
|
1977
|
+
font-weight: 500;
|
|
1978
|
+
cursor: pointer;
|
|
1979
|
+
margin-top: var(--spacing-md);
|
|
1980
|
+
margin-left: var(--spacing-sm);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
.btn-fail:hover {
|
|
1984
|
+
background: var(--color-error-dark, #903531);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/* Success Page */
|
|
1988
|
+
.success-container {
|
|
1989
|
+
text-align: center;
|
|
1990
|
+
padding: var(--spacing-lg) 0;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
.success-icon {
|
|
1994
|
+
width: 120px;
|
|
1995
|
+
height: 120px;
|
|
1996
|
+
margin: 0 auto var(--spacing-lg);
|
|
1997
|
+
background: linear-gradient(135deg, var(--success-color) 0%, var(--color-success-dark, #136c34) 100%);
|
|
1998
|
+
border-radius: 50%;
|
|
1999
|
+
display: flex;
|
|
2000
|
+
align-items: center;
|
|
2001
|
+
justify-content: center;
|
|
2002
|
+
animation: successPulse 0.6s ease-out;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
@keyframes successPulse {
|
|
2006
|
+
0% {
|
|
2007
|
+
transform: scale(0);
|
|
2008
|
+
opacity: 0;
|
|
2009
|
+
}
|
|
2010
|
+
50% {
|
|
2011
|
+
transform: scale(1.1);
|
|
2012
|
+
}
|
|
2013
|
+
100% {
|
|
2014
|
+
transform: scale(1);
|
|
2015
|
+
opacity: 1;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.success-icon svg {
|
|
2020
|
+
width: 70px;
|
|
2021
|
+
height: 70px;
|
|
2022
|
+
stroke: var(--color-white, #fff);
|
|
2023
|
+
stroke-width: 3;
|
|
2024
|
+
stroke-linecap: round;
|
|
2025
|
+
stroke-linejoin: round;
|
|
2026
|
+
fill: none;
|
|
2027
|
+
stroke-dasharray: 100;
|
|
2028
|
+
stroke-dashoffset: 100;
|
|
2029
|
+
animation: checkmark 0.6s ease-out 0.3s forwards;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
@keyframes checkmark {
|
|
2033
|
+
to {
|
|
2034
|
+
stroke-dashoffset: 0;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
.success-container h2 {
|
|
2039
|
+
color: var(--success-color);
|
|
2040
|
+
margin-bottom: var(--spacing-md);
|
|
2041
|
+
font-size: 32px;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.success-container p {
|
|
2045
|
+
color: var(--gray-medium);
|
|
2046
|
+
font-size: 16px;
|
|
2047
|
+
line-height: 1.6;
|
|
2048
|
+
margin-bottom: var(--spacing-sm);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
.success-details {
|
|
2052
|
+
background: var(--gray-light);
|
|
2053
|
+
border-radius: var(--border-radius-lg);
|
|
2054
|
+
padding: var(--spacing-lg);
|
|
2055
|
+
margin: var(--spacing-lg) 0;
|
|
2056
|
+
text-align: left;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
.success-details h3 {
|
|
2060
|
+
color: var(--color-headline, #0f2a39);
|
|
2061
|
+
margin-bottom: var(--spacing-md);
|
|
2062
|
+
font-size: 18px;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
.detail-item {
|
|
2066
|
+
display: flex;
|
|
2067
|
+
justify-content: space-between;
|
|
2068
|
+
padding: var(--spacing-sm) 0;
|
|
2069
|
+
border-bottom: 1px solid var(--border-color);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
.detail-item:last-child {
|
|
2073
|
+
border-bottom: none;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
.detail-label {
|
|
2077
|
+
color: var(--gray-medium);
|
|
2078
|
+
font-size: 14px;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
.detail-value {
|
|
2082
|
+
color: var(--color-headline, #0f2a39);
|
|
2083
|
+
font-weight: 500;
|
|
2084
|
+
font-size: 14px;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
/* ==================== MOBILE RESPONSIVE STYLES ==================== */
|
|
2088
|
+
|
|
2089
|
+
/* Tablet breakpoint (768px and below) */
|
|
2090
|
+
@media screen and (max-width: 768px) {
|
|
2091
|
+
.modal-overlay {
|
|
2092
|
+
padding: 10px;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
.modal-container {
|
|
2096
|
+
max-height: 95vh;
|
|
2097
|
+
border-radius: var(--border-radius);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
.modal-header {
|
|
2101
|
+
padding: var(--spacing-md);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
.modal-body {
|
|
2105
|
+
padding: var(--spacing-md);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
.modal-footer {
|
|
2109
|
+
padding: var(--spacing-md);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
.modal-body-full {
|
|
2113
|
+
padding: var(--spacing-md);
|
|
2114
|
+
max-height: calc(95vh - 50px);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
.modal-close-btn {
|
|
2118
|
+
top: 12px;
|
|
2119
|
+
right: 12px;
|
|
2120
|
+
padding: 6px;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
/* Stepper - show only current step label on tablet */
|
|
2124
|
+
.stepper-header {
|
|
2125
|
+
gap: var(--spacing-sm);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.step-circle {
|
|
2129
|
+
width: 36px;
|
|
2130
|
+
height: 36px;
|
|
2131
|
+
font-size: 14px;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
.step-label {
|
|
2135
|
+
font-size: 11px;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/* Form grid - single column on tablet */
|
|
2139
|
+
.form-grid {
|
|
2140
|
+
grid-template-columns: 1fr;
|
|
2141
|
+
gap: var(--spacing-sm);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
.step-content {
|
|
2145
|
+
padding: var(--spacing-md);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
.step-content h2 {
|
|
2149
|
+
font-size: 20px;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
/* Representative cards */
|
|
2153
|
+
.representative-card {
|
|
2154
|
+
padding: var(--spacing-sm);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
.card-header h3 {
|
|
2158
|
+
font-size: 14px;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
/* Navigation footer */
|
|
2162
|
+
.navigation-footer {
|
|
2163
|
+
flex-wrap: wrap;
|
|
2164
|
+
gap: var(--spacing-sm);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
.navigation-footer button {
|
|
2168
|
+
padding: 10px 16px;
|
|
2169
|
+
font-size: 13px;
|
|
2170
|
+
height: 38px;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/* Drag drop area */
|
|
2174
|
+
.drag-drop-area {
|
|
2175
|
+
padding: var(--spacing-lg);
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/* Success/Error containers */
|
|
2179
|
+
.success-icon,
|
|
2180
|
+
.error-icon {
|
|
2181
|
+
width: 100px;
|
|
2182
|
+
height: 100px;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
.success-container h2,
|
|
2186
|
+
.error-container h2 {
|
|
2187
|
+
font-size: 24px;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
.success-details {
|
|
2191
|
+
padding: var(--spacing-md);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
/* Mobile breakpoint (480px and below) */
|
|
2196
|
+
@media screen and (max-width: 480px) {
|
|
2197
|
+
.modal-overlay {
|
|
2198
|
+
padding: 0;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.modal-container {
|
|
2202
|
+
max-height: 100vh;
|
|
2203
|
+
height: 100vh;
|
|
2204
|
+
border-radius: 0;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
.modal-layout {
|
|
2208
|
+
max-height: 100vh;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
.modal-body-full {
|
|
2212
|
+
max-height: calc(100vh - 50px);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
.modal-header {
|
|
2216
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
.modal-body {
|
|
2220
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.modal-footer {
|
|
2224
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
.modal-close-btn {
|
|
2228
|
+
top: 8px;
|
|
2229
|
+
right: 8px;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/* Stepper - compact mobile version */
|
|
2233
|
+
.stepper-header {
|
|
2234
|
+
justify-content: center;
|
|
2235
|
+
gap: 4px;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.stepper-header::before {
|
|
2239
|
+
top: 16px;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.step-indicator {
|
|
2243
|
+
flex: 0 0 auto;
|
|
2244
|
+
min-width: 50px;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
.step-circle {
|
|
2248
|
+
width: 32px;
|
|
2249
|
+
height: 32px;
|
|
2250
|
+
font-size: 12px;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
.step-label {
|
|
2254
|
+
font-size: 10px;
|
|
2255
|
+
white-space: nowrap;
|
|
2256
|
+
overflow: hidden;
|
|
2257
|
+
text-overflow: ellipsis;
|
|
2258
|
+
max-width: 60px;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/* Form fields */
|
|
2262
|
+
.form-field {
|
|
2263
|
+
margin-bottom: var(--spacing-sm);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
.form-field label {
|
|
2267
|
+
font-size: 13px;
|
|
2268
|
+
margin-bottom: 4px;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
.form-field input,
|
|
2272
|
+
.form-field select {
|
|
2273
|
+
padding: 8px;
|
|
2274
|
+
font-size: 16px; /* Prevents iOS zoom on focus */
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
.step-content {
|
|
2278
|
+
padding: var(--spacing-sm);
|
|
2279
|
+
margin-bottom: var(--spacing-sm);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
.step-content h2 {
|
|
2283
|
+
font-size: 18px;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
.step-content > p {
|
|
2287
|
+
font-size: 13px;
|
|
2288
|
+
margin-bottom: var(--spacing-sm);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
.form-section h2 {
|
|
2292
|
+
font-size: 18px;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
.form-section > p {
|
|
2296
|
+
font-size: 13px;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/* Representative cards */
|
|
2300
|
+
.representative-card {
|
|
2301
|
+
padding: var(--spacing-sm);
|
|
2302
|
+
margin-bottom: var(--spacing-sm);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
.card-header {
|
|
2306
|
+
margin-bottom: var(--spacing-sm);
|
|
2307
|
+
padding-bottom: 4px;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
.card-header h3 {
|
|
2311
|
+
font-size: 13px;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
.remove-btn {
|
|
2315
|
+
font-size: 12px;
|
|
2316
|
+
padding: 4px;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
.add-representative-btn {
|
|
2320
|
+
padding: 10px;
|
|
2321
|
+
font-size: 13px;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
/* Navigation footer - stack buttons on mobile */
|
|
2325
|
+
.navigation-footer {
|
|
2326
|
+
flex-direction: column-reverse;
|
|
2327
|
+
gap: var(--spacing-sm);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
.navigation-footer button {
|
|
2331
|
+
width: 100%;
|
|
2332
|
+
padding: 12px;
|
|
2333
|
+
font-size: 14px;
|
|
2334
|
+
height: 44px; /* Touch-friendly height */
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
.btn-skip {
|
|
2338
|
+
margin-left: 0;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
/* Radio group - stack on mobile */
|
|
2342
|
+
.radio-group {
|
|
2343
|
+
flex-direction: column;
|
|
2344
|
+
gap: var(--spacing-sm);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/* Drag drop area */
|
|
2348
|
+
.drag-drop-area {
|
|
2349
|
+
padding: var(--spacing-md);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
.drag-drop-content svg {
|
|
2353
|
+
width: 40px;
|
|
2354
|
+
height: 40px;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
/* File items */
|
|
2358
|
+
.file-item {
|
|
2359
|
+
flex-direction: column;
|
|
2360
|
+
align-items: flex-start;
|
|
2361
|
+
gap: var(--spacing-sm);
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
/* Success/Error containers */
|
|
2365
|
+
.success-icon,
|
|
2366
|
+
.error-icon {
|
|
2367
|
+
width: 80px;
|
|
2368
|
+
height: 80px;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
.success-icon svg,
|
|
2372
|
+
.error-icon svg {
|
|
2373
|
+
width: 40px;
|
|
2374
|
+
height: 40px;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
.success-container h2,
|
|
2378
|
+
.error-container h2 {
|
|
2379
|
+
font-size: 20px;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
.success-container p,
|
|
2383
|
+
.error-container p {
|
|
2384
|
+
font-size: 14px;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
.success-details {
|
|
2388
|
+
padding: var(--spacing-sm);
|
|
2389
|
+
margin: var(--spacing-md) 0;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.success-details h3 {
|
|
2393
|
+
font-size: 16px;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
.detail-item {
|
|
2397
|
+
flex-direction: column;
|
|
2398
|
+
gap: 4px;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
.detail-label,
|
|
2402
|
+
.detail-value {
|
|
2403
|
+
font-size: 13px;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
/* Trigger button - full width on mobile */
|
|
2407
|
+
.onboarding-trigger-btn {
|
|
2408
|
+
width: 100%;
|
|
2409
|
+
justify-content: center;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/* Small mobile breakpoint (320px and below) */
|
|
2414
|
+
@media screen and (max-width: 320px) {
|
|
2415
|
+
.step-label {
|
|
2416
|
+
display: none;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
.step-circle {
|
|
2420
|
+
width: 28px;
|
|
2421
|
+
height: 28px;
|
|
2422
|
+
font-size: 11px;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
.stepper-header::before {
|
|
2426
|
+
top: 14px;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
.step-content h2,
|
|
2430
|
+
.form-section h2 {
|
|
2431
|
+
font-size: 16px;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
.navigation-footer button {
|
|
2435
|
+
font-size: 13px;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
</style>
|
|
2439
|
+
`;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
renderStepperHeader() {
|
|
2443
|
+
return `
|
|
2444
|
+
<div class="stepper-header">
|
|
2445
|
+
${this.STEPS.map((step, index) =>
|
|
2446
|
+
this.renderStepIndicator(step, index)
|
|
2447
|
+
).join("")}
|
|
2448
|
+
</div>
|
|
2449
|
+
`;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
renderStepIndicator(step, index) {
|
|
2453
|
+
const isComplete = this.state.completedSteps.has(index);
|
|
2454
|
+
const isCurrent = this.state.currentStep === index;
|
|
2455
|
+
const isClickable = isComplete || index < this.state.currentStep;
|
|
2456
|
+
|
|
2457
|
+
return `
|
|
2458
|
+
<div class="step-indicator ${isCurrent ? "active" : ""} ${isComplete ? "complete" : ""
|
|
2459
|
+
} ${isClickable ? "clickable" : ""}"
|
|
2460
|
+
${isClickable ? `data-step="${index}"` : ""}>
|
|
2461
|
+
<div class="step-circle">
|
|
2462
|
+
${isComplete ? "✓" : index + 1}
|
|
2463
|
+
</div>
|
|
2464
|
+
<div class="step-label">${step.title}</div>
|
|
2465
|
+
</div>
|
|
2466
|
+
`;
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
renderCurrentStep() {
|
|
2470
|
+
// This method is kept for backwards compatibility
|
|
2471
|
+
// but the modal now uses renderFormContent() instead
|
|
2472
|
+
return this.renderFormContent();
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
renderUnderwritingStep() {
|
|
2476
|
+
return this.renderUnderwritingForm();
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
renderUnderwritingForm() {
|
|
2480
|
+
const data = this.state.formData.underwriting;
|
|
2481
|
+
const underwritingDocuments = data.underwritingDocuments || [];
|
|
2482
|
+
const error = this.getFieldError("underwritingDocuments");
|
|
2483
|
+
const showErrors = this.state.uiState.showErrors;
|
|
2484
|
+
|
|
2485
|
+
return `
|
|
2486
|
+
<div class="form-section">
|
|
2487
|
+
<h2>Underwriting Documents</h2>
|
|
2488
|
+
<p>Upload supporting documents (required, max 10 files, 10MB each)</p>
|
|
2489
|
+
|
|
2490
|
+
<div class="form-grid">
|
|
2491
|
+
<div class="form-field full-width ${showErrors && error ? "has-error" : ""
|
|
2492
|
+
}">
|
|
2493
|
+
<label for="underwritingDocs">
|
|
2494
|
+
Upload Documents <span class="required-asterisk">*</span>
|
|
2495
|
+
<span style="font-size: 12px; color: var(--gray-medium); font-weight: normal;">
|
|
2496
|
+
(PDF, JPG, PNG, DOC, DOCX - Max 10MB each)
|
|
2497
|
+
</span>
|
|
2498
|
+
</label>
|
|
2499
|
+
|
|
2500
|
+
<div class="drag-drop-area" id="dragDropArea">
|
|
2501
|
+
<div class="drag-drop-content">
|
|
2502
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin: 0 auto var(--spacing-sm); display: block; color: var(--gray-medium);">
|
|
2503
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
2504
|
+
<polyline points="17 8 12 3 7 8"></polyline>
|
|
2505
|
+
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
2506
|
+
</svg>
|
|
2507
|
+
<p style="margin-bottom: var(--spacing-sm); color: var(--color-headline, #0f2a39); font-weight: 500;">
|
|
2508
|
+
Drag and drop files here
|
|
2509
|
+
</p>
|
|
2510
|
+
<p style="font-size: 14px; color: var(--gray-medium); margin-bottom: var(--spacing-md);">
|
|
2511
|
+
or
|
|
2512
|
+
</p>
|
|
2513
|
+
<button type="button" class="btn-browse" style="
|
|
2514
|
+
padding: 10px 20px;
|
|
2515
|
+
background: var(--primary-color);
|
|
2516
|
+
color: var(--color-white, #fff);
|
|
2517
|
+
border: none;
|
|
2518
|
+
border-radius: var(--border-radius-sm);
|
|
2519
|
+
cursor: pointer;
|
|
2520
|
+
font-size: 14px;
|
|
2521
|
+
font-weight: 500;
|
|
2522
|
+
">Browse Files</button>
|
|
2523
|
+
<input
|
|
2524
|
+
type="file"
|
|
2525
|
+
id="underwritingDocs"
|
|
2526
|
+
name="underwritingDocs"
|
|
2527
|
+
multiple
|
|
2528
|
+
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx"
|
|
2529
|
+
style="display: none;"
|
|
2530
|
+
/>
|
|
2531
|
+
</div>
|
|
2532
|
+
</div>
|
|
2533
|
+
|
|
2534
|
+
<div id="fileList" style="margin-top: var(--spacing-md);">
|
|
2535
|
+
${underwritingDocuments.length > 0
|
|
2536
|
+
? this.renderFileList(underwritingDocuments)
|
|
2537
|
+
: ""
|
|
2538
|
+
}
|
|
2539
|
+
</div>
|
|
2540
|
+
|
|
2541
|
+
${showErrors && error
|
|
2542
|
+
? `<span class="error-message">${error}</span>`
|
|
2543
|
+
: ""
|
|
2544
|
+
}
|
|
2545
|
+
</div>
|
|
2546
|
+
</div>
|
|
2547
|
+
</div>
|
|
2548
|
+
`;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
renderBusinessDetailsStep() {
|
|
2552
|
+
return this.renderBusinessDetailsForm();
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
renderFileList(files) {
|
|
2556
|
+
return `
|
|
2557
|
+
<div class="uploaded-files">
|
|
2558
|
+
<p style="font-size: 14px; font-weight: 500; margin-bottom: var(--spacing-sm); color: var(--color-headline, #0f2a39);">
|
|
2559
|
+
${files.length} file(s) uploaded:
|
|
2560
|
+
</p>
|
|
2561
|
+
${files
|
|
2562
|
+
.map(
|
|
2563
|
+
(file, index) => `
|
|
2564
|
+
<div class="file-item" data-index="${index}" style="
|
|
2565
|
+
display: flex;
|
|
2566
|
+
align-items: center;
|
|
2567
|
+
justify-content: space-between;
|
|
2568
|
+
padding: var(--spacing-sm);
|
|
2569
|
+
background: var(--gray-light);
|
|
2570
|
+
border-radius: var(--border-radius-sm);
|
|
2571
|
+
margin-bottom: var(--spacing-sm);
|
|
2572
|
+
">
|
|
2573
|
+
<div style="display: flex; align-items: center; gap: var(--spacing-sm); flex: 1; min-width: 0;">
|
|
2574
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0;">
|
|
2575
|
+
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
|
|
2576
|
+
<polyline points="13 2 13 9 20 9"></polyline>
|
|
2577
|
+
</svg>
|
|
2578
|
+
<span style="font-size: 14px; color: var(--color-headline, #0f2a39); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
|
2579
|
+
${file.name}
|
|
2580
|
+
</span>
|
|
2581
|
+
<span style="font-size: 12px; color: var(--gray-medium); white-space: nowrap;">
|
|
2582
|
+
(${(file.size / 1024).toFixed(1)} KB)
|
|
2583
|
+
</span>
|
|
2584
|
+
</div>
|
|
2585
|
+
<button type="button" class="btn-remove-file" data-index="${index}" style="
|
|
2586
|
+
background: none;
|
|
2587
|
+
border: none;
|
|
2588
|
+
color: var(--error-color);
|
|
2589
|
+
cursor: pointer;
|
|
2590
|
+
padding: var(--spacing-sm);
|
|
2591
|
+
font-size: 14px;
|
|
2592
|
+
flex-shrink: 0;
|
|
2593
|
+
">✕</button>
|
|
2594
|
+
</div>
|
|
2595
|
+
`
|
|
2596
|
+
)
|
|
2597
|
+
.join("")}
|
|
2598
|
+
</div>
|
|
2599
|
+
`;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
renderBusinessDetailsForm() {
|
|
2603
|
+
const data = this.state.formData.businessDetails;
|
|
2604
|
+
|
|
2605
|
+
return `
|
|
2606
|
+
<div class="form-section">
|
|
2607
|
+
<h2>Business Information</h2>
|
|
2608
|
+
<p>Provide your business details</p>
|
|
2609
|
+
|
|
2610
|
+
<div class="form-grid">
|
|
2611
|
+
${this.renderField({
|
|
2612
|
+
name: "businessName",
|
|
2613
|
+
label: "Business Name *",
|
|
2614
|
+
value: data.businessName,
|
|
2615
|
+
error: this.getFieldError("businessName"),
|
|
2616
|
+
})}
|
|
2617
|
+
|
|
2618
|
+
${this.renderField({
|
|
2619
|
+
name: "doingBusinessAs",
|
|
2620
|
+
label: "Doing Business As (DBA) *",
|
|
2621
|
+
value: data.doingBusinessAs,
|
|
2622
|
+
error: this.getFieldError("doingBusinessAs"),
|
|
2623
|
+
})}
|
|
2624
|
+
|
|
2625
|
+
${this.renderField({
|
|
2626
|
+
name: "ein",
|
|
2627
|
+
label: "EIN *",
|
|
2628
|
+
value: data.ein,
|
|
2629
|
+
error: this.getFieldError("ein"),
|
|
2630
|
+
placeholder: "12-3456789",
|
|
2631
|
+
maxLength: 10,
|
|
2632
|
+
dataFormat: "ein",
|
|
2633
|
+
className: "full-width",
|
|
2634
|
+
})}
|
|
2635
|
+
|
|
2636
|
+
${this.renderField({
|
|
2637
|
+
name: "businessWebsite",
|
|
2638
|
+
label: "Business Website *",
|
|
2639
|
+
type: "url",
|
|
2640
|
+
value: data.businessWebsite,
|
|
2641
|
+
error: this.getFieldError("businessWebsite"),
|
|
2642
|
+
placeholder: "https://example.com",
|
|
2643
|
+
className: "full-width",
|
|
2644
|
+
})}
|
|
2645
|
+
|
|
2646
|
+
${this.renderField({
|
|
2647
|
+
name: "businessPhoneNumber",
|
|
2648
|
+
label: "Business Phone *",
|
|
2649
|
+
type: "tel",
|
|
2650
|
+
value: data.businessPhoneNumber,
|
|
2651
|
+
error: this.getFieldError("businessPhoneNumber"),
|
|
2652
|
+
placeholder: "(555) 123-4567",
|
|
2653
|
+
dataFormat: "phone",
|
|
2654
|
+
})}
|
|
2655
|
+
|
|
2656
|
+
${this.renderField({
|
|
2657
|
+
name: "businessEmail",
|
|
2658
|
+
label: "Business Email *",
|
|
2659
|
+
type: "email",
|
|
2660
|
+
value: data.businessEmail,
|
|
2661
|
+
error: this.getFieldError("businessEmail"),
|
|
2662
|
+
readOnly: false,
|
|
2663
|
+
})}
|
|
2664
|
+
|
|
2665
|
+
${this.renderField({
|
|
2666
|
+
name: "BusinessAddress1",
|
|
2667
|
+
label: "Street Address *",
|
|
2668
|
+
value: data.BusinessAddress1,
|
|
2669
|
+
error: this.getFieldError("BusinessAddress1"),
|
|
2670
|
+
className: "full-width",
|
|
2671
|
+
})}
|
|
2672
|
+
|
|
2673
|
+
${this.renderField({
|
|
2674
|
+
name: "businessCity",
|
|
2675
|
+
label: "City *",
|
|
2676
|
+
value: data.businessCity,
|
|
2677
|
+
error: this.getFieldError("businessCity"),
|
|
2678
|
+
})}
|
|
2679
|
+
|
|
2680
|
+
<div class="form-field ${this.getFieldError("businessState") ? "has-error" : ""
|
|
2681
|
+
}">
|
|
2682
|
+
<label for="businessState">State <span class="required-asterisk">*</span></label>
|
|
2683
|
+
<select id="businessState" name="businessState">
|
|
2684
|
+
<option value="">Select State</option>
|
|
2685
|
+
${this.US_STATES.map(
|
|
2686
|
+
(state) => `
|
|
2687
|
+
<option value="${state}" ${data.businessState === state ? "selected" : ""
|
|
2688
|
+
}>${state}</option>
|
|
2689
|
+
`
|
|
2690
|
+
).join("")}
|
|
2691
|
+
</select>
|
|
2692
|
+
${this.getFieldError("businessState")
|
|
2693
|
+
? `<span class="error-message">${this.getFieldError(
|
|
2694
|
+
"businessState"
|
|
2695
|
+
)}</span>`
|
|
2696
|
+
: ""
|
|
2697
|
+
}
|
|
2698
|
+
</div>
|
|
2699
|
+
|
|
2700
|
+
${this.renderField({
|
|
2701
|
+
name: "businessPostalCode",
|
|
2702
|
+
label: "ZIP Code *",
|
|
2703
|
+
value: data.businessPostalCode,
|
|
2704
|
+
error: this.getFieldError("businessPostalCode"),
|
|
2705
|
+
placeholder: "12345",
|
|
2706
|
+
maxLength: 5,
|
|
2707
|
+
})}
|
|
2708
|
+
</div>
|
|
2709
|
+
</div>
|
|
2710
|
+
`;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
renderRepresentativesStep() {
|
|
2714
|
+
return this.renderRepresentativesForm();
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
renderRepresentativesForm() {
|
|
2718
|
+
const representatives = this.state.formData.representatives;
|
|
2719
|
+
|
|
2720
|
+
return `
|
|
2721
|
+
<div class="form-section">
|
|
2722
|
+
<h2>Business Representatives</h2>
|
|
2723
|
+
<p>Add business representatives (optional)</p>
|
|
2724
|
+
|
|
2725
|
+
<div class="representatives-list">
|
|
2726
|
+
${representatives.length === 0
|
|
2727
|
+
? `
|
|
2728
|
+
<div class="empty-state">
|
|
2729
|
+
<p>No representatives added yet. Click below to add one.</p>
|
|
2730
|
+
</div>
|
|
2731
|
+
`
|
|
2732
|
+
: ""
|
|
2733
|
+
}
|
|
2734
|
+
${representatives
|
|
2735
|
+
.map((rep, index) => this.renderRepresentativeCard(rep, index))
|
|
2736
|
+
.join("")}
|
|
2737
|
+
</div>
|
|
2738
|
+
|
|
2739
|
+
<button type="button" class="add-representative-btn">
|
|
2740
|
+
+ Add Representative
|
|
2741
|
+
</button>
|
|
2742
|
+
</div>
|
|
2743
|
+
`;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
renderRepresentativeCard(representative, index) {
|
|
2747
|
+
return `
|
|
2748
|
+
<div class="representative-card" data-index="${index}">
|
|
2749
|
+
<div class="card-header">
|
|
2750
|
+
<h3>Representative ${index + 1}</h3>
|
|
2751
|
+
<button type="button" class="remove-btn" data-index="${index}">Remove</button>
|
|
2752
|
+
</div>
|
|
2753
|
+
<div class="card-body">
|
|
2754
|
+
<div class="form-grid">
|
|
2755
|
+
${this.renderField({
|
|
2756
|
+
name: "representativeFirstName",
|
|
2757
|
+
label: "First Name *",
|
|
2758
|
+
value: representative.representativeFirstName,
|
|
2759
|
+
error: this.getFieldError("representativeFirstName", index),
|
|
2760
|
+
dataRepIndex: index,
|
|
2761
|
+
})}
|
|
2762
|
+
|
|
2763
|
+
${this.renderField({
|
|
2764
|
+
name: "representativeLastName",
|
|
2765
|
+
label: "Last Name *",
|
|
2766
|
+
value: representative.representativeLastName,
|
|
2767
|
+
error: this.getFieldError("representativeLastName", index),
|
|
2768
|
+
dataRepIndex: index,
|
|
2769
|
+
})}
|
|
2770
|
+
|
|
2771
|
+
${this.renderField({
|
|
2772
|
+
name: "representativeJobTitle",
|
|
2773
|
+
label: "Job Title *",
|
|
2774
|
+
value: representative.representativeJobTitle,
|
|
2775
|
+
error: this.getFieldError("representativeJobTitle", index),
|
|
2776
|
+
dataRepIndex: index,
|
|
2777
|
+
className: "full-width",
|
|
2778
|
+
})}
|
|
2779
|
+
|
|
2780
|
+
${this.renderField({
|
|
2781
|
+
name: "representativePhone",
|
|
2782
|
+
label: "Phone *",
|
|
2783
|
+
type: "tel",
|
|
2784
|
+
value: representative.representativePhone,
|
|
2785
|
+
error: this.getFieldError("representativePhone", index),
|
|
2786
|
+
placeholder: "(555) 123-4567",
|
|
2787
|
+
dataRepIndex: index,
|
|
2788
|
+
dataFormat: "phone",
|
|
2789
|
+
})}
|
|
2790
|
+
|
|
2791
|
+
${this.renderField({
|
|
2792
|
+
name: "representativeEmail",
|
|
2793
|
+
label: "Email *",
|
|
2794
|
+
type: "email",
|
|
2795
|
+
value: representative.representativeEmail,
|
|
2796
|
+
error: this.getFieldError("representativeEmail", index),
|
|
2797
|
+
dataRepIndex: index,
|
|
2798
|
+
})}
|
|
2799
|
+
|
|
2800
|
+
${this.renderField({
|
|
2801
|
+
name: "representativeDateOfBirth",
|
|
2802
|
+
label: "Date of Birth *",
|
|
2803
|
+
type: "date",
|
|
2804
|
+
value: representative.representativeDateOfBirth,
|
|
2805
|
+
error: this.getFieldError("representativeDateOfBirth", index),
|
|
2806
|
+
dataRepIndex: index,
|
|
2807
|
+
className: "full-width",
|
|
2808
|
+
})}
|
|
2809
|
+
|
|
2810
|
+
${this.renderField({
|
|
2811
|
+
name: "representativeAddress",
|
|
2812
|
+
label: "Address *",
|
|
2813
|
+
value: representative.representativeAddress,
|
|
2814
|
+
error: this.getFieldError("representativeAddress", index),
|
|
2815
|
+
dataRepIndex: index,
|
|
2816
|
+
className: "full-width",
|
|
2817
|
+
})}
|
|
2818
|
+
|
|
2819
|
+
${this.renderField({
|
|
2820
|
+
name: "representativeCity",
|
|
2821
|
+
label: "City *",
|
|
2822
|
+
value: representative.representativeCity,
|
|
2823
|
+
error: this.getFieldError("representativeCity", index),
|
|
2824
|
+
dataRepIndex: index,
|
|
2825
|
+
})}
|
|
2826
|
+
|
|
2827
|
+
<div class="form-field ${this.getFieldError("representativeState", index)
|
|
2828
|
+
? "has-error"
|
|
2829
|
+
: ""
|
|
2830
|
+
}">
|
|
2831
|
+
<label for="representativeState-${index}">State <span class="required-asterisk">*</span></label>
|
|
2832
|
+
<select id="representativeState-${index}" name="representativeState" data-rep-index="${index}">
|
|
2833
|
+
<option value="">Select State</option>
|
|
2834
|
+
${this.US_STATES.map(
|
|
2835
|
+
(state) => `
|
|
2836
|
+
<option value="${state}" ${representative.representativeState === state
|
|
2837
|
+
? "selected"
|
|
2838
|
+
: ""
|
|
2839
|
+
}>${state}</option>
|
|
2840
|
+
`
|
|
2841
|
+
).join("")}
|
|
2842
|
+
</select>
|
|
2843
|
+
${this.getFieldError("representativeState", index)
|
|
2844
|
+
? `<span class="error-message">${this.getFieldError(
|
|
2845
|
+
"representativeState",
|
|
2846
|
+
index
|
|
2847
|
+
)}</span>`
|
|
2848
|
+
: ""
|
|
2849
|
+
}
|
|
2850
|
+
</div>
|
|
2851
|
+
|
|
2852
|
+
${this.renderField({
|
|
2853
|
+
name: "representativeZip",
|
|
2854
|
+
label: "ZIP Code *",
|
|
2855
|
+
value: representative.representativeZip,
|
|
2856
|
+
error: this.getFieldError("representativeZip", index),
|
|
2857
|
+
placeholder: "12345",
|
|
2858
|
+
maxLength: 5,
|
|
2859
|
+
dataRepIndex: index,
|
|
2860
|
+
})}
|
|
2861
|
+
</div>
|
|
2862
|
+
</div>
|
|
2863
|
+
</div>
|
|
2864
|
+
`;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
renderBankDetailsStep() {
|
|
2868
|
+
return this.renderBankDetailsForm();
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
renderBankDetailsForm() {
|
|
2872
|
+
const data = this.state.formData.bankDetails;
|
|
2873
|
+
|
|
2874
|
+
return `
|
|
2875
|
+
<div class="form-section">
|
|
2876
|
+
<h2>Bank Account</h2>
|
|
2877
|
+
<p>Link your bank account</p>
|
|
2878
|
+
|
|
2879
|
+
<div class="form-grid">
|
|
2880
|
+
${this.renderField({
|
|
2881
|
+
name: "bankAccountHolderName",
|
|
2882
|
+
label: "Account Holder Name *",
|
|
2883
|
+
value: data.bankAccountHolderName,
|
|
2884
|
+
error: this.getFieldError("bankAccountHolderName"),
|
|
2885
|
+
className: "full-width",
|
|
2886
|
+
})}
|
|
2887
|
+
|
|
2888
|
+
<div class="form-field full-width">
|
|
2889
|
+
<label>Account Type <span class="required-asterisk">*</span></label>
|
|
2890
|
+
<div class="radio-group">
|
|
2891
|
+
<div class="radio-option">
|
|
2892
|
+
<input type="radio" id="checking" name="bankAccountType" value="checking" ${data.bankAccountType === "checking" ? "checked" : ""
|
|
2893
|
+
}>
|
|
2894
|
+
<label for="checking">Checking</label>
|
|
2895
|
+
</div>
|
|
2896
|
+
<div class="radio-option">
|
|
2897
|
+
<input type="radio" id="savings" name="bankAccountType" value="savings" ${data.bankAccountType === "savings" ? "checked" : ""
|
|
2898
|
+
}>
|
|
2899
|
+
<label for="savings">Savings</label>
|
|
2900
|
+
</div>
|
|
2901
|
+
</div>
|
|
2902
|
+
</div>
|
|
2903
|
+
|
|
2904
|
+
${this.renderField({
|
|
2905
|
+
name: "bankRoutingNumber",
|
|
2906
|
+
label: "Routing Number *",
|
|
2907
|
+
value: data.bankRoutingNumber,
|
|
2908
|
+
error: this.getFieldError("bankRoutingNumber"),
|
|
2909
|
+
placeholder: "123456789",
|
|
2910
|
+
maxLength: 9,
|
|
2911
|
+
})}
|
|
2912
|
+
|
|
2913
|
+
${this.renderField({
|
|
2914
|
+
name: "bankAccountNumber",
|
|
2915
|
+
label: "Account Number *",
|
|
2916
|
+
value: data.bankAccountNumber,
|
|
2917
|
+
error: this.getFieldError("bankAccountNumber"),
|
|
2918
|
+
placeholder: "1234567890",
|
|
2919
|
+
})}
|
|
2920
|
+
</div>
|
|
2921
|
+
</div>
|
|
2922
|
+
`;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
renderField({
|
|
2926
|
+
name,
|
|
2927
|
+
label,
|
|
2928
|
+
type = "text",
|
|
2929
|
+
value = "",
|
|
2930
|
+
error = "",
|
|
2931
|
+
readOnly = false,
|
|
2932
|
+
placeholder = "",
|
|
2933
|
+
className = "",
|
|
2934
|
+
maxLength = null,
|
|
2935
|
+
dataRepIndex = null,
|
|
2936
|
+
dataFormat = null,
|
|
2937
|
+
}) {
|
|
2938
|
+
const fieldClass = `form-field ${error ? "has-error" : ""} ${className}`;
|
|
2939
|
+
const fieldId = dataRepIndex !== null ? `${name}-${dataRepIndex}` : name;
|
|
2940
|
+
|
|
2941
|
+
return `
|
|
2942
|
+
<div class="${fieldClass}">
|
|
2943
|
+
<label for="${fieldId}">${label.replace(
|
|
2944
|
+
" *",
|
|
2945
|
+
' <span class="required-asterisk">*</span>'
|
|
2946
|
+
)}</label>
|
|
2947
|
+
<input
|
|
2948
|
+
type="${type}"
|
|
2949
|
+
id="${fieldId}"
|
|
2950
|
+
name="${name}"
|
|
2951
|
+
value="${value}"
|
|
2952
|
+
${readOnly ? "readonly" : ""}
|
|
2953
|
+
${placeholder ? `placeholder="${placeholder}"` : ""}
|
|
2954
|
+
${maxLength ? `maxlength="${maxLength}"` : ""}
|
|
2955
|
+
${dataRepIndex !== null ? `data-rep-index="${dataRepIndex}"` : ""}
|
|
2956
|
+
${dataFormat ? `data-format="${dataFormat}"` : ""}
|
|
2957
|
+
/>
|
|
2958
|
+
${error ? `<span class="error-message">${error}</span>` : ""}
|
|
2959
|
+
</div>
|
|
2960
|
+
`;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
renderNavigationFooter() {
|
|
2964
|
+
const isFirstStep = this.state.currentStep === 0;
|
|
2965
|
+
const isLastStep = this.state.currentStep === this.state.totalSteps - 1;
|
|
2966
|
+
const canSkip = this.STEPS[this.state.currentStep].canSkip;
|
|
2967
|
+
console.log("[FORM DATA]: ", this.state.formData);
|
|
2968
|
+
|
|
2969
|
+
// Hide back button on first step (Business Details)
|
|
2970
|
+
const showBack = !isFirstStep;
|
|
2971
|
+
|
|
2972
|
+
return `
|
|
2973
|
+
<div class="navigation-footer">
|
|
2974
|
+
${showBack ? '<button type="button" class="btn-back">Back</button>' : ""
|
|
2975
|
+
}
|
|
2976
|
+
${canSkip ? '<button type="button" class="btn-skip">Skip</button>' : ""}
|
|
2977
|
+
<button type="button" class="btn-next">
|
|
2978
|
+
${isLastStep ? "Submit" : "Next"}
|
|
2979
|
+
</button>
|
|
2980
|
+
</div>
|
|
2981
|
+
`;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
renderSuccessPage() {
|
|
2985
|
+
const { businessDetails, representatives, bankDetails } =
|
|
2986
|
+
this.state.formData;
|
|
2987
|
+
|
|
2988
|
+
return `
|
|
2989
|
+
<div class="success-container">
|
|
2990
|
+
<div class="success-icon">
|
|
2991
|
+
<svg viewBox="0 0 52 52">
|
|
2992
|
+
<path d="M14 27l7 7 16-16"/>
|
|
2993
|
+
</svg>
|
|
2994
|
+
</div>
|
|
2995
|
+
|
|
2996
|
+
<h2>Onboarding Complete! 🎉</h2>
|
|
2997
|
+
<p>Your operator application has been successfully submitted.</p>
|
|
2998
|
+
<p style="margin-top: var(--spacing-lg); color: var(--color-headline, #0f2a39);">
|
|
2999
|
+
<strong>You can now close this dialog.</strong>
|
|
3000
|
+
</p>
|
|
3001
|
+
|
|
3002
|
+
<div class="success-details">
|
|
3003
|
+
<h3>Submission Summary</h3>
|
|
3004
|
+
<div class="detail-item">
|
|
3005
|
+
<span class="detail-label">Business Name</span>
|
|
3006
|
+
<span class="detail-value">${businessDetails.businessName}</span>
|
|
3007
|
+
</div>
|
|
3008
|
+
<div class="detail-item">
|
|
3009
|
+
<span class="detail-label">Business Email</span>
|
|
3010
|
+
<span class="detail-value">${businessDetails.businessEmail}</span>
|
|
3011
|
+
</div>
|
|
3012
|
+
<div class="detail-item">
|
|
3013
|
+
<span class="detail-label">Phone Number</span>
|
|
3014
|
+
<span class="detail-value">${businessDetails.businessPhoneNumber
|
|
3015
|
+
}</span>
|
|
3016
|
+
</div>
|
|
3017
|
+
<div class="detail-item">
|
|
3018
|
+
<span class="detail-label">Representatives</span>
|
|
3019
|
+
<span class="detail-value">${representatives.length} added</span>
|
|
3020
|
+
</div>
|
|
3021
|
+
<div class="detail-item">
|
|
3022
|
+
<span class="detail-label">Bank Account</span>
|
|
3023
|
+
<span class="detail-value">${bankDetails.bankAccountType === "checking"
|
|
3024
|
+
? "Checking"
|
|
3025
|
+
: "Savings"
|
|
3026
|
+
} (****${bankDetails.bankAccountNumber.slice(-4)})</span>
|
|
3027
|
+
</div>
|
|
3028
|
+
</div>
|
|
3029
|
+
|
|
3030
|
+
<p style="font-size: 14px; color: var(--gray-medium); margin-top: var(--spacing-lg);">
|
|
3031
|
+
A confirmation email has been sent to <strong>${businessDetails.businessEmail
|
|
3032
|
+
}</strong>
|
|
3033
|
+
</p>
|
|
3034
|
+
|
|
3035
|
+
<button class="btn-confirm-success" type="button">
|
|
3036
|
+
Done
|
|
3037
|
+
</button>
|
|
3038
|
+
</div>
|
|
3039
|
+
`;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
renderSubmissionFailurePage() {
|
|
3043
|
+
const { errorMessage } = this.state.uiState;
|
|
3044
|
+
|
|
3045
|
+
return `
|
|
3046
|
+
<div class="error-container">
|
|
3047
|
+
<div class="error-icon">
|
|
3048
|
+
<svg viewBox="0 0 52 52">
|
|
3049
|
+
<circle cx="26" cy="26" r="25" fill="none"/>
|
|
3050
|
+
<path d="M16 16 L36 36 M36 16 L16 36"/>
|
|
3051
|
+
</svg>
|
|
3052
|
+
</div>
|
|
3053
|
+
|
|
3054
|
+
<h2>Submission Failed</h2>
|
|
3055
|
+
<p>Your onboarding submission could not be processed.</p>
|
|
3056
|
+
|
|
3057
|
+
<div class="error-details">
|
|
3058
|
+
<h3>Error Details</h3>
|
|
3059
|
+
<p><strong>Issue:</strong> ${errorMessage || "The submission failed due to a server error."
|
|
3060
|
+
}</p>
|
|
3061
|
+
<p style="margin-top: var(--spacing-md);">
|
|
3062
|
+
Please try submitting again. If the problem persists, contact support.
|
|
3063
|
+
</p>
|
|
3064
|
+
</div>
|
|
3065
|
+
|
|
3066
|
+
<div style="margin-top: var(--spacing-lg); display: flex; gap: var(--spacing-sm); justify-content: center;">
|
|
3067
|
+
<button type="button" class="btn-resubmit" style="
|
|
3068
|
+
padding: 12px 24px;
|
|
3069
|
+
background: var(--primary-color);
|
|
3070
|
+
color: var(--color-white, #fff);
|
|
3071
|
+
border: none;
|
|
3072
|
+
border-radius: var(--border-radius);
|
|
3073
|
+
font-size: 14px;
|
|
3074
|
+
font-weight: 500;
|
|
3075
|
+
cursor: pointer;
|
|
3076
|
+
">Resubmit</button>
|
|
3077
|
+
</div>
|
|
3078
|
+
|
|
3079
|
+
<p style="margin-top: var(--spacing-md); font-size: 12px; color: var(--gray-medium);">
|
|
3080
|
+
Or you can close this dialog.
|
|
3081
|
+
</p>
|
|
3082
|
+
</div>
|
|
3083
|
+
`;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// ==================== EVENT HANDLING ====================
|
|
3087
|
+
|
|
3088
|
+
attachSubmissionFailureListeners() {
|
|
3089
|
+
const shadow = this.shadowRoot;
|
|
3090
|
+
|
|
3091
|
+
// Resubmit button (on submission failure page)
|
|
3092
|
+
const resubmitBtn = shadow.querySelector(".btn-resubmit");
|
|
3093
|
+
if (resubmitBtn) {
|
|
3094
|
+
resubmitBtn.addEventListener("mousedown", async (e) => {
|
|
3095
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3096
|
+
// Call onError callback with resubmit action
|
|
3097
|
+
if (this.onError && typeof this.onError === "function") {
|
|
3098
|
+
this.onError({ action: "resubmit", formData: this.state.formData });
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// For now, just reset to last step
|
|
3102
|
+
// TODO: Implement actual resubmission logic
|
|
3103
|
+
this.setState({
|
|
3104
|
+
isSubmissionFailed: false,
|
|
3105
|
+
currentStep: this.state.totalSteps - 1,
|
|
3106
|
+
uiState: { showErrors: false, errorMessage: null },
|
|
3107
|
+
});
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
attachFailurePageListeners() {
|
|
3113
|
+
const shadow = this.shadowRoot;
|
|
3114
|
+
// This method is currently unused but kept for future error handling
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
attachEventListeners() {
|
|
3118
|
+
const shadow = this.shadowRoot;
|
|
3119
|
+
|
|
3120
|
+
// Trigger button to open modal
|
|
3121
|
+
const triggerBtn = shadow.querySelector(".onboarding-trigger-btn");
|
|
3122
|
+
if (triggerBtn) {
|
|
3123
|
+
triggerBtn.addEventListener("click", () => this.openModal());
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
// Modal close button
|
|
3127
|
+
const closeBtn = shadow.querySelector(".modal-close-btn");
|
|
3128
|
+
if (closeBtn) {
|
|
3129
|
+
closeBtn.addEventListener("click", () => this.closeModal());
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// Close on overlay click (outside modal) - stop event from bubbling from modal-container
|
|
3133
|
+
// Prevent closing during submission and on success screen (require confirm button)
|
|
3134
|
+
const overlay = shadow.querySelector(".modal-overlay");
|
|
3135
|
+
const modalContainer = shadow.querySelector(".modal-container");
|
|
3136
|
+
if (overlay) {
|
|
3137
|
+
overlay.addEventListener("click", (e) => {
|
|
3138
|
+
// Prevent closing during submission or on success screen
|
|
3139
|
+
if (this.state.uiState.isLoading || this.state.isSubmitted) {
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
// Only close if clicking exactly on the overlay, not inside the modal
|
|
3143
|
+
if (e.target === overlay) {
|
|
3144
|
+
this.closeModal();
|
|
3145
|
+
}
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
// Prevent clicks inside modal container from bubbling to overlay
|
|
3149
|
+
if (modalContainer) {
|
|
3150
|
+
modalContainer.addEventListener("click", (e) => {
|
|
3151
|
+
e.stopPropagation();
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
// Close on Escape key (prevent during submission and on success screen)
|
|
3157
|
+
if (this.state.isModalOpen) {
|
|
3158
|
+
this._escapeHandler = (e) => {
|
|
3159
|
+
// Prevent closing during submission or on success screen
|
|
3160
|
+
if (this.state.uiState.isLoading || this.state.isSubmitted) {
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (e.key === "Escape") {
|
|
3164
|
+
this.closeModal();
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
document.addEventListener("keydown", this._escapeHandler);
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// Attach submission failure listeners if needed
|
|
3171
|
+
if (this.state.isSubmissionFailed) {
|
|
3172
|
+
this.attachSubmissionFailureListeners();
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
// Success confirmation button
|
|
3176
|
+
const confirmBtn = shadow.querySelector(".btn-confirm-success");
|
|
3177
|
+
if (confirmBtn) {
|
|
3178
|
+
confirmBtn.addEventListener("click", () => this.handleSuccessConfirm());
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// Form inputs - blur validation (only when modal is open)
|
|
3182
|
+
if (this.state.isModalOpen) {
|
|
3183
|
+
shadow.querySelectorAll("input, select").forEach((input) => {
|
|
3184
|
+
input.addEventListener("blur", (e) => this.handleFieldBlur(e));
|
|
3185
|
+
input.addEventListener("input", (e) => this.handleFieldInput(e));
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
// Navigation buttons - use mousedown to prevent blur interference
|
|
3190
|
+
const nextBtn = shadow.querySelector(".btn-next");
|
|
3191
|
+
if (nextBtn) {
|
|
3192
|
+
nextBtn.addEventListener("mousedown", (e) => {
|
|
3193
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3194
|
+
this.goToNextStep();
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
const backBtn = shadow.querySelector(".btn-back");
|
|
3199
|
+
if (backBtn) {
|
|
3200
|
+
backBtn.addEventListener("mousedown", (e) => {
|
|
3201
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3202
|
+
this.goToPreviousStep();
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
const skipBtn = shadow.querySelector(".btn-skip");
|
|
3207
|
+
if (skipBtn) {
|
|
3208
|
+
skipBtn.addEventListener("mousedown", (e) => {
|
|
3209
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3210
|
+
this.skipStep();
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// Step indicators (for navigation)
|
|
3215
|
+
shadow.querySelectorAll("[data-step]").forEach((indicator) => {
|
|
3216
|
+
indicator.addEventListener("click", (e) => {
|
|
3217
|
+
const stepIndex = parseInt(e.currentTarget.dataset.step);
|
|
3218
|
+
this.goToStep(stepIndex);
|
|
3219
|
+
});
|
|
3220
|
+
});
|
|
3221
|
+
|
|
3222
|
+
// Representative CRUD - use mousedown to prevent blur interference
|
|
3223
|
+
const addBtn = shadow.querySelector(".add-representative-btn");
|
|
3224
|
+
if (addBtn) {
|
|
3225
|
+
addBtn.addEventListener("mousedown", (e) => {
|
|
3226
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3227
|
+
this.addRepresentative();
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
shadow.querySelectorAll(".remove-btn").forEach((btn) => {
|
|
3232
|
+
btn.addEventListener("mousedown", (e) => {
|
|
3233
|
+
e.preventDefault(); // Prevent blur from interfering
|
|
3234
|
+
const index = parseInt(e.target.dataset.index);
|
|
3235
|
+
this.removeRepresentative(index);
|
|
3236
|
+
});
|
|
3237
|
+
});
|
|
3238
|
+
|
|
3239
|
+
// File upload handlers for underwriting documents
|
|
3240
|
+
const fileInput = shadow.querySelector("#underwritingDocs");
|
|
3241
|
+
const dragDropArea = shadow.querySelector("#dragDropArea");
|
|
3242
|
+
const browseBtn = shadow.querySelector(".btn-browse");
|
|
3243
|
+
|
|
3244
|
+
if (fileInput && dragDropArea) {
|
|
3245
|
+
// Browse button click
|
|
3246
|
+
if (browseBtn) {
|
|
3247
|
+
browseBtn.addEventListener("click", (e) => {
|
|
3248
|
+
e.stopPropagation();
|
|
3249
|
+
fileInput.click();
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// Click on drag area
|
|
3254
|
+
dragDropArea.addEventListener("click", () => {
|
|
3255
|
+
fileInput.click();
|
|
3256
|
+
});
|
|
3257
|
+
|
|
3258
|
+
// Drag and drop events
|
|
3259
|
+
dragDropArea.addEventListener("dragenter", (e) => {
|
|
3260
|
+
e.preventDefault();
|
|
3261
|
+
e.stopPropagation();
|
|
3262
|
+
dragDropArea.classList.add("drag-over");
|
|
3263
|
+
});
|
|
3264
|
+
|
|
3265
|
+
dragDropArea.addEventListener("dragover", (e) => {
|
|
3266
|
+
e.preventDefault();
|
|
3267
|
+
e.stopPropagation();
|
|
3268
|
+
dragDropArea.classList.add("drag-over");
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3271
|
+
dragDropArea.addEventListener("dragleave", (e) => {
|
|
3272
|
+
e.preventDefault();
|
|
3273
|
+
e.stopPropagation();
|
|
3274
|
+
if (e.target === dragDropArea) {
|
|
3275
|
+
dragDropArea.classList.remove("drag-over");
|
|
3276
|
+
}
|
|
3277
|
+
});
|
|
3278
|
+
|
|
3279
|
+
dragDropArea.addEventListener("drop", (e) => {
|
|
3280
|
+
e.preventDefault();
|
|
3281
|
+
e.stopPropagation();
|
|
3282
|
+
dragDropArea.classList.remove("drag-over");
|
|
3283
|
+
|
|
3284
|
+
const files = Array.from(e.dataTransfer.files);
|
|
3285
|
+
this.handleFileUpload(files);
|
|
3286
|
+
});
|
|
3287
|
+
|
|
3288
|
+
// File input change
|
|
3289
|
+
fileInput.addEventListener("change", (e) => {
|
|
3290
|
+
const files = Array.from(e.target.files);
|
|
3291
|
+
this.handleFileUpload(files);
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// Remove file buttons
|
|
3296
|
+
shadow.querySelectorAll(".btn-remove-file").forEach((btn) => {
|
|
3297
|
+
btn.addEventListener("click", (e) => {
|
|
3298
|
+
e.stopPropagation();
|
|
3299
|
+
const index = parseInt(btn.dataset.index);
|
|
3300
|
+
this.removeFile(index);
|
|
3301
|
+
});
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
handleFileUpload(files) {
|
|
3306
|
+
// Validate files
|
|
3307
|
+
const errors = [];
|
|
3308
|
+
const validFiles = [];
|
|
3309
|
+
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
3310
|
+
const maxFiles = 10;
|
|
3311
|
+
const allowedTypes = [".pdf", ".jpg", ".jpeg", ".png", ".doc", ".docx"];
|
|
3312
|
+
|
|
3313
|
+
// Get existing documents
|
|
3314
|
+
const existingDocs =
|
|
3315
|
+
this.state.formData.underwriting.underwritingDocuments || [];
|
|
3316
|
+
const totalFiles = existingDocs.length + files.length;
|
|
3317
|
+
|
|
3318
|
+
if (totalFiles > maxFiles) {
|
|
3319
|
+
errors.push(
|
|
3320
|
+
`Maximum ${maxFiles} files allowed (you have ${existingDocs.length} already)`
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
files.forEach((file) => {
|
|
3325
|
+
// Check file size
|
|
3326
|
+
if (file.size > maxSize) {
|
|
3327
|
+
errors.push(`${file.name} exceeds 10MB limit`);
|
|
3328
|
+
} else if (file.size === 0) {
|
|
3329
|
+
errors.push(`${file.name} is empty`);
|
|
3330
|
+
} else {
|
|
3331
|
+
// Check file type
|
|
3332
|
+
const ext = "." + file.name.split(".").pop().toLowerCase();
|
|
3333
|
+
if (allowedTypes.includes(ext)) {
|
|
3334
|
+
validFiles.push(file);
|
|
3335
|
+
} else {
|
|
3336
|
+
errors.push(`${file.name} is not an allowed file type`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3340
|
+
|
|
3341
|
+
// Combine with existing documents
|
|
3342
|
+
const allDocs = [...existingDocs, ...validFiles].slice(0, maxFiles);
|
|
3343
|
+
|
|
3344
|
+
console.log("[FILE UPLOAD ERRORS]: ", errors);
|
|
3345
|
+
|
|
3346
|
+
// Update state with valid files
|
|
3347
|
+
this.setState({
|
|
3348
|
+
formData: {
|
|
3349
|
+
underwriting: {
|
|
3350
|
+
...this.state.formData.underwriting,
|
|
3351
|
+
underwritingDocuments: allDocs,
|
|
3352
|
+
},
|
|
3353
|
+
},
|
|
3354
|
+
uiState: {
|
|
3355
|
+
errorMessage: errors.length > 0 ? errors.join("; ") : null,
|
|
3356
|
+
},
|
|
3357
|
+
});
|
|
3358
|
+
|
|
3359
|
+
// Show errors if any
|
|
3360
|
+
if (errors.length > 0) {
|
|
3361
|
+
const fileList = this.shadowRoot.querySelector("#fileList");
|
|
3362
|
+
if (fileList) {
|
|
3363
|
+
const errorDiv = document.createElement("div");
|
|
3364
|
+
errorDiv.style.color = "var(--error-color)";
|
|
3365
|
+
errorDiv.style.fontSize = "12px";
|
|
3366
|
+
errorDiv.style.marginTop = "var(--spacing-sm)";
|
|
3367
|
+
errorDiv.textContent = errors.join("; ");
|
|
3368
|
+
fileList.prepend(errorDiv);
|
|
3369
|
+
|
|
3370
|
+
// Remove error message after 5 seconds
|
|
3371
|
+
setTimeout(() => errorDiv.remove(), 5000);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
removeFile(index) {
|
|
3377
|
+
const underwritingDocuments = [
|
|
3378
|
+
...this.state.formData.underwriting.underwritingDocuments,
|
|
3379
|
+
];
|
|
3380
|
+
|
|
3381
|
+
underwritingDocuments.splice(index, 1);
|
|
3382
|
+
|
|
3383
|
+
this.setState({
|
|
3384
|
+
formData: {
|
|
3385
|
+
underwriting: {
|
|
3386
|
+
...this.state.formData.underwriting,
|
|
3387
|
+
underwritingDocuments,
|
|
3388
|
+
},
|
|
3389
|
+
},
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
handleFieldBlur(e) {
|
|
3394
|
+
const input = e.target;
|
|
3395
|
+
const name = input.name;
|
|
3396
|
+
const value = input.value;
|
|
3397
|
+
const repIndex = input.dataset.repIndex;
|
|
3398
|
+
|
|
3399
|
+
// Phone number formatting on blur
|
|
3400
|
+
if (input.dataset.format === "phone") {
|
|
3401
|
+
input.value = this.formatPhoneNumber(value);
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
// EIN formatting on blur
|
|
3405
|
+
if (input.dataset.format === "ein") {
|
|
3406
|
+
input.value = this.formatEIN(value);
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// URL normalization on blur
|
|
3410
|
+
if (input.type === "url" && value) {
|
|
3411
|
+
const validationResult = this.validators.url(value);
|
|
3412
|
+
if (validationResult.isValid && validationResult.normalizedValue) {
|
|
3413
|
+
input.value = validationResult.normalizedValue;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
// Update state based on step - directly modify state without re-rendering
|
|
3418
|
+
const stepId = this.STEPS[this.state.currentStep].id;
|
|
3419
|
+
|
|
3420
|
+
if (stepId === "business-details") {
|
|
3421
|
+
this.state.formData.businessDetails[name] = input.value;
|
|
3422
|
+
} else if (stepId === "representatives" && repIndex !== undefined) {
|
|
3423
|
+
const idx = parseInt(repIndex);
|
|
3424
|
+
if (this.state.formData.representatives[idx]) {
|
|
3425
|
+
this.state.formData.representatives[idx][name] = input.value;
|
|
3426
|
+
}
|
|
3427
|
+
} else if (stepId === "underwriting") {
|
|
3428
|
+
this.state.formData.underwriting[name] = input.value;
|
|
3429
|
+
} else if (stepId === "bank-details") {
|
|
3430
|
+
this.state.formData.bankDetails[name] = input.value;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
handleFieldInput(e) {
|
|
3435
|
+
const input = e.target;
|
|
3436
|
+
const name = input.name;
|
|
3437
|
+
let value = input.value;
|
|
3438
|
+
const repIndex = input.dataset.repIndex;
|
|
3439
|
+
|
|
3440
|
+
// Apply real-time formatting for EIN
|
|
3441
|
+
if (input.dataset.format === "ein") {
|
|
3442
|
+
const cursorPosition = input.selectionStart;
|
|
3443
|
+
const oldValue = value;
|
|
3444
|
+
value = this.formatEIN(value);
|
|
3445
|
+
input.value = value;
|
|
3446
|
+
|
|
3447
|
+
// Adjust cursor position after formatting
|
|
3448
|
+
if (oldValue.length < value.length) {
|
|
3449
|
+
// If a hyphen was added, move cursor after it
|
|
3450
|
+
input.setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
|
3451
|
+
} else {
|
|
3452
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// Apply real-time formatting for phone numbers
|
|
3457
|
+
if (input.dataset.format === "phone") {
|
|
3458
|
+
const cursorPosition = input.selectionStart;
|
|
3459
|
+
const oldValue = value;
|
|
3460
|
+
value = this.formatPhoneNumber(value);
|
|
3461
|
+
input.value = value;
|
|
3462
|
+
|
|
3463
|
+
// Adjust cursor position after formatting
|
|
3464
|
+
const diff = value.length - oldValue.length;
|
|
3465
|
+
if (diff > 0) {
|
|
3466
|
+
// Characters were added (formatting), move cursor forward
|
|
3467
|
+
input.setSelectionRange(cursorPosition + diff, cursorPosition + diff);
|
|
3468
|
+
} else {
|
|
3469
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
// Update state in real-time
|
|
3474
|
+
const stepId = this.STEPS[this.state.currentStep].id;
|
|
3475
|
+
|
|
3476
|
+
if (stepId === "business-details") {
|
|
3477
|
+
this.state.formData.businessDetails[name] = value;
|
|
3478
|
+
} else if (stepId === "representatives" && repIndex !== undefined) {
|
|
3479
|
+
const idx = parseInt(repIndex);
|
|
3480
|
+
if (this.state.formData.representatives[idx]) {
|
|
3481
|
+
this.state.formData.representatives[idx][name] = value;
|
|
3482
|
+
}
|
|
3483
|
+
} else if (stepId === "underwriting") {
|
|
3484
|
+
// TODO: Add underwriting field handling here when fields are added
|
|
3485
|
+
this.state.formData.underwriting[name] = value;
|
|
3486
|
+
} else if (stepId === "bank-details") {
|
|
3487
|
+
this.state.formData.bankDetails[name] = value;
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// Real-time validation: validate the field and update error state
|
|
3491
|
+
const stepKey = `step${this.state.currentStep}`;
|
|
3492
|
+
|
|
3493
|
+
// Initialize errors object if it doesn't exist
|
|
3494
|
+
if (!this.state.validationState[stepKey]) {
|
|
3495
|
+
this.state.validationState[stepKey] = { isValid: true, errors: {} };
|
|
3496
|
+
}
|
|
3497
|
+
if (!this.state.validationState[stepKey].errors) {
|
|
3498
|
+
this.state.validationState[stepKey].errors = {};
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// Only validate if showErrors is true (after first submit attempt)
|
|
3502
|
+
if (this.state.uiState.showErrors) {
|
|
3503
|
+
// Get field configuration for validation
|
|
3504
|
+
let validators = [];
|
|
3505
|
+
let fieldLabel = name;
|
|
3506
|
+
|
|
3507
|
+
if (stepId === "business-details") {
|
|
3508
|
+
const fieldConfigs = {
|
|
3509
|
+
businessName: { validators: ["required"], label: "Business Name" },
|
|
3510
|
+
doingBusinessAs: {
|
|
3511
|
+
validators: ["required"],
|
|
3512
|
+
label: "Doing Business As (DBA)",
|
|
3513
|
+
},
|
|
3514
|
+
ein: { validators: ["required", "ein"], label: "EIN" },
|
|
3515
|
+
businessWebsite: { validators: ["url"], label: "Business Website" },
|
|
3516
|
+
businessPhoneNumber: {
|
|
3517
|
+
validators: ["required", "usPhone"],
|
|
3518
|
+
label: "Business Phone",
|
|
3519
|
+
},
|
|
3520
|
+
businessEmail: {
|
|
3521
|
+
validators: ["required", "email"],
|
|
3522
|
+
label: "Business Email",
|
|
3523
|
+
},
|
|
3524
|
+
BusinessAddress1: {
|
|
3525
|
+
validators: ["required"],
|
|
3526
|
+
label: "Street Address",
|
|
3527
|
+
},
|
|
3528
|
+
businessCity: { validators: ["required"], label: "City" },
|
|
3529
|
+
businessState: { validators: ["required"], label: "State" },
|
|
3530
|
+
businessPostalCode: {
|
|
3531
|
+
validators: ["required", "postalCode"],
|
|
3532
|
+
label: "ZIP Code",
|
|
3533
|
+
},
|
|
3534
|
+
};
|
|
3535
|
+
if (fieldConfigs[name]) {
|
|
3536
|
+
validators = fieldConfigs[name].validators;
|
|
3537
|
+
fieldLabel = fieldConfigs[name].label;
|
|
3538
|
+
}
|
|
3539
|
+
} else if (stepId === "representatives" && repIndex !== undefined) {
|
|
3540
|
+
const fieldConfigs = {
|
|
3541
|
+
representativeFirstName: {
|
|
3542
|
+
validators: ["required"],
|
|
3543
|
+
label: "First Name",
|
|
3544
|
+
},
|
|
3545
|
+
representativeLastName: {
|
|
3546
|
+
validators: ["required"],
|
|
3547
|
+
label: "Last Name",
|
|
3548
|
+
},
|
|
3549
|
+
representativeJobTitle: {
|
|
3550
|
+
validators: ["required"],
|
|
3551
|
+
label: "Job Title",
|
|
3552
|
+
},
|
|
3553
|
+
representativePhone: {
|
|
3554
|
+
validators: ["required", "usPhone"],
|
|
3555
|
+
label: "Phone",
|
|
3556
|
+
},
|
|
3557
|
+
representativeEmail: {
|
|
3558
|
+
validators: ["required", "email"],
|
|
3559
|
+
label: "Email",
|
|
3560
|
+
},
|
|
3561
|
+
representativeDateOfBirth: {
|
|
3562
|
+
validators: ["required"],
|
|
3563
|
+
label: "Date of Birth",
|
|
3564
|
+
},
|
|
3565
|
+
representativeAddress: { validators: ["required"], label: "Address" },
|
|
3566
|
+
representativeCity: { validators: ["required"], label: "City" },
|
|
3567
|
+
representativeState: { validators: ["required"], label: "State" },
|
|
3568
|
+
representativeZip: {
|
|
3569
|
+
validators: ["required", "postalCode"],
|
|
3570
|
+
label: "ZIP Code",
|
|
3571
|
+
},
|
|
3572
|
+
};
|
|
3573
|
+
if (fieldConfigs[name]) {
|
|
3574
|
+
validators = fieldConfigs[name].validators;
|
|
3575
|
+
fieldLabel = fieldConfigs[name].label;
|
|
3576
|
+
}
|
|
3577
|
+
} else if (stepId === "underwriting") {
|
|
3578
|
+
// TODO: Add underwriting field validation configs here when fields are added
|
|
3579
|
+
// Example:
|
|
3580
|
+
// const fieldConfigs = {
|
|
3581
|
+
// industryType: { validators: ["required"], label: "Industry Type" },
|
|
3582
|
+
// };
|
|
3583
|
+
// if (fieldConfigs[name]) {
|
|
3584
|
+
// validators = fieldConfigs[name].validators;
|
|
3585
|
+
// fieldLabel = fieldConfigs[name].label;
|
|
3586
|
+
// }
|
|
3587
|
+
} else if (stepId === "bank-details") {
|
|
3588
|
+
const fieldConfigs = {
|
|
3589
|
+
accountHolderName: {
|
|
3590
|
+
validators: ["required"],
|
|
3591
|
+
label: "Account Holder Name",
|
|
3592
|
+
},
|
|
3593
|
+
accountType: { validators: ["required"], label: "Account Type" },
|
|
3594
|
+
routingNumber: {
|
|
3595
|
+
validators: ["required", "routingNumber"],
|
|
3596
|
+
label: "Routing Number",
|
|
3597
|
+
},
|
|
3598
|
+
accountNumber: {
|
|
3599
|
+
validators: ["required", "accountNumber"],
|
|
3600
|
+
label: "Account Number",
|
|
3601
|
+
},
|
|
3602
|
+
};
|
|
3603
|
+
if (fieldConfigs[name]) {
|
|
3604
|
+
validators = fieldConfigs[name].validators;
|
|
3605
|
+
fieldLabel = fieldConfigs[name].label;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// Validate the field
|
|
3610
|
+
if (validators.length > 0) {
|
|
3611
|
+
const error = this.validateField(value, validators, fieldLabel);
|
|
3612
|
+
|
|
3613
|
+
if (repIndex !== undefined) {
|
|
3614
|
+
// Handle representative field errors
|
|
3615
|
+
const repKey = `rep${repIndex}`;
|
|
3616
|
+
if (!this.state.validationState[stepKey].errors[repKey]) {
|
|
3617
|
+
this.state.validationState[stepKey].errors[repKey] = {};
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
if (error) {
|
|
3621
|
+
this.state.validationState[stepKey].errors[repKey][name] = error;
|
|
3622
|
+
} else {
|
|
3623
|
+
delete this.state.validationState[stepKey].errors[repKey][name];
|
|
3624
|
+
// If no more errors for this rep, remove the rep key
|
|
3625
|
+
if (
|
|
3626
|
+
Object.keys(this.state.validationState[stepKey].errors[repKey])
|
|
3627
|
+
.length === 0
|
|
3628
|
+
) {
|
|
3629
|
+
delete this.state.validationState[stepKey].errors[repKey];
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
} else {
|
|
3633
|
+
// Handle regular field errors
|
|
3634
|
+
if (error) {
|
|
3635
|
+
this.state.validationState[stepKey].errors[name] = error;
|
|
3636
|
+
} else {
|
|
3637
|
+
delete this.state.validationState[stepKey].errors[name];
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
// Update error message in DOM without full re-render
|
|
3642
|
+
this.updateFieldErrorDisplay(input, error);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
updateFieldErrorDisplay(input, error) {
|
|
3648
|
+
// Find the parent form-field div
|
|
3649
|
+
const formField = input.closest(".form-field");
|
|
3650
|
+
if (!formField) return;
|
|
3651
|
+
|
|
3652
|
+
// Update has-error class
|
|
3653
|
+
if (error) {
|
|
3654
|
+
formField.classList.add("has-error");
|
|
3655
|
+
} else {
|
|
3656
|
+
formField.classList.remove("has-error");
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// Find or create error message element
|
|
3660
|
+
let errorSpan = formField.querySelector(".error-message");
|
|
3661
|
+
|
|
3662
|
+
if (error) {
|
|
3663
|
+
if (errorSpan) {
|
|
3664
|
+
// Update existing error message
|
|
3665
|
+
errorSpan.textContent = error;
|
|
3666
|
+
} else {
|
|
3667
|
+
// Create new error message
|
|
3668
|
+
errorSpan = document.createElement("span");
|
|
3669
|
+
errorSpan.className = "error-message";
|
|
3670
|
+
errorSpan.textContent = error;
|
|
3671
|
+
formField.appendChild(errorSpan);
|
|
3672
|
+
}
|
|
3673
|
+
} else {
|
|
3674
|
+
// Remove error message if exists
|
|
3675
|
+
if (errorSpan) {
|
|
3676
|
+
errorSpan.remove();
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
// ==================== LIFECYCLE METHODS ====================
|
|
3682
|
+
|
|
3683
|
+
connectedCallback() {
|
|
3684
|
+
// Component is added to the DOM
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
disconnectedCallback() {
|
|
3688
|
+
// Component is removed from the DOM
|
|
3689
|
+
// Clean up escape key handler
|
|
3690
|
+
if (this._escapeHandler) {
|
|
3691
|
+
document.removeEventListener("keydown", this._escapeHandler);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
3696
|
+
// Handle on-success attribute
|
|
3697
|
+
if (name === "on-success" && newValue) {
|
|
3698
|
+
// Use the setter to assign the callback from window scope
|
|
3699
|
+
this.onSuccess = window[newValue];
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
// Handle on-error attribute
|
|
3703
|
+
if (name === "on-error" && newValue) {
|
|
3704
|
+
// Use the setter to assign the callback from window scope
|
|
3705
|
+
this.onError = window[newValue];
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
// Handle on-submit attribute
|
|
3709
|
+
if (name === "on-submit" && newValue) {
|
|
3710
|
+
// Use the setter to assign the callback from window scope
|
|
3711
|
+
this.onSubmit = window[newValue];
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
// Handle on-load attribute (expects JSON string or global variable name)
|
|
3715
|
+
if (name === "on-load" && newValue) {
|
|
3716
|
+
try {
|
|
3717
|
+
// Try to parse as JSON first
|
|
3718
|
+
const data = JSON.parse(newValue);
|
|
3719
|
+
this.onLoad = data;
|
|
3720
|
+
} catch (e) {
|
|
3721
|
+
// If not JSON, try to get from window scope (global variable)
|
|
3722
|
+
if (window[newValue]) {
|
|
3723
|
+
this.onLoad = window[newValue];
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
adoptedCallback() {
|
|
3730
|
+
// Component moved to new document
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
// Register the custom element only if it hasn't been registered yet
|
|
3735
|
+
if (!customElements.get("operator-onboarding")) {
|
|
3736
|
+
customElements.define("operator-onboarding", OperatorOnboarding);
|
|
3737
|
+
}
|
|
3738
|
+
|
|
3739
|
+
// Export for module usage (ES6)
|
|
3740
|
+
export { OperatorOnboarding };
|
|
3741
|
+
|
|
3742
|
+
// Make available globally for script tag usage
|
|
3743
|
+
if (typeof window !== "undefined") {
|
|
3744
|
+
window.OperatorOnboarding = OperatorOnboarding;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// Export for CommonJS (Node.js)
|
|
3748
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
3749
|
+
module.exports = { OperatorOnboarding };
|
|
3750
|
+
}
|