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