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/README.md ADDED
@@ -0,0 +1,749 @@
1
+ # Operator Onboarding Web Component
2
+
3
+ A complete, self-contained web component for operator onboarding with 4-step stepper form, file uploads, validations, and success page.
4
+
5
+ ## Installation
6
+
7
+ ```html
8
+ <script src="https://cdn.jsdelivr.net/npm/web-components-moov@1.0.17/component.js"></script>
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ### Minimal Setup (No Callbacks)
14
+ The simplest way to use the component:
15
+
16
+ ```html
17
+ <script src="https://cdn.jsdelivr.net/npm/web-components-moov@1.0.17/component.js"></script>
18
+ <operator-onboarding></operator-onboarding>
19
+ ```
20
+
21
+ When submitted, form data is automatically logged to console and success page is shown.
22
+
23
+ ---
24
+
25
+ ## Configuration
26
+
27
+ The component can be configured with optional attributes for API integration:
28
+
29
+ ```html
30
+ <operator-onboarding
31
+ api-base-url="https://your-api-domain.com"
32
+ embeddable-key="your-embeddable-key">
33
+ </operator-onboarding>
34
+ ```
35
+
36
+ ### Attributes
37
+
38
+ | Attribute | Type | Default | Description |
39
+ |-----------|------|---------|-------------|
40
+ | `api-base-url` | `String` | `https://bison-jib-development.azurewebsites.net` | Base URL for API endpoints |
41
+ | `embeddable-key` | `String` | Default key provided | Authentication key for API requests |
42
+ | `on-success` | `String` | - | Name of global function to call on success |
43
+ | `on-error` | `String` | - | Name of global function to call on error |
44
+ | `on-load` | `String` | - | JSON string or global variable name for initial data |
45
+
46
+ ---
47
+
48
+ ## Form Flow
49
+
50
+ The onboarding process consists of a 4-step stepper form:
51
+
52
+ 1. **Business Details** - Company information and address
53
+ 2. **Representatives** (Optional) - Add business representatives
54
+ 3. **Bank Account** - Link bank account details
55
+ 4. **Underwriting** - Upload required documents
56
+
57
+ ---
58
+
59
+ ## Form Steps in Detail
60
+
61
+ ### Step 1: Business Details
62
+ - Business name *
63
+ - Doing Business As (DBA) *
64
+ - EIN (Employer Identification Number) * (auto-formatted as XX-XXXXXXX)
65
+ - Business website (auto-normalized to include https://)
66
+ - Business phone * (auto-formatted as (555) 123-4567)
67
+ - Business email *
68
+ - Full address * (street, city, state, ZIP)
69
+
70
+ ### Step 2: Representatives (Optional)
71
+ - Add/remove multiple representatives
72
+ - Full CRUD interface
73
+ - Each representative requires:
74
+ - First name, last name *
75
+ - Job title *
76
+ - Phone * (auto-formatted)
77
+ - Email *
78
+ - Date of birth *
79
+ - Full address * (street, city, state, ZIP)
80
+ - Can skip entire step if no representatives to add
81
+
82
+ ### Step 3: Bank Account
83
+ - Account holder name *
84
+ - Account type * (checking/savings)
85
+ - Routing number * (9 digits)
86
+ - Account number * (4-17 digits)
87
+
88
+ ### Step 4: Underwriting
89
+ - Upload supporting documents * (required)
90
+ - Drag-and-drop or browse file selection
91
+ - Maximum 10 files
92
+ - Maximum 10MB per file
93
+ - Accepted formats: PDF, JPG, JPEG, PNG, DOC, DOCX
94
+
95
+ ---
96
+
97
+ ## Pre-populating Form Data
98
+
99
+ ### Method 1: Direct Property Assignment (Recommended)
100
+
101
+ ```javascript
102
+ const component = document.querySelector('operator-onboarding');
103
+
104
+ // Pre-populate with existing data
105
+ component.onLoad = {
106
+ businessDetails: {
107
+ businessName: 'Acme Corp',
108
+ doingBusinessAs: 'Acme',
109
+ ein: '12-3456789',
110
+ businessWebsite: 'https://acme.com',
111
+ businessPhoneNumber: '5551234567',
112
+ businessEmail: 'contact@acme.com',
113
+ BusinessAddress1: '123 Main St',
114
+ businessCity: 'San Francisco',
115
+ businessState: 'CA',
116
+ businessPostalCode: '94105'
117
+ },
118
+ representatives: [
119
+ {
120
+ representativeFirstName: 'John',
121
+ representativeLastName: 'Doe',
122
+ representativeJobTitle: 'CEO',
123
+ representativePhone: '5559876543',
124
+ representativeEmail: 'john@company.com',
125
+ representativeDateOfBirth: '1980-01-15',
126
+ representativeAddress: '456 Oak Ave',
127
+ representativeCity: 'San Francisco',
128
+ representativeState: 'CA',
129
+ representativeZip: '94105'
130
+ }
131
+ ],
132
+ underwriting: {
133
+ underwritingDocuments: [] // Can be pre-populated with File objects
134
+ },
135
+ bankDetails: {
136
+ bankAccountHolderName: 'Acme Corp',
137
+ bankAccountType: 'checking',
138
+ bankRoutingNumber: '123456789',
139
+ bankAccountNumber: '987654321'
140
+ }
141
+ };
142
+ ```
143
+
144
+ ### Method 2: HTML Attribute with JSON
145
+
146
+ ```html
147
+ <operator-onboarding on-load='{"businessDetails":{"businessName":"Acme Corp"}}'></operator-onboarding>
148
+ ```
149
+
150
+ ### Method 3: HTML Attribute with Global Variable
151
+
152
+ ```html
153
+ <script>
154
+ const initialData = {
155
+ businessDetails: {
156
+ businessName: 'Acme Corp',
157
+ businessEmail: 'test@company.com'
158
+ }
159
+ };
160
+ </script>
161
+
162
+ <operator-onboarding on-load="initialData"></operator-onboarding>
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Advanced Usage with Callbacks
168
+
169
+ ### Method 1: Direct Property Assignment (Recommended for Frameworks)
170
+
171
+ **Perfect for React, Vue, Angular, and vanilla JavaScript:**
172
+
173
+ ```javascript
174
+ const component = document.querySelector('operator-onboarding');
175
+
176
+ // Success callback
177
+ component.onSuccess = (formData) => {
178
+ console.log('Onboarding complete!', formData);
179
+
180
+ // Send to your backend
181
+ fetch('/api/operators/onboard', {
182
+ method: 'POST',
183
+ headers: { 'Content-Type': 'application/json' },
184
+ body: JSON.stringify(formData)
185
+ });
186
+
187
+ // Close your modal
188
+ closeModal();
189
+ };
190
+
191
+ // Error callback
192
+ component.onError = (errorData) => {
193
+ console.error('Onboarding error:', errorData);
194
+
195
+ if (errorData.action === 'resubmit') {
196
+ // User clicked resubmit button
197
+ console.log('User wants to retry submission');
198
+ }
199
+ };
200
+ ```
201
+
202
+ ### Method 2: HTML Attribute
203
+
204
+ Good for simple cases with global functions:
205
+
206
+ ```html
207
+ <operator-onboarding
208
+ on-success="handleSuccess"
209
+ on-error="handleError">
210
+ </operator-onboarding>
211
+
212
+ <script>
213
+ function handleSuccess(data) {
214
+ console.log('Success!', data);
215
+ closeModal();
216
+ }
217
+
218
+ function handleError(errorData) {
219
+ console.error('Error:', errorData);
220
+ }
221
+ </script>
222
+ ```
223
+
224
+ ### Method 3: Event Listeners
225
+
226
+ Listen to custom events:
227
+
228
+ ```javascript
229
+ // Success event
230
+ component.addEventListener('formComplete', (event) => {
231
+ const formData = event.detail;
232
+ console.log('Form completed!', formData);
233
+ closeModal();
234
+ });
235
+
236
+ // Submission failure event
237
+ component.addEventListener('submissionFailed', (event) => {
238
+ const errorData = event.detail;
239
+ console.error('Submission failed:', errorData);
240
+ });
241
+ ```
242
+
243
+ ---
244
+
245
+ ## React Integration
246
+
247
+ ### Recommended Pattern (Using useRef)
248
+
249
+ ```jsx
250
+ import { useEffect, useRef } from 'react';
251
+
252
+ function OnboardingModal({ isOpen, onClose }) {
253
+ const componentRef = useRef(null);
254
+
255
+ useEffect(() => {
256
+ if (componentRef.current) {
257
+ // Success handler
258
+ componentRef.current.onSuccess = (data) => {
259
+ console.log('Onboarding complete:', data);
260
+
261
+ // Send to API
262
+ fetch('/api/onboard', {
263
+ method: 'POST',
264
+ body: JSON.stringify(data)
265
+ });
266
+
267
+ onClose();
268
+ };
269
+
270
+ // Error handler
271
+ componentRef.current.onError = (errorData) => {
272
+ console.error('Error:', errorData);
273
+ // Handle errors appropriately
274
+ };
275
+ }
276
+ }, [onClose]);
277
+
278
+ if (!isOpen) return null;
279
+
280
+ return (
281
+ <div className="modal">
282
+ <operator-onboarding
283
+ ref={componentRef}
284
+ api-base-url="https://your-api.com"
285
+ embeddable-key="your-key">
286
+ </operator-onboarding>
287
+ </div>
288
+ );
289
+ }
290
+ ```
291
+
292
+ ### With Pre-populated Data
293
+
294
+ ```jsx
295
+ function EditOperatorModal({ operatorId, isOpen, onClose }) {
296
+ const componentRef = useRef(null);
297
+ const [initialData, setInitialData] = useState(null);
298
+
299
+ useEffect(() => {
300
+ if (isOpen && operatorId) {
301
+ // Fetch existing operator data
302
+ fetch(`/api/operators/${operatorId}`)
303
+ .then(res => res.json())
304
+ .then(data => setInitialData(data));
305
+ }
306
+ }, [isOpen, operatorId]);
307
+
308
+ useEffect(() => {
309
+ if (componentRef.current) {
310
+ // Pre-populate form
311
+ if (initialData) {
312
+ componentRef.current.onLoad = initialData;
313
+ }
314
+
315
+ // Set success handler
316
+ componentRef.current.onSuccess = (updatedData) => {
317
+ fetch(`/api/operators/${operatorId}`, {
318
+ method: 'PUT',
319
+ body: JSON.stringify(updatedData)
320
+ });
321
+ onClose();
322
+ };
323
+ }
324
+ }, [initialData, operatorId, onClose]);
325
+
326
+ if (!isOpen) return null;
327
+
328
+ return (
329
+ <div className="modal">
330
+ <operator-onboarding ref={componentRef} />
331
+ </div>
332
+ );
333
+ }
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Data Structure
339
+
340
+ The component returns a complete data object:
341
+
342
+ ```javascript
343
+ {
344
+ "businessDetails": {
345
+ "businessName": "Acme Corp",
346
+ "doingBusinessAs": "Acme",
347
+ "ein": "12-3456789",
348
+ "businessWebsite": "https://acme.com",
349
+ "businessPhoneNumber": "(555) 123-4567",
350
+ "businessEmail": "contact@acme.com",
351
+ "BusinessAddress1": "123 Main St",
352
+ "businessCity": "San Francisco",
353
+ "businessState": "CA",
354
+ "businessPostalCode": "94105"
355
+ },
356
+ "representatives": [
357
+ {
358
+ "id": "uuid-here",
359
+ "representativeFirstName": "John",
360
+ "representativeLastName": "Doe",
361
+ "representativeJobTitle": "CEO",
362
+ "representativePhone": "(555) 987-6543",
363
+ "representativeEmail": "john@company.com",
364
+ "representativeDateOfBirth": "1980-01-15",
365
+ "representativeAddress": "456 Oak Ave",
366
+ "representativeCity": "San Francisco",
367
+ "representativeState": "CA",
368
+ "representativeZip": "94105"
369
+ }
370
+ ],
371
+ "underwriting": {
372
+ "underwritingDocuments": [
373
+ // Array of File objects
374
+ File { name: "document.pdf", size: 1234567, type: "application/pdf" }
375
+ ]
376
+ },
377
+ "bankDetails": {
378
+ "bankAccountHolderName": "Acme Corp",
379
+ "bankAccountType": "checking",
380
+ "bankRoutingNumber": "123456789",
381
+ "bankAccountNumber": "987654321"
382
+ }
383
+ }
384
+ ```
385
+
386
+ ---
387
+
388
+ ## Features
389
+
390
+ ✅ **4-Step Stepper Form** - Visual progress indicator
391
+ ✅ **Field Validation** - Real-time validation on blur
392
+ ✅ **Auto-Formatting** - Phone numbers, EIN, URLs
393
+ ✅ **File Upload** - Drag-and-drop with validation
394
+ ✅ **CRUD Representatives** - Add/remove multiple reps
395
+ ✅ **Error Handling** - Verification and submission failures
396
+ ✅ **Success Page** - Animated completion screen
397
+ ✅ **Framework Friendly** - Easy integration with React, Vue, etc.
398
+ ✅ **Shadow DOM** - Fully encapsulated styles
399
+ ✅ **Zero Dependencies** - Pure vanilla JavaScript
400
+ ✅ **API Integration** - Ready for backend integration
401
+
402
+ ---
403
+
404
+ ## API Reference
405
+
406
+ ### Properties
407
+
408
+ | Property | Type | Description |
409
+ |----------|------|-------------|
410
+ | `onSuccess` | `Function` | Callback function called when form is successfully submitted. Receives complete form data as parameter. |
411
+ | `onError` | `Function` | Callback function called when submission fails. Receives error data as parameter. |
412
+ | `onLoad` | `Object` | Pre-populate form fields with initial data. Accepts partial or complete form data object. |
413
+ | `apiBaseURL` | `String` | Base URL for API endpoints. |
414
+ | `embeddableKey` | `String` | Authentication key for API requests. |
415
+
416
+ ### Events
417
+
418
+ | Event | Detail | Description |
419
+ |-------|--------|-------------|
420
+ | `formComplete` | `Object` | Emitted when form is successfully submitted. `event.detail` contains complete form data. |
421
+ | `submissionFailed` | `Object` | Emitted when form submission fails. `event.detail` contains error information. |
422
+
423
+ ### Global Functions
424
+
425
+ | Function | Parameters | Returns | Description |
426
+ |----------|------------|---------|-------------|
427
+ | `verifyOperator(operatorEmail, mockResult)` | `operatorEmail: string`, `mockResult: boolean` | `boolean` | Verify if an operator email exists. |
428
+
429
+ ---
430
+
431
+ ## Operator Verification
432
+
433
+ You can verify operator emails:
434
+
435
+ ```javascript
436
+ // Check if operator exists
437
+ const exists = verifyOperator('operator@company.com', true);
438
+
439
+ if (exists) {
440
+ // Proceed with operation
441
+ console.log('Operator verified');
442
+ } else {
443
+ // Show error
444
+ console.error('Operator not found');
445
+ }
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Error Handling
451
+
452
+ The component provides comprehensive error handling:
453
+
454
+ ### Submission Failures
455
+
456
+ When form submission fails:
457
+
458
+ ```javascript
459
+ component.addEventListener('submissionFailed', (event) => {
460
+ const { formData, message, timestamp } = event.detail;
461
+ console.error('Submission failed:', message);
462
+ // Retry or show error
463
+ });
464
+
465
+ // Or use callback
466
+ component.onError = (errorData) => {
467
+ if (errorData.action === 'resubmit') {
468
+ // User clicked resubmit button
469
+ // Your retry logic here
470
+ }
471
+ };
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Modal Integration Example
477
+
478
+ ```html
479
+ <div id="onboardingModal" class="modal">
480
+ <div class="modal-content">
481
+ <operator-onboarding
482
+ on-success="closeOnboardingModal"
483
+ on-error="handleOnboardingError">
484
+ </operator-onboarding>
485
+ </div>
486
+ </div>
487
+
488
+ <script>
489
+ function closeOnboardingModal(data) {
490
+ console.log('Onboarding complete for:', data.businessDetails.businessName);
491
+
492
+ // Close modal
493
+ document.getElementById('onboardingModal').style.display = 'none';
494
+
495
+ // Send data to backend
496
+ fetch('/api/onboarding', {
497
+ method: 'POST',
498
+ headers: { 'Content-Type': 'application/json' },
499
+ body: JSON.stringify(data)
500
+ });
501
+ }
502
+
503
+ function handleOnboardingError(errorData) {
504
+ console.error('Onboarding error:', errorData);
505
+ // Show error notification
506
+ }
507
+ </script>
508
+ ```
509
+
510
+ ---
511
+
512
+ ## File Upload Validation
513
+
514
+ The underwriting step includes file upload with the following restrictions:
515
+
516
+ - **Maximum files:** 10
517
+ - **Maximum size per file:** 10MB
518
+ - **Allowed formats:** PDF, JPG, JPEG, PNG, DOC, DOCX
519
+ - **Validation:** Real-time with error messages
520
+
521
+ Files are validated on both drag-and-drop and browse selection. Invalid files are rejected with clear error messages.
522
+
523
+ ---
524
+
525
+ ## Browser Support
526
+
527
+ - Chrome/Edge (latest)
528
+ - Firefox (latest)
529
+ - Safari (latest)
530
+ - Any browser supporting Custom Elements V1 and Shadow DOM
531
+
532
+ ---
533
+
534
+ ## BisonJibPayAPI - Direct API Access
535
+
536
+ In addition to the web component, you can use the `BisonJibPayAPI` class directly for API integration without the UI. This is useful when you need to interact with the BisonJibPay API programmatically.
537
+
538
+ ### Installation & Import
539
+
540
+ The API class is automatically exported when you load the component:
541
+
542
+ ```javascript
543
+ // ES Module
544
+ import { BisonJibPayAPI } from './component.js';
545
+
546
+ // Or access from window (script tag)
547
+ const BisonJibPayAPI = window.BisonJibPayAPI;
548
+ ```
549
+
550
+ ### Basic Usage
551
+
552
+ ```javascript
553
+ // Create API instance
554
+ const api = new BisonJibPayAPI(
555
+ 'https://bison-jib-development.azurewebsites.net',
556
+ 'YOUR_EMBEDDABLE_KEY'
557
+ );
558
+
559
+ // Validate operator email
560
+ try {
561
+ const result = await api.validateOperatorEmail(
562
+ 'operator@example.com',
563
+ 'OP123456'
564
+ );
565
+ console.log('Operator email is valid:', result);
566
+ } catch (error) {
567
+ console.error('Validation failed:', error);
568
+ }
569
+
570
+ // Register operator
571
+ const formData = new FormData();
572
+ formData.append('businessName', 'Acme Corp');
573
+ formData.append('businessEmail', 'contact@acme.com');
574
+ // ... add more fields
575
+
576
+ try {
577
+ const result = await api.registerOperator(formData);
578
+ console.log('Operator registered successfully:', result);
579
+ } catch (error) {
580
+ console.error('Registration failed:', error);
581
+ }
582
+ ```
583
+
584
+ ### API Methods
585
+
586
+ #### `validateOperatorEmail(email, operatorId)`
587
+ Validates an operator email address.
588
+
589
+ **Parameters:**
590
+ - `email` (string) - The operator email address to validate
591
+ - `operatorId` (string) - The operator ID to validate
592
+
593
+ **Returns:**
594
+ - Promise resolving to the API response
595
+
596
+ **Example:**
597
+ ```javascript
598
+ const result = await api.validateOperatorEmail('operator@company.com', 'OP123456');
599
+ ```
600
+
601
+ #### `registerOperator(formData)`
602
+ Registers a new operator with complete form data.
603
+
604
+ **Parameters:**
605
+ - `formData` (FormData) - FormData object containing all operator information
606
+
607
+ **Returns:**
608
+ - Promise resolving to the API response
609
+
610
+ **Example:**
611
+ ```javascript
612
+ const formData = new FormData();
613
+ formData.append('businessName', 'Acme Corp');
614
+ formData.append('businessEmail', 'contact@acme.com');
615
+ formData.append('ein', '12-3456789');
616
+ // ... add all required fields
617
+
618
+ const result = await api.registerOperator(formData);
619
+ ```
620
+
621
+ ### React Integration Example
622
+
623
+ ```jsx
624
+ import { useEffect, useState } from 'react';
625
+ import { BisonJibPayAPI } from './component.js';
626
+
627
+ function EmailValidator() {
628
+ const [api] = useState(() => new BisonJibPayAPI(
629
+ 'https://bison-jib-development.azurewebsites.net',
630
+ 'YOUR_KEY'
631
+ ));
632
+ const [email, setEmail] = useState('');
633
+ const [operatorId, setOperatorId] = useState('');
634
+ const [isValid, setIsValid] = useState(null);
635
+ const [isLoading, setIsLoading] = useState(false);
636
+
637
+ const validateEmail = async () => {
638
+ setIsLoading(true);
639
+ try {
640
+ await api.validateOperatorEmail(email, operatorId);
641
+ setIsValid(true);
642
+ } catch (error) {
643
+ setIsValid(false);
644
+ console.error('Validation failed:', error);
645
+ } finally {
646
+ setIsLoading(false);
647
+ }
648
+ };
649
+
650
+ return (
651
+ <div>
652
+ <input
653
+ type="text"
654
+ value={operatorId}
655
+ onChange={(e) => setOperatorId(e.target.value)}
656
+ placeholder="Enter operator ID"
657
+ />
658
+ <input
659
+ type="email"
660
+ value={email}
661
+ onChange={(e) => setEmail(e.target.value)}
662
+ placeholder="Enter operator email"
663
+ />
664
+ <button onClick={validateEmail} disabled={isLoading}>
665
+ {isLoading ? 'Validating...' : 'Validate'}
666
+ </button>
667
+ {isValid !== null && (
668
+ <p>{isValid ? '✓ Valid' : '✗ Invalid'}</p>
669
+ )}
670
+ </div>
671
+ );
672
+ }
673
+ ```
674
+
675
+ ### Error Handling
676
+
677
+ The API methods throw structured errors that you can catch:
678
+
679
+ ```javascript
680
+ try {
681
+ await api.validateOperatorEmail('invalid@email.com', 'OP123456');
682
+ } catch (error) {
683
+ console.error('Status:', error.status);
684
+ console.error('Message:', error.data.message);
685
+ console.error('Errors:', error.data.errors);
686
+ }
687
+ ```
688
+
689
+ Error structure:
690
+ ```javascript
691
+ {
692
+ status: 400, // HTTP status code
693
+ data: {
694
+ success: false,
695
+ message: "Validation failed",
696
+ errors: ["Email does not exist in our system"]
697
+ }
698
+ }
699
+ ```
700
+
701
+ ### Using with Different Environments
702
+
703
+ ```javascript
704
+ // Development
705
+ const devApi = new BisonJibPayAPI(
706
+ 'https://bison-jib-development.azurewebsites.net',
707
+ 'DEV_KEY'
708
+ );
709
+
710
+ // Production
711
+ const prodApi = new BisonJibPayAPI(
712
+ 'https://bison-jib-production.azurewebsites.net',
713
+ 'PROD_KEY'
714
+ );
715
+
716
+ // Use environment variables
717
+ const api = new BisonJibPayAPI(
718
+ process.env.REACT_APP_API_URL,
719
+ process.env.REACT_APP_EMBEDDABLE_KEY
720
+ );
721
+ ```
722
+
723
+ ---
724
+
725
+ ## API Integration
726
+
727
+ The component is designed to work with the BisonJibPay API. Configure your endpoints:
728
+
729
+ ```html
730
+ <operator-onboarding
731
+ api-base-url="https://your-api-domain.com"
732
+ embeddable-key="your-embeddable-key">
733
+ </operator-onboarding>
734
+ ```
735
+
736
+ ### API Endpoints Used
737
+
738
+ - `POST /api/embeddable/validate/operator-email` - Validate operator email
739
+ - `POST /api/embeddable/operator-registration` - Register operator with form data
740
+
741
+ ---
742
+
743
+ ## License
744
+
745
+ MIT
746
+
747
+ ## Author
748
+
749
+ @kfajardo