Added client registration form
This commit is contained in:
parent
9f086d55ba
commit
87da60613a
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,70 +1,357 @@
|
|||
body {
|
||||
background-color: #212529;
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
}
|
||||
|
||||
.card-registration {
|
||||
border-radius: 15px;
|
||||
}
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.logo-placeholder img{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 196px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
.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;
|
||||
}
|
||||
|
||||
.card-registration .select-input.form-control[readonly]:not([disabled]) {
|
||||
font-size: 1rem;
|
||||
line-height: 2.15;
|
||||
padding-left: .75em;
|
||||
padding-right: .75em;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue