Fixed issues and bugs

This commit is contained in:
Aakash Mittal 2025-11-20 20:15:16 +05:30
parent 9429caacd6
commit aecb4af2ac
9 changed files with 1562 additions and 113 deletions

View File

@ -475,4 +475,43 @@ body {
font-size: 40px;
margin-bottom: 40px;
}
}
}
.step-error-container {
background-color: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 20px;
animation: slideDown 0.3s ease-out;
}
.error-message {
color: #dc2626;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.error-message::before {
content: "⚠";
font-size: 16px;
}
.field-error {
border: 1px solid #dc2626 !important;
background-color: #fef2f2;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -109,7 +109,22 @@
// Add input event listeners to handle label animation
document.querySelectorAll('.input-field').forEach(input => {
document.querySelectorAll('.input-field:not(.dob)').forEach(input => {
setTimeout(() => {
if (input.value) {
input.nextElementSibling.style.top = '4px';
input.nextElementSibling.style.fontSize = '12px';
input.nextElementSibling.style.transform = 'none';
}
}, 100);
input.addEventListener('animationstart', function() {
if (this.value) {
this.nextElementSibling.style.top = '4px';
this.nextElementSibling.style.fontSize = '12px';
this.nextElementSibling.style.transform = 'none';
}
});
input.addEventListener('input', function() {
if (this.value) {
this.nextElementSibling.style.top = '4px';
@ -139,6 +154,13 @@
// Handle select field label animation
document.querySelectorAll('.select-field').forEach(select => {
setTimeout(() => {
if (select.value) {
select.nextElementSibling.style.top = '4px';
select.nextElementSibling.style.fontSize = '12px';
select.nextElementSibling.style.transform = 'none';
}
}, 100);
select.addEventListener('change', function() {
if (this.value) {
this.nextElementSibling.style.top = '4px';
@ -228,10 +250,14 @@
return maxStep;
}
async function handleNextStep(step) {
await nextStep(step);
}
// Function to navigate to next step
function nextStep(step) {
async function nextStep(step) {
// Validate current step before proceeding
if (!validateStep(currentStep)) {
if (!(await validateStep(currentStep))) {
return;
}
@ -306,9 +332,13 @@
}
// Function to validate current step
function validateStep(step) {
async function validateStep(step) {
let isValid = true;
const form = document.getElementById(getFormId(step));
// Hide any previous errors for this step
hideError(step);
clearFieldErrors();
if (form) {
// For account setup step
@ -319,21 +349,31 @@
// Email validation
if (!validateEmail(email)) {
alert('Please enter a valid email address');
showError('Please enter a valid email address', 'email', step);
isValid = false;
} else {
// Email verification - wait for the result
const isEmailVerified = await verifyEmail(email);
if (!isEmailVerified) {
showError('Email already registered. Please use a different email address.', 'email', step);
isValid = false;
}
}
// Password validation
if (password.length < 8) {
alert('Password must be at least 8 characters long');
showError('Password must be at least 8 characters long', 'password', step);
isValid = false;
}
if (password !== confirmPassword) {
document.getElementById('password-match-error').style.display = 'block';
document.getElementById('confirmPassword').classList.add('field-error');
showError('Passwords do not match', 'confirmPassword', step);
isValid = false;
} else {
document.getElementById('password-match-error').style.display = 'none';
document.getElementById('confirmPassword').classList.remove('field-error');
}
}
@ -342,15 +382,18 @@
const firstName = document.getElementById('firstName').value;
if (!firstName) {
document.getElementById('profile-incomplete').style.display = 'block';
document.getElementById('firstName').classList.add('field-error');
showError('First name is required', 'firstName', step);
isValid = false;
} else {
document.getElementById('profile-incomplete').style.display = 'none';
document.getElementById('firstName').classList.remove('field-error');
}
// Age validation
const age = document.getElementById('age').value;
if (age && (age < 18 || age > 100)) {
alert('You must be 18 years or older to register');
showError('You must be 18 years or older to register', 'dob', step);
isValid = false;
}
@ -359,12 +402,12 @@
const state = document.getElementById('state').value;
if (!country) {
alert('Please select your country');
showError('Please select your country', 'country', step);
isValid = false;
}
if (!state) {
alert('Please select your state/region');
showError('Please select your state/region', 'state', step);
isValid = false;
}
@ -373,9 +416,40 @@
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
field.classList.add('field-error');
if (isValid === false) {
showError(`Please fill in ${field.labels[0]?.textContent || 'this field'}`, field.id, step);
}
} else {
field.style.border = '';
field.classList.remove('field-error');
}
});
}
if (step === 3) {
// Phone number validation
const phone = document.getElementById('phone').value;
if (!phone) {
showError('Please enter your phone number', 'phone', step);
isValid = false;
} else if (!validatePhoneNumberStrict(phone)) {
document.getElementById('phone-error').style.display = 'block';
document.getElementById('phone').classList.add('field-error');
showError('Please enter a valid phone number (10-15 digits)', 'phone', step);
isValid = false;
} else {
document.getElementById('phone-error').style.display = 'none';
document.getElementById('phone').classList.remove('field-error');
}
// Validate other required fields in step 3
const requiredFields = form.querySelectorAll('[required]');
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.classList.add('field-error');
} else {
field.classList.remove('field-error');
}
});
}
@ -384,20 +458,20 @@
if (step === 4) {
const selectedPayment = document.querySelector('.payment-option.selected');
if (!selectedPayment) {
alert('Please select a payment method');
showError('Please select a payment method', null, step);
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');
showError('Please enter your Payoneer account number', 'payoneerAccount', step);
isValid = false;
}
} else if (paymentMethod === 'veem') {
const veemAccount = document.getElementById('veemAccount').value;
if (!veemAccount) {
alert('Please enter your Veem account number');
showError('Please enter your Veem account number', 'veemAccount', step);
isValid = false;
}
}
@ -410,9 +484,9 @@
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
field.style.border = '1px solid #ef4444';
field.classList.add('field-error');
} else {
field.style.border = '';
field.classList.remove('field-error');
}
});
}
@ -598,12 +672,12 @@
}, 1000);
} else {
// Handle server errors
alert('There was an error submitting your registration. Please try again.');
showError('There was an error submitting your registration. Please try again.', null, 4);
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
alert('There was a network error. Please check your connection and try again.');
showError('There was a network error. Please check your connection and try again.', null, 4);
} finally {
// Restore button state
submitBtn.textContent = originalText;
@ -611,3 +685,218 @@
}
}
async function verifyEmail(email) {
try {
const response = await fetch('/api/v1/va/verify-va-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({"email": email})
});
if (response.ok) {
return false;
} else {
return true;
}
} catch (error) {
// Handle network errors
console.error('Error:', error);
showError('There was a network error. Please check your connection and try again.', null, 1);
return false;
}
}
// Function to calculate age from date of birth
function calculateAge(dob) {
if (!dob) return '';
const birthDate = new Date(dob);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
// Adjust age if birthday hasn't occurred this year yet
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
// Function to handle date of birth change and update age
function handleDOBChange() {
const dob = document.getElementById('dob').value;
const ageInput = document.getElementById('age');
if (dob) {
const age = calculateAge(dob);
ageInput.value = age;
// Trigger input event to update the label position
ageInput.dispatchEvent(new Event('input'));
// Validate age
if (age < 18) {
alert('You must be 18 years or older to register');
ageInput.style.border = '1px solid #ef4444';
} else if (age > 100) {
alert('Please enter a valid age');
ageInput.style.border = '1px solid #ef4444';
} else {
ageInput.style.border = '';
}
} else {
// Clear age if DOB is cleared
ageInput.value = '';
ageInput.dispatchEvent(new Event('input'));
}
}
// Function to handle age manual input and update DOB (optional)
function handleAgeChange() {
const age = document.getElementById('age').value;
const dobInput = document.getElementById('dob');
if (age && age >= 18 && age <= 100) {
// Calculate approximate DOB based on age
const today = new Date();
const birthYear = today.getFullYear() - age;
const approximateDOB = `${birthYear}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
// You can set this as the DOB or just leave it for reference
// dobInput.value = approximateDOB;
// dobInput.dispatchEvent(new Event('change'));
}
}
// Function to validate phone number in real-time
function validatePhoneNumber(input) {
const phoneError = document.getElementById('phone-error');
const value = input.value;
// Remove any non-digit characters
const digitsOnly = value.replace(/\D/g, '');
// Update the input value with digits only
if (value !== digitsOnly) {
input.value = digitsOnly;
}
// Validate length
if (digitsOnly.length > 0 && (digitsOnly.length < 10 || digitsOnly.length > 15)) {
phoneError.style.display = 'block';
input.style.border = '1px solid #ef4444';
return false;
} else {
phoneError.style.display = 'none';
input.style.border = '';
return true;
}
}
// Function to validate phone number on form submission
function validatePhoneNumberStrict(phone) {
const phoneRegex = /^[0-9]{10,15}$/;
return phoneRegex.test(phone);
}
// Function to validate Payoneer account number
function validatePayoneerAccount(input) {
const payoneerError = document.getElementById('payoneer-error');
const value = input.value.trim();
// Payoneer typically uses email format or alphanumeric IDs
// Adjust validation rules based on Payoneer's actual format requirements
if (value.length > 0 && (value.length < 5 || value.length > 20)) {
payoneerError.textContent = 'Payoneer account must be 5-20 characters';
payoneerError.style.display = 'block';
input.style.border = '1px solid #ef4445';
return false;
} else {
payoneerError.style.display = 'none';
input.style.border = '';
return true;
}
}
// Function to validate Veem account number
function validateVeemAccount(input) {
const veemError = document.getElementById('veem-error');
const value = input.value.trim();
// Veem account numbers are typically longer alphanumeric strings
if (value.length > 0 && (value.length < 8 || value.length > 25)) {
veemError.textContent = 'Veem account must be 8-25 characters';
veemError.style.display = 'block';
input.style.border = '1px solid #ef4445';
return false;
} else {
veemError.style.display = 'none';
input.style.border = '';
return true;
}
}
function showError(message, fieldId = null, step = null) {
// Determine the current step if not provided
const currentStep = step || getCurrentStep();
const errorContainer = document.getElementById(`step-${currentStep}-error`);
const errorMessage = document.getElementById(`step-${currentStep}-error-message`);
if (errorContainer && errorMessage) {
errorMessage.textContent = message;
errorContainer.style.display = 'block';
// Scroll to error container
errorContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Highlight specific field if provided
if (fieldId) {
const field = document.getElementById(fieldId);
if (field) {
field.classList.add('field-error');
// Remove error after 5 seconds
setTimeout(() => {
field.classList.remove('field-error');
}, 5000);
}
}
// Auto-hide error after 8 seconds
setTimeout(() => {
hideError(currentStep);
}, 8000);
}
}
function hideError(step = null) {
if (step) {
// Hide specific step error
const errorContainer = document.getElementById(`step-${step}-error`);
if (errorContainer) {
errorContainer.style.display = 'none';
}
} else {
// Hide all step errors
for (let i = 1; i <= 5; i++) {
const errorContainer = document.getElementById(`step-${i}-error`);
if (errorContainer) {
errorContainer.style.display = 'none';
}
}
}
}
function clearFieldErrors() {
document.querySelectorAll('.field-error').forEach(field => {
field.classList.remove('field-error');
});
}
function getCurrentStep() {
return currentStep;
}

View File

@ -0,0 +1,32 @@
import { ClientModel } from "../../models/vaModel.js";
// Verify VA Email
async function client_verify_email(req, res) {
try {
// const payload = req.body ? JSON.parse(req.body) : {};
const payload = req.body || {};
const { email } = payload;
if (!email) {
return res.status(400).json({ success: false, message: "Please provide email" });
}
// Find user by email
const user = await ClientModel.findOne({ "userDetails.email": email });
if (!user) {
return res.status(404).json({ success: false, message: "Email not found" });
}
// Return accessToken and user info
return res.status(200).json({
success: true,
email: user.userDetails?.email,
message: "Email ID is already registerd"
});
} catch (error) {
console.error("va_register error:", error);
return res.status(500).json({ success: false, message: "Internal Server Error" });
}
}
export { client_verify_email };

View File

@ -0,0 +1,32 @@
import { VAModel } from "../../../models/vaModel.js";
// Verify VA Email
async function va_verify_email(req, res) {
try {
// const payload = req.body ? JSON.parse(req.body) : {};
const payload = req.body || {};
const { email } = payload;
if (!email) {
return res.status(400).json({ success: false, message: "Please provide email" });
}
// Find user by email
const user = await VAModel.findOne({ "userDetails.email": email });
if (!user) {
return res.status(404).json({ success: false, message: "Invalid Email ID" });
}
// Return accessToken and user info
return res.status(200).json({
success: true,
email: user.userDetails?.email,
message: "Email verified successfully"
});
} catch (error) {
console.error("va_register error:", error);
return res.status(500).json({ success: false, message: "Internal Server Error" });
}
}
export { va_verify_email };

View File

@ -9,13 +9,15 @@ import { client_refresh_token_callback } from "../../controllers/client/clientRe
import { client_request_password_reset } from "../../controllers/passwordReset.controllers.js";
import { clientAuthValidator } from "../../middlewares/auth.middleware.js";
import { client_dashboard } from "./../../controllers/client/dashboard/clientDashboard.controller.js";
import { client_dashboard_va_log } from "../../controllers/client/dashboard/clientDashboardVaLog.controller.js"
import { client_dashboard_va_log } from "../../controllers/client/dashboard/clientDashboardVaLog.controller.js";
import { client_verify_email } from "../../controllers/client/vaClientEmailVerify.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.post("/forgot-password", client_request_password_reset);
clientRoutes.post("/verify-client-email", client_verify_email);
// clientRoutes.get("/:clientID?", va_clients_callback); // VA Clients Callback for getting all VA's or specific client by ID (NC)
clientRoutes.get("/:clientId/projects/:projectId", va_clients_projects_callback); // VA Clients Projects Callback (NC)

View File

@ -22,6 +22,7 @@ import { va_timesheet_summary_function } from "../../controllers/va/vaTimesheetA
import { va_timesheet_adjustment_function } from "../../controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js";
import { get_adjustment_requests_list } from "../../controllers/va/vaTimesheetAdjustment/vaAllTimesheetAdjustmentReuqest.controller.js"
import { va_timesheet_manage_adjustment_request } from "../../controllers/va/vaTimesheetAdjustment/vaManageTimesheetAdjustment.controller.js"
import { va_verify_email } from "../../controllers/va/vaAuth/vaVerifyEmail.controller.js";
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
@ -30,6 +31,7 @@ vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for l
vaRoutes.post("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token
vaRoutes.post("/register", va_register); // Register a new VA
vaRoutes.post("/forgot-password", va_request_password_reset);
vaRoutes.post("/verify-va-email", va_verify_email);
vaRoutes.get("/profile", authValidator, va_profile_callback); // Get VA Profile
vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile

View File

@ -92,12 +92,18 @@
<!-- Step 1: Create Account -->
<div class="form-step active" id="step-1">
<h2 class="form-title">Create Your Account</h2>
<div class="step-error-container" id="step-1-error" style="display: none;">
<div class="error-message" id="step-1-error-message"></div>
</div>
<form id="account-form">
<div class="form-group">
<div class="input-container">
<input type="text" id="company-name" class="input-field" placeholder=" " required>
<input type="text" id="company-name" class="input-field" placeholder=" " oninput="validateCompanyName(this)" required>
<label for="company-name" class="input-label">Company / Client Name *</label>
</div>
<div class="invalid-feedback" id="company-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Company name should only contain letters, numbers, spaces, hyphens, apostrophes, ampersands, and periods
</div>
</div>
<div class="form-group">
<div class="input-container">
@ -107,7 +113,7 @@
</div>
<div class="form-actions">
<div></div> <!-- Empty div for spacing -->
<button type="button" class="btn btn-primary" onclick="nextStep(2)">Continue</button>
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
</div>
</form>
</div>
@ -115,6 +121,9 @@
<!-- Step 2: Business Information -->
<div class="form-step" id="step-2">
<h2 class="form-title">Business Information</h2>
<div class="step-error-container" id="step-2-error" style="display: none;">
<div class="error-message" id="step-2-error-message"></div>
</div>
<form id="business-form">
<div class="form-group">
<div class="input-container">
@ -124,9 +133,12 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="city" class="input-field" placeholder=" " required>
<input type="text" id="city" class="input-field" placeholder=" " oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)" required>
<label for="city" class="input-label">City *</label>
</div>
<div class="invalid-feedback" id="city-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
City name should contain letters and cannot be only numbers or special characters
</div>
</div>
<div class="form-group">
<div class="input-container">
@ -141,13 +153,16 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="zip" class="input-field" placeholder=" " required>
<input type="text" id="zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
<label for="zip" class="input-label">ZIP *</label>
</div>
<div class="invalid-feedback" id="zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
ZIP code should contain only numbers
</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>
<button type="button" class="btn btn-primary" onclick="handleNextStep(3)">Continue</button>
</div>
</form>
</div>
@ -155,6 +170,9 @@
<!-- Step 3: Billing Address -->
<div class="form-step" id="step-3">
<h2 class="form-title">Billing Address</h2>
<div class="step-error-container" id="step-3-error" style="display: none;">
<div class="error-message" id="step-3-error-message"></div>
</div>
<form id="billing-form">
<div class="checkbox-group">
<input type="checkbox" id="use-business-address">
@ -174,9 +192,12 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-city" class="input-field" placeholder=" " required>
<input type="text" id="billing-city" class="input-field" placeholder=" " oninput="validateCity(this)" onkeypress="return preventInvalidCityChars(event)" required>
<label for="billing-city" class="input-label">City *</label>
</div>
<div class="invalid-feedback" id="billing-city-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
City name should contain letters and cannot be only numbers or special characters
</div>
</div>
<div class="form-group">
<div class="input-container">
@ -190,9 +211,12 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="billing-zip" class="input-field" placeholder=" " required>
<input type="text" id="billing-zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
<label for="billing-zip" class="input-label">ZIP *</label>
</div>
<div class="invalid-feedback" id="billing-zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
ZIP code should contain only numbers
</div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="use-account-email">
@ -206,7 +230,7 @@
</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>
<button type="button" class="btn btn-primary" onclick="handleNextStep(4)">Continue</button>
</div>
</form>
</div>
@ -214,21 +238,30 @@
<!-- Step 4: Authorized Signatory -->
<div class="form-step" id="step-4">
<h2 class="form-title">Authorized Signatory</h2>
<div class="step-error-container" id="step-4-error" style="display: none;">
<div class="error-message" id="step-4-error-message"></div>
</div>
<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>
<input type="text" id="position" class="input-field" placeholder=" " oninput="validatePosition(this)" onkeypress="return preventInvalidPositionChars(event)" required>
<label for="position" class="input-label">Position (Owner / CEO / Manager / etc.) *</label>
</div>
<div class="invalid-feedback" id="position-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Position should only contain letters, spaces, and common punctuation
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="signatory-name" class="input-field" placeholder=" " required>
<input type="text" id="signatory-name" class="input-field" placeholder=" " oninput="validateSignatoryName(this)" onkeypress="return preventNumbers(event)" required>
<label for="signatory-name" class="input-label">Name *</label>
</div>
<div class="invalid-feedback" id="signatory-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Name should not contain numbers
</div>
</div>
<div class="form-group">
<div class="input-container">
@ -238,7 +271,7 @@
</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>
<button type="button" class="btn btn-primary" onclick="handleNextStep(5)">Continue</button>
</div>
</form>
</div>
@ -246,6 +279,9 @@
<!-- Step 5: Payment Details -->
<div class="form-step" id="step-5">
<h2 class="form-title">Payment Details</h2>
<div class="step-error-container" id="step-5-error" style="display: none;">
<div class="error-message" id="step-5-error-message"></div>
</div>
<form id="payment-form">
<div class="payment-option" onclick="selectPaymentOption('card')">
<h4>Credit or Debit Card</h4>
@ -267,16 +303,22 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="security-code" class="input-field" placeholder=" " required>
<input type="text" id="security-code" class="input-field" placeholder=" " oninput="validateCVVVV(this)" nkeypress="return preventInvalidCVVChars(event)" maxlength="4" required>
<label for="security-code" class="input-label">Security Code *</label>
</div>
<div class="invalid-feedback" id="security-code-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Please enter a valid security code
</div>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="card-zip" class="input-field" placeholder=" " required>
<input type="text" id="card-zip" class="input-field" placeholder=" " oninput="validateZipCode(this)" onkeypress="return preventNonNumericZip(event)" maxlength="10" required>
<label for="card-zip" class="input-label">ZIP Code *</label>
</div>
<div class="invalid-feedback" id="card-zip-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
ZIP code should contain only numbers
</div>
</div>
</div>
<div class="payment-option" onclick="selectPaymentOption('bank')">
@ -286,15 +328,21 @@
<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>
<input type="text" id="bank-name" class="input-field" placeholder=" " oninput="validateBankName(this)" onkeypress="return preventInvalidBankNameChars(event)" required>
<label for="bank-name" class="input-label">Bank Name *</label>
</div>
<div class="invalid-feedback" id="bank-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Bank name should contain letters and cannot be only numbers or special characters
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="account-number" class="input-field" placeholder=" " required>
<input type="text" id="account-number" class="input-field" placeholder=" " oninput="validateAccountNumber(this)" onkeypress="return preventInvalidAccountNumberChars(event)" required>
<label for="account-number" class="input-label">Account Number *</label>
</div>
<div class="invalid-feedback" id="account-number-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Please enter a valid account number
</div>
</div>
<div class="form-group">
<div class="input-container">
@ -314,9 +362,12 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="text" id="account-holder-name" class="input-field" placeholder=" " required>
<input type="text" id="account-holder-name" class="input-field" placeholder=" " oninput="validateAccountHolderName(this)" onkeypress="return preventInvalidAccountHolderChars(event)" required>
<label for="account-holder-name" class="input-label">Account Holder Name *</label>
</div>
<div class="invalid-feedback" id="account-holder-name-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Account holder name should only contain letters and spaces
</div>
</div>
</div>
<div class="form-actions">

View File

@ -165,17 +165,20 @@
<!-- Step 1: Account Setup -->
<div class="form-step active" id="step-1">
<h2 class="form-title">Account Setup</h2>
<div class="step-error-container" id="step-1-error" style="display: none;">
<div class="error-message" id="step-1-error-message"></div>
</div>
<form id="account-form">
<div class="form-group">
<div class="input-container">
<input type="email" id="email" class="input-field" placeholder=" " required>
<input type="email" id="email" class="input-field" placeholder=" " required autocomplete="off">
<label for="email" class="input-label">Email Address *</label>
</div>
</div>
<div class="form-group">
<div class="input-container">
<input type="password" id="password" class="input-field" placeholder=" " required>
<input type="password" id="password" class="input-field" placeholder=" " required autocomplete="off">
<label for="password" class="input-label">Create Password *</label>
</div>
<div class="password-actions">
@ -186,7 +189,7 @@
<div class="form-group">
<div class="input-container">
<input type="password" id="confirmPassword" class="input-field" placeholder=" " required>
<input type="password" id="confirmPassword" class="input-field" placeholder=" " required autocomplete="off">
<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>
@ -194,7 +197,7 @@
<div class="form-actions">
<div></div>
<button type="button" class="btn btn-primary" onclick="nextStep(2)">Continue</button>
<button type="button" class="btn btn-primary" onclick="handleNextStep(2)">Continue</button>
</div>
</form>
</div>
@ -202,7 +205,9 @@
<!-- Step 2: Personal Information -->
<div class="form-step" id="step-2">
<h2 class="form-title">Personal Information</h2>
<div class="step-error-container" id="step-2-error" style="display: none;">
<div class="error-message" id="step-2-error-message"></div>
</div>
<div class="profile-incomplete" id="profile-incomplete" style="display: none;">
Profile is not yet complete, first name is missing
</div>
@ -232,7 +237,7 @@
</div>
<div class="form-group">
<div class="input-container">
<input type="date" id="dob" class="input-field" placeholder=" ">
<input type="date" id="dob" class="input-field dob" placeholder=" " onchange="handleDOBChange()">
<label for="dob" class="input-label">Date of Birth</label>
</div>
</div>
@ -274,7 +279,7 @@
<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>
<button type="button" class="btn btn-primary" onclick="handleNextStep(3)">Continue</button>
</div>
</form>
</div>
@ -282,13 +287,19 @@
<!-- Step 3: Contact Information -->
<div class="form-step" id="step-3">
<h2 class="form-title">Contact Information</h2>
<div class="step-error-container" id="step-3-error" style="display: none;">
<div class="error-message" id="step-3-error-message"></div>
</div>
<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>
<input type="tel" id="phone" class="input-field" placeholder=" " pattern="[0-9]*" minlength="10" maxlength="15" oninput="validatePhoneNumber(this)" required>
<label for="phone" class="input-label">+ Phone Number</label>
</div>
<div class="invalid-feedback" id="phone-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Please enter a valid phone number (10-15 digits)
</div>
</div>
<div class="form-group">
@ -300,7 +311,7 @@
<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>
<button type="button" class="btn btn-primary" onclick="handleNextStep(4)">Continue</button>
</div>
</form>
</div>
@ -308,6 +319,9 @@
<!-- Step 4: Payment Details -->
<div class="form-step" id="step-4">
<h2 class="form-title">Payment Details</h2>
<div class="step-error-container" id="step-4-error" style="display: none;">
<div class="error-message" id="step-4-error-message"></div>
</div>
<form id="payment-form">
<div class="form-group">
<div class="payment-option" onclick="selectPaymentMethod('payoneer')">
@ -317,9 +331,12 @@
<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>
<input type="text" id="payoneerAccount" class="input-field" placeholder=" " minlength="5" maxlength="20" oninput="validatePayoneerAccount(this)" required>
<label for="payoneerAccount" class="input-label">Payoneer Account Number *</label>
</div>
<div class="invalid-feedback" id="payoneer-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Account number must be 5-20 characters
</div>
</div>
</div>
@ -330,9 +347,12 @@
<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>
<input type="text" id="veemAccount" class="input-field" placeholder=" " minlength="8" maxlength="25" oninput="validateVeemAccount(this)" required>
<label for="veemAccount" class="input-label">Veem Account Number *</label>
</div>
<div class="invalid-feedback" id="veem-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
Account number must be 8-25 characters
</div>
</div>
</div>
</div>