Added client registration form

This commit is contained in:
Aakash Mittal 2025-11-10 20:04:21 +05:30
parent 9f086d55ba
commit 87da60613a
9 changed files with 1527 additions and 82 deletions

155
package-lock.json generated
View File

@ -44,8 +44,11 @@
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"autoprefixer": "^10.4.21",
"babel-loader": "^9.2.1",
"copy-webpack-plugin": "^12.0.2",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.17",
"terser-webpack-plugin": "^5.3.11",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1",
@ -1827,9 +1830,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@ -2531,6 +2534,44 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -2991,9 +3032,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001692",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
"integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
"version": "1.0.30001754",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
"dev": true,
"funding": [
{
@ -3867,9 +3908,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4620,6 +4661,20 @@
"node": ">= 0.6"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "patreon",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -6498,6 +6553,25 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"license": "ISC"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@ -6676,6 +6750,16 @@
"node": ">=0.10.0"
}
},
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
@ -7138,6 +7222,42 @@
"node": ">=8"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@ -8186,6 +8306,16 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@ -8352,6 +8482,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwindcss": {
"version": "4.1.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",

View File

@ -1,70 +1,357 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
body {
background-color: #212529;
background-color: #fff;
color: #333;
}
.card-registration {
border-radius: 15px;
}
.card-registration .form-control {
border-radius: 0.5rem;
}
.form-label {
font-weight: 500;
}
.btn-warning {
background-color: #ffb300;
border: none;
color: #000;
font-weight: 600;
}
.btn-warning:hover {
background-color: #e6a000;
}
.form-outline .form-control+.form-label {
position: absolute;
top: 6px;
max-width: 90%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
left: .75rem;
padding-top: .37rem;
pointer-events: none;
transform-origin: 0 0;
transition: all .2s ease-out;
color: var(--mdb-form-control-label-color);
margin-bottom: 0;
background-color: #ffffff;
}
.form-outline .form-control {
outline: none;
box-shadow: none;
}
.form-outline {
position: relative;
.container {
display: flex;
height: 100vh;
width: 100%;
}
.form-outline .form-control.not-required:placeholder-shown + .form-label,
.form-outline .form-control.not-required:empty + .form-label {
top: 6px;
/* Sidebar Styles */
.sidebar {
display: none;
flex-direction: column;
width: 344px;
background: linear-gradient(to bottom, #f4f4f5, #d0f9f9, #cac5f7);
padding: 24px;
border-radius: 14px;
margin: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.form-outline .form-control:focus + .form-label,
.form-outline .form-control:valid + .form-label,
.form-outline .form-control.not-required:not(:placeholder-shown) + .form-label,
.form-outline .form-control.not-required:focus + .form-label {
top: -17px;
.logo {
display: flex;
justify-content: center;
margin-bottom: 32px;
}
.logo-placeholder img{
width: 100%;
height: auto;
max-width: 196px;
}
.card-registration .select-input.form-control[readonly]:not([disabled]) {
font-size: 1rem;
line-height: 2.15;
padding-left: .75em;
padding-right: .75em;
.progress-steps {
display: flex;
flex-direction: column;
gap: 8px;
}
.step {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 12px;
border: 2px solid transparent;
cursor: pointer;
position: relative;
}
.step.active {
border-color: #070065;
}
.step.completed .step-number {
background-color: #070065;
border-color: #070065;
color: white;
}
.step-number {
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #a1a1aa;
font-weight: 600;
color: #a1a1aa;
transition: all 0.3s ease;
}
.step.active .step-number {
border-color: #000;
color: #000;
}
.step-text {
font-size: 14px;
color: #a1a1aa;
}
.step.active .step-text {
color: #000;
}
.step-connector {
height: 24px;
width: 2px;
background-color: #a1a1aa;
margin-left: 16px;
position: absolute;
top: 46px;
left: 12px;
}
.step.active .step-connector {
background-color: #000;
}
/* Main Content Styles */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow-y: auto;
}
.mobile-progress {
background-color: #f4f4f5;
padding: 16px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow-x: auto;
}
.mobile-progress ol {
display: flex;
list-style: none;
gap: 12px;
min-width: max-content;
}
.mobile-progress .step {
flex-shrink: 0;
}
.form-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 0;
}
.form-card {
width: 100%;
max-width: 450px;
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
padding: 24px;
}
.form-title {
text-align: center;
font-size: 20px;
font-weight: 600;
margin-bottom: 24px;
color: #000;
}
.form-step {
display: none;
}
.form-step.active {
display: block;
}
.form-group {
margin-bottom: 16px;
}
.input-container {
position: relative;
background-color: #f9fafb;
border-radius: 6px;
overflow: hidden;
}
.input-label {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: #6b7280;
transition: all 0.2s ease;
pointer-events: none;
}
.input-field, .select-field {
width: 100%;
padding: 16px 12px;
border: none;
background: transparent;
outline: none;
font-size: 14px;
}
.select-field {
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
}
.input-field:focus + .input-label,
.input-field:not(:placeholder-shown) + .input-label,
.select-field:focus + .input-label,
.select-field:valid + .input-label {
top: 2px;
font-size: 12px;
transform: none;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.checkbox-group input {
width: 16px;
height: 16px;
}
.checkbox-group label {
font-size: 14px;
}
.form-actions {
display: flex;
justify-content: space-between;
margin-top: 24px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background-color: #070065;
color: white;
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
background-color: #f3f4f6;
color: #374151;
}
.btn-secondary:hover {
background-color: #e5e7eb;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.payment-option {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
cursor: pointer;
}
.payment-option.selected {
border-color: #070065;
background-color: #f0f9ff;
}
.payment-option h4 {
margin-bottom: 8px;
font-size: 16px;
}
.payment-option p {
font-size: 14px;
color: #6b7280;
}
.payment-details {
display: none;
margin-top: 16px;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background-color: #f9fafb;
}
.payment-details.active {
display: block;
}
.success-message {
text-align: center;
padding: 40px 20px;
}
.success-icon {
font-size: 48px;
color: #10b981;
margin-bottom: 16px;
}
.success-message h3 {
font-size: 24px;
margin-bottom: 8px;
}
.success-message p {
color: #6b7280;
margin-bottom: 24px;
}
.location-loading {
display: none;
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.row {
display: flex;
gap: 12px;
}
.row .form-group {
flex: 1;
}
/* Responsive styles */
@media (min-width: 1024px) {
.sidebar {
display: flex;
}
.mobile-progress {
display: none;
}
}

View File

@ -0,0 +1,531 @@
// // Example: Basic client-side validation or UX improvements
// document.addEventListener("DOMContentLoaded", function () {
// const form = document.querySelector("form");
// form.addEventListener("submit", async (e) => {
// e.preventDefault();
// handleSubmit();
// });
// });
// function handleSubmit() {
// // Example: collect form data manually
// const form = document.querySelector("form");
// const formData = new FormData(form);
// const payload = { userDetails: {} };
// formData.forEach((value, key) => {
// // handle nested names like userDetails[firstName]
// if (key.startsWith("userDetails[")) {
// const innerKey = key.match(/userDetails\[(.*?)\]/)[1];
// payload.userDetails[innerKey] = value;
// } else {
// payload[key] = value;
// }
// });
// // You could then send it via fetch:
// fetch("/api/v1/va/client/register", {
// method: "POST",
// body: JSON.stringify(payload),
// })
// .then((res) => res.json())
// .then((data) => {
// console.log("Server Response:", data);
// alert("Form submitted successfully!");
// })
// .catch((err) => console.error("Error:", err));
// }
const formData = {
userDetails: {}
};
// Current step tracking
let currentStep = 1;
// const statesByCountry = {
// 'US': [
// 'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware',
// 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky',
// 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi',
// 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico',
// 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania',
// 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont',
// 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'
// ],
// 'CA': [
// 'Alberta', 'British Columbia', 'Manitoba', 'New Brunswick', 'Newfoundland and Labrador',
// 'Nova Scotia', 'Ontario', 'Prince Edward Island', 'Quebec', 'Saskatchewan'
// ],
// 'UK': [
// 'England', 'Scotland', 'Wales', 'Northern Ireland'
// ],
// 'AU': [
// 'New South Wales', 'Queensland', 'South Australia', 'Tasmania', 'Victoria', 'Western Australia'
// ]
// };
// Function to navigate to any step (for tab navigation)
function goToStep(step) {
// Only allow navigation to completed steps or the next step
if (step > currentStep && step > getMaxCompletedStep() + 1) {
return;
}
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show target step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to get the maximum completed step
function getMaxCompletedStep() {
const completedSteps = document.querySelectorAll('.step.completed');
let maxStep = 0;
completedSteps.forEach(step => {
const stepNum = parseInt(step.getAttribute('data-step'));
if (stepNum > maxStep) maxStep = stepNum;
});
return maxStep;
}
// Function to navigate to next step
function nextStep(step) {
// Validate current step before proceeding
if (!validateStep(currentStep)) {
return;
}
// Save current step data
saveStepData(currentStep);
// Mark current step as completed
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#070065';
stepNumber.style.borderColor = '#070065';
stepNumber.style.color = 'white';
});
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show next step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to navigate to previous step
function prevStep(step) {
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Show previous step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
currentStep = step;
}
// Function to validate current step
function validateStep(step) {
let isValid = true;
const form = document.getElementById(getFormId(step));
if (form) {
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else {
field.style.border = '';
}
});
}
return isValid;
}
// Function to get form ID based on step
function getFormId(step) {
switch(step) {
case 1: return 'account-form';
case 2: return 'business-form';
case 3: return 'billing-form';
case 4: return 'signatory-form';
case 5: return 'payment-form';
default: return '';
}
}
// Function to save step data to formData object
function saveStepData(step) {
switch(step) {
case 1:
formData.userDetails.name = document.getElementById('company-name').value;
formData.userDetails.email = document.getElementById('email').value;
break;
case 2:
formData.userDetails.address = document.getElementById('address').value;
formData.userDetails.city = document.getElementById('city').value;
formData.userDetails.state = document.getElementById('state').value;
formData.userDetails.zip = document.getElementById('zip').value;
break;
case 3:
formData.userDetails.billingName = document.getElementById('billing-name').value;
formData.userDetails.billingAddress = document.getElementById('billing-address').value;
formData.userDetails.billingCity = document.getElementById('billing-city').value;
formData.userDetails.billingState = document.getElementById('billing-state').value;
formData.userDetails.billingZip = document.getElementById('billing-zip').value;
formData.userDetails.billingEmail = document.getElementById('billing-email').value;
break;
case 4:
formData.userDetails.position = document.getElementById('position').value;
formData.userDetails.signatoryName = document.getElementById('signatory-name').value;
formData.userDetails.signatoryEmail = document.getElementById('signatory-email').value;
break;
case 5:
// Payment method already handled in selectPaymentOption
break;
case 6:
formData.userDetails.paymentMethod = formData.payment?.type || '';
break;
}
}
// Function to select payment option
function selectPaymentOption(type) {
// Remove selected class from all options
document.querySelectorAll('.payment-option').forEach(option => {
option.classList.remove('selected');
});
document.querySelectorAll('.payment-details').forEach(details => {
details.classList.remove('active');
});
// Add selected class to clicked option
event.currentTarget.classList.add('selected');
if (type === 'card') {
document.getElementById('card-details').classList.add('active');
formData.userDetails.paymentMethod = 'card';
formData.userDetails.cardNumber = '';
formData.userDetails.expiryDate = '';
formData.userDetails.securityCode = '';
formData.userDetails.cardZip = '';
} else if (type === 'bank') {
document.getElementById('bank-details').classList.add('active');
formData.userDetails.paymentMethod = 'bank';
formData.userDetails.bankName = '';
formData.userDetails.accountNumber = '';
formData.userDetails.routingNumber = '';
formData.userDetails.accountType = '';
formData.userDetails.accountHolderName = '';
}
}
// Function to submit the form
async function submitForm() {
// Validate current step
if (!validateStep(currentStep)) {
return;
}
// Save current step data
saveStepData(currentStep);
// Save payment details based on selected method
if (formData.userDetails.paymentMethod === 'card') {
formData.userDetails.cardNumber = document.getElementById('card-number').value;
formData.userDetails.expiryDate = document.getElementById('expiry-date').value;
formData.userDetails.securityCode = document.getElementById('security-code').value;
formData.userDetails.cardZip = document.getElementById('card-zip').value;
} else if (formData.userDetails.paymentMethod === 'bank') {
formData.userDetails.bankName = document.getElementById('bank-name').value;
formData.userDetails.accountNumber = document.getElementById('account-number').value;
formData.userDetails.routingNumber = document.getElementById('routing-number').value;
formData.userDetails.accountType = document.getElementById('account-type').value;
formData.userDetails.accountHolderName = document.getElementById('account-holder-name').value;
}
// Show loading state
const submitBtn = document.querySelector('#payment-form .btn-primary');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Submitting...';
submitBtn.disabled = true;
try {
// Send data to server using fetch
const response = await fetch('/api/v1/va/client/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData)
});
if (response.ok) {
// Success - move to success step
// Mark current step as completed
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#070065';
stepNumber.style.borderColor = '#070065';
stepNumber.style.color = 'white';
});
nextStep(6);
} else {
// Handle server errors
alert('There was an error submitting your registration. Please try again.');
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
alert('There was a network error. Please check your connection and try again.');
} finally {
// Restore button state
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
}
// Function to reset the form
function resetForm() {
// Reset all form fields
document.querySelectorAll('input').forEach(input => {
input.value = '';
input.style.border = '';
});
document.querySelectorAll('select').forEach(select => {
select.value = '';
select.style.border = '';
});
// Reset payment selection
document.querySelectorAll('.payment-option').forEach(option => {
option.classList.remove('selected');
});
document.querySelectorAll('.payment-details').forEach(details => {
details.classList.remove('active');
});
// Reset form data
formData.userDetails = {};
document.querySelectorAll('.step').forEach(step => {
step.classList.remove('completed', 'active');
const stepNumber = step.querySelector('.step-number');
if (stepNumber) {
stepNumber.textContent = step.getAttribute('data-step');
stepNumber.style.backgroundColor = '';
stepNumber.style.borderColor = '#a1a1aa';
stepNumber.style.color = '#a1a1aa';
}
});
// Go back to step 1
document.getElementById('step-1').classList.add('active');
document.querySelectorAll('.step[data-step="1"]').forEach(el => {
el.classList.add('active');
});
currentStep = 1;
}
// Function to fetch states from API
async function fetchStates(countryCode) {
try {
// Replace with your actual API endpoint
const response = await fetch(`/api/states/${countryCode}`);
if (response.ok) {
const states = await response.json();
return states;
} else {
console.error('Failed to fetch states');
return [];
}
} catch (error) {
console.error('Error fetching states:', error);
return [];
}
}
// Function to populate states dropdown
function populateStates(countryCode, selectElementId) {
const selectElement = document.getElementById(selectElementId);
const states = statesByCountry[countryCode] || [];
// Clear existing options except the first empty one
while (selectElement.options.length > 1) {
selectElement.remove(1);
}
// Add states as options
states.forEach(state => {
const option = document.createElement('option');
option.value = state;
option.textContent = state;
selectElement.appendChild(option);
});
}
// Function to detect user location
function detectLocation() {
// Show loading indicators
document.getElementById('state-loading').style.display = 'block';
// Using a free IP geolocation API
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
// Hide loading indicators
document.getElementById('state-loading').style.display = 'none';
// Populate city and state based on detected location
document.getElementById('city').value = data.city || '';
document.getElementById('zip').value = data.postal || '';
// Populate states dropdown for the detected country
populateStates(data.country_code, 'state');
// Set the detected state
if (data.region) {
document.getElementById('state').value = data.region;
}
// Also populate billing states dropdown
populateStates(data.country_code, 'billing-state');
})
.catch(error => {
console.error('Error detecting location:', error);
// Hide loading indicators
document.getElementById('state-loading').style.display = 'none';
// Fallback to US states if detection fails
populateStates('US', 'state');
populateStates('US', 'billing-state');
});
}
// Initialize form interactions
document.addEventListener('DOMContentLoaded', function() {
// Add input event listeners to handle label animation
document.querySelectorAll('.input-field').forEach(input => {
input.addEventListener('input', function() {
if (this.value) {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
input.addEventListener('focus', function() {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
input.addEventListener('blur', function() {
if (!this.value) {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
});
// Handle select field label animation
document.querySelectorAll('.select-field').forEach(select => {
select.addEventListener('change', function() {
if (this.value) {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
select.addEventListener('focus', function() {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
select.addEventListener('blur', function() {
if (!this.value) {
this.nextElementSibling.style.top = '50%';
this.nextElementSibling.style.fontSize = '14px';
this.nextElementSibling.style.transform = 'translateY(-50%)';
}
});
});
// Handle use business address checkbox
document.getElementById('use-business-address').addEventListener('change', function() {
if (this.checked) {
document.getElementById('billing-name').value = formData.userDetails.name || '';
document.getElementById('billing-address').value = formData.userDetails.address || '';
document.getElementById('billing-city').value = formData.userDetails.city || '';
document.getElementById('billing-state').value = formData.userDetails.state || '';
document.getElementById('billing-zip').value = formData.userDetails.zip || '';
// Trigger input events to update labels
document.querySelectorAll('#billing-form .input-field').forEach(input => {
input.dispatchEvent(new Event('input'));
});
// Trigger change event for select
document.getElementById('billing-state').dispatchEvent(new Event('change'));
}
});
// Handle use account email checkbox
document.getElementById('use-account-email').addEventListener('change', function() {
if (this.checked) {
document.getElementById('billing-email').value = formData.account.email || '';
document.getElementById('billing-email').dispatchEvent(new Event('input'));
}
});
// Detect user location when the page loads
detectLocation();
});

View File

@ -0,0 +1,43 @@
import { ClientModel } from "../../../models/vaModel.js";
// Register a new VA
async function va_register_client(req, res) {
try {
const payload = req.body ? JSON.parse(req.body) : {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.name) {
return res.status(400).json({ success: false, message: "Missing required fields" });
}
// ✅ Check if user already exists by email
const existingUser = await ClientModel.findOne({ "userDetails.email": userDetails.email.toLowerCase() });
if (existingUser) {
return res.status(409).json({ success: false, message: "User already exists with this email" });
}
// sanitize/construct allowed fields
const toCreate = {
userDetails: {
name: userDetails.name,
email: userDetails.email,
dob: userDetails.dob || null,
bankName: userDetails.bankName || null,
accountHolderName: userDetails.accountHolderName || null,
accountNumber: userDetails.accountNumber || null,
routingNumber: userDetails.routingNumber || null,
},
vaIds: payload.vaIds || [],
projectIds: payload.projectIds || [],
};
const created = await ClientModel.create(toCreate);
return res.status(201).json({ success: true, data: created });
} catch (error) {
console.error("va_register_client error:", error);
return res.status(500).json({ success: false, message: "Internal Server Error" });
}
}
export { va_register_client };

View File

@ -4,7 +4,7 @@ import { VAModel } from "../../../models/vaModel.js";
// Register a new VA
async function va_register(req, res) {
try {
const payload = req.body || {};
const payload = req.body ? JSON.parse(req.body) : {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.password) {

View File

@ -73,16 +73,36 @@ const projectSchema = new Schema({
],
});
/* ---------------------- Client Schema ---------------------- */
const clientSchema = new Schema({
userDetails: {
/* ---------------------- Client Details Schema ---------------------- */
const clientDetailsSchema = new Schema({
name: {
type: String,
},
email: {
type: String,
required: true,
unique: true,
},
dob: {
type: String,
},
bankName: {
type: String,
},
accountHolderName: {
type: String,
},
accountNumber: {
type: String,
},
routingNumber: {
type: String,
},
});
/* ---------------------- Client Schema ---------------------- */
const clientSchema = new Schema({
userDetails: clientDetailsSchema,
vaIds: [
{
type: Types.ObjectId,
@ -95,9 +115,14 @@ const clientSchema = new Schema({
ref: "Project",
},
],
totalLoggedHours: {
type: Number,
accessToken: {
type: String,
default: null
},
refreshToken: {
type: String,
default: null
}
});

View File

@ -3,6 +3,7 @@ const vaRoutes = express.Router();
import { va_username_callback } from "../../controllers/va/vaUsername.controller.js";
import { va_register } from "../../controllers/va/vaProfile/vaRegister.controller.js";
import { va_register_client } from "../../controllers/va/vaClient/vaClient.controller.js";
import { va_profile_callback } from "../../controllers/va/vaProfile/vaProfile.controller.js";
import { va_profile_update_callback } from "../../controllers/va/vaProfile/vaProfileUpdate.controller.js";
import { va_login_callback } from "../../controllers/va/vaAuth/vaLogin.controller.js";
@ -27,21 +28,22 @@ vaRoutes.post("/login", va_login_callback); // VA Callback for login VA
vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for login VA
vaRoutes.post("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token
vaRoutes.post("/register", va_register); // Register a new VA
vaRoutes.post("/client/register", va_register_client); // Register a new VA
vaRoutes.get("/profile", authValidator, va_profile_callback); // Get VA Profile
vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback
vaRoutes.get("/:userName", va_username_callback); // VA Username Callback (NC)
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)
vaRoutes.get("/search", va_filter_search);
vaRoutes.get("/clients/:clientID", va_clients_callback); // VA Clients Callback for getting all clients or specific client by ID
vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callback); // VA Clients Projects Callback
vaRoutes.get("/clients/:clientID", va_clients_callback); // VA Clients Callback for getting all clients or specific client by ID (NC)
vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callback); // VA Clients Projects Callback (NC)

View File

@ -36,6 +36,10 @@ app.get('/varegister', (req, res) => {
res.render('va_register', { title: "VA Registration" });
});
app.get('/clientregister', (req, res) => {
res.render('va_client_register', { title: "Client Registration" });
});
app.get('/health', (req, res) => {
res.status(201).send({ status: "Server is healthy" });
})

View File

@ -0,0 +1,416 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VA Registration</title>
<!-- Bootstrap -->
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> -->
<link href="/public/css/va-register.css" rel="stylesheet">
</head>
<body>
<!-- <section class="h-100 py-5">
<div class="container">
<div class="row justify-content-center align-items-center">
<div class="col-lg-10 col-xl-9">
<div class="card card-registration shadow-lg">
<div class="row g-0">
<div class="col-xl-5 d-none d-xl-block">
<img
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-registration/img4.webp"
alt="Registration"
class="img-fluid"
/>
</div>
<div class="col-xl-7 bg-white">
<div class="card-body text-black">
<h3 class="text-center mb-4">DocTracker Clients Registration</h3>
<form id="vaClientForm">
<div class="mb-3">
<label class="form-label" for="firstName">Client Name/Company *</label>
<input type="text" id="firstName" name="userDetails[name]" class="form-control form-control-lg" required>
</div>
<div class="mb-3">
<label class="form-label" for="email">Email *</label>
<input type="email" id="email" name="userDetails[email]" class="form-control form-control-lg" required>
</div>
<div class="mb-3">
<label class="form-label" for="dob">Date of Birth</label>
<input type="date" id="dob" name="userDetails[dob]" class="form-control form-control-lg">
</div>
<h5>Bank Details</h5>
<div class="mb-3">
<label class="form-label" for="bankName">Bank Name</label>
<input type="text" id="bankName" name="userDetails[bankName]" class="form-control form-control-lg" required>
</div>
<div class="mb-3">
<label class="form-label" for="accountHolderName">Account Holder Name</label>
<input type="text" id="accountHolderName" name="userDetails[accountHolderName]" class="form-control form-control-lg" required>
</div>
<div class="mb-3">
<label class="form-label" for="accountNumber">Account Number</label>
<input type="text" id="accountNumber" name="userDetails[accountNumber]" class="form-control form-control-lg" required>
</div>
<div class="mb-3">
<label class="form-label" for="routingNumber">Routing Number</label>
<input type="text" id="routingNumber" name="userDetails[routingNumber]" class="form-control form-control-lg" required>
</div>
<div class="d-flex justify-content-end gap-2 pt-3">
<button type="reset" class="btn btn-light btn-lg">Reset</button>
<button type="submit" class="btn btn-warning btn-lg">Submit</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section> -->
<div class="container">
<!-- Sidebar for desktop -->
<div class="sidebar">
<div class="logo">
<div class="logo-placeholder">
<img
src="https://images.docva.com/logo-colored-vertical.svg"
/>
</div>
</div>
<nav class="progress-steps">
<div class="step active" data-step="1" onclick="goToStep(1)">
<div class="step-number">1</div>
<!-- <div class="step-number">✓</div> -->
<div class="step-text">Create Your Account</div>
<div class="step-connector"></div>
</div>
<div class="step" data-step="2" onclick="goToStep(2)">
<div class="step-number">2</div>
<div class="step-text">Business Information</div>
<div class="step-connector"></div>
</div>
<div class="step" data-step="3" onclick="goToStep(3)">
<div class="step-number">3</div>
<div class="step-text">Billing Address</div>
<div class="step-connector"></div>
</div>
<div class="step" data-step="4" onclick="goToStep(4)">
<div class="step-number">4</div>
<div class="step-text">Authorized Signatory</div>
<div class="step-connector"></div>
</div>
<div class="step" data-step="5" onclick="goToStep(5)">
<div class="step-number">5</div>
<div class="step-text">Payment Details</div>
<div class="step-connector"></div>
</div>
<div class="step" data-step="6" onclick="goToStep(6)">
<div class="step-number">6</div>
<div class="step-text">Done</div>
</div>
</nav>
</div>
<!-- Main content -->
<div class="main-content">
<!-- Mobile progress indicator -->
<div class="mobile-progress">
<ol>
<li class="step active" data-step="1" onclick="goToStep(1)">
<div class="step-number">1</div>
<!-- <div class="step-number">✓</div> -->
<div class="step-text">Create Account</div>
</li>
<li class="step" data-step="2" onclick="goToStep(2)">
<div class="step-number">2</div>
<div class="step-text">Business Info</div>
</li>
<li class="step" data-step="3" onclick="goToStep(3)">
<div class="step-number">3</div>
<div class="step-text">Billing</div>
</li>
<li class="step" data-step="4" onclick="goToStep(4)">
<div class="step-number">4</div>
<div class="step-text">Signatory</div>
</li>
<li class="step" data-step="5" onclick="goToStep(5)">
<div class="step-number">5</div>
<div class="step-text">Payment</div>
</li>
<li class="step" data-step="6" onclick="goToStep(6)">
<div class="step-number">6</div>
<div class="step-text">Done</div>
</li>
</ol>
</div>
<!-- Form container -->
<div class="form-container">
<div class="form-card">
<!-- Step 1: Create Account -->
<div class="form-step active" id="step-1">
<h2 class="form-title">Create Your Account</h2>
<form id="account-form">
<div class="form-group">
<div class="input-container">
<input type="text" id="company-name" class="input-field" placeholder=" " required>
<label for="company-name" class="input-label">Company / Client Name *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="email" id="email" class="input-field" placeholder=" " required>
<label for="email" class="input-label">Email *</label>
</div>
</div>
<div class="form-actions">
<div></div> <!-- Empty div for spacing -->
<button type="button" class="btn btn-primary" onclick="nextStep(2)">Continue</button>
</div>
</form>
</div>
<!-- Step 2: Business Information -->
<div class="form-step" id="step-2">
<h2 class="form-title">Business Information</h2>
<form id="business-form">
<div class="form-group">
<div class="input-container">
<input type="text" id="address" class="input-field" placeholder=" " required>
<label for="address" class="input-label">Address *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="city" class="input-field" placeholder=" " required>
<label for="city" class="input-label">City *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<!-- <input type="text" id="state" class="input-field" placeholder=" " required> -->
<select id="state" class="select-field" required>
<option value=""></option>
<!-- States will be populated by JavaScript -->
</select>
<label for="state" class="input-label">State *</label>
<div class="location-loading" id="state-loading">Detecting your location...</div>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="zip" class="input-field" placeholder=" " required>
<label for="zip" class="input-label">ZIP *</label>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(1)">Back</button>
<button type="button" class="btn btn-primary" onclick="nextStep(3)">Continue</button>
</div>
</form>
</div>
<!-- Step 3: Billing Address -->
<div class="form-step" id="step-3">
<h2 class="form-title">Billing Address</h2>
<form id="billing-form">
<div class="checkbox-group">
<input type="checkbox" id="use-business-address">
<label for="use-business-address">Use business details for billing address</label>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-name" class="input-field" placeholder=" " required>
<label for="billing-name" class="input-label">Name *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-address" class="input-field" placeholder=" " required>
<label for="billing-address" class="input-label">Address *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-city" class="input-field" placeholder=" " required>
<label for="billing-city" class="input-label">City *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<!-- <input type="text" id="billing-state" class="input-field" placeholder=" " required> -->
<select id="billing-state" class="select-field" required>
<option value=""></option>
<!-- States will be populated by JavaScript -->
</select>
<label for="billing-state" class="input-label">State *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-zip" class="input-field" placeholder=" " required>
<label for="billing-zip" class="input-label">ZIP *</label>
</div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="use-account-email">
<label for="use-account-email">Use account email as primary billing contact</label>
</div>
<div class="form-group">
<div class="input-container">
<input type="email" id="billing-email" class="input-field" placeholder=" " required>
<label for="billing-email" class="input-label">Email Address *</label>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(2)">Back</button>
<button type="button" class="btn btn-primary" onclick="nextStep(4)">Continue</button>
</div>
</form>
</div>
<!-- Step 4: Authorized Signatory -->
<div class="form-step" id="step-4">
<h2 class="form-title">Authorized Signatory</h2>
<p style="margin-bottom: 20px; color: #6b7280; font-size: 14px;">
We will use the details below in the agreement that we will send to your prior to entering the project.
</p>
<form id="signatory-form">
<div class="form-group">
<div class="input-container">
<input type="text" id="position" class="input-field" placeholder=" " required>
<label for="position" class="input-label">Position (Owner / CEO / Manager / etc.) *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="signatory-name" class="input-field" placeholder=" " required>
<label for="signatory-name" class="input-label">Name *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="email" id="signatory-email" class="input-field" placeholder=" " required>
<label for="signatory-email" class="input-label">Email *</label>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(3)">Back</button>
<button type="button" class="btn btn-primary" onclick="nextStep(5)">Continue</button>
</div>
</form>
</div>
<!-- Step 5: Payment Details -->
<div class="form-step" id="step-5">
<h2 class="form-title">Payment Details</h2>
<form id="payment-form">
<div class="payment-option" onclick="selectPaymentOption('card')">
<h4>Credit or Debit Card</h4>
<p>Pay securely with your card</p>
</div>
<div id="card-details" class="payment-details">
<div class="form-group">
<div class="input-container">
<input type="text" id="card-number" class="input-field" placeholder=" " required>
<label for="card-number" class="input-label">Card Number *</label>
</div>
</div>
<div class="row">
<div class="form-group">
<div class="input-container">
<input type="text" id="expiry-date" class="input-field" placeholder=" " required>
<label for="expiry-date" class="input-label">Expiry Date (MM/YY) *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="security-code" class="input-field" placeholder=" " required>
<label for="security-code" class="input-label">Security Code *</label>
</div>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="card-zip" class="input-field" placeholder=" " required>
<label for="card-zip" class="input-label">ZIP Code *</label>
</div>
</div>
</div>
<div class="payment-option" onclick="selectPaymentOption('bank')">
<h4>Bank Account (ACR)</h4>
<p>Direct bank transfer</p>
</div>
<div id="bank-details" class="payment-details">
<div class="form-group">
<div class="input-container">
<input type="text" id="bank-name" class="input-field" placeholder=" " required>
<label for="bank-name" class="input-label">Bank Name *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="account-number" class="input-field" placeholder=" " required>
<label for="account-number" class="input-label">Account Number *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="routing-number" class="input-field" placeholder=" " required>
<label for="routing-number" class="input-label">Routing Number *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<select id="account-type" class="select-field" required>
<option value=""></option>
<option value="checking">Checking</option>
<option value="savings">Savings</option>
</select>
<label for="account-type" class="input-label">Account Type *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="account-holder-name" class="input-field" placeholder=" " required>
<label for="account-holder-name" class="input-label">Account Holder Name *</label>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(4)">Back</button>
<button type="button" class="btn btn-primary" onclick="submitForm()">Submit</button>
</div>
</form>
</div>
<!-- Step 6: Success -->
<div class="form-step" id="step-6">
<div class="success-message">
<div class="success-icon">✓</div>
<h3>Registration Complete!</h3>
<p>Your account has been successfully created. You will receive a confirmation email shortly.</p>
<button type="button" class="btn btn-primary" onclick="resetForm()">Create Another Account</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/public/js/va-client-register.js"></script>
</body>
</html>