Files
auditly/pages/EmployeeQuestionnaire_Backup.tsx
2025-08-20 06:54:19 -07:00

676 lines
34 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { FigmaRatingScale, FigmaTextArea, FigmaNavigationButtons } from '../components/figma/FigmaQuestion';
import { FigmaMultipleChoice } from '../components/figma/FigmaMultipleChoice';
// Icon SVG Component
const AuditlyIcon: React.FC = () => (
<svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M2.57408 17.8138C3.11835 18.3649 3.11834 19.2585 2.57406 19.8097L2.54619 19.8379C2.00191 20.389 1.11946 20.389 0.57519 19.8379C0.030919 19.2867 0.0309274 18.3931 0.575208 17.842L0.603083 17.8137C1.14736 17.2626 2.02981 17.2626 2.57408 17.8138Z" fill="url(#paint0_linear_981_10577)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M9.12583 18.2374C9.66912 18.7896 9.66752 19.6832 9.12226 20.2333L5.2617 24.1286C4.71644 24.6787 3.83399 24.6771 3.2907 24.125C2.74741 23.5728 2.74901 22.6792 3.29427 22.1291L7.15483 18.2338C7.70009 17.6837 8.58254 17.6853 9.12583 18.2374Z" fill="url(#paint1_linear_981_10577)" />
<defs>
<linearGradient id="paint0_linear_981_10577" x1="1.57463" y1="17.4004" x2="1.57463" y2="20.2513" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_981_10577" x1="6.20827" y1="17.8223" x2="6.20827" y2="24.5401" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
);
// Progress Bar Component for Section Headers
const SectionProgressBar: React.FC<{ currentStep: number; totalSteps: number }> = ({ currentStep, totalSteps }) => {
return (
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{Array.from({ length: Math.min(7, totalSteps) }, (_, index) => {
const isActive = index < Math.ceil((currentStep / totalSteps) * 7);
return (
<div key={index}>
{isActive ? (
<div className="w-6 h-1 bg-Brand-Orange rounded-3xl" />
) : (
<svg width="4" height="4" viewBox="0 0 4 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="4" height="4" rx="2" fill="var(--Neutrals-NeutralSlate300, #D5D7DA)" />
</svg>
)}
</div>
);
})}
</div>
);
};
// Question Input Component
const QuestionInput: React.FC<{
question: EmployeeQuestion;
value: string;
onChange: (value: string) => void;
}> = ({ question, value, onChange }) => {
switch (question.type) {
case 'scale':
return (
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question.prompt}
</div>
<div className="self-stretch flex justify-between items-center gap-2">
<span className="text-sm text-Neutrals-NeutralSlate500">{question.scaleLabels?.min}</span>
<div className="flex gap-2">
{Array.from({ length: question.scaleMax! - question.scaleMin! + 1 }, (_, index) => {
const ratingValue = question.scaleMin! + index;
const isSelected = parseInt(value) === ratingValue;
return (
<button
key={ratingValue}
onClick={() => onChange(ratingValue.toString())}
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium transition-colors ${
isSelected
? 'bg-Brand-Orange text-white'
: 'bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate700 hover:bg-Neutrals-NeutralSlate200'
}`}
>
{ratingValue}
</button>
);
})}
</div>
<span className="text-sm text-Neutrals-NeutralSlate500">{question.scaleLabels?.max}</span>
</div>
</div>
);
case 'yesno':
return (
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question.prompt}
</div>
<div className="self-stretch inline-flex justify-center items-center gap-3">
<div
onClick={() => onChange('No')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${
value === 'No'
? 'bg-Neutrals-NeutralSlate800'
: 'bg-Neutrals-NeutralSlate100 hover:bg-Neutrals-NeutralSlate200'
}`}
>
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${
value === 'No' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950'
}`}>
No
</div>
</div>
<div
onClick={() => onChange('Yes')}
className={`w-20 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${
value === 'Yes'
? 'bg-Neutrals-NeutralSlate800'
: 'bg-Neutrals-NeutralSlate100 hover:bg-Neutrals-NeutralSlate200'
}`}
>
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${
value === 'Yes' ? 'text-Neutrals-NeutralSlate0' : 'text-Neutrals-NeutralSlate950'
}`}>
Yes
</div>
</div>
</div>
</div>
);
case 'textarea':
return (
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question.prompt}
</div>
<div className="self-stretch min-h-40 p-5 relative bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-start gap-2.5">
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-base font-normal font-['Inter'] leading-normal placeholder:text-Neutrals-NeutralSlate500 outline-none resize-none"
placeholder={question.placeholder || "Type your answer...."}
rows={6}
/>
<div className="w-3 h-3 absolute right-5 bottom-5">
<div className="w-2 h-2 absolute top-0.5 left-0.5 outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
<div className="w-1 h-1 absolute bottom-0 right-0 outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
</div>
</div>
</div>
);
default: // text input
return (
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question.prompt}
</div>
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input
type={question.id === 'email' ? 'email' : 'text'}
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none"
placeholder={question.placeholder || "Enter your answer..."}
/>
</div>
</div>
);
}
};
// Navigation Buttons Component
const NavigationButtons: React.FC<{
onBack?: () => void;
onNext: () => void;
onSkip?: () => void;
nextDisabled?: boolean;
isSubmitting?: boolean;
currentStep: number;
totalSteps: number;
isLastStep?: boolean;
}> = ({ onBack, onNext, onSkip, nextDisabled, isSubmitting, currentStep, totalSteps, isLastStep }) => {
return (
<div className="self-stretch inline-flex justify-start items-start gap-2">
{onBack && (
<button
onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-Neutrals-NeutralSlate200"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div>
</button>
)}
<button
onClick={onNext}
disabled={nextDisabled || isSubmitting}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">
{isSubmitting ? 'Submitting...' : (isLastStep ? 'Submit' : 'Next')}
</div>
</div>
</button>
{onSkip && (
<div
onClick={onSkip}
className="px-3 py-1.5 absolute right-6 top-6 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200"
>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div>
</div>
)}
</div>
);
};
// Welcome Screen Component
const WelcomeScreen: React.FC<{
onStart: () => void;
currentEmployee?: any;
isInviteFlow: boolean;
error?: string;
}> = ({ onStart, currentEmployee, isInviteFlow, error }) => {
return (
<div className="w-full h-screen bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-full px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute">
<AuditlyIcon />
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
Welcome to Auditly!
</div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal">
Please complete this questionnaire to help us understand your role and create personalized insights.
</div>
{currentEmployee && (
<div className="inline-flex items-center px-4 py-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
<span className="text-sm text-blue-800 dark:text-blue-200">
👋 Hello {currentEmployee.name}! {currentEmployee.role && `(${currentEmployee.role})`}
</span>
</div>
)}
{error && (
<div className="inline-flex items-center px-4 py-2 bg-red-100 dark:bg-red-900 rounded-lg">
<span className="text-sm text-red-800 dark:text-red-200">
{error}
</span>
</div>
)}
</div>
</div>
<button
onClick={onStart}
className="self-stretch px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 inline-flex justify-center items-center gap-1 overflow-hidden"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Start Assessment</div>
</div>
</button>
</div>
</div>
<div className="flex-1 h-full px-20 py-16 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 self-stretch origin-top-left rotate-180 rounded-3xl inline-flex flex-col justify-center items-center gap-2.5 overflow-hidden">
<img className="self-stretch flex-1" src="https://placehold.co/560x682" alt="Welcome" />
</div>
</div>
</div>
);
};
// Question Step Component
const QuestionStep: React.FC<{
question: EmployeeQuestion;
value: string;
onChange: (value: string) => void;
onNext: () => void;
onBack?: () => void;
onSkip?: () => void;
currentStep: number;
totalSteps: number;
isSubmitting?: boolean;
isLastStep?: boolean;
}> = ({ question, value, onChange, onNext, onBack, onSkip, currentStep, totalSteps, isSubmitting, isLastStep }) => {
const isRequired = question.required;
const isAnswered = value && value.trim().length > 0;
const nextDisabled = isRequired ? !isAnswered : false;
return (
<div className="w-full h-screen py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<QuestionInput question={question} value={value} onChange={onChange} />
<NavigationButtons
onBack={onBack}
onNext={onNext}
onSkip={onSkip}
nextDisabled={nextDisabled}
isSubmitting={isSubmitting}
currentStep={currentStep}
totalSteps={totalSteps}
isLastStep={isLastStep}
/>
</div>
{/* Progress indicators */}
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none">
{currentStep} of {totalSteps}
</div>
</div>
<div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4">
<SectionProgressBar currentStep={currentStep} totalSteps={totalSteps} />
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate500 text-base font-medium font-['Neue_Montreal'] leading-normal">
Employee Assessment
</div>
</div>
</div>
);
};
// Thank You Page Component
const ThankYouPage: React.FC = () => {
return (
<div className="w-full h-screen bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-full px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute">
<AuditlyIcon />
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-5xl font-medium font-['Neue_Montreal'] leading-[48px]">
Thank you! Your assessment has been submitted!
</div>
<div className="self-stretch justify-center text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal">
Your responses have been recorded and your AI-powered performance report will be generated shortly.
</div>
</div>
</div>
</div>
</div>
<div className="flex-1 h-full px-20 py-16 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 self-stretch origin-top-left rotate-180 rounded-3xl inline-flex flex-col justify-center items-center gap-2.5 overflow-hidden">
<img className="self-stretch flex-1" src="https://placehold.co/560x682" alt="Thank you" />
</div>
</div>
</div>
);
};
// Main Component
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(0); // 0 = welcome screen
const [answers, setAnswers] = useState<EmployeeSubmissionAnswers>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
const [inviteEmployee, setInviteEmployee] = useState<any>(null);
const [isLoadingInvite, setIsLoadingInvite] = useState(false);
// Get non-followup questions (we'll handle followups conditionally)
const visibleQuestions = EMPLOYEE_QUESTIONS.filter(q => !q.followupTo);
const totalSteps = visibleQuestions.length;
// 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 handleAnswerChange = (questionId: string, value: string) => {
setAnswers(prev => ({ ...prev, [questionId]: value }));
};
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 {
// Validate required questions
const requiredQuestions = visibleQuestions.filter(q => q.required);
const missingAnswers = requiredQuestions.filter(q => !answers[q.id]?.trim());
if (missingAnswers.length > 0) {
setError(`Please answer all required questions`);
setIsSubmitting(false);
return;
}
// 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(totalSteps + 1); // 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(totalSteps + 1);
} 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 = () => {
if (currentStep === 0) {
// From welcome screen to first question
setCurrentStep(1);
} else if (currentStep === totalSteps) {
// From last question to submission
handleSubmit();
} else {
// Between questions
setCurrentStep(currentStep + 1);
}
};
const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const handleSkip = () => {
if (currentStep < totalSteps) {
setCurrentStep(currentStep + 1);
}
};
// Early return for invite flow loading state
if (isInviteFlow && isLoadingInvite) {
return (
<div className="min-h-screen bg-[--background-primary] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-[--accent] rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4">
A
</div>
<h1 className="text-3xl font-bold text-[--text-primary] mb-4">Loading Your Invitation...</h1>
<p className="text-[--text-secondary]">Please wait while we verify your invitation.</p>
</div>
</div>
);
}
// Early return for invite flow error state
if (isInviteFlow && error && currentStep === 0) {
return (
<div className="min-h-screen bg-[--background-primary] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-red-500 rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4">
!
</div>
<h1 className="text-3xl font-bold text-[--text-primary] mb-4">Invitation Error</h1>
<p className="text-[--text-secondary] mb-6">{error}</p>
<button
onClick={() => window.location.href = '/'}
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Return to Homepage
</button>
</div>
</div>
);
}
// Render current step
if (currentStep === 0) {
// Welcome screen
return (
<WelcomeScreen
onStart={handleNext}
currentEmployee={currentEmployee}
isInviteFlow={isInviteFlow}
error={!currentEmployee && !isInviteFlow ? `Employee info not found. User: ${user?.email}` : undefined}
/>
);
} else if (currentStep > totalSteps) {
// Thank you page
return <ThankYouPage />;
} else {
// Question step
const question = visibleQuestions[currentStep - 1];
const isLastStep = currentStep === totalSteps;
return (
<QuestionStep
question={question}
value={answers[question.id] || ''}
onChange={(value) => handleAnswerChange(question.id, value)}
onNext={handleNext}
onBack={currentStep > 1 ? handleBack : undefined}
onSkip={!question.required ? handleSkip : undefined}
currentStep={currentStep}
totalSteps={totalSteps}
isSubmitting={isSubmitting}
isLastStep={isLastStep}
/>
);
}
};
export default EmployeeQuestionnaire;