Added va and client signup form

This commit is contained in:
Aakash Mittal 2025-11-11 19:44:32 +05:30
parent 87da60613a
commit f6e9eeffa9
18 changed files with 2297 additions and 689 deletions

View File

@ -3,49 +3,63 @@
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
}
body {
body {
background-color: #fff;
color: #333;
}
}
.container {
.container {
display: flex;
height: 100vh;
width: 100%;
}
}
/* Sidebar Styles */
.sidebar {
/* Sidebar Styles */
.sidebar {
display: none;
flex-direction: column;
width: 344px;
background: linear-gradient(to bottom, #f4f4f5, #d0f9f9, #cac5f7);
background: linear-gradient(to bottom, #0b57d0bd, #0b57d0d6, #0b57d0);
padding: 24px;
border-radius: 14px;
margin: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
}
.logo {
.logo {
display: flex;
justify-content: center;
margin-bottom: 32px;
}
.logo-placeholder img{
}
.logo-placeholder img{
width: 100%;
height: auto;
max-width: 196px;
}
}
.logo-placeholder h2 {
font-size: 32px;
font-weight: 700;
color: #000;
text-align: center;
}
.progress-steps {
.form-title-sec {
font-size: 24px;
font-weight: 700;
color: #000;
text-align: center;
margin-bottom: 24px;
}
.progress-steps {
display: flex;
flex-direction: column;
gap: 8px;
}
}
.step {
.step {
display: flex;
align-items: center;
gap: 12px;
@ -54,97 +68,97 @@
border: 2px solid transparent;
cursor: pointer;
position: relative;
}
}
.step.active {
border-color: #070065;
}
.step.active {
border-color: #000000;
}
.step.completed .step-number {
background-color: #070065;
border-color: #070065;
.step.completed .step-number {
background-color: #000000;
border-color: #000000;
color: white;
}
}
.step-number {
.step-number {
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #a1a1aa;
border: 2px solid #00000070;
font-weight: 600;
color: #a1a1aa;
color: #000000b5;
transition: all 0.3s ease;
}
}
.step.active .step-number {
.step.active .step-number {
border-color: #000;
color: #000;
}
}
.step-text {
.step-text {
font-size: 14px;
color: #a1a1aa;
}
color: #000000b5;
}
.step.active .step-text {
.step.active .step-text {
color: #000;
}
}
.step-connector {
.step-connector {
height: 24px;
width: 2px;
background-color: #a1a1aa;
background-color: #00000070;
margin-left: 16px;
position: absolute;
top: 46px;
left: 12px;
}
}
.step.active .step-connector {
.step.active .step-connector {
background-color: #000;
}
}
/* Main Content Styles */
.main-content {
/* Main Content Styles */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow-y: auto;
}
}
.mobile-progress {
.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 {
.mobile-progress ol {
display: flex;
list-style: none;
gap: 12px;
min-width: max-content;
}
}
.mobile-progress .step {
.mobile-progress .step {
flex-shrink: 0;
}
}
.form-container {
.form-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 0;
}
}
.form-card {
.form-card {
width: 100%;
max-width: 450px;
background: white;
@ -152,36 +166,37 @@
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
padding: 24px;
}
}
.form-title {
.form-title {
text-align: center;
font-size: 20px;
font-weight: 600;
margin-bottom: 24px;
color: #000;
}
}
.form-step {
.form-step {
display: none;
}
}
.form-step.active {
.form-step.active {
display: block;
}
}
.form-group {
.form-group {
margin-bottom: 16px;
}
}
.input-container {
.input-container {
position: relative;
background-color: #f9fafb;
border-radius: 6px;
overflow: hidden;
}
padding: 2px 0;
}
.input-label {
.input-label {
position: absolute;
left: 12px;
top: 50%;
@ -190,33 +205,35 @@
color: #6b7280;
transition: all 0.2s ease;
pointer-events: none;
}
}
.input-field, .select-field {
.input-field, .select-field {
width: 100%;
padding: 16px 12px;
padding: 18px 12px 14px;
border: none;
background: transparent;
outline: none;
font-size: 14px;
}
border-radius: 6px;
}
.select-field {
.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;
border-radius: 6px;
}
.input-field:focus + .input-label,
.input-field:not(:placeholder-shown) + .input-label,
.select-field:focus + .input-label,
.select-field:valid + .input-label {
.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;
@ -251,7 +268,7 @@
}
.btn-primary {
background-color: #070065;
background-color: #000000;
color: white;
}
@ -268,67 +285,84 @@
background-color: #e5e7eb;
}
.btn:disabled {
.btn-outline {
background-color: transparent;
border: 1px solid #070065;
color: #070065;
}
.btn-outline:hover {
background-color: #070065;
color: white;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.payment-option {
.btn-sm {
padding: 6px 12px;
font-size: 12px;
}
.payment-option {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
cursor: pointer;
}
}
.payment-option.selected {
border-color: #070065;
.payment-option.selected {
border-color: #000000;
background-color: #f0f9ff;
}
}
.payment-option h4 {
.payment-option h4 {
margin-bottom: 8px;
font-size: 16px;
}
}
.payment-option p {
.payment-option p {
font-size: 14px;
color: #6b7280;
}
}
.payment-details {
.payment-details {
display: none;
margin-top: 16px;
margin: 4px 0 16px;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background-color: #f9fafb;
background-color: #bdbdbd;
}
.payment-details.active {
display: block;
}
.success-message {
.success-message {
text-align: center;
padding: 40px 20px;
}
}
.success-icon {
.success-icon {
font-size: 48px;
color: #10b981;
margin-bottom: 16px;
}
}
.success-message h3 {
.success-message h3 {
font-size: 24px;
margin-bottom: 8px;
}
}
.success-message p {
.success-message p {
color: #6b7280;
margin-bottom: 24px;
}
}
.location-loading {
display: none;
@ -346,6 +380,85 @@
flex: 1;
}
.profile-incomplete {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1.5rem;
color: #856404;
}
.password-strength {
height: 5px;
background: #e9ecef;
border-radius: 5px;
margin-top: 5px;
overflow: hidden;
}
.password-strength-bar {
height: 100%;
width: 0;
border-radius: 5px;
transition: width 0.3s ease;
}
.password-weak {
background: #dc3545;
width: 25%;
}
.password-medium {
background: #ffc107;
width: 50%;
}
.password-strong {
background: #28a745;
width: 100%;
}
.password-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
}
.password-toggle {
background: none;
border: none;
color: #070065;
cursor: pointer;
font-size: 12px;
}
.password-generate {
background: none;
border: none;
color: #070065;
cursor: pointer;
font-size: 12px;
text-decoration: underline;
}
.form-text {
font-size: 12px;
color: #6b7280;
margin-top: 4px;
}
.detect-location {
background: none;
border: none;
color: #070065;
cursor: pointer;
font-size: 12px;
text-decoration: underline;
margin-top: 5px;
}
/* Responsive styles */
@media (min-width: 1024px) {
.sidebar {
@ -354,4 +467,9 @@
.mobile-progress {
display: none;
}
.form-title-sec {
font-size: 40px;
margin-bottom: 40px;
}
}

View File

@ -1,42 +1,121 @@
// // Example: Basic client-side validation or UX improvements
// document.addEventListener("DOMContentLoaded", function () {
// const form = document.querySelector("form");
// Email validation function
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// form.addEventListener("submit", async (e) => {
// e.preventDefault();
// Card type detection
function detectCardType(cardNumber) {
// Remove spaces and dashes
const cleanNumber = cardNumber.replace(/[\s-]/g, '');
// handleSubmit();
// });
// });
const cardPatterns = {
visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
mastercard: /^5[1-5][0-9]{14}$/,
amex: /^3[47][0-9]{13}$/,
discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
jcb: /^(?:2131|1800|35\d{3})\d{11}$/
};
// function handleSubmit() {
for (const [type, pattern] of Object.entries(cardPatterns)) {
if (pattern.test(cleanNumber)) {
return type;
}
}
return 'unknown';
}
// // Example: collect form data manually
// const form = document.querySelector("form");
// const formData = new FormData(form);
// const payload = { userDetails: {} };
// Card number validation (Luhn algorithm)
function validateCardNumber(cardNumber) {
const cleanNumber = cardNumber.replace(/[\s-]/g, '');
// 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));
// }
// Check if it's all digits and has proper length
if (!/^\d+$/.test(cleanNumber) || cleanNumber.length < 13 || cleanNumber.length > 19) {
return false;
}
// Luhn algorithm
let sum = 0;
let isEven = false;
for (let i = cleanNumber.length - 1; i >= 0; i--) {
let digit = parseInt(cleanNumber.charAt(i));
if (isEven) {
digit *= 2;
if (digit > 9) {
digit -= 9;
}
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
// Expiry date validation
function validateExpiryDate(expiryDate) {
const expiryRegex = /^(0[1-9]|1[0-2])\/([0-9]{2})$/;
if (!expiryRegex.test(expiryDate)) {
return false;
}
const [month, year] = expiryDate.split('/');
const expiry = new Date(2000 + parseInt(year), parseInt(month));
const currentDate = new Date();
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth());
return expiry >= currentMonth;
}
// CVV validation
function validateCVV(cvv, cardType) {
if (!/^\d+$/.test(cvv)) {
return false;
}
const cvvLengths = {
visa: 3,
mastercard: 3,
amex: 4,
discover: 3,
diners: 3,
jcb: 3
};
const expectedLength = cvvLengths[cardType] || 3;
return cvv.length === expectedLength;
}
// ZIP code validation
function validateZipCode(zipCode) {
return /^\d{5}(-\d{4})?$/.test(zipCode);
}
// Bank account validation
function validateBankAccount(accountNumber) {
return /^\d{8,17}$/.test(accountNumber.replace(/\s/g, ''));
}
// Routing number validation (basic US routing number check)
function validateRoutingNumber(routingNumber) {
const cleanRouting = routingNumber.replace(/\s/g, '');
if (!/^\d{9}$/.test(cleanRouting)) {
return false;
}
// Simple checksum validation
const digits = cleanRouting.split('').map(Number);
const sum = (3 * (digits[0] + digits[3] + digits[6])) +
(7 * (digits[1] + digits[4] + digits[7])) +
(digits[2] + digits[5] + digits[8]);
return sum % 10 === 0;
}
const formData = {
userDetails: {}
@ -45,27 +124,27 @@ const formData = {
// 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'
// ]
// };
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) {
@ -115,8 +194,8 @@ function nextStep(step) {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#070065';
stepNumber.style.borderColor = '#070065';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
@ -158,6 +237,163 @@ function validateStep(step) {
const form = document.getElementById(getFormId(step));
if (form) {
if (step === 5) {
const selectedPayment = document.querySelector('.payment-option.selected');
if (!selectedPayment) {
isValid = false;
alert('Please select a payment method');
return isValid;
}
const activePaymentDetails = document.querySelector('.payment-details.active');
if (activePaymentDetails) {
const requiredFields = activePaymentDetails.querySelectorAll('[required]');
// Validate card payment
if (formData.userDetails.paymentMethod === 'card') {
// Card number validation
const cardNumber = document.getElementById('card-number').value;
if (!validateCardNumber(cardNumber)) {
isValid = false;
document.getElementById('card-number').style.border = '1px solid #ef4444';
alert('Please enter a valid card number');
} else {
document.getElementById('card-number').style.border = '';
// Show card type
const cardType = detectCardType(cardNumber);
if (cardType !== 'unknown') {
// You can display card type to user if needed
console.log('Detected card type:', cardType);
}
}
// Expiry date validation
const expiryDate = document.getElementById('expiry-date').value;
if (!validateExpiryDate(expiryDate)) {
isValid = false;
document.getElementById('expiry-date').style.border = '1px solid #ef4444';
alert('Please enter a valid expiry date (MM/YY)');
} else {
document.getElementById('expiry-date').style.border = '';
}
// CVV validation
const cvv = document.getElementById('security-code').value;
const cardType = detectCardType(cardNumber);
if (!validateCVV(cvv, cardType)) {
isValid = false;
document.getElementById('security-code').style.border = '1px solid #ef4444';
alert(`Please enter a valid ${cardType === 'amex' ? '4-digit' : '3-digit'} CVV`);
} else {
document.getElementById('security-code').style.border = '';
}
// ZIP code validation
const zipCode = document.getElementById('card-zip').value;
if (!validateZipCode(zipCode)) {
isValid = false;
document.getElementById('card-zip').style.border = '1px solid #ef4444';
alert('Please enter a valid ZIP code');
} else {
document.getElementById('card-zip').style.border = '';
}
}
// Validate bank payment
else if (formData.userDetails.paymentMethod === 'bank') {
// Account number validation
const accountNumber = document.getElementById('account-number').value;
if (!validateBankAccount(accountNumber)) {
isValid = false;
document.getElementById('account-number').style.border = '1px solid #ef4444';
alert('Please enter a valid account number (8-17 digits)');
} else {
document.getElementById('account-number').style.border = '';
}
// Routing number validation
const routingNumber = document.getElementById('routing-number').value;
if (!validateRoutingNumber(routingNumber)) {
isValid = false;
document.getElementById('routing-number').style.border = '1px solid #ef4444';
alert('Please enter a valid routing number');
} else {
document.getElementById('routing-number').style.border = '';
}
// Basic validation for other bank fields
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else {
field.style.border = '';
}
});
}
}
} else if (step === 1) {
const email = document.getElementById('email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else if (step === 3) {
const email = document.getElementById('billing-email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('billing-email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('billing-email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else if (step === 4) {
const email = document.getElementById('signatory-email').value;
if (!validateEmail(email)) {
isValid = false;
document.getElementById('signatory-email').style.border = '1px solid #ef4444';
alert('Please enter a valid email address');
} else {
document.getElementById('signatory-email').style.border = '';
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (field.id !== 'email' && !field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else if (field.id !== 'email') {
field.style.border = '';
}
});
} else {
// For other steps, validate all required fields normally
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
@ -168,6 +404,7 @@ function validateStep(step) {
}
});
}
}
return isValid;
}
@ -213,9 +450,6 @@ function saveStepData(step) {
case 5:
// Payment method already handled in selectPaymentOption
break;
case 6:
formData.userDetails.paymentMethod = formData.payment?.type || '';
break;
}
}
@ -228,6 +462,10 @@ function selectPaymentOption(type) {
document.querySelectorAll('.payment-details').forEach(details => {
details.classList.remove('active');
// Remove required attributes from all payment fields
details.querySelectorAll('[required]').forEach(field => {
field.removeAttribute('required');
});
});
// Add selected class to clicked option
@ -236,18 +474,23 @@ function selectPaymentOption(type) {
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 = '';
// Add required attributes only to card fields
document.getElementById('card-number').setAttribute('required', 'true');
document.getElementById('expiry-date').setAttribute('required', 'true');
document.getElementById('security-code').setAttribute('required', 'true');
document.getElementById('card-zip').setAttribute('required', 'true');
} 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 = '';
// Add required attributes only to bank fields
document.getElementById('bank-name').setAttribute('required', 'true');
document.getElementById('account-number').setAttribute('required', 'true');
document.getElementById('routing-number').setAttribute('required', 'true');
document.getElementById('account-type').setAttribute('required', 'true');
document.getElementById('account-holder-name').setAttribute('required', 'true');
}
}
@ -282,8 +525,9 @@ async function submitForm() {
submitBtn.disabled = true;
try {
// console.log('Submitting form data:', formData);
// Send data to server using fetch
const response = await fetch('/api/v1/va/client/register', {
const response = await fetch('/api/v1/client/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -298,15 +542,19 @@ async function submitForm() {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#070065';
stepNumber.style.borderColor = '#070065';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
nextStep(6);
} else {
// Handle server errors
if (response.status === 409) {
alert('A user with this email already exists. Please use a different email.');
} else {
alert('There was an error submitting your registration. Please try again.');
}
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
@ -318,51 +566,6 @@ async function submitForm() {
}
}
// 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 {
@ -382,7 +585,7 @@ async function fetchStates(countryCode) {
}
// Function to populate states dropdown
function populateStates(countryCode, selectElementId) {
async function populateStates(countryCode, selectElementId) {
const selectElement = document.getElementById(selectElementId);
const states = statesByCountry[countryCode] || [];
@ -398,17 +601,34 @@ function populateStates(countryCode, selectElementId) {
option.textContent = state;
selectElement.appendChild(option);
});
// const selectElement = document.getElementById(selectElementId);
// // Clear existing options except the first empty one
// while (selectElement.options.length > 1) {
// selectElement.remove(1);
// }
// // Fetch states from API
// const states = await fetchStates(countryCode);
// // Add states as options
// states.forEach(state => {
// const option = document.createElement('option');
// option.value = state.code || state.name;
// option.textContent = state.name;
// selectElement.appendChild(option);
// });
}
// Function to detect user location
function detectLocation() {
async 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 => {
try {
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
// Hide loading indicators
document.getElementById('state-loading').style.display = 'none';
@ -417,36 +637,95 @@ function detectLocation() {
document.getElementById('zip').value = data.postal || '';
// Populate states dropdown for the detected country
populateStates(data.country_code, 'state');
await populateStates(data.country_code, 'state');
// Set the detected state
if (data.region) {
document.getElementById('state').value = data.region;
document.getElementById('state').dispatchEvent(new Event('change'));
}
// Also populate billing states dropdown
populateStates(data.country_code, 'billing-state');
})
.catch(error => {
await 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');
});
await populateStates('US', 'state');
await populateStates('US', 'billing-state');
}
}
// Initialize form interactions
document.addEventListener('DOMContentLoaded', function() {
// Real-time card number formatting and validation
const cardNumberInput = document.getElementById('card-number');
if (cardNumberInput) {
cardNumberInput.addEventListener('input', function(e) {
// Format card number with spaces
let value = e.target.value.replace(/\s/g, '');
let formattedValue = '';
for (let i = 0; i < value.length; i++) {
if (i > 0 && i % 4 === 0) {
formattedValue += ' ';
}
formattedValue += value[i];
}
e.target.value = formattedValue;
// Real-time card type detection
const cardType = detectCardType(value);
if (cardType !== 'unknown') {
// You can add visual feedback for card type here
console.log('Card type:', cardType);
}
});
}
// Real-time expiry date formatting
const expiryInput = document.getElementById('expiry-date');
if (expiryInput) {
expiryInput.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 2) {
value = value.substring(0, 2) + '/' + value.substring(2, 4);
}
e.target.value = value;
});
}
// Real-time CVV validation
const cvvInput = document.getElementById('security-code');
if (cvvInput) {
cvvInput.addEventListener('input', function(e) {
e.target.value = e.target.value.replace(/\D/g, '').substring(0, 4);
});
}
// Real-time email validation
const emailInputs = document.querySelectorAll('input[type="email"]');
emailInputs.forEach(emailInput => {
if (emailInput) {
emailInput.addEventListener('blur', function(e) {
if (e.target.value && !validateEmail(e.target.value)) {
e.target.style.border = '1px solid #ef4444';
} else {
e.target.style.border = '';
}
});
}
});
// 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.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
@ -457,7 +736,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
input.addEventListener('focus', function() {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
@ -475,7 +754,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.select-field').forEach(select => {
select.addEventListener('change', function() {
if (this.value) {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
} else {
@ -486,7 +765,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
select.addEventListener('focus', function() {
this.nextElementSibling.style.top = '2px';
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
});
@ -522,7 +801,7 @@ document.addEventListener('DOMContentLoaded', function() {
// 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').value = formData.userDetails.email || '';
document.getElementById('billing-email').dispatchEvent(new Event('input'));
}
});

View File

@ -1,46 +1,733 @@
// Example: Basic client-side validation or UX improvements
document.addEventListener("DOMContentLoaded", function () {
const form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const password = document.getElementById("password").value;
const cPassword = document.getElementById("cPassword").value;
// Form data object
const formData = {
userDetails: {}
};
if (password !== cPassword) {
alert("Passwords do not match!");
// Current step tracking
let currentStep = 1;
// Countries and states data
const countries = [
{ code: 'US', name: 'United States' },
{ code: 'CA', name: 'Canada' },
{ code: 'GB', name: 'United Kingdom' },
{ code: 'AU', name: 'Australia' },
{ code: 'IN', name: 'India' },
{ code: 'DE', name: 'Germany' },
{ code: 'FR', name: 'France' },
{ code: 'BR', name: 'Brazil' },
{ code: 'JP', name: 'Japan' },
{ code: 'CN', name: 'China' }
];
const states = {
'US': [
{ code: 'CA', name: 'California' }, { code: 'TX', name: 'Texas' }, { code: 'FL', name: 'Florida' },
{ code: 'NY', name: 'New York' }, { code: 'IL', name: 'Illinois' }, { code: 'PA', name: 'Pennsylvania' },
{ code: 'OH', name: 'Ohio' }, { code: 'GA', name: 'Georgia' }, { code: 'NC', name: 'North Carolina' },
{ code: 'MI', name: 'Michigan' }
],
'CA': [
{ code: 'ON', name: 'Ontario' }, { code: 'QC', name: 'Quebec' }, { code: 'BC', name: 'British Columbia' },
{ code: 'AB', name: 'Alberta' }, { code: 'MB', name: 'Manitoba' }, { code: 'SK', name: 'Saskatchewan' }
],
'GB': [
{ code: 'ENG', name: 'England' }, { code: 'SCT', name: 'Scotland' }, { code: 'WLS', name: 'Wales' },
{ code: 'NIR', name: 'Northern Ireland' }
],
'AU': [
{ code: 'NSW', name: 'New South Wales' }, { code: 'VIC', name: 'Victoria' }, { code: 'QLD', name: 'Queensland' },
{ code: 'WA', name: 'Western Australia' }, { code: 'SA', name: 'South Australia' }, { code: 'TAS', name: 'Tasmania' }
],
'IN': [
{ code: 'MH', name: 'Maharashtra' }, { code: 'TN', name: 'Tamil Nadu' }, { code: 'UP', name: 'Uttar Pradesh' },
{ code: 'KA', name: 'Karnataka' }, { code: 'GJ', name: 'Gujarat' }, { code: 'RJ', name: 'Rajasthan' }
],
'DE': [
{ code: 'BW', name: 'Baden-Württemberg' }, { code: 'BY', name: 'Bavaria' }, { code: 'BE', name: 'Berlin' },
{ code: 'HB', name: 'Bremen' }, { code: 'HH', name: 'Hamburg' }, { code: 'HE', name: 'Hesse' }
],
'FR': [
{ code: 'ARA', name: 'Auvergne-Rhône-Alpes' }, { code: 'BFC', name: 'Bourgogne-Franche-Comté' },
{ code: 'BRE', name: 'Brittany' }, { code: 'CVL', name: 'Centre-Val de Loire' },
{ code: 'COR', name: 'Corsica' }, { code: 'GES', name: 'Grand Est' }
],
'BR': [
{ code: 'SP', name: 'São Paulo' }, { code: 'RJ', name: 'Rio de Janeiro' }, { code: 'MG', name: 'Minas Gerais' },
{ code: 'BA', name: 'Bahia' }, { code: 'RS', name: 'Rio Grande do Sul' }, { code: 'PR', name: 'Paraná' }
],
'JP': [
{ code: 'TK', name: 'Tokyo' }, { code: 'OS', name: 'Osaka' }, { code: 'KY', name: 'Kyoto' },
{ code: 'HK', name: 'Hokkaido' }, { code: 'OK', name: 'Okinawa' }, { code: 'KN', name: 'Kanagawa' }
],
'CN': [
{ code: 'BJ', name: 'Beijing' }, { code: 'SH', name: 'Shanghai' }, { code: 'TJ', name: 'Tianjin' },
{ code: 'CQ', name: 'Chongqing' }, { code: 'GD', name: 'Guangdong' }, { code: 'JS', name: 'Jiangsu' }
]
};
// Initialize the form
document.addEventListener('DOMContentLoaded', function() {
// Populate country dropdown
const countrySelect = document.getElementById('country');
countrySelect.innerHTML = '<option value=""></option>';
countries.forEach(country => {
const option = document.createElement('option');
option.value = country.code;
option.textContent = country.name;
countrySelect.appendChild(option);
});
// Add event listener for country change
countrySelect.addEventListener('change', function() {
const countryCode = this.value;
const stateSelect = document.getElementById('state');
// Show loading
document.getElementById('state-loading').style.display = 'block';
// Clear current states
stateSelect.innerHTML = '<option value=""></option>';
// Simulate API delay
setTimeout(() => {
if (countryCode && states[countryCode]) {
states[countryCode].forEach(state => {
const option = document.createElement('option');
option.value = state.code;
option.textContent = state.name;
stateSelect.appendChild(option);
});
}
// Hide loading
document.getElementById('state-loading').style.display = 'none';
}, 500);
});
// Password strength indicator
document.getElementById('password').addEventListener('input', function() {
updatePasswordStrength(this.value);
});
// 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 = '4px';
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 = '4px';
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 = '4px';
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 = '4px';
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%)';
}
});
});
// Step navigation for desktop
document.querySelectorAll('.step').forEach(step => {
step.addEventListener('click', function() {
const stepNum = parseInt(this.getAttribute('data-step'));
if (stepNum <= currentStep || stepNum === getMaxCompletedStep() + 1) {
goToStep(stepNum);
}
});
});
// Step navigation for mobile
document.querySelectorAll('.mobile-step').forEach(step => {
step.addEventListener('click', function() {
const stepNum = parseInt(this.getAttribute('data-step'));
if (stepNum <= currentStep || stepNum === getMaxCompletedStep() + 1) {
goToStep(stepNum);
}
});
});
});
// 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;
}
handleSubmit();
// Hide current step
document.getElementById(`step-${currentStep}`).classList.remove('active');
document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
});
function handleSubmit() {
document.querySelectorAll(`.mobile-step[data-step="${currentStep}"]`).forEach(el => {
el.classList.remove('active');
});
// Example: collect form data manually
const form = document.querySelector("form");
const formData = new FormData(form);
const payload = { userDetails: {} };
// Show target step
document.getElementById(`step-${step}`).classList.add('active');
document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => {
el.classList.add('active');
});
formData.forEach((value, key) => {
// handle nested names like userDetails[firstName]
if (key.startsWith("userDetails[")) {
const innerKey = key.match(/userDetails\[(.*?)\]/)[1];
payload.userDetails[innerKey] = value;
document.querySelectorAll(`.mobile-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 = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
document.querySelectorAll(`.mobile-step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
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');
});
document.querySelectorAll(`.mobile-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');
});
document.querySelectorAll(`.mobile-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');
});
document.querySelectorAll(`.mobile-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');
});
document.querySelectorAll(`.mobile-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) {
// For account setup step
if (step === 1) {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// Email validation
if (!validateEmail(email)) {
alert('Please enter a valid email address');
isValid = false;
}
// Password validation
if (password.length < 8) {
alert('Password must be at least 8 characters long');
isValid = false;
}
if (password !== confirmPassword) {
document.getElementById('password-match-error').style.display = 'block';
isValid = false;
} else {
payload[key] = value;
document.getElementById('password-match-error').style.display = 'none';
}
}
// For personal information step
if (step === 2) {
const firstName = document.getElementById('firstName').value;
if (!firstName) {
document.getElementById('profile-incomplete').style.display = 'block';
isValid = false;
} else {
document.getElementById('profile-incomplete').style.display = 'none';
}
// Age validation
const age = document.getElementById('age').value;
if (age && (age < 18 || age > 100)) {
alert('You must be 18 years or older to register');
isValid = false;
}
// Country and state validation
const country = document.getElementById('country').value;
const state = document.getElementById('state').value;
if (!country) {
alert('Please select your country');
isValid = false;
}
if (!state) {
alert('Please select your state/region');
isValid = false;
}
// Validate other required fields
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
} else {
field.style.border = '';
}
});
// You could then send it via fetch:
fetch("/api/v1/va/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));
}
}
// For payment step
if (step === 4) {
const selectedPayment = document.querySelector('.payment-option.selected');
if (!selectedPayment) {
alert('Please select a payment method');
isValid = false;
} else {
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
if (paymentMethod === 'payoneer') {
const payoneerAccount = document.getElementById('payoneerAccount').value;
if (!payoneerAccount) {
alert('Please enter your Payoneer account number');
isValid = false;
}
} else if (paymentMethod === 'veem') {
const veemAccount = document.getElementById('veemAccount').value;
if (!veemAccount) {
alert('Please enter your Veem account number');
isValid = false;
}
}
}
}
// For other steps
if (step !== 1 && step !== 2 && step !== 4) {
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;
}
// Email validation function
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Function to get form ID based on step
function getFormId(step) {
switch(step) {
case 1: return 'account-form';
case 2: return 'personal-form';
case 3: return 'contact-form';
case 4: return 'payment-form';
default: return '';
}
}
// Function to save step data to formData object
function saveStepData(step) {
switch(step) {
case 1:
formData.userDetails.email = document.getElementById('email').value;
formData.userDetails.password = document.getElementById('password').value;
break;
case 2:
formData.userDetails.firstName = document.getElementById('firstName').value;
formData.userDetails.lastName = document.getElementById('lastName').value;
formData.userDetails.age = document.getElementById('age').value;
formData.userDetails.dob = document.getElementById('dob').value;
formData.userDetails.address = document.getElementById('address').value;
formData.userDetails.city = document.getElementById('city').value;
formData.userDetails.state = document.getElementById('state').value;
formData.userDetails.country = document.getElementById('country').value;
break;
case 3:
formData.userDetails.phone = document.getElementById('phone').value;
formData.userDetails.skype = document.getElementById('skype').value;
break;
case 4:
const selectedPayment = document.querySelector('.payment-option.selected');
if (selectedPayment) {
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
formData.userDetails.paymentMethod = paymentMethod;
if (paymentMethod === 'payoneer') {
formData.userDetails.payoneerAccount = document.getElementById('payoneerAccount').value;
} else if (paymentMethod === 'veem') {
formData.userDetails.veemAccount = document.getElementById('veemAccount').value;
}
}
break;
}
}
// Function to select payment method
function selectPaymentMethod(method) {
// Remove selected class from all options
document.querySelectorAll('.payment-option').forEach(option => {
option.classList.remove('selected');
});
// Hide all payment details
document.querySelectorAll('.payment-details').forEach(details => {
details.style.display = 'none';
});
// Add selected class to clicked option
event.currentTarget.classList.add('selected');
// Show appropriate payment details
if (method === 'payoneer') {
document.getElementById('payoneer-details').style.display = 'block';
} else if (method === 'veem') {
document.getElementById('veem-details').style.display = 'block';
}
}
// Function to generate a strong password
function generatePassword() {
const length = 12;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
// Ensure at least one of each required character type
password += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(Math.random() * 26)];
password += "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)];
password += "0123456789"[Math.floor(Math.random() * 10)];
password += "!@#$%^&*"[Math.floor(Math.random() * 8)];
// Fill the rest randomly
for (let i = password.length; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
// Shuffle the password
password = password.split('').sort(() => 0.5 - Math.random()).join('');
// Set the password field
document.getElementById('password').value = password;
document.getElementById('confirmPassword').value = password;
// Trigger input events to update UI
document.getElementById('password').dispatchEvent(new Event('input'));
document.getElementById('confirmPassword').dispatchEvent(new Event('input'));
// Update password strength indicator
updatePasswordStrength(password);
}
// Function to toggle password visibility
function togglePasswordVisibility() {
const passwordField = document.getElementById('password');
const confirmField = document.getElementById('confirmPassword');
const toggleButton = document.querySelector('.password-toggle');
if (passwordField.type === 'password') {
passwordField.type = 'text';
confirmField.type = 'text';
toggleButton.textContent = 'Hide Password';
} else {
passwordField.type = 'password';
confirmField.type = 'password';
toggleButton.textContent = 'Show Password';
}
}
// Function to update password strength indicator
function updatePasswordStrength(password) {
const strengthBar = document.getElementById('password-strength-bar');
// Reset classes
strengthBar.className = 'password-strength-bar';
if (password.length === 0) {
strengthBar.style.width = '0';
} else if (password.length < 6) {
strengthBar.classList.add('password-weak');
} else if (password.length < 10) {
strengthBar.classList.add('password-medium');
} else {
// Check for complexity
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
const complexityScore = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecial].filter(Boolean).length;
if (complexityScore >= 3) {
strengthBar.classList.add('password-strong');
} else {
strengthBar.classList.add('password-medium');
}
}
}
// Function to submit the form
async function submitForm() {
// Validate current step
if (!validateStep(currentStep)) {
return;
}
// Save current step data
saveStepData(currentStep);
// Show loading state
const submitBtn = document.querySelector('#payment-form .btn-primary');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Processing...';
submitBtn.disabled = true;
try {
// Send data to server using fetch
// console.log('Submitting form data:', formData);
const response = await fetch('/api/v1/va/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 = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
document.querySelectorAll(`.mobile-step[data-step="${currentStep}"]`).forEach(el => {
el.classList.add('completed');
const stepNumber = el.querySelector('.step-number');
stepNumber.textContent = '✓';
stepNumber.style.backgroundColor = '#000000';
stepNumber.style.borderColor = '#000000';
stepNumber.style.color = 'white';
});
nextStep(5);
} 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;
}
}
// Initialize form interactions
// document.addEventListener('DOMContentLoaded', function() {
// // Password strength indicator
// document.getElementById('password').addEventListener('input', function() {
// updatePasswordStrength(this.value);
// });
// // 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 = '4px';
// 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 = '4px';
// 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 = '4px';
// 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 = '4px';
// 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%)';
// }
// });
// });
// // Step navigation for desktop
// document.querySelectorAll('.step').forEach(step => {
// step.addEventListener('click', function() {
// const stepNum = parseInt(this.getAttribute('data-step'));
// if (stepNum <= currentStep || stepNum === getMaxCompletedStep() + 1) {
// goToStep(stepNum);
// }
// });
// });
// // Step navigation for mobile
// document.querySelectorAll('.mobile-step').forEach(step => {
// step.addEventListener('click', function() {
// const stepNum = parseInt(this.getAttribute('data-step'));
// if (stepNum <= currentStep || stepNum === getMaxCompletedStep() + 1) {
// goToStep(stepNum);
// }
// });
// });
// });

View File

@ -0,0 +1,65 @@
import jwt from "jsonwebtoken";
import { ClientModel } from "../../models/vaModel.js";
// Refresh Token VA
async function client_refresh_token_callback(req, res) {
try {
const { refreshToken } = req.body || {};
if (!refreshToken) {
return res.status(400).json({
success: false,
message: "Refresh token missing",
});
}
// 🔍 Verify refresh token
jwt.verify(
refreshToken,
process.env.JWT_SECRET_REFRESH_TOKEN || "DocTrackerSecretKeyRefreshToken",
async (err, decoded) => {
if (err) {
return res.status(403).json({
success: false,
message: "Invalid or expired refresh token",
});
}
// Check if user still exists
const user = await ClientModel.findById(decoded.id);
if (!user) {
return res.status(404).json({
success: false,
message: "User no longer exists",
});
}
// Generate a new access token
const newAccessToken = jwt.sign(
{
id: user._id,
email: user.userDetails.email,
name: user.userDetails.firstName,
},
process.env.JWT_SECRET_ACCESS_TOKEN || "DocTrackerAccessKey",
{ expiresIn: "24h" }
);
await ClientModel.updateOne({ "userDetails.email": user.userDetails.email }, {
$set: { accessToken: newAccessToken }
})
return res.status(200).json({
success: true,
message: "New access token generated",
accessToken: newAccessToken,
});
}
);
} catch (error) {
console.error("va_register error:", error);
return res.status(500).json({ success: false, message: "Internal Server Error" });
}
}
export { client_refresh_token_callback };

View File

@ -0,0 +1,76 @@
import VaClientRepository from "../../repository/vaClient.repository.js";
import VaClientService from "../../services/vaClient.service.js";
import logger from "../../config/logger.js";
const vaClientService = new VaClientService(new VaClientRepository());
async function va_register_client(req, res) {
logger.info("ClientController - Register Client Hit ...");
try {
const payload = req.body || {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.name) {
return res.status(400).json({
success: false,
message: "Missing required fields: name and email are required"
});
}
const created = await vaClientService.registerClient(payload);
logger.info(`ClientController - Client registered successfully: ${userDetails.email}`);
return res.status(201).json({
success: true,
data: created,
message: "Client registered successfully"
});
} catch (error) {
logger.error(`ClientController - Register error: ${error.message}`);
if (error.message === "User already exists with this email") {
return res.status(409).json({
success: false,
message: error.message
});
}
return res.status(500).json({
success: false,
message: "Internal Server Error"
});
}
}
// Additional controller methods can be added here
async function get_client_by_email(req, res) {
try {
const { email } = req.params;
const client = await vaClientService.getClientByEmail(email);
if (!client) {
return res.status(404).json({
success: false,
message: "Client not found"
});
}
return res.status(200).json({
success: true,
data: client
});
} catch (error) {
logger.error(`ClientController - Get client error: ${error.message}`);
return res.status(500).json({
success: false,
message: "Internal Server Error"
});
}
}
export {
va_register_client,
get_client_by_email
};

View File

@ -0,0 +1,79 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { ClientModel } from "../../models/vaModel.js";
// Login VA
async function va_login_client(req, res) {
try {
// const payload = req.body ? JSON.parse(req.body) : {};
const payload = req.body || {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.password) {
return res.status(400).json({ success: false, message: "Please provide email and password" });
}
// Find user by email
const user = await ClientModel.findOne({ "userDetails.email": userDetails.email });
if (!user) {
return res.status(404).json({ success: false, message: "Invalid credentials" });
}
// const saltRounds = 10;
// const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds);
// Compare password with stored hash
const isMatch = await bcrypt.compare(
userDetails.password,
user.userDetails.password
);
if (!isMatch) {
return res.status(401).json({ success: false, message: "Invalid password" });
}
// Generate JWT accessToken with 24h expiry
const accessToken = jwt.sign(
{
id: user._id,
email: user.userDetails.email,
name: user.userDetails.firstName,
},
process.env.JWT_SECRET_ACCESS_TOKEN || "DocTrackerSecretKey",
{ expiresIn: "24h" }
);
// Generate JWT refreshToken with 24h expiry
const refreshToken = jwt.sign(
{
id: user._id,
email: user.userDetails.email,
name: user.userDetails.firstName,
},
process.env.JWT_SECRET_REFRESH_TOKEN || "DocTrackerSecretKeyRefreshToken",
{ expiresIn: "30d" }
);
await ClientModel.updateOne({ "userDetails.email": userDetails.email }, {
$set: { accessToken, refreshToken }
})
// Return accessToken and user info
return res.status(200).json({
success: true,
message: "Login Successfull",
accessToken,
refreshToken,
user: {
id: user._id,
name: user.userDetails.firstName,
email: user.userDetails.email,
},
});
} catch (error) {
console.error("va_register error:", error);
return res.status(500).json({ success: false, message: "Internal Server Error" });
}
}
export { va_login_client };

View File

@ -1,43 +0,0 @@
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,17 +4,13 @@ import { VAModel } from "../../../models/vaModel.js";
// Register a new VA
async function va_register(req, res) {
try {
const payload = req.body ? JSON.parse(req.body) : {};
const payload = req.body|| {};
const { userDetails } = payload;
if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.password) {
return res.status(400).json({ success: false, message: "Missing required fields" });
}
if (userDetails.password !== userDetails.cPassword) {
return res.status(400).json({ success: false, message: "password not matching" });
}
// ✅ Check if user already exists by email
const existingUser = await VAModel.findOne({ "userDetails.email": userDetails.email.toLowerCase() });
if (existingUser) {
@ -33,11 +29,15 @@ async function va_register(req, res) {
dob: userDetails.dob || null,
age: userDetails.age || null,
address: userDetails.address || null,
city: userDetails.city || null,
state: userDetails.state || null,
country: userDetails.country || null,
password: hashedPassword,
bankName: userDetails.bankName || null,
accountHolderName: userDetails.accountHolderName || null,
accountNumber: userDetails.accountNumber || null,
routingNumber: userDetails.routingNumber || null,
paymentMethod: userDetails.paymentMethod || null,
payoneerAccount: userDetails.payoneerAccount || null,
veemAccount: userDetails.veemAccount || null,
phone: userDetails.phone || null,
skype: userDetails.skype || null,
},
tracking: payload.tracking || undefined,
clientIds: payload.clientIds || [],

View File

@ -25,19 +25,31 @@ const userDetailsSchema = new Schema({
address: {
type: String,
},
city: {
type: String,
},
state: {
type: String,
},
country: {
type: String,
},
password: {
type: String,
},
bankName: {
paymentMethod: {
type: String,
},
accountHolderName: {
payoneerAccount: {
type: String,
},
accountNumber: {
veemAccount: {
type: String,
},
routingNumber: {
phone: {
type: String,
},
skype: {
type: String,
},
});
@ -61,6 +73,9 @@ const projectSchema = new Schema({
type: String,
required: true,
},
hourlyRate: {
type: String,
},
totalLoggedHours: {
type: Number,
default: 0
@ -83,21 +98,78 @@ const clientDetailsSchema = new Schema({
required: true,
unique: true,
},
dob: {
address: {
type: String,
},
city: {
type: String,
},
state: {
type: String,
},
zip: {
type: String,
},
billingName: {
type: String,
},
billingEmail: {
type: String,
},
billingAddress: {
type: String,
},
billingCity: {
type: String,
},
billingState: {
type: String,
},
billingZip: {
type: String,
},
position: {
type: String,
},
signatoryName: {
type: String,
},
signatoryEmail: {
type: String,
},
paymentMethod: {
type: String,
},
bankName: {
type: String,
},
accountHolderName: {
type: String,
},
accountNumber: {
type: String,
},
routingNumber: {
type: String,
},
accountType: {
type: String,
},
accountHolderName: {
type: String,
},
cardNumber: {
type: String,
},
expiryDate: {
type: String,
},
securityCode: {
type: String,
},
cardZip: {
type: String,
},
password: {
type: String,
},
});
/* ---------------------- Client Schema ---------------------- */

View File

@ -0,0 +1,55 @@
import { ClientModel } from "../models/vaModel.js";
class VaClientRepository {
async create(data) {
try {
const newClient = new ClientModel({ ...data });
const response = await newClient.save();
return response;
} catch (error) {
throw error;
}
}
async findByEmail(email) {
try {
const response = await ClientModel.findOne({ "userDetails.email": email.toLowerCase() });
return response;
} catch (error) {
throw error;
}
}
async findById(id) {
try {
const response = await ClientModel.findById(id);
return response;
} catch (error) {
throw error;
}
}
async update(id, data) {
try {
const response = await ClientModel.findByIdAndUpdate(
id,
{ $set: { ...data } },
{ new: true }
);
return response;
} catch (error) {
throw error;
}
}
async delete(id) {
try {
const response = await ClientModel.findByIdAndDelete(id);
return response;
} catch (error) {
throw error;
}
}
}
export default VaClientRepository;

View File

@ -0,0 +1,29 @@
import express from "express";
const clientRoutes = express.Router();
import { va_register_client } from "../../controllers/client/vaClient.controller.js";
import { va_clients_callback } from "../../controllers/va/vaClients/vaClients.controller.js";
import { va_clients_projects_callback } from "../../controllers/va/vaClients/vaClientProject.controller.js";
import { va_login_client } from "../../controllers/client/vaClientLogin.controller.js";
import { client_refresh_token_callback } from "../../controllers/client/clientRefreshToken.controller.js";
// clients routes
clientRoutes.post("/register", va_register_client); // Register a new VA
clientRoutes.post("/login", va_login_client); // Login Client
clientRoutes.get("/refreshToken", client_refresh_token_callback); // Client Refresh Token
clientRoutes.get("/:clientID", va_clients_callback); // VA Clients Callback for getting all clients or specific client by ID (NC)
clientRoutes.get("/:clientId/projects/:projectId", va_clients_projects_callback); // VA Clients Projects Callback (NC)
// // API endpoint (for frontend to call)
// clientRoutes.post("/forgot-password", va_client_request_password_reset);
// // EJS rendering routes
// clientRoutes.get("/reset-form", va_client_render_reset_form);
// clientRoutes.post("/reset", va_client_process_reset);
export default clientRoutes;

View File

@ -1,6 +1,7 @@
import path from 'path';
import authRoutes from './auth.routes.js';
import vaRoutes from './va.routes.js';
import clientRoutes from './client.routes.js';
import express from "express";
@ -19,6 +20,7 @@ v1Routes.get("/auth", function(req, res) {
v1Routes.use("/auth_callback", authRoutes);
v1Routes.use("/va", vaRoutes);
v1Routes.use("/client", clientRoutes);
v1Routes.get("/", function(req, res) {
res.json({"connected": true})

View File

@ -3,7 +3,6 @@ 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";
@ -12,10 +11,7 @@ import { va_logout_callback } from "../../controllers/va/vaAuth/vaLogout.control
import { va_filter_search } from "../../controllers/va/vaFilterSearch.controller.js";
import { va_log_callback } from "../../controllers/va/vaLog/vaLog.controller.js";
import { va_callback } from "../../controllers/va/vaLog/vaListLog.controller.js";
import { va_clients_callback } from "../../controllers/va/vaClients/vaClients.controller.js";
import { va_clients_projects_callback } from "../../controllers/va/vaClients/vaClientProject.controller.js";
import { va_project_log_callback } from "../../controllers/va/vaProjects/vaProjectLog.controller.js";
import { va_projects_callback } from "../../controllers/va/vaProjects/vaProjects.controller.js";
import { authValidator } from "../../middlewares/auth.middleware.js";
import { va_log_track_work_callback } from "../../controllers/va/vaLog/vaLogTrackWork.controller.js";
import { va_log_work_report_callback } from "../../controllers/va/vaLog/vaLogWorkReport.js";
@ -28,11 +24,12 @@ 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 (NC)
@ -42,19 +39,10 @@ vaRoutes.get("/:userName", va_username_callback); // VA Username Callback (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 (NC)
vaRoutes.get("/clients/:clientId/projects/:projectId", va_clients_projects_callback); // VA Clients Projects Callback (NC)
// GET Windows Tab Logged Data for VA IN Between Login and LOGOUT
vaRoutes.get("/report/project/:projectId/:from?/:to?", authValidator, va_projects_report_function);
// Getting Project List assigned to VA should be ACTIVE and add total logged hours added by VA
vaRoutes.get("/list/projects/:projectId?/:from?/:to?", authValidator, va_projects_log_list_function);
@ -67,7 +55,12 @@ vaRoutes.get("/log/project/:projectId/:from?/:to?", authValidator, va_log_callba
// VA filter search: supports query params `name`, `email`, `from`, `to`
// // API endpoint (for frontend to call)
// vaRoutes.post("/forgot-password", va_request_password_reset);
// // EJS rendering routes
// vaRoutes.get("/reset-form", va_render_reset_form);
// vaRoutes.post("/reset", va_process_reset);
export default vaRoutes;

View File

@ -0,0 +1,107 @@
import logger from "../config/logger.js";
class VaClientService {
constructor(vaClientRepository) {
this.clientRepository = vaClientRepository;
}
async registerClient(data) {
try {
// Check if user already exists
const existingUser = await this.clientRepository.findByEmail(data.userDetails.email);
if (existingUser) {
throw new Error("User already exists with this email");
}
// Sanitize and prepare data
const clientData = this.sanitizeClientData(data);
const response = await this.clientRepository.create(clientData);
return response;
} catch (error) {
logger.error("Client Service - registerClient error: " + error.message);
throw error;
}
}
async getClientByEmail(email) {
try {
const response = await this.clientRepository.findByEmail(email);
return response;
} catch (error) {
logger.error("Client Service - getClientByEmail error: " + error.message);
throw error;
}
}
async getClientById(id) {
try {
const response = await this.clientRepository.findById(id);
if (!response) throw new Error("Client not found");
return response;
} catch (error) {
logger.error("Client Service - getClientById error: " + error.message);
throw error;
}
}
async updateClient(id, data) {
try {
const response = await this.clientRepository.update(id, data);
if (!response) throw new Error("Client not found");
return response;
} catch (error) {
logger.error("Client Service - updateClient error: " + error.message);
throw error;
}
}
async deleteClient(id) {
try {
const response = await this.clientRepository.delete(id);
return response;
} catch (error) {
logger.error("Client Service - deleteClient error: " + error.message);
throw error;
}
}
// Helper method to sanitize data
sanitizeClientData(payload) {
const { userDetails } = payload;
return {
userDetails: {
name: userDetails.name,
email: userDetails.email.toLowerCase(),
address: userDetails.address || null,
city: userDetails.city || null,
state: userDetails.state || null,
zip: userDetails.zip || null,
billingName: userDetails.billingName || null,
billingAddress: userDetails.billingAddress || null,
billingCity: userDetails.billingCity || null,
billingState: userDetails.billingState || null,
billingZip: userDetails.billingZip || null,
billingEmail: userDetails.billingEmail || null,
position: userDetails.position || null,
signatoryName: userDetails.signatoryName || null,
signatoryEmail: userDetails.signatoryEmail || null,
paymentMethod: userDetails.paymentMethod || null,
bankName: userDetails.bankName || null,
accountNumber: userDetails.accountNumber || null,
routingNumber: userDetails.routingNumber || null,
accountType: userDetails.accountType || null,
accountHolderName: userDetails.accountHolderName || null,
cardNumber: userDetails.cardNumber || null,
expiryDate: userDetails.expiryDate || null,
securityCode: userDetails.securityCode || null,
cardZip: userDetails.cardZip || null,
},
vaIds: payload.vaIds || [],
projectIds: payload.projectIds || [],
};
}
}
export default VaClientService;

View File

@ -3,92 +3,19 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VA Registration</title>
<title>Client 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"
/>
<h2>DocTracker</h2>
</div>
</div>
<nav class="progress-steps">
@ -160,6 +87,7 @@
<!-- Form container -->
<div class="form-container">
<h2 class="form-title-sec">Client Registration</h2>
<div class="form-card">
<!-- Step 1: Create Account -->
<div class="form-step active" id="step-1">
@ -404,7 +332,6 @@
<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>

View File

@ -3,13 +3,12 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VA Registration</title>
<title>Virtual Assistant 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">
<style>
body {
/* body {
background-color: #f8f9fa;
font-family: 'Segoe UI', sans-serif;
}
@ -89,117 +88,280 @@
.card-registration img {
display: none;
}
}
} */
</style>
</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">
<!-- Left Image -->
<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"
/>
<!-- Sidebar for desktop -->
<div class="sidebar">
<div class="logo">
<div class="logo-placeholder">
<h2>DocTracker</h2>
</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-text">Account Setup</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">Personal 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">Contact Information</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">Payment Details</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">Done</div>
</div>
</nav>
</div>
<!-- Right Form -->
<div class="col-xl-7 bg-white">
<div class="card-body text-black">
<h3 class="text-center mb-4">Virtual Assistant Registration</h3>
<form id="vaForm">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label" for="firstName">First Name *</label>
<input type="text" id="firstName" name="userDetails[firstName]" class="form-control form-control-lg" required>
<!-- 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-text">Account</div>
</li>
<li class="step" data-step="2" onclick="goToStep(2)">
<div class="step-number">2</div>
<div class="step-text">Personal</div>
</li>
<li class="step" data-step="3" onclick="goToStep(3)">
<div class="step-number">3</div>
<div class="step-text">Contact</div>
</li>
<li class="step" data-step="4" onclick="goToStep(4)">
<div class="step-number">4</div>
<div class="step-text">Payment</div>
</li>
<li class="step" data-step="5" onclick="goToStep(5)">
<div class="step-number">5</div>
<div class="step-text">Done</div>
</li>
</ol>
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="lastName">Last Name *</label>
<input type="text" id="lastName" name="userDetails[lastName]" class="form-control form-control-lg" required>
<!-- Form container -->
<div class="form-container">
<h2 class="form-title-sec">Virtual Assistant Registration</h2>
<div class="form-card">
<!-- Step 1: Account Setup -->
<div class="form-step active" id="step-1">
<h2 class="form-title">Account Setup</h2>
<form id="account-form">
<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 Address *</label>
</div>
</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 class="form-group">
<div class="input-container">
<input type="password" id="password" class="input-field" placeholder=" " required>
<label for="password" class="input-label">Create Password *</label>
</div>
<div class="password-strength">
<div class="password-strength-bar" id="password-strength-bar"></div>
</div>
<div class="password-actions">
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">Show Password</button>
<button type="button" class="password-generate" onclick="generatePassword()">Generate Strong Password</button>
</div>
<div class="form-text">Use at least 8 characters with a mix of letters, numbers, and symbols</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label" for="age">Age</label>
<input type="text" id="age" name="userDetails[age]" class="form-control form-control-lg">
</div>
<div class="col-md-6 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 class="form-group">
<div class="input-container">
<input type="password" id="confirmPassword" class="input-field" placeholder=" " required>
<label for="confirmPassword" class="input-label">Confirm Password *</label>
</div>
<div class="invalid-feedback" id="password-match-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">Passwords do not match</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label" for="password">Password *</label>
<input type="password" id="password" name="userDetails[password]" class="form-control form-control-lg" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="cPassword">Confirm Password *</label>
<input type="password" id="cPassword" name="userDetails[cPassword]" class="form-control form-control-lg" required>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="address">Address</label>
<input type="text" id="address" name="userDetails[address]" 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="mb-4">
<label class="form-label" for="status">Status</label>
<select id="status" name="status" class="form-select form-select-lg">
<option value="FULLTIME">Full-Time</option>
<option value="PARTTIME">Part-Time</option>
</select>
</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 class="form-actions">
<div></div>
<button type="button" class="btn btn-primary" onclick="nextStep(2)">Continue</button>
</div>
</form>
</div>
<!-- Step 2: Personal Information -->
<div class="form-step" id="step-2">
<h2 class="form-title">Personal Information</h2>
<div class="profile-incomplete" id="profile-incomplete" style="display: none;">
Profile is not yet complete, first name is missing
</div>
<form id="personal-form">
<div class="row">
<div class="form-group">
<div class="input-container">
<input type="text" id="firstName" class="input-field" placeholder=" " required>
<label for="firstName" class="input-label">First Name *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="lastName" class="input-field" placeholder=" " required>
<label for="lastName" class="input-label">Last Name *</label>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="row">
<div class="form-group">
<div class="input-container">
<input type="number" id="age" class="input-field" placeholder=" " min="18" max="100">
<label for="age" class="input-label">Age</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="date" id="dob" class="input-field" placeholder=" ">
<label for="dob" class="input-label">Date of Birth</label>
</div>
</div>
</div>
<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="row">
<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 / Town *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<select id="country" class="select-field" required>
<option value=""></option>
</select>
<label for="country" class="input-label">Country *</label>
</div>
</div>
</div>
<div class="form-group">
<div class="input-container">
<select id="state" class="select-field" required>
<option value=""></option>
</select>
<label for="state" class="input-label">State / Region *</label>
<div class="location-loading" id="state-loading">Loading states...</div>
</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: Contact Information -->
<div class="form-step" id="step-3">
<h2 class="form-title">Contact Information</h2>
<form id="contact-form">
<div class="form-group">
<label for="phone" class="input-label">Phone Number *</label>
<div class="input-container">
<input type="tel" id="phone" class="input-field" placeholder=" " required>
<label for="phone" class="input-label" style="left: 40px;">Phone Number</label>
<span style="position: absolute; left: 12px; top: 50%; transform: translateY(-50%);">+</span>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="skype" class="input-field" placeholder=" ">
<label for="skype" class="input-label">Skype ID</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: Payment Details -->
<div class="form-step" id="step-4">
<h2 class="form-title">Payment Details</h2>
<form id="payment-form">
<div class="form-group">
<label class="input-label">Payment Method *</label>
<div class="payment-option" onclick="selectPaymentMethod('payoneer')">
<h4>Payoneer</h4>
<p>Receive payments via Payoneer</p>
</div>
<div id="payoneer-details" class="payment-details">
<div class="form-group">
<div class="input-container">
<input type="text" id="payoneerAccount" class="input-field" placeholder=" " required>
<label for="payoneerAccount" class="input-label">Payoneer Account Number *</label>
</div>
</div>
</div>
<div class="payment-option" onclick="selectPaymentMethod('veem')">
<h4>Veem</h4>
<p>Receive payments via Veem</p>
</div>
<div id="veem-details" class="payment-details">
<div class="form-group">
<div class="input-container">
<input type="text" id="veemAccount" class="input-field" placeholder=" " required>
<label for="veemAccount" class="input-label">Veem Account Number *</label>
</div>
</div>
</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="submitForm()">Complete Registration</button>
</div>
</form>
</div>
<!-- Step 5: Success -->
<div class="form-step" id="step-5">
<div class="success-message">
<div class="success-icon">✓</div>
<h3>Registration Complete!</h3>
<p>Your Virtual Assistant account has been successfully created..</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/public/js/va-register.js"></script>
</body>
</html>