A simple demo for integrated moov drops turned into web components
npm install web-components-moovA complete, self-contained web component for operator onboarding with 4-step stepper form, file uploads, validations, and success page.
``html`
`html`
When submitted, form data is automatically logged to console and success page is shown.
---
The component can be configured with optional attributes for API integration:
`html`
embeddable-key="your-embeddable-key">
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| api-base-url | String | https://bison-jib-development.azurewebsites.net | Base URL for API endpoints |embeddable-key
| | String | Default key provided | Authentication key for API requests |on-success
| | String | - | Name of global function to call on success |on-error
| | String | - | Name of global function to call on error |on-load
| | String | - | JSON string or global variable name for initial data |
---
The onboarding process consists of a 4-step stepper form:
1. Business Details - Company information and address
2. Representatives (Optional) - Add business representatives
3. Bank Account - Link bank account details
4. Underwriting - Upload required documents
---
---
`javascript
const component = document.querySelector('operator-onboarding');
// Pre-populate with existing data
component.onLoad = {
businessDetails: {
businessName: 'Acme Corp',
doingBusinessAs: 'Acme',
ein: '12-3456789',
businessWebsite: 'https://acme.com',
businessPhoneNumber: '5551234567',
businessEmail: 'contact@acme.com',
BusinessAddress1: '123 Main St',
businessCity: 'San Francisco',
businessState: 'CA',
businessPostalCode: '94105'
},
representatives: [
{
representativeFirstName: 'John',
representativeLastName: 'Doe',
representativeJobTitle: 'CEO',
representativePhone: '5559876543',
representativeEmail: 'john@company.com',
representativeDateOfBirth: '1980-01-15',
representativeAddress: '456 Oak Ave',
representativeCity: 'San Francisco',
representativeState: 'CA',
representativeZip: '94105'
}
],
underwriting: {
underwritingDocuments: [] // Can be pre-populated with File objects
},
bankDetails: {
bankAccountHolderName: 'Acme Corp',
bankAccountType: 'checking',
bankRoutingNumber: '123456789',
bankAccountNumber: '987654321'
}
};
`
`html`
`html
`
---
Perfect for React, Vue, Angular, and vanilla JavaScript:
`javascript
const component = document.querySelector('operator-onboarding');
// Success callback
component.onSuccess = (formData) => {
console.log('Onboarding complete!', formData);
// Send to your backend
fetch('/api/operators/onboard', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
// Close your modal
closeModal();
};
// Error callback
component.onError = (errorData) => {
console.error('Onboarding error:', errorData);
if (errorData.action === 'resubmit') {
// User clicked resubmit button
console.log('User wants to retry submission');
}
};
`
Good for simple cases with global functions:
`html
on-error="handleError">
`
Listen to custom events:
`javascript
// Success event
component.addEventListener('formComplete', (event) => {
const formData = event.detail;
console.log('Form completed!', formData);
closeModal();
});
// Submission failure event
component.addEventListener('submissionFailed', (event) => {
const errorData = event.detail;
console.error('Submission failed:', errorData);
});
`
---
`jsx
import { useEffect, useRef } from 'react';
function OnboardingModal({ isOpen, onClose }) {
const componentRef = useRef(null);
useEffect(() => {
if (componentRef.current) {
// Success handler
componentRef.current.onSuccess = (data) => {
console.log('Onboarding complete:', data);
// Send to API
fetch('/api/onboard', {
method: 'POST',
body: JSON.stringify(data)
});
onClose();
};
// Error handler
componentRef.current.onError = (errorData) => {
console.error('Error:', errorData);
// Handle errors appropriately
};
}
}, [onClose]);
if (!isOpen) return null;
return (
$3
`jsx
function EditOperatorModal({ operatorId, isOpen, onClose }) {
const componentRef = useRef(null);
const [initialData, setInitialData] = useState(null);
useEffect(() => {
if (isOpen && operatorId) {
// Fetch existing operator data
fetch(/api/operators/${operatorId})
.then(res => res.json())
.then(data => setInitialData(data));
}
}, [isOpen, operatorId]);
useEffect(() => {
if (componentRef.current) {
// Pre-populate form
if (initialData) {
componentRef.current.onLoad = initialData;
}
// Set success handler
componentRef.current.onSuccess = (updatedData) => {
fetch(/api/operators/${operatorId}, {
method: 'PUT',
body: JSON.stringify(updatedData)
});
onClose();
};
}
}, [initialData, operatorId, onClose]);
if (!isOpen) return null;
return (
);
}
`---
Data Structure
The component returns a complete data object:
`javascript
{
"businessDetails": {
"businessName": "Acme Corp",
"doingBusinessAs": "Acme",
"ein": "12-3456789",
"businessWebsite": "https://acme.com",
"businessPhoneNumber": "(555) 123-4567",
"businessEmail": "contact@acme.com",
"BusinessAddress1": "123 Main St",
"businessCity": "San Francisco",
"businessState": "CA",
"businessPostalCode": "94105"
},
"representatives": [
{
"id": "uuid-here",
"representativeFirstName": "John",
"representativeLastName": "Doe",
"representativeJobTitle": "CEO",
"representativePhone": "(555) 987-6543",
"representativeEmail": "john@company.com",
"representativeDateOfBirth": "1980-01-15",
"representativeAddress": "456 Oak Ave",
"representativeCity": "San Francisco",
"representativeState": "CA",
"representativeZip": "94105"
}
],
"underwriting": {
"underwritingDocuments": [
// Array of File objects
File { name: "document.pdf", size: 1234567, type: "application/pdf" }
]
},
"bankDetails": {
"bankAccountHolderName": "Acme Corp",
"bankAccountType": "checking",
"bankRoutingNumber": "123456789",
"bankAccountNumber": "987654321"
}
}
`---
Features
✅ 4-Step Stepper Form - Visual progress indicator
✅ Field Validation - Real-time validation on blur
✅ Auto-Formatting - Phone numbers, EIN, URLs
✅ File Upload - Drag-and-drop with validation
✅ CRUD Representatives - Add/remove multiple reps
✅ Error Handling - Verification and submission failures
✅ Success Page - Animated completion screen
✅ Framework Friendly - Easy integration with React, Vue, etc.
✅ Shadow DOM - Fully encapsulated styles
✅ Zero Dependencies - Pure vanilla JavaScript
✅ API Integration - Ready for backend integration
---
API Reference
$3
| Property | Type | Description |
|----------|------|-------------|
|
onSuccess | Function | Callback function called when form is successfully submitted. Receives complete form data as parameter. |
| onError | Function | Callback function called when submission fails. Receives error data as parameter. |
| onLoad | Object | Pre-populate form fields with initial data. Accepts partial or complete form data object. |
| apiBaseURL | String | Base URL for API endpoints. |
| embeddableKey | String | Authentication key for API requests. |$3
| Event | Detail | Description |
|-------|--------|-------------|
|
formComplete | Object | Emitted when form is successfully submitted. event.detail contains complete form data. |
| submissionFailed | Object | Emitted when form submission fails. event.detail contains error information. |$3
| Function | Parameters | Returns | Description |
|----------|------------|---------|-------------|
|
verifyOperator(operatorEmail, mockResult) | operatorEmail: string, mockResult: boolean | boolean | Verify if an operator email exists. |---
Operator Verification
You can verify operator emails:
`javascript
// Check if operator exists
const exists = verifyOperator('operator@company.com', true);if (exists) {
// Proceed with operation
console.log('Operator verified');
} else {
// Show error
console.error('Operator not found');
}
`---
Error Handling
The component provides comprehensive error handling:
$3
When form submission fails:
`javascript
component.addEventListener('submissionFailed', (event) => {
const { formData, message, timestamp } = event.detail;
console.error('Submission failed:', message);
// Retry or show error
});// Or use callback
component.onError = (errorData) => {
if (errorData.action === 'resubmit') {
// User clicked resubmit button
// Your retry logic here
}
};
`---
Modal Integration Example
`html
`---
File Upload Validation
The underwriting step includes file upload with the following restrictions:
- Maximum files: 10
- Maximum size per file: 10MB
- Allowed formats: PDF, JPG, JPEG, PNG, DOC, DOCX
- Validation: Real-time with error messages
Files are validated on both drag-and-drop and browse selection. Invalid files are rejected with clear error messages.
---
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Any browser supporting Custom Elements V1 and Shadow DOM
---
BisonJibPayAPI - Direct API Access
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.$3
The API class is automatically exported when you load the component:
`javascript
// ES Module
import { BisonJibPayAPI } from './component.js';// Or access from window (script tag)
const BisonJibPayAPI = window.BisonJibPayAPI;
`$3
`javascript
// Create API instance
const api = new BisonJibPayAPI(
'https://bison-jib-development.azurewebsites.net',
'YOUR_EMBEDDABLE_KEY'
);// Validate operator email
try {
const result = await api.validateOperatorEmail(
'operator@example.com',
'OP123456'
);
console.log('Operator email is valid:', result);
} catch (error) {
console.error('Validation failed:', error);
}
// Register operator
const formData = new FormData();
formData.append('businessName', 'Acme Corp');
formData.append('businessEmail', 'contact@acme.com');
// ... add more fields
try {
const result = await api.registerOperator(formData);
console.log('Operator registered successfully:', result);
} catch (error) {
console.error('Registration failed:', error);
}
`$3
####
validateOperatorEmail(email, operatorId)
Validates an operator email address.Parameters:
-
email (string) - The operator email address to validate
- operatorId (string) - The operator ID to validateReturns:
- Promise resolving to the API response
Example:
`javascript
const result = await api.validateOperatorEmail('operator@company.com', 'OP123456');
`####
registerOperator(formData)
Registers a new operator with complete form data.Parameters:
-
formData (FormData) - FormData object containing all operator informationReturns:
- Promise resolving to the API response
Example:
`javascript
const formData = new FormData();
formData.append('businessName', 'Acme Corp');
formData.append('businessEmail', 'contact@acme.com');
formData.append('ein', '12-3456789');
// ... add all required fieldsconst result = await api.registerOperator(formData);
`$3
`jsx
import { useEffect, useState } from 'react';
import { BisonJibPayAPI } from './component.js';function EmailValidator() {
const [api] = useState(() => new BisonJibPayAPI(
'https://bison-jib-development.azurewebsites.net',
'YOUR_KEY'
));
const [email, setEmail] = useState('');
const [operatorId, setOperatorId] = useState('');
const [isValid, setIsValid] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const validateEmail = async () => {
setIsLoading(true);
try {
await api.validateOperatorEmail(email, operatorId);
setIsValid(true);
} catch (error) {
setIsValid(false);
console.error('Validation failed:', error);
} finally {
setIsLoading(false);
}
};
return (
type="text"
value={operatorId}
onChange={(e) => setOperatorId(e.target.value)}
placeholder="Enter operator ID"
/>
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter operator email"
/>
{isValid !== null && (
{isValid ? '✓ Valid' : '✗ Invalid'}
)}
);
}
`$3
The API methods throw structured errors that you can catch:
`javascript
try {
await api.validateOperatorEmail('invalid@email.com', 'OP123456');
} catch (error) {
console.error('Status:', error.status);
console.error('Message:', error.data.message);
console.error('Errors:', error.data.errors);
}
`Error structure:
`javascript
{
status: 400, // HTTP status code
data: {
success: false,
message: "Validation failed",
errors: ["Email does not exist in our system"]
}
}
`$3
`javascript
// Development
const devApi = new BisonJibPayAPI(
'https://bison-jib-development.azurewebsites.net',
'DEV_KEY'
);// Production
const prodApi = new BisonJibPayAPI(
'https://bison-jib-production.azurewebsites.net',
'PROD_KEY'
);
// Use environment variables
const api = new BisonJibPayAPI(
process.env.REACT_APP_API_URL,
process.env.REACT_APP_EMBEDDABLE_KEY
);
`---
API Integration
The component is designed to work with the BisonJibPay API. Configure your endpoints:
`html
api-base-url="https://your-api-domain.com"
embeddable-key="your-embeddable-key">
`$3
-
POST /api/embeddable/validate/operator-email - Validate operator email
- POST /api/embeddable/operator-registration` - Register operator with form data---
MIT
@kfajardo