diff --git a/public/css/dashboard.css b/public/css/dashboard.css index 303b46f..71fea48 100644 --- a/public/css/dashboard.css +++ b/public/css/dashboard.css @@ -1850,4 +1850,131 @@ tr[data-vaid]:hover { font-size: 0.75rem; font-weight: 600; margin-left: 0.5rem; +} + + +.payment-option { + border: 1px solid #e5e7eb; + border-radius: 8px; + padding: 16px; + cursor: pointer; +} + +.payment-option.selected { + border-color: var(--border-color); + transition: all 0.3s ease; + background: var(--bg-card); +} + +.payment-option:hover { + border-color: var(--accent-light); +} + +.payment-option.selected { + border-color: var(--accent-color); + background-color: rgba(99, 102, 241, 0.1); +} + +[data-theme="dark"] .payment-option.selected { + background-color: rgba(99, 102, 241, 0.2); +} + +.payment-option h4 { + margin-bottom: 8px; + font-size: 16px; + color: var(--text-primary); +} + +.payment-option p { + font-size: 14px; + color: var(--text-secondary); +} + +.payment-details { + display: none; + margin: 4px 0 16px; + padding: 16px; + border: 1px solid var(--border-color); + border-radius: 8px; + transition: all 0.3s ease; +} + +.payment-details.active { + display: block; + animation: slideDown 0.3s ease-out; +} + + +.form-group { + margin-bottom: 16px; + position: relative; +} + +.input-container { + position: relative; + background-color: var(--bg-secondary); + border-radius: 6px; + overflow: hidden; + padding: 2px 0; + border: 1px solid transparent; + transition: all 0.3s ease; +} + +.input-container:focus-within { + border-color: var(--accent-light); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +.input-label { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + font-size: 14px; + color: var(--text-secondary); + transition: all 0.2s ease; + pointer-events: none; +} + +.input-field, .select-field { + width: 100%; + padding: 18px 12px 14px; + border: none; + background: transparent; + outline: none; + font-size: 14px; + border-radius: 6px; + color: var(--text-primary); + transition: all 0.3s ease; +} + +[data-theme="dark"] .input-field, [data-theme="dark"] .select-field { + background-color: #ffffff1a; +} + +.input-field::placeholder { + color: var(--text-muted); +} + +.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; +} + +[data-theme="dark"] .select-field { + 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='%23d1d5db' 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"); +} + +.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; + color: var(--accent-light); } \ No newline at end of file diff --git a/public/js/va-client-register.js b/public/js/va-client-register.js index 1e31d69..23b6c77 100644 --- a/public/js/va-client-register.js +++ b/public/js/va-client-register.js @@ -491,10 +491,11 @@ async function validateStep(step) { } } else if (step === 1) { const email = document.getElementById('email').value; - const companyName = document.getElementById('company-name').value; + const firstName = document.getElementById('first-name').value; + const lastName = document.getElementById('last-name').value; const password = document.getElementById('password').value; const confirmPassword = document.getElementById('confirmPassword').value; - if (!validateEmail(email) && !companyName && !password && !confirmPassword) { + if (!validateEmail(email) && !firstName && !lastName && !password && !confirmPassword) { isValid = false; showError('Fields are Required', null, step); let requiredFields1 = form.querySelectorAll('[required]'); @@ -518,9 +519,14 @@ async function validateStep(step) { isValid = false; } } - if (!companyName) { + if (!firstName) { isValid = false; - showError('Company / Client Name is Required', null, step); + showError('First Name is Required', null, step); + return isValid; + } + if (!lastName) { + isValid = false; + showError('Last Name is Required', null, step); return isValid; } @@ -627,7 +633,8 @@ function getFormId(step) { function saveStepData(step) { switch(step) { case 1: - formData.userDetails.name = document.getElementById('company-name').value; + formData.userDetails.firstName = document.getElementById('first-name').value; + formData.userDetails.lastName = document.getElementById('last-name').value; formData.userDetails.email = document.getElementById('email').value; formData.userDetails.password = document.getElementById('password').value; break; @@ -1085,7 +1092,7 @@ document.addEventListener('DOMContentLoaded', function() { // Handle use business address checkbox document.getElementById('use-business-address').addEventListener('change', function() { if (this.checked) { - document.getElementById('billing-name').value = formData.userDetails.name || ''; + document.getElementById('billing-name').value = formData.userDetails.firstName || ''; document.getElementById('billing-address').value = formData.userDetails.address || ''; document.getElementById('billing-city').value = formData.userDetails.city || ''; document.getElementById('billing-state').value = formData.userDetails.state || ''; @@ -1258,13 +1265,13 @@ function preventNumbers(event) { return false; } -// Function to validate company/client name -function validateCompanyName(input) { - const errorElement = document.getElementById('company-name-error'); +// Function to validate first name +function validateFirstName(input) { + const errorElement = document.getElementById('first-name-error'); const value = input.value; // Check various invalid patterns - const validationResult = validateCompanyNameContent(value); + const validationResult = validateFirstNameContent(value); if (!validationResult.isValid && value.trim().length > 0) { errorElement.textContent = validationResult.message; @@ -1278,16 +1285,16 @@ function validateCompanyName(input) { } } -function validateCompanyNameContent(companyName) { - const trimmed = companyName.trim(); +function validateFirstNameContent(firstName) { + const trimmed = firstName.trim(); if (trimmed.length === 0) { - return { isValid: false, message: 'Company name is required' }; + return { isValid: false, message: 'First name is required' }; } // Check minimum length if (trimmed.length < 2) { - return { isValid: false, message: 'Company name must be at least 2 characters' }; + return { isValid: false, message: 'First name must be at least 2 characters' }; } // Remove whitespace for content analysis @@ -1295,25 +1302,112 @@ function validateCompanyNameContent(companyName) { // Check if contains only special characters if (/^[^a-zA-Z0-9]+$/.test(content)) { - return { isValid: false, message: 'Company name cannot be only special characters' }; + return { isValid: false, message: 'First name cannot be only special characters' }; } // Check if contains at least some valid content (letters or numbers) if (!/[a-zA-Z0-9]/.test(content)) { - return { isValid: false, message: 'Company name must contain letters or numbers' }; + return { isValid: false, message: 'First name must contain letters or numbers' }; } // Optional: Check for excessive special characters (more than 50%) const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length; if (specialCharCount > content.length / 2) { - return { isValid: false, message: 'Company name contains too many special characters' }; + return { isValid: false, message: 'First name contains too many special characters' }; } return { isValid: true, message: '' }; } // Function to prevent invalid character input in real-time -function preventInvalidCompanyChars(event) { +function preventInvalidFirstNameChars(event) { + const charCode = event.which ? event.which : event.keyCode; + const key = event.key; + + // Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas + // Also allow common editing keys + if ((charCode >= 65 && charCode <= 90) || // A-Z + (charCode >= 97 && charCode <= 122) || // a-z + (charCode >= 48 && charCode <= 57) || // 0-9 + charCode === 32 || // space + charCode === 45 || // hyphen + charCode === 39 || // apostrophe + charCode === 38 || // ampersand (&) + charCode === 46 || // period (.) + charCode === 44 || // comma (,) + charCode === 8 || // backspace + charCode === 9 || // tab + charCode === 37 || // left arrow + charCode === 39 || // right arrow + charCode === 16 || // shift + charCode === 17 || // ctrl + charCode === 18 || // alt + charCode === 91 || // cmd (Mac) + /^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters + return true; + } + + // Prevent invalid characters + event.preventDefault(); + return false; +} + +// Function to validate last name +function validateLastName(input) { + const errorElement = document.getElementById('last-name-error'); + const value = input.value; + + // Check various invalid patterns + const validationResult = validateLastNameContent(value); + + if (!validationResult.isValid && value.trim().length > 0) { + errorElement.textContent = validationResult.message; + errorElement.style.display = 'block'; + input.classList.add('field-error'); + return false; + } else { + errorElement.style.display = 'none'; + input.classList.remove('field-error'); + return true; + } +} + +function validateLastNameContent(lastName) { + const trimmed = lastName.trim(); + + if (trimmed.length === 0) { + return { isValid: false, message: 'Last name is required' }; + } + + // Check minimum length + if (trimmed.length < 2) { + return { isValid: false, message: 'Last name must be at least 2 characters' }; + } + + // Remove whitespace for content analysis + const content = trimmed.replace(/\s/g, ''); + + // Check if contains only special characters + if (/^[^a-zA-Z0-9]+$/.test(content)) { + return { isValid: false, message: 'Last name cannot be only special characters' }; + } + + // Check if contains at least some valid content (letters or numbers) + if (!/[a-zA-Z0-9]/.test(content)) { + return { isValid: false, message: 'Last name must contain letters or numbers' }; + } + + // Optional: Check for excessive special characters (more than 50%) + const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length; + if (specialCharCount > content.length / 2) { + return { isValid: false, message: 'Last name contains too many special characters' }; + } + + return { isValid: true, message: '' }; +} + +// Function to prevent invalid character input in real-time +function preventInvalidLastNameChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; diff --git a/src/controllers/client/vaClient.controller.js b/src/controllers/client/vaClient.controller.js index 25c3307..87695cc 100644 --- a/src/controllers/client/vaClient.controller.js +++ b/src/controllers/client/vaClient.controller.js @@ -25,10 +25,10 @@ async function va_register_client(req, res) { try { const payload = req.body || {}; const { userDetails } = payload; - if (!userDetails || !userDetails.email || !userDetails.name || !userDetails.password) { + if (!userDetails || !userDetails.email || !userDetails.firstName || !userDetails.lastName || !userDetails.password) { return res.status(400).json({ success: false, - message: "Missing required fields: name, email, and password are required" + message: "Missing required fields: firstName, lastName, email, and password are required" }); } diff --git a/src/models/vaModel.js b/src/models/vaModel.js index d585f87..6d1fdc4 100644 --- a/src/models/vaModel.js +++ b/src/models/vaModel.js @@ -136,6 +136,12 @@ const clientDetailsSchema = new Schema({ name: { type: String, }, + firstName: { + type: String, + }, + lastName: { + type: String, + }, email: { type: String, required: true, diff --git a/src/services/salesforce.service.js b/src/services/salesforce.service.js index b5acc62..78d4d9a 100644 --- a/src/services/salesforce.service.js +++ b/src/services/salesforce.service.js @@ -7,10 +7,8 @@ class SalesforceService { async registerClientInSalesforce(clientData, _id) { try { const url = `${process.env.SF_DOCTRACKER_API_URL}/services/apexrest/docva/api/client`; - // Extract only the required data for Salesforce const salesforcePayload = this.prepareSalesforcePayload(clientData, _id); - logger.info(`SalesforceService - Sending client data to Salesforce: ${clientData.userDetails.email}`); const response = await authFetch(url, { @@ -21,12 +19,12 @@ class SalesforceService { body: JSON.stringify(salesforcePayload), timeout: 30000, // 30 seconds timeout }); - logger.info(`SalesforceService - Client registered successfully in Salesforce: ${clientData.userDetails.email}`); + return response.data; } catch (error) { logger.error(`SalesforceService - Error registering client in Salesforce: ${error.message}`); - + if (error.response) { // Salesforce returned an error response logger.error(`SalesforceService - Salesforce API error: ${error.response.status} - ${JSON.stringify(error.response.data)}`); @@ -44,11 +42,12 @@ class SalesforceService { prepareSalesforcePayload(clientData, _id) { const { userDetails } = clientData; - + return { userDetails: { _id: _id, - name: userDetails.name, + firstName: userDetails.firstName, + lastName: userDetails.lastName, email: userDetails.email.toLowerCase(), address: userDetails.address || null, city: userDetails.city || null, diff --git a/src/services/vaClient.service.js b/src/services/vaClient.service.js index 563fc26..a55deff 100644 --- a/src/services/vaClient.service.js +++ b/src/services/vaClient.service.js @@ -75,7 +75,7 @@ class VaClientService { // Define allowed fields for update (excluding password) const allowedFields = [ - "name", "address", "city", "state", "zip", + "firstName", "lastName", "address", "city", "state", "zip", "billingName", "billingEmail", "billingAddress", "billingCity", "billingState", "billingZip", "position", "signatoryName", "signatoryEmail", "paymentMethod", "bankName", "accountNumber", @@ -271,7 +271,9 @@ class VaClientService { return { userDetails: { - name: userDetails.name, + firstName: userDetails.firstName, + lastName: userDetails.lastName, + name: `${userDetails.firstName} ${userDetails.lastName}`, email: userDetails.email.toLowerCase(), password: hashedPassword, address: userDetails.address || null, diff --git a/views/client/dashboard-profile.ejs b/views/client/dashboard-profile.ejs index 8dcb40b..b656b2a 100644 --- a/views/client/dashboard-profile.ejs +++ b/views/client/dashboard-profile.ejs @@ -136,9 +136,13 @@ - +
-
+
+
+ + +