);
};
// Main Controller Component with Backend Integration - Merged Logic
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);
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
const submitResponse = await fetch(`${API_URL}/submitEmployeeAnswers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inviteCode: inviteCode,
answers: answers,
orgId: orgId
})
});
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;
if (formData.title_department) answers['title_department'] = formData.title_department;
// 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(38); // 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(38);
} 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);
}
if (currentStep === 37) {
// Submit form data here
handleSubmit();
} else {
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 = () => {
// NOTE: Step components need to be imported from EmployeeFormsController
// For now, showing placeholder that preserves all 38 steps
switch (currentStep) {
case 1:
return ;
case 2:
return (
handleNext()}
/>
);
case 3:
return
Step 3 - Personal Information Form
;
case 4:
return
Step 4 - Email Validation
;
case 5:
return
Step 5 - Department Details
;
case 6:
return
Step 6 - Role Description
;
case 7:
return
Step 7 - Experience Level
;
case 8:
return
Step 8 - Skills Assessment
;
case 9:
return (
handleNext()}
/>
);
case 10:
return
Step 10 - Team Dynamics
;
case 11:
return
Step 11 - Communication Style
;
case 12:
return
Step 12 - Work Preferences
;
case 13:
return
Step 13 - Collaboration Rating
;
case 14:
return
Step 14 - Remote Work
;
case 15:
return
Step 15 - Work-Life Balance
;
case 16:
return (
handleNext()}
/>
);
case 17:
return
Step 17 - Goal Setting
;
case 18:
return
Step 18 - Achievement Rating
;
case 19:
return
Step 19 - Performance Metrics
;
case 20:
return
Step 20 - Career Aspirations
;
case 21:
return (
handleNext()}
/>
);
case 22:
return
Step 22 - Learning Style
;
case 23:
return
Step 23 - Skill Development
;
case 24:
return
Step 24 - Training Preferences
;
case 25:
return (
handleNext()}
/>
);
case 26:
return
Step 26 - Feedback Style
;
case 27:
return
Step 27 - Recognition Preferences
;
case 28:
return
Step 28 - Performance Reviews
;
case 29:
return
Step 29 - Manager Relationship
;
case 30:
return (
handleNext()}
/>
);
case 31:
return
Step 31 - Problem Solving
;
case 32:
return
Step 32 - Innovation Rating
;
case 33:
return
Step 33 - Creative Thinking
;
case 34:
return (
handleNext()}
/>
);
case 35:
return
Step 35 - Leadership Style
;
case 36:
return
Step 36 - Organizational Structure
;
case 37:
return
Step 37 - Final Feedback
;
case 38:
return (
Thank you! Your assessment has been submitted!
Your responses have been recorded and your AI-powered performance report will be generated shortly.