Fix organization setup flow: redirect to onboarding for incomplete setup
This commit is contained in:
381
pages/EmployeeQuestionnaireSteps.tsx
Normal file
381
pages/EmployeeQuestionnaireSteps.tsx
Normal file
@@ -0,0 +1,381 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useOrg } from '../contexts/OrgContext';
|
||||
import { Card, Button } from '../components/UiKit';
|
||||
import { EMPLOYEE_QUESTIONS, EmployeeSubmissionAnswers } from '../employeeQuestions';
|
||||
import { Question } from '../components/ui/Question';
|
||||
import { QuestionInput } from '../components/ui/QuestionInput';
|
||||
import { FigmaQuestion } from '../components/figma/FigmaQuestion';
|
||||
import { EnhancedFigmaQuestion } from '../components/figma/EnhancedFigmaQuestion';
|
||||
import { LinearProgress } from '../components/ui/Progress';
|
||||
import { Alert } from '../components/ui/Alert';
|
||||
|
||||
const EmployeeQuestionnaireSteps: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user } = useAuth();
|
||||
const { submitEmployeeAnswers, generateEmployeeReport, employees } = useOrg();
|
||||
const [answers, setAnswers] = useState<EmployeeSubmissionAnswers>({});
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// Get employee info from multiple sources
|
||||
const invitedEmployee = location.state?.invitedEmployee;
|
||||
|
||||
// Find current employee info - try multiple strategies
|
||||
let currentEmployee = invitedEmployee || employees.find(emp => emp.email === user?.email);
|
||||
|
||||
// Additional matching strategies for edge cases
|
||||
if (!currentEmployee && user?.email) {
|
||||
// Try case-insensitive email matching
|
||||
currentEmployee = employees.find(emp =>
|
||||
emp.email?.toLowerCase() === user.email?.toLowerCase()
|
||||
);
|
||||
|
||||
// Try matching by name if email doesn't work (for invite flow)
|
||||
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];
|
||||
}
|
||||
|
||||
// Filter out followup questions that shouldn't be shown yet
|
||||
const getVisibleQuestions = () => {
|
||||
return EMPLOYEE_QUESTIONS.filter(question => {
|
||||
if (!question.followupTo) return true;
|
||||
|
||||
const parentAnswer = answers[question.followupTo];
|
||||
if (question.followupTo === 'has_kpis') {
|
||||
return parentAnswer === 'Yes';
|
||||
}
|
||||
if (question.followupTo === 'unclear_responsibilities') {
|
||||
return parentAnswer === 'Yes';
|
||||
}
|
||||
if (question.followupTo === 'role_shift_interest') {
|
||||
return parentAnswer === 'Yes';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const visibleQuestions = getVisibleQuestions();
|
||||
const currentQuestion = visibleQuestions[currentStep];
|
||||
|
||||
const handleAnswerChange = (value: string) => {
|
||||
if (currentQuestion) {
|
||||
setAnswers(prev => ({ ...prev, [currentQuestion.id]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep < visibleQuestions.length - 1) {
|
||||
setCurrentStep(prev => prev + 1);
|
||||
} else {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep > 0) {
|
||||
setCurrentStep(prev => prev - 1);
|
||||
}
|
||||
};
|
||||
|
||||
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: ${missingAnswers.map(q => q.prompt).join(', ')}`);
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentEmployee) {
|
||||
// Enhanced fallback logic
|
||||
if (employees.length > 0) {
|
||||
// Try to find employee by matching with the user's email more aggressively
|
||||
let fallbackEmployee = employees.find(emp =>
|
||||
emp.email?.toLowerCase().includes(user?.email?.toLowerCase().split('@')[0] || '')
|
||||
);
|
||||
|
||||
// If still no match, use the most recent employee or one with matching domain
|
||||
if (!fallbackEmployee) {
|
||||
const userDomain = user?.email?.split('@')[1];
|
||||
fallbackEmployee = employees.find(emp =>
|
||||
emp.email?.split('@')[1] === userDomain
|
||||
) || employees[employees.length - 1];
|
||||
}
|
||||
|
||||
console.log('Using enhanced fallback employee:', fallbackEmployee);
|
||||
|
||||
const success = await submitEmployeeAnswers(fallbackEmployee.id, answers);
|
||||
if (success) {
|
||||
try {
|
||||
const report = await generateEmployeeReport(fallbackEmployee);
|
||||
if (report) {
|
||||
console.log('Report generated successfully for fallback employee:', report);
|
||||
}
|
||||
} catch (reportError) {
|
||||
console.error('Failed to generate report for fallback employee:', reportError);
|
||||
}
|
||||
navigate('/questionnaire-complete', {
|
||||
replace: true,
|
||||
state: {
|
||||
employeeId: fallbackEmployee.id,
|
||||
employeeName: fallbackEmployee.name,
|
||||
message: 'Questionnaire submitted successfully! Your responses have been recorded.'
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setError(`We couldn't match your account (${user?.email}) with an employee record. Please contact your administrator to ensure your invite was set up correctly.`);
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await submitEmployeeAnswers(currentEmployee.id, answers);
|
||||
|
||||
if (result.success) {
|
||||
const message = result.reportGenerated
|
||||
? 'Questionnaire submitted successfully! Your AI-powered performance report has been generated.'
|
||||
: 'Questionnaire submitted successfully! Your report will be available shortly.';
|
||||
|
||||
navigate('/questionnaire-complete', {
|
||||
state: {
|
||||
employeeId: currentEmployee.id,
|
||||
employeeName: currentEmployee.name,
|
||||
reportGenerated: result.reportGenerated,
|
||||
message: message
|
||||
}
|
||||
});
|
||||
} 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 getProgressPercentage = () => {
|
||||
return Math.round(((currentStep + 1) / visibleQuestions.length) * 100);
|
||||
};
|
||||
|
||||
const isCurrentQuestionAnswered = () => {
|
||||
if (!currentQuestion) return false;
|
||||
const answer = answers[currentQuestion.id];
|
||||
return answer && answer.trim().length > 0;
|
||||
};
|
||||
|
||||
if (!currentQuestion) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[--background-primary] py-8 px-4">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-2xl font-bold text-[--text-primary] mb-4">
|
||||
No questions available
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[--background-primary] py-8 px-4">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="w-16 h-16 bg-blue-500 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-2">
|
||||
Welcome to Auditly!
|
||||
</h1>
|
||||
<p className="text-[--text-secondary] mb-4">
|
||||
Please complete this questionnaire to help us understand your role and create personalized insights.
|
||||
</p>
|
||||
{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>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="inline-flex items-center px-4 py-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
|
||||
<span className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||
⚠️ Employee info not found. User: {user?.email}, Employees: {employees.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs text-[--text-secondary] max-w-md mx-auto">
|
||||
<p>Don't worry - your account was created successfully! This is likely a temporary sync issue.</p>
|
||||
<p className="mt-1">You can still complete the questionnaire, and we'll match it to your profile automatically.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="mb-8">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm text-[--text-secondary]">
|
||||
Question {currentStep + 1} of {visibleQuestions.length}
|
||||
</span>
|
||||
<span className="text-sm text-[--text-secondary]">{getProgressPercentage()}%</span>
|
||||
</div>
|
||||
<LinearProgress value={getProgressPercentage()} />
|
||||
</div>
|
||||
|
||||
{/* Enhanced Question Card */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="w-full max-w-2xl">
|
||||
<EnhancedFigmaQuestion
|
||||
questionNumber={`Q${currentStep + 1}`}
|
||||
question={currentQuestion}
|
||||
answer={answers[currentQuestion.id] || ''}
|
||||
onAnswerChange={handleAnswerChange}
|
||||
onBack={currentStep > 0 ? handleBack : undefined}
|
||||
onNext={
|
||||
currentStep < visibleQuestions.length - 1
|
||||
? handleNext
|
||||
: (isCurrentQuestionAnswered() || !currentQuestion.required)
|
||||
? handleSubmit
|
||||
: undefined
|
||||
}
|
||||
nextLabel={
|
||||
currentStep < visibleQuestions.length - 1
|
||||
? 'Next'
|
||||
: isSubmitting
|
||||
? 'Submitting...'
|
||||
: 'Submit & Generate Report'
|
||||
}
|
||||
showNavigation={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Original Figma Question Card for Comparison */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="w-full max-w-2xl">
|
||||
<FigmaQuestion
|
||||
questionNumber={`Q${currentStep + 1}`}
|
||||
title={currentQuestion.prompt}
|
||||
description={currentQuestion.required ? 'Required' : 'Optional'}
|
||||
answer={answers[currentQuestion.id] || ''}
|
||||
onAnswerChange={handleAnswerChange}
|
||||
onBack={currentStep > 0 ? handleBack : undefined}
|
||||
onNext={
|
||||
currentStep < visibleQuestions.length - 1
|
||||
? handleNext
|
||||
: (isCurrentQuestionAnswered() || !currentQuestion.required)
|
||||
? handleSubmit
|
||||
: undefined
|
||||
}
|
||||
nextLabel={
|
||||
currentStep < visibleQuestions.length - 1
|
||||
? 'Next'
|
||||
: isSubmitting
|
||||
? 'Submitting...'
|
||||
: 'Submit & Generate Report'
|
||||
}
|
||||
showNavigation={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Alternative Input for Different Question Types */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="w-full max-w-2xl">
|
||||
<Card className="p-6">
|
||||
<Question
|
||||
label={`${currentStep + 1}. ${currentQuestion.prompt}`}
|
||||
required={currentQuestion.required}
|
||||
description={`Category: ${currentQuestion.category}`}
|
||||
>
|
||||
<QuestionInput
|
||||
question={currentQuestion}
|
||||
value={answers[currentQuestion.id] || ''}
|
||||
onChange={handleAnswerChange}
|
||||
/>
|
||||
</Question>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<div className="mb-6 flex justify-center">
|
||||
<div className="w-full max-w-2xl">
|
||||
<Alert variant="error" title="Error">
|
||||
{error}
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<div className="flex justify-center gap-4">
|
||||
{currentStep > 0 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBack}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentStep < visibleQuestions.length - 1 ? (
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={currentQuestion.required && !isCurrentQuestionAnswered()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
(currentQuestion.required && !isCurrentQuestionAnswered())
|
||||
}
|
||||
>
|
||||
{isSubmitting ? 'Submitting & Generating Report...' : 'Submit & Generate AI Report'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Help Text */}
|
||||
<div className="text-center mt-6">
|
||||
<p className="text-sm text-[--text-secondary]">
|
||||
{currentQuestion.required && !isCurrentQuestionAnswered()
|
||||
? 'Please answer this required question to continue.'
|
||||
: 'You can skip optional questions or come back to them later.'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmployeeQuestionnaireSteps;
|
||||
Reference in New Issue
Block a user