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

@ -476,3 +476,42 @@ body {
margin-bottom: 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 // 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() { input.addEventListener('input', function() {
if (this.value) { if (this.value) {
this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.top = '4px';
@ -139,6 +154,13 @@
// Handle select field label animation // Handle select field label animation
document.querySelectorAll('.select-field').forEach(select => { 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() { select.addEventListener('change', function() {
if (this.value) { if (this.value) {
this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.top = '4px';
@ -228,10 +250,14 @@
return maxStep; return maxStep;
} }
async function handleNextStep(step) {
await nextStep(step);
}
// Function to navigate to next step // Function to navigate to next step
function nextStep(step) { async function nextStep(step) {
// Validate current step before proceeding // Validate current step before proceeding
if (!validateStep(currentStep)) { if (!(await validateStep(currentStep))) {
return; return;
} }
@ -306,10 +332,14 @@
} }
// Function to validate current step // Function to validate current step
function validateStep(step) { async function validateStep(step) {
let isValid = true; let isValid = true;
const form = document.getElementById(getFormId(step)); const form = document.getElementById(getFormId(step));
// Hide any previous errors for this step
hideError(step);
clearFieldErrors();
if (form) { if (form) {
// For account setup step // For account setup step
if (step === 1) { if (step === 1) {
@ -319,21 +349,31 @@
// Email validation // Email validation
if (!validateEmail(email)) { if (!validateEmail(email)) {
alert('Please enter a valid email address'); showError('Please enter a valid email address', 'email', step);
isValid = false; 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 // Password validation
if (password.length < 8) { 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; isValid = false;
} }
if (password !== confirmPassword) { if (password !== confirmPassword) {
document.getElementById('password-match-error').style.display = 'block'; document.getElementById('password-match-error').style.display = 'block';
document.getElementById('confirmPassword').classList.add('field-error');
showError('Passwords do not match', 'confirmPassword', step);
isValid = false; isValid = false;
} else { } else {
document.getElementById('password-match-error').style.display = 'none'; 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; const firstName = document.getElementById('firstName').value;
if (!firstName) { if (!firstName) {
document.getElementById('profile-incomplete').style.display = 'block'; document.getElementById('profile-incomplete').style.display = 'block';
document.getElementById('firstName').classList.add('field-error');
showError('First name is required', 'firstName', step);
isValid = false; isValid = false;
} else { } else {
document.getElementById('profile-incomplete').style.display = 'none'; document.getElementById('profile-incomplete').style.display = 'none';
document.getElementById('firstName').classList.remove('field-error');
} }
// Age validation // Age validation
const age = document.getElementById('age').value; const age = document.getElementById('age').value;
if (age && (age < 18 || age > 100)) { 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; isValid = false;
} }
@ -359,12 +402,12 @@
const state = document.getElementById('state').value; const state = document.getElementById('state').value;
if (!country) { if (!country) {
alert('Please select your country'); showError('Please select your country', 'country', step);
isValid = false; isValid = false;
} }
if (!state) { if (!state) {
alert('Please select your state/region'); showError('Please select your state/region', 'state', step);
isValid = false; isValid = false;
} }
@ -373,9 +416,40 @@
requiredFields.forEach(field => { requiredFields.forEach(field => {
if (!field.value.trim()) { if (!field.value.trim()) {
isValid = false; 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 { } 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) { if (step === 4) {
const selectedPayment = document.querySelector('.payment-option.selected'); const selectedPayment = document.querySelector('.payment-option.selected');
if (!selectedPayment) { if (!selectedPayment) {
alert('Please select a payment method'); showError('Please select a payment method', null, step);
isValid = false; isValid = false;
} else { } else {
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1]; const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
if (paymentMethod === 'payoneer') { if (paymentMethod === 'payoneer') {
const payoneerAccount = document.getElementById('payoneerAccount').value; const payoneerAccount = document.getElementById('payoneerAccount').value;
if (!payoneerAccount) { if (!payoneerAccount) {
alert('Please enter your Payoneer account number'); showError('Please enter your Payoneer account number', 'payoneerAccount', step);
isValid = false; isValid = false;
} }
} else if (paymentMethod === 'veem') { } else if (paymentMethod === 'veem') {
const veemAccount = document.getElementById('veemAccount').value; const veemAccount = document.getElementById('veemAccount').value;
if (!veemAccount) { if (!veemAccount) {
alert('Please enter your Veem account number'); showError('Please enter your Veem account number', 'veemAccount', step);
isValid = false; isValid = false;
} }
} }
@ -410,9 +484,9 @@
requiredFields.forEach(field => { requiredFields.forEach(field => {
if (!field.value.trim()) { if (!field.value.trim()) {
isValid = false; isValid = false;
field.style.border = '1px solid #ef4444'; field.classList.add('field-error');
} else { } else {
field.style.border = ''; field.classList.remove('field-error');
} }
}); });
} }
@ -598,12 +672,12 @@
}, 1000); }, 1000);
} else { } else {
// Handle server errors // 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) { } catch (error) {
// Handle network errors // Handle network errors
console.error('Error:', error); 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 { } finally {
// Restore button state // Restore button state
submitBtn.textContent = originalText; 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 { client_request_password_reset } from "../../controllers/passwordReset.controllers.js";
import { clientAuthValidator } from "../../middlewares/auth.middleware.js"; import { clientAuthValidator } from "../../middlewares/auth.middleware.js";
import { client_dashboard } from "./../../controllers/client/dashboard/clientDashboard.controller.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 // clients routes
clientRoutes.post("/register", va_register_client); // Register a new VA clientRoutes.post("/register", va_register_client); // Register a new VA
clientRoutes.post("/login", va_login_client); // Login Client clientRoutes.post("/login", va_login_client); // Login Client
clientRoutes.get("/refreshToken", client_refresh_token_callback); // Client Refresh Token clientRoutes.get("/refreshToken", client_refresh_token_callback); // Client Refresh Token
clientRoutes.post("/forgot-password", client_request_password_reset); 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?", 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) 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 { va_timesheet_adjustment_function } from "../../controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js";
import { get_adjustment_requests_list } from "../../controllers/va/vaTimesheetAdjustment/vaAllTimesheetAdjustmentReuqest.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_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 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("/refreshToken", va_refresh_token_callback); // VA Callback for refresh token
vaRoutes.post("/register", va_register); // Register a new VA vaRoutes.post("/register", va_register); // Register a new VA
vaRoutes.post("/forgot-password", va_request_password_reset); 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.get("/profile", authValidator, va_profile_callback); // Get VA Profile
vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update VA Profile

View File

@ -92,12 +92,18 @@
<!-- Step 1: Create Account --> <!-- Step 1: Create Account -->
<div class="form-step active" id="step-1"> <div class="form-step active" id="step-1">
<h2 class="form-title">Create Your Account</h2> <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"> <form id="account-form">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="company-name" class="input-label">Company / Client Name *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -107,7 +113,7 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<div></div> <!-- Empty div for spacing --> <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> </div>
</form> </form>
</div> </div>
@ -115,6 +121,9 @@
<!-- Step 2: Business Information --> <!-- Step 2: Business Information -->
<div class="form-step" id="step-2"> <div class="form-step" id="step-2">
<h2 class="form-title">Business Information</h2> <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"> <form id="business-form">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -124,9 +133,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="city" class="input-label">City *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -141,13 +153,16 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="zip" class="input-label">ZIP *</label>
</div> </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>
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(1)">Back</button> <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> </div>
</form> </form>
</div> </div>
@ -155,6 +170,9 @@
<!-- Step 3: Billing Address --> <!-- Step 3: Billing Address -->
<div class="form-step" id="step-3"> <div class="form-step" id="step-3">
<h2 class="form-title">Billing Address</h2> <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"> <form id="billing-form">
<div class="checkbox-group"> <div class="checkbox-group">
<input type="checkbox" id="use-business-address"> <input type="checkbox" id="use-business-address">
@ -174,9 +192,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="billing-city" class="input-label">City *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -190,9 +211,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="billing-zip" class="input-label">ZIP *</label>
</div> </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>
<div class="checkbox-group"> <div class="checkbox-group">
<input type="checkbox" id="use-account-email"> <input type="checkbox" id="use-account-email">
@ -206,7 +230,7 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(2)">Back</button> <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> </div>
</form> </form>
</div> </div>
@ -214,21 +238,30 @@
<!-- Step 4: Authorized Signatory --> <!-- Step 4: Authorized Signatory -->
<div class="form-step" id="step-4"> <div class="form-step" id="step-4">
<h2 class="form-title">Authorized Signatory</h2> <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;"> <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. We will use the details below in the agreement that we will send to your prior to entering the project.
</p> </p>
<form id="signatory-form"> <form id="signatory-form">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="position" class="input-label">Position (Owner / CEO / Manager / etc.) *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="signatory-name" class="input-label">Name *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -238,7 +271,7 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(3)">Back</button> <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> </div>
</form> </form>
</div> </div>
@ -246,6 +279,9 @@
<!-- Step 5: Payment Details --> <!-- Step 5: Payment Details -->
<div class="form-step" id="step-5"> <div class="form-step" id="step-5">
<h2 class="form-title">Payment Details</h2> <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"> <form id="payment-form">
<div class="payment-option" onclick="selectPaymentOption('card')"> <div class="payment-option" onclick="selectPaymentOption('card')">
<h4>Credit or Debit Card</h4> <h4>Credit or Debit Card</h4>
@ -267,16 +303,22 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="security-code" class="input-label">Security Code *</label>
</div> </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> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="card-zip" class="input-label">ZIP Code *</label>
</div> </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> </div>
<div class="payment-option" onclick="selectPaymentOption('bank')"> <div class="payment-option" onclick="selectPaymentOption('bank')">
@ -286,15 +328,21 @@
<div id="bank-details" class="payment-details"> <div id="bank-details" class="payment-details">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="bank-name" class="input-label">Bank Name *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="account-number" class="input-label">Account Number *</label>
</div> </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>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <div class="input-container">
@ -314,9 +362,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="account-holder-name" class="input-label">Account Holder Name *</label>
</div> </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> </div>
<div class="form-actions"> <div class="form-actions">

View File

@ -165,17 +165,20 @@
<!-- Step 1: Account Setup --> <!-- Step 1: Account Setup -->
<div class="form-step active" id="step-1"> <div class="form-step active" id="step-1">
<h2 class="form-title">Account Setup</h2> <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"> <form id="account-form">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="email" class="input-label">Email Address *</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="password" class="input-label">Create Password *</label>
</div> </div>
<div class="password-actions"> <div class="password-actions">
@ -186,7 +189,7 @@
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="confirmPassword" class="input-label">Confirm Password *</label>
</div> </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 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 class="form-actions">
<div></div> <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> </div>
</form> </form>
</div> </div>
@ -202,7 +205,9 @@
<!-- Step 2: Personal Information --> <!-- Step 2: Personal Information -->
<div class="form-step" id="step-2"> <div class="form-step" id="step-2">
<h2 class="form-title">Personal Information</h2> <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;"> <div class="profile-incomplete" id="profile-incomplete" style="display: none;">
Profile is not yet complete, first name is missing Profile is not yet complete, first name is missing
</div> </div>
@ -232,7 +237,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="dob" class="input-label">Date of Birth</label>
</div> </div>
</div> </div>
@ -274,7 +279,7 @@
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(1)">Back</button> <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> </div>
</form> </form>
</div> </div>
@ -282,13 +287,19 @@
<!-- Step 3: Contact Information --> <!-- Step 3: Contact Information -->
<div class="form-step" id="step-3"> <div class="form-step" id="step-3">
<h2 class="form-title">Contact Information</h2> <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"> <form id="contact-form">
<div class="form-group"> <div class="form-group">
<label for="phone" class="input-label">Phone Number *</label> <label for="phone" class="input-label">Phone Number *</label>
<div class="input-container"> <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> <label for="phone" class="input-label">+ Phone Number</label>
</div> </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>
<div class="form-group"> <div class="form-group">
@ -300,7 +311,7 @@
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="prevStep(2)">Back</button> <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> </div>
</form> </form>
</div> </div>
@ -308,6 +319,9 @@
<!-- Step 4: Payment Details --> <!-- Step 4: Payment Details -->
<div class="form-step" id="step-4"> <div class="form-step" id="step-4">
<h2 class="form-title">Payment Details</h2> <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"> <form id="payment-form">
<div class="form-group"> <div class="form-group">
<div class="payment-option" onclick="selectPaymentMethod('payoneer')"> <div class="payment-option" onclick="selectPaymentMethod('payoneer')">
@ -317,9 +331,12 @@
<div id="payoneer-details" class="payment-details"> <div id="payoneer-details" class="payment-details">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="payoneerAccount" class="input-label">Payoneer Account Number *</label>
</div> </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>
</div> </div>
@ -330,9 +347,12 @@
<div id="veem-details" class="payment-details"> <div id="veem-details" class="payment-details">
<div class="form-group"> <div class="form-group">
<div class="input-container"> <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> <label for="veemAccount" class="input-label">Veem Account Number *</label>
</div> </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> </div>
</div> </div>