From 8cc6f197b2b3a1a65b77dc9667f3aee14f3baae8 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Tue, 16 Dec 2025 10:04:40 +0530 Subject: [PATCH 01/44] adds service files for quicklab --- client/src/service/labAdminService.js | 34 ++++++++++++++++ client/src/service/labAppointmentService.js | 19 +++++++++ client/src/service/labPublicService.js | 11 ++++++ client/src/service/labReportService.js | 28 +++++++++++++ client/src/service/labStaffService.js | 44 +++++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 client/src/service/labAdminService.js create mode 100644 client/src/service/labAppointmentService.js create mode 100644 client/src/service/labPublicService.js create mode 100644 client/src/service/labReportService.js create mode 100644 client/src/service/labStaffService.js diff --git a/client/src/service/labAdminService.js b/client/src/service/labAdminService.js new file mode 100644 index 0000000..28d14f8 --- /dev/null +++ b/client/src/service/labAdminService.js @@ -0,0 +1,34 @@ +import apiService from './apiservice'; +import { createFormDataFromObject } from '../utility/formDataHelper'; + +export const createLabAdminProfile = (data) => apiService.post('/lab-admin/profile', data); + +export const createLab = (labData, logoFile, photoFiles = []) => { + const formData = createFormDataFromObject(labData, { logo: logoFile, photos: photoFiles }); + return apiService.post('/lab-admin/lab', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +}; + +export const searchLabStaff = (query) => + apiService.get('/lab-admin/staff/search', { params: query }); +export const addStaffToLab = (staffId) => apiService.post('/lab-admin/staff/add', { staffId }); +export const removeStaffFromLab = (staffId) => apiService.delete(`/lab-admin/staff/${staffId}`); +export const getLabStaff = () => apiService.get('/lab-admin/staff'); + +export const addLabTest = (testData) => apiService.post('/lab-admin/tests', testData); +export const updateLabTest = (testId, updates) => + apiService.put(`/lab-admin/tests/${testId}`, updates); +export const getLabInfo = () => apiService.get('/lab-admin/lab/info'); + +export default { + createLabAdminProfile, + createLab, + searchLabStaff, + addStaffToLab, + removeStaffFromLab, + getLabStaff, + addLabTest, + updateLabTest, + getLabInfo, +}; diff --git a/client/src/service/labAppointmentService.js b/client/src/service/labAppointmentService.js new file mode 100644 index 0000000..4e66749 --- /dev/null +++ b/client/src/service/labAppointmentService.js @@ -0,0 +1,19 @@ +import apiService from './apiservice'; + +export const bookLabAppointment = (payload) => apiService.post('/lab-appointment/book', payload); +export const getPatientLabAppointments = (params = {}) => + apiService.get('/lab-appointment/patient', { params }); +export const getLabAppointments = (params = {}) => + apiService.get('/lab-appointment/lab', { params }); +export const assignStaffForCollection = (appointmentId, staffId) => + apiService.put(`/lab-appointment/${appointmentId}/assign-staff`, { staffId }); +export const updateLabAppointmentStatus = (appointmentId, status) => + apiService.put(`/lab-appointment/${appointmentId}/status`, { status }); + +export default { + bookLabAppointment, + getPatientLabAppointments, + getLabAppointments, + assignStaffForCollection, + updateLabAppointmentStatus, +}; diff --git a/client/src/service/labPublicService.js b/client/src/service/labPublicService.js new file mode 100644 index 0000000..4ccf0a6 --- /dev/null +++ b/client/src/service/labPublicService.js @@ -0,0 +1,11 @@ +import apiService from './apiservice'; + +export const searchLabs = (params = {}) => apiService.get('/lab/search', { params }); +export const getLabDetails = (labId) => apiService.get(`/lab/${labId}`); +export const getLabTests = (labId) => apiService.get(`/lab/${labId}/tests`); + +export default { + searchLabs, + getLabDetails, + getLabTests, +}; diff --git a/client/src/service/labReportService.js b/client/src/service/labReportService.js new file mode 100644 index 0000000..3163581 --- /dev/null +++ b/client/src/service/labReportService.js @@ -0,0 +1,28 @@ +import apiService from './apiservice'; + +export const uploadLabReport = (appointmentId, file, testResults) => { + const formData = new FormData(); + if (file) { + formData.append('reportFile', file); + } + if (testResults) { + formData.append('testResults', JSON.stringify(testResults)); + } + return apiService.post(`/lab-report/upload/${appointmentId}`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +}; + +export const getPatientLabReports = () => apiService.get('/lab-report/patient'); +export const getDoctorPatientReports = () => apiService.get('/lab-report/doctor'); +export const addDoctorRemarks = (reportId, remarks) => + apiService.put(`/lab-report/${reportId}/remarks`, { remarks }); +export const getReportDetails = (reportId) => apiService.get(`/lab-report/${reportId}`); + +export default { + uploadLabReport, + getPatientLabReports, + getDoctorPatientReports, + addDoctorRemarks, + getReportDetails, +}; diff --git a/client/src/service/labStaffService.js b/client/src/service/labStaffService.js new file mode 100644 index 0000000..16ab6d1 --- /dev/null +++ b/client/src/service/labStaffService.js @@ -0,0 +1,44 @@ +import apiService from './apiservice'; +import { createFormDataFromObject } from '../utility/formDataHelper'; + +export const createStaffProfile = (profileData, profilePicture) => { + const formData = createFormDataFromObject(profileData, { photos: [], logo: null }); + if (profilePicture) { + formData.append('profilePicture', profilePicture); + } + return apiService.post('/lab-staff/profile', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +}; + +export const updateStaffProfile = (updates, profilePicture) => { + const formData = createFormDataFromObject(updates, { photos: [], logo: null }); + if (profilePicture) { + formData.append('profilePicture', profilePicture); + } + return apiService.put('/lab-staff/profile', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); +}; + +export const getStaffProfile = () => apiService.get('/lab-staff/profile'); +export const checkStaffProfileExists = () => apiService.get('/lab-staff/profile/check'); +export const getMyAssignments = (params = {}) => + apiService.get('/lab-staff/assignments', { params }); +export const getAssignmentDetails = (appointmentId) => + apiService.get(`/lab-staff/assignments/${appointmentId}`); +export const updateMyAssignmentStatus = (appointmentId, status, notes) => + apiService.put(`/lab-staff/assignments/${appointmentId}/status`, { status, notes }); +export const completeAssignment = (appointmentId, notes) => + apiService.post(`/lab-staff/assignments/${appointmentId}/complete`, { notes }); + +export default { + createStaffProfile, + updateStaffProfile, + getStaffProfile, + checkStaffProfileExists, + getMyAssignments, + getAssignmentDetails, + updateMyAssignmentStatus, + completeAssignment, +}; From a0f0ffdd9d4a6357e54881d8b773714d44346f12 Mon Sep 17 00:00:00 2001 From: Aditya Shah Date: Tue, 16 Dec 2025 11:15:11 +0530 Subject: [PATCH 02/44] modifis login and register page for lab admin and lab staff --- client/src/App.jsx | 3 + client/src/components/auth/AuthButton.jsx | 12 +- client/src/components/auth/ErrorMessage.jsx | 32 +- client/src/components/auth/PasswordInput.jsx | 34 +- client/src/pages/public/LoginPage.jsx | 242 ++++++++---- client/src/pages/public/RegisterPage.jsx | 344 +++++++++++++----- client/src/pages/quicklab/LabAdminAddLab.jsx | 341 +++++++++++++++++ .../quicklab/LabAdminProfileComplete.jsx | 239 ++++++++++++ client/src/routes/QuickLabRoutes.jsx | 52 ++- .../routes/guards/LabAdminPreventGuard.jsx | 67 ++++ .../src/routes/guards/LabAdminSetupGuard.jsx | 67 ++++ client/src/service/labAdminService.js | 16 + client/src/utility/getDashboardPath.js | 3 + .../QuickLab/labAdminController.js | 98 ++++- server/Routes/QuickLab/labAdminRoutes.js | 9 + 15 files changed, 1345 insertions(+), 214 deletions(-) create mode 100644 client/src/pages/quicklab/LabAdminAddLab.jsx create mode 100644 client/src/pages/quicklab/LabAdminProfileComplete.jsx create mode 100644 client/src/routes/guards/LabAdminPreventGuard.jsx create mode 100644 client/src/routes/guards/LabAdminSetupGuard.jsx diff --git a/client/src/App.jsx b/client/src/App.jsx index c96cd82..65ae8c2 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -60,6 +60,9 @@ function AppInner() { return ; case 'doctor': return ; + case 'lab_admin': + case 'lab_staff': + return ; default: // Fallback for unknown roles navigate('/', { replace: true }); diff --git a/client/src/components/auth/AuthButton.jsx b/client/src/components/auth/AuthButton.jsx index 5846e7f..e039b0b 100644 --- a/client/src/components/auth/AuthButton.jsx +++ b/client/src/components/auth/AuthButton.jsx @@ -1,7 +1,5 @@ import { Loader2 } from 'lucide-react'; -import Loading from '../ui/Loading'; - export const AuthButton = ({ children, isLoading = false, @@ -12,12 +10,11 @@ export const AuthButton = ({ variant = 'primary', }) => { const baseClasses = - 'w-full flex justify-center items-center gap-2 px-8 py-4 border border-transparent rounded-xl text-lg font-semibold transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-[1.02] active:scale-[0.98]'; + 'w-full flex justify-center items-center gap-2 px-4 py-3 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed'; const variantClasses = { - primary: - 'text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:ring-blue-500 shadow-lg hover:shadow-xl', - secondary: 'text-blue-700 bg-blue-50 border-blue-200 hover:bg-blue-100 focus:ring-blue-500', + primary: 'text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500', + secondary: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-gray-500', }; const isDisabled = disabled || isLoading; @@ -31,7 +28,8 @@ export const AuthButton = ({ > {isLoading ? ( <> - + + Please wait... ) : ( children diff --git a/client/src/components/auth/ErrorMessage.jsx b/client/src/components/auth/ErrorMessage.jsx index 7218ff6..edb012d 100644 --- a/client/src/components/auth/ErrorMessage.jsx +++ b/client/src/components/auth/ErrorMessage.jsx @@ -1,13 +1,31 @@ -import { AlertCircle } from 'lucide-react'; +import { AlertCircle, X } from 'lucide-react'; +import { useState } from 'react'; export const ErrorMessage = ({ error, className = '' }) => { - if (!error) return null; + const [isDismissed, setIsDismissed] = useState(false); + + if (!error || isDismissed) return null; + return ( -
- -

{error}

+
+
+
+
+
+ +
+
+
+

{error}

+
+ +
+
); }; diff --git a/client/src/components/auth/PasswordInput.jsx b/client/src/components/auth/PasswordInput.jsx index 539fedf..12dc115 100644 --- a/client/src/components/auth/PasswordInput.jsx +++ b/client/src/components/auth/PasswordInput.jsx @@ -1,4 +1,4 @@ -import { Eye, EyeOff, Lock } from 'lucide-react'; +import { Eye, EyeOff } from 'lucide-react'; export const PasswordInput = ({ id, @@ -13,38 +13,38 @@ export const PasswordInput = ({ error = null, }) => { return ( -
- {label && ( - - )} +
-
- -
- {error &&

{error}

} + {error &&

{error}

}
); }; diff --git a/client/src/pages/public/LoginPage.jsx b/client/src/pages/public/LoginPage.jsx index e413536..9cfd739 100644 --- a/client/src/pages/public/LoginPage.jsx +++ b/client/src/pages/public/LoginPage.jsx @@ -1,19 +1,24 @@ -import { Mail } from 'lucide-react'; +import { Mail, ArrowRight, Shield } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { AuthButton } from '../../components/auth/AuthButton.jsx'; -import { AuthLayout } from '../../components/auth/AuthLayout.jsx'; import { ErrorMessage } from '../../components/auth/ErrorMessage.jsx'; import { PasswordInput } from '../../components/auth/PasswordInput.jsx'; import { useAuth } from '../../context/authContext.jsx'; const LoginPage = ({ error, setError }) => { - const [formData, setFormData] = useState({ email: '', password: '' }); + const navigate = useNavigate(); + const { user, login, isAuthenticated } = useAuth(); + + const [formData, setFormData] = useState({ + email: '', + password: '', + }); + const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [rememberMe, setRememberMe] = useState(false); const [fieldErrors, setFieldErrors] = useState({}); - const { login, isAuthenticated, user } = useAuth(); - const navigate = useNavigate(); useEffect(() => { if (isAuthenticated && user) { @@ -22,19 +27,27 @@ const LoginPage = ({ error, setError }) => { navigate('/doctor-dashboard'); break; case 'admin': - navigate('/admin/complete-profile'); + navigate('/admin-dashboard'); + break; + case 'lab_admin': + navigate('/lab-admin/dashboard'); + break; + case 'lab_staff': + navigate('/lab-staff/dashboard'); break; default: navigate('/patient-dashboard'); } } - }, [isAuthenticated, navigate]); + }, [isAuthenticated, navigate, user]); const validateForm = () => { const errors = {}; if (!formData.email) errors.email = 'Email is required'; - else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.email = 'Email is invalid'; + else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.email = 'Invalid email address'; + if (!formData.password) errors.password = 'Password is required'; + setFieldErrors(errors); return Object.keys(errors).length === 0; }; @@ -48,102 +61,171 @@ const LoginPage = ({ error, setError }) => { const handleSubmit = async (e) => { e.preventDefault(); - if (!validateForm()) return; setIsLoading(true); - setError(''); // Clear any existing errors + setError(''); - // Call the login function and handle the result const result = await login({ email: formData.email, password: formData.password, }); if (!result.success) { - setError(result.error || 'Login failed. Please try again.'); + setError(result.error || 'Invalid credentials. Please try again.'); } - setIsLoading(false); }; return ( - -
-
-

Welcome Back

-

Sign in to your account

-
- - - -
- {/* Email Input */} -
- -
- - +
+ {/* Left Side - Form */} +
+
+ {/* Header */} +
+
+
- {fieldErrors.email &&

{fieldErrors.email}

} +

+ Sign in to Quick Clinic +

+

+ Access your healthcare dashboard +

- {/* Password Input */} -
- - - {fieldErrors.password && ( -

{fieldErrors.password}

- )} + {/* Form */} +
+ + + + {/* Email */} +
+ + + {fieldErrors.email && ( +

{fieldErrors.email}

+ )} +
+ + {/* Password */} +
+
+ + + Forgot password? + +
+ +
+ + {/* Remember Me */} +
+ setRememberMe(e.target.checked)} + className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" + /> + +
+ + {/* Submit */} + + Sign in + +
- {/* Forgot Password Link */} -
- - Forgot password? + {/* Sign Up Link */} +

+ Don't have an account?{' '} + + Create account -

- - {/* Submit Button */} - - {isLoading ? 'Signing In...' : 'Sign In'} - +

+
+
- {/* Register Link */} -
-

- Don't have an account?{' '} - - Create one - -

+ {/* Right Side - Branding */} +
+
+

Welcome back to Quick Clinic

+

+ Manage appointments, access medical reports, and connect with your healthcare team—all + in one secure platform. +

+
+
+
+ Secure & encrypted +
+
+
+ 24/7 access to your health data +
+
+
+ HIPAA compliant +
- +
- +
); }; diff --git a/client/src/pages/public/RegisterPage.jsx b/client/src/pages/public/RegisterPage.jsx index 142302d..43b5395 100644 --- a/client/src/pages/public/RegisterPage.jsx +++ b/client/src/pages/public/RegisterPage.jsx @@ -1,8 +1,7 @@ -import { Check, Mail, Shield, Stethoscope, User } from 'lucide-react'; +import { Check, Mail, Shield, Stethoscope, User, Microscope, FlaskConical } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { AuthButton } from '../../components/auth/AuthButton.jsx'; -import { AuthLayout } from '../../components/auth/AuthLayout.jsx'; import { ErrorMessage } from '../../components/auth/ErrorMessage.jsx'; import { PasswordInput } from '../../components/auth/PasswordInput.jsx'; import { useAuth } from '../../context/authContext.jsx'; @@ -17,6 +16,7 @@ const RegisterPage = ({ error, setError }) => { confirmPassword: '', role: 'patient', }); + const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -31,19 +31,26 @@ const RegisterPage = ({ error, setError }) => { case 'admin': navigate('/admin/complete-profile'); break; + case 'lab_admin': + navigate('/quick-lab/lab-admin/dashboard'); + break; + case 'lab_staff': + navigate('/quick-lab/lab-staff/dashboard'); + break; default: navigate('/patient-dashboard'); } } - }, [isAuthenticated, navigate]); + }, [isAuthenticated, navigate, user]); const validateForm = () => { const errors = {}; if (!formData.email) errors.email = 'Email is required'; - else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.email = 'Email is invalid'; + else if (!/\S+@\S+\.\S+/.test(formData.email)) errors.email = 'Invalid email address'; + if (!formData.password) errors.password = 'Password is required'; - else if (formData.password.length < 8) - errors.password = 'Password must be at least 8 characters'; + else if (formData.password.length < 8) errors.password = 'Must be at least 8 characters'; + if (formData.password !== formData.confirmPassword) errors.confirmPassword = 'Passwords do not match'; @@ -70,116 +77,251 @@ const RegisterPage = ({ error, setError }) => { password: formData.password, role: formData.role, }); + if (!result.success) { - console.log(result); setError(result.error || 'Registration failed. Please try again.'); } setIsLoading(false); }; const roleOptions = [ - { value: 'patient', label: 'Patient', icon: User, color: 'blue' }, - { value: 'doctor', label: 'Doctor', icon: Stethoscope, color: 'green' }, - { value: 'admin', label: 'Admin', icon: Shield, color: 'purple' }, + { + value: 'patient', + label: 'Patient', + icon: User, + description: 'Book appointments & manage health records', + }, + { + value: 'doctor', + label: 'Doctor', + icon: Stethoscope, + description: 'Manage consultations & patient care', + }, + { + value: 'admin', + label: 'Administrator', + icon: Shield, + description: 'Oversee clinic operations', + }, + { + value: 'lab_admin', + label: 'Lab Administrator', + icon: Microscope, + description: 'Manage laboratory operations', + }, + { + value: 'lab_staff', + label: 'Lab Staff', + icon: FlaskConical, + description: 'Process tests & upload results', + }, ]; return ( - -
- - -
- -
- {roleOptions.map((option) => ( - - ))} +
+ {/* Left Side - Branding */} +
+
+

Join Quick Clinic

+

+ One unified platform for patients, doctors, administrators, and laboratory staff. +

+
+
+
+ Role-based access control +
+
+
+ Enterprise-grade security +
+
+
+ Seamless healthcare management +
+
-
-
- + {/* Right Side - Form */} +
+
+ {/* Header - Compact */} +
+
+ +
+

+ Create your account +

+

+ Get started with Quick Clinic today +

- -
- {fieldErrors.email && ( -

{fieldErrors.email}

- )} - - - - - -
- - Create Account - -
-

- Already have an account?{' '} - - Sign In - -

- - + {/* Form - Compact */} +
+
+ + + {/* Role Selection - Compact Grid */} +
+ +
+ {roleOptions.map((role) => { + const Icon = role.icon; + const isSelected = formData.role === role.value; + + return ( + + ); + })} +
+
+ + {/* Email & Passwords in 2 Columns */} +
+ {/* Email */} +
+ + + {fieldErrors.email && ( +

+ {fieldErrors.email} +

+ )} +
+ + {/* Password */} +
+ + +
+ + {/* Confirm Password */} +
+ + +
+
+ + {/* Terms - Compact */} +

+ By creating an account, you agree to our{' '} + + Terms + {' '} + and{' '} + + Privacy Policy + + . +

+ + {/* Submit - Compact */} + + Create account + + +
+ + {/* Sign In Link - Compact */} +

+ Already have an account?{' '} + + Sign in + +

+
+
+
); }; diff --git a/client/src/pages/quicklab/LabAdminAddLab.jsx b/client/src/pages/quicklab/LabAdminAddLab.jsx new file mode 100644 index 0000000..1c33ea2 --- /dev/null +++ b/client/src/pages/quicklab/LabAdminAddLab.jsx @@ -0,0 +1,341 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Building2, Phone, Mail, Globe, MapPin, Upload, Image } from 'lucide-react'; +import Loading from '../../components/ui/Loading'; +import { createLab } from '../../service/labAdminService'; +import '../../quicklab.css'; + +const initialState = { + name: '', + description: '', + contact: { + phone: '', + email: '', + website: '', + }, + address: { + formattedAddress: '', + city: '', + state: '', + zipCode: '', + country: '', + }, + generalHomeCollectionFee: '', +}; + +export default function LabAdminAddLab() { + const navigate = useNavigate(); + const [form, setForm] = useState(initialState); + const [logoFile, setLogoFile] = useState(null); + const [photoFiles, setPhotoFiles] = useState([]); + const [logoPreview, setLogoPreview] = useState(null); + const [photoPreviews, setPhotoPreviews] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const handleInputChange = (path, value) => { + setForm((prev) => { + // Safe deep clone for nested updates + const updated = JSON.parse(JSON.stringify(prev)); + const keys = path.split('.'); + let target = updated; + keys.slice(0, -1).forEach((k) => { + target[k] = target[k] || {}; + target = target[k]; + }); + target[keys[keys.length - 1]] = value; + return updated; + }); + }; + + const handleLogoChange = (e) => { + const file = e.target.files?.[0]; + setLogoFile(file || null); + setLogoPreview(file ? URL.createObjectURL(file) : null); + }; + + const handlePhotosChange = (e) => { + const files = Array.from(e.target.files || []); + setPhotoFiles(files); + setPhotoPreviews(files.map((file) => URL.createObjectURL(file))); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setLoading(true); + + try { + if (!form.name.trim() || !form.contact.phone.trim()) { + setError('Lab name and contact phone are required.'); + setLoading(false); + return; + } + + const payload = { + ...form, + generalHomeCollectionFee: form.generalHomeCollectionFee + ? Number(form.generalHomeCollectionFee) + : undefined, + }; + + await createLab(payload, logoFile, photoFiles); + navigate('/quick-lab/dashboard', { replace: true }); + } catch (err) { + setError(err?.response?.data?.message || 'Failed to create lab'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+ +
+

Add Your Lab

+

+ Provide your lab details so patients and doctors can find and trust your services. +

+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+
+
+ +
+ handleInputChange('name', e.target.value)} + className="w-full pl-10 pr-4 py-3 rounded-lg border border-lab-black-200 text-lab-black-900 placeholder-lab-black-400 focus:outline-none focus:ring-2 focus:ring-lab-yellow-400 focus:border-transparent" + placeholder="QuickLab Diagnostics" + required + /> + +
+
+ +
+ +
+ handleInputChange('contact.phone', e.target.value)} + className="w-full pl-10 pr-4 py-3 rounded-lg border border-lab-black-200 text-lab-black-900 placeholder-lab-black-400 focus:outline-none focus:ring-2 focus:ring-lab-yellow-400 focus:border-transparent" + placeholder="+1 (555) 000-0000" + required + /> + +
+
+
+ +
+ +