import React, { useState, useEffect } from 'react'; import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { useOrg } from '../contexts/OrgContext'; import { EMPLOYEE_QUESTIONS, EmployeeSubmissionAnswers } from '../employeeQuestions'; import { API_URL } from '../constants'; import { WelcomeScreen, SectionIntro, PersonalInfoForm, TextAreaQuestion, RatingScaleQuestion, YesNoChoice, ThankYouPage } from '../components/figma/FigmaEmployeeForms'; /** * Enhanced Employee Questionnaire with Exact Figma Design Implementation * * Features: * - Exact Figma design system styling * - Invite-based flow (no authentication required) * - Company owner can invite employees with metadata * - LLM processing via cloud functions * - Report generation with company context * - Firestore storage for reports */ const EmployeeQuestionnaire: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const params = useParams(); const { user } = useAuth(); // Check if this is an invite-based flow (no auth/org needed) const inviteCode = params.inviteCode; const isInviteFlow = !!inviteCode; // Only use org context for authenticated flows let submitEmployeeAnswers, generateEmployeeReport, employees; if (!isInviteFlow) { const orgContext = useOrg(); ({ submitEmployeeAnswers, generateEmployeeReport, employees } = orgContext); } else { // For invite flows, we don't need these functions from org context submitEmployeeAnswers = null; generateEmployeeReport = null; employees = []; } const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); const [inviteEmployee, setInviteEmployee] = useState(null); const [isLoadingInvite, setIsLoadingInvite] = useState(false); // Load invite details if this is an invite flow useEffect(() => { if (inviteCode) { loadInviteDetails(inviteCode); } }, [inviteCode]); const loadInviteDetails = async (code: string) => { setIsLoadingInvite(true); try { const response = await fetch(`${API_URL}/getInvitationStatus?code=${code}`); if (response.ok) { const data = await response.json(); if (data.used) { setError('This invitation has already been used'); } else if (data.employee) { setInviteEmployee(data.employee); // Pre-populate form data with invite metadata setFormData({ name: data.employee.name || '', email: data.employee.email || '', company: data.employee.company || data.employee.department || '' }); setError(''); } else { setError('Invalid invitation data'); } } else { const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); setError(errorData.error || 'Invalid or expired invitation link'); } } catch (err) { console.error('Error loading invite details:', err); setError('Failed to load invitation details'); } finally { setIsLoadingInvite(false); } }; // Get employee info from multiple sources const invitedEmployee = location.state?.invitedEmployee; // Determine current employee - for invite flow, use invite employee data let currentEmployee; if (isInviteFlow) { currentEmployee = inviteEmployee; } else { // Original auth-based logic currentEmployee = invitedEmployee || employees.find(emp => emp.email === user?.email); if (!currentEmployee && user?.email) { // Try case-insensitive email matching currentEmployee = employees.find(emp => emp.email?.toLowerCase() === user.email?.toLowerCase() ); if (!currentEmployee && invitedEmployee) { currentEmployee = employees.find(emp => emp.name === invitedEmployee.name || emp.id === invitedEmployee.id ); } } // Demo mode fallbacks if (!currentEmployee && user?.email === 'demo@auditly.local' && employees.length > 0) { currentEmployee = employees[employees.length - 1]; } if (!currentEmployee && employees.length === 1) { currentEmployee = employees[0]; } } const submitViaInvite = async (answers: EmployeeSubmissionAnswers, inviteCode: string) => { try { // First, consume the invite to mark it as used const consumeResponse = await fetch(`${API_URL}/consumeInvitation`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: inviteCode }) }); if (!consumeResponse.ok) { throw new Error('Failed to process invitation'); } // Get orgId from the consume response const consumeData = await consumeResponse.json(); const orgId = consumeData.orgId; // Submit the questionnaire answers using Cloud Function // This will include company onboarding questions and answers for LLM context const submitResponse = await fetch(`${API_URL}/submitEmployeeAnswers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inviteCode: inviteCode, answers: answers, orgId: orgId, includeCompanyContext: true // Flag to include company Q&A in LLM processing }) }); if (!submitResponse.ok) { const errorData = await submitResponse.json(); throw new Error(errorData.error || 'Failed to submit questionnaire'); } const result = await submitResponse.json(); return { success: true, reportGenerated: !!result.report }; } catch (error) { console.error('Invite submission error:', error); return { success: false, error: error.message }; } }; const handleSubmit = async () => { setIsSubmitting(true); setError(''); try { // Convert form data to EMPLOYEE_QUESTIONS format for backend const answers: EmployeeSubmissionAnswers = {}; // Map form data to question IDs if (formData.name) answers['full_name'] = formData.name; if (formData.email) answers['email'] = formData.email; if (formData.company) answers['company_department'] = formData.company; // Add all other form data fields Object.keys(formData).forEach(key => { if (formData[key] && !answers[key]) { answers[key] = formData[key]; } }); // Submit answers - different logic for invite vs auth flow let result; if (isInviteFlow) { // Direct API submission for invite flow (no auth needed) result = await submitViaInvite(answers, inviteCode); } else { // Use org context for authenticated flow if (!currentEmployee) { // Enhanced fallback logic for authenticated users if (employees.length > 0) { let fallbackEmployee = employees.find(emp => emp.email?.toLowerCase().includes(user?.email?.toLowerCase().split('@')[0] || '') ); if (!fallbackEmployee) { const userDomain = user?.email?.split('@')[1]; fallbackEmployee = employees.find(emp => emp.email?.split('@')[1] === userDomain ) || employees[employees.length - 1]; } const success = await submitEmployeeAnswers(fallbackEmployee.id, answers); if (success) { try { const report = await generateEmployeeReport(fallbackEmployee); console.log('Report generated successfully:', report); } catch (reportError) { console.error('Failed to generate report:', reportError); } // Navigate to completion setCurrentStep(999); // Thank you page return; } } setError(`We couldn't match your account (${user?.email}) with an employee record. Please contact your administrator.`); setIsSubmitting(false); return; } result = await submitEmployeeAnswers(currentEmployee.id, answers); } if (result.success) { // Show thank you page setCurrentStep(999); } else { setError(result.message || 'Failed to submit questionnaire'); } } catch (error) { console.error('Submission error:', error); setError('Failed to submit questionnaire. Please try again.'); } finally { setIsSubmitting(false); } }; const handleNext = (stepData?: any) => { if (stepData) { const newFormData = { ...formData, ...stepData }; setFormData(newFormData); } setCurrentStep(currentStep + 1); }; const handleBack = () => { setCurrentStep(currentStep - 1); }; // Early return for invite flow loading state if (isInviteFlow && isLoadingInvite) { return (
A

Loading Your Invitation...

Please wait while we verify your invitation.

); } // Early return for invite flow error state if (isInviteFlow && error && currentStep === 1) { return (
!

Invitation Error

{error}

); } const renderStep = () => { switch (currentStep) { case 1: return ( handleNext()} /> ); case 2: return ( setFormData({ ...formData, ...data })} onNext={() => handleNext()} /> ); case 3: return ( handleNext()} /> ); case 4: return ( setFormData({ ...formData, titleAndDepartment: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Your Role & Responsibilities" /> ); case 5: return ( setFormData({ ...formData, dailyResponsibilities: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Your Role & Responsibilities" placeholder="Describe what you do on a typical day..." /> ); case 6: return ( setFormData({ ...formData, roleClarity: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={3} totalSteps={7} sectionName="Your Role & Responsibilities" scale={10} /> ); case 7: return ( handleNext()} /> ); case 8: return ( setFormData({ ...formData, weeklyOutput: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Output & Accountability" scale={10} /> ); case 9: return ( setFormData({ ...formData, topDeliverables: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Output & Accountability" /> ); case 10: return ( setFormData({ ...formData, hasKPIs: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={3} totalSteps={7} sectionName="Output & Accountability" /> ); case 11: return ( setFormData({ ...formData, reportingStructure: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={4} totalSteps={7} sectionName="Output & Accountability" /> ); case 12: return ( handleNext()} /> ); case 13: return ( setFormData({ ...formData, closeCollaborators: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Team & Collaboration" /> ); case 14: return ( setFormData({ ...formData, teamCommunication: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Team & Collaboration" scale={10} /> ); case 15: return ( setFormData({ ...formData, teamSupport: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={3} totalSteps={7} sectionName="Team & Collaboration" /> ); case 16: return ( handleNext()} /> ); case 17: return ( setFormData({ ...formData, currentTools: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Tools & Resources" /> ); case 18: return ( setFormData({ ...formData, toolEffectiveness: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Tools & Resources" scale={10} /> ); case 19: return ( setFormData({ ...formData, missingTools: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={3} totalSteps={7} sectionName="Tools & Resources" /> ); case 20: return ( handleNext()} /> ); case 21: return ( setFormData({ ...formData, keySkills: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Skills & Development" /> ); case 22: return ( setFormData({ ...formData, skillDevelopment: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Skills & Development" /> ); case 23: return ( setFormData({ ...formData, awareOfTraining: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={3} totalSteps={7} sectionName="Skills & Development" /> ); case 24: return ( setFormData({ ...formData, careerGoals: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={4} totalSteps={7} sectionName="Skills & Development" /> ); case 25: return ( handleNext()} /> ); case 26: return ( setFormData({ ...formData, companyImprovements: value })} onNext={() => handleNext()} currentStep={1} totalSteps={7} sectionName="Feedback & Improvement" /> ); case 27: return ( setFormData({ ...formData, jobSatisfaction: value })} onBack={() => handleBack()} onNext={() => handleNext()} currentStep={2} totalSteps={7} sectionName="Feedback & Improvement" scale={10} /> ); case 28: return ( setFormData({ ...formData, additionalFeedback: value })} onBack={() => handleBack()} onNext={() => handleSubmit()} currentStep={3} totalSteps={7} sectionName="Feedback & Improvement" placeholder="Share any thoughts, suggestions, or feedback..." /> ); case 999: // Thank you page return ; default: return ; } }; if (isSubmitting) { return (
A

Submitting Your Responses...

Please wait while we process your assessment and generate your report.

); } return (
{renderStep()} {error && (
{error}
)}
); }; export default EmployeeQuestionnaire;