- Remove all authentication and org context dependencies - Simplify component to work only with invite codes from URL - Remove complex user/employee matching logic - Keep exact Figma UI components and styling - Use only submitViaInvite function for API submissions - Employees never need to log in, only use invite link
237 lines
9.0 KiB
TypeScript
237 lines
9.0 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import { useOrg } from '../contexts/OrgContext';
|
|
import { onboardingSteps, OnboardingData, initializeOnboardingData } from '../data/onboardingSteps';
|
|
import { secureApi } from '../services/secureApi';
|
|
import {
|
|
FigmaOnboardingIntro,
|
|
FigmaOnboardingQuestion,
|
|
FigmaOnboardingMultipleChoice,
|
|
FigmaOnboardingForm
|
|
} from '../components/onboarding/FigmaOnboardingComponents';
|
|
|
|
const Onboarding: React.FC = () => {
|
|
const { org, upsertOrg, generateCompanyWiki } = useOrg();
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
if (org?.onboardingCompleted) {
|
|
navigate('/reports', { replace: true });
|
|
}
|
|
}, [org, navigate]);
|
|
|
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
const [isGeneratingReport, setIsGeneratingReport] = useState(false);
|
|
const [formData, setFormData] = useState<OnboardingData>(initializeOnboardingData());
|
|
|
|
const currentStep = onboardingSteps[currentStepIndex];
|
|
const totalSteps = onboardingSteps.length;
|
|
|
|
const updateFormData = (field: keyof OnboardingData, value: string | string[]) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const canProceed = () => {
|
|
if (!currentStep) return false;
|
|
|
|
switch (currentStep.type) {
|
|
case 'form':
|
|
// Check required fields for form step - company name and user name
|
|
const companyName = formData.companyName;
|
|
const yourName = formData.yourName;
|
|
return typeof companyName === 'string' && companyName.trim().length > 0 &&
|
|
typeof yourName === 'string' && yourName.trim().length > 0;
|
|
|
|
case 'question':
|
|
// Check if field is filled
|
|
if (currentStep.field) {
|
|
const fieldValue = formData[currentStep.field as keyof OnboardingData];
|
|
return Array.isArray(fieldValue) ? fieldValue.length > 0 : String(fieldValue || '').trim().length > 0;
|
|
}
|
|
return false;
|
|
|
|
case 'multiple_choice':
|
|
// Check if option is selected
|
|
if (currentStep.field) {
|
|
const fieldValue = formData[currentStep.field as keyof OnboardingData];
|
|
return String(fieldValue || '').trim().length > 0;
|
|
}
|
|
return false;
|
|
|
|
case 'intro':
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const handleNext = async () => {
|
|
if (isGeneratingReport) return;
|
|
|
|
if (currentStepIndex < totalSteps - 1) {
|
|
setCurrentStepIndex(prev => prev + 1);
|
|
return;
|
|
}
|
|
|
|
// Final step: submit all data and complete onboarding
|
|
setIsGeneratingReport(true);
|
|
try {
|
|
await upsertOrg({
|
|
...org,
|
|
companyName: formData.companyName,
|
|
companyLogo: formData.companyLogo,
|
|
onboardingData: formData,
|
|
onboardingCompleted: true,
|
|
updatedAt: Date.now(),
|
|
});
|
|
navigate('/reports', { replace: true });
|
|
} catch (error) {
|
|
console.error('Error completing onboarding:', error);
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
alert(`There was an error completing the setup: ${errorMessage}. Please check the console for more details and try again.`);
|
|
} finally {
|
|
setIsGeneratingReport(false);
|
|
}
|
|
};
|
|
|
|
const handleBack = () => {
|
|
if (currentStepIndex > 0) {
|
|
setCurrentStepIndex(prev => prev - 1);
|
|
}
|
|
};
|
|
|
|
const handleLogoUpload = (file: File) => {
|
|
// Create object URL for preview
|
|
const logoUrl = URL.createObjectURL(file);
|
|
updateFormData('companyLogo', logoUrl);
|
|
};
|
|
|
|
const getSectionInfo = () => {
|
|
const sectionSteps = onboardingSteps.filter(step => step.section === currentStep.section);
|
|
const currentSectionIndex = sectionSteps.findIndex(step => step.id === currentStep.id);
|
|
const sectionName = currentStep.sectionName || `Section ${currentStep.section}`;
|
|
|
|
return {
|
|
sectionPosition: currentStep.section,
|
|
totalInSection: sectionSteps.length,
|
|
sectionName,
|
|
stepInSection: currentSectionIndex + 1
|
|
};
|
|
};
|
|
|
|
const renderStepContent = () => {
|
|
if (!currentStep) return null;
|
|
|
|
const sectionInfo = getSectionInfo();
|
|
|
|
switch (currentStep.type) {
|
|
case 'intro':
|
|
return (
|
|
<FigmaOnboardingIntro
|
|
section={currentStep.section}
|
|
totalSections={7}
|
|
title={currentStep.sectionName || `Section ${currentStep.section}`}
|
|
description={currentStep.description || ''}
|
|
onStart={handleNext}
|
|
/>
|
|
);
|
|
|
|
case 'form':
|
|
return (
|
|
<FigmaOnboardingForm
|
|
companyName={formData.companyName}
|
|
yourName={formData.yourName}
|
|
companyLogo={formData.companyLogo}
|
|
onCompanyNameChange={(value) => updateFormData('companyName', value)}
|
|
onYourNameChange={(value) => updateFormData('yourName', value)}
|
|
onLogoUpload={handleLogoUpload}
|
|
onNext={handleNext}
|
|
canProceed={canProceed()}
|
|
/>
|
|
);
|
|
|
|
case 'question':
|
|
const questionValue = currentStep.field
|
|
? String(formData[currentStep.field as keyof OnboardingData] || '')
|
|
: '';
|
|
|
|
return (
|
|
<FigmaOnboardingQuestion
|
|
question={currentStep.title || ''}
|
|
placeholder={currentStep.placeholder}
|
|
value={questionValue}
|
|
onChange={(value) => {
|
|
if (currentStep.field) {
|
|
updateFormData(currentStep.field as keyof OnboardingData, value);
|
|
}
|
|
}}
|
|
onBack={handleBack}
|
|
onNext={handleNext}
|
|
sectionPosition={sectionInfo.sectionPosition}
|
|
totalInSection={sectionInfo.totalInSection}
|
|
sectionName={sectionInfo.sectionName}
|
|
canProceed={canProceed()}
|
|
canSkip={!currentStep.required}
|
|
rows={6}
|
|
/>
|
|
);
|
|
|
|
case 'multiple_choice':
|
|
const multipleChoiceValue = currentStep.field
|
|
? String(formData[currentStep.field as keyof OnboardingData] || '')
|
|
: '';
|
|
|
|
return (
|
|
<FigmaOnboardingMultipleChoice
|
|
question={currentStep.title || ''}
|
|
options={currentStep.options || []}
|
|
selectedValue={multipleChoiceValue}
|
|
onSelect={(value) => {
|
|
if (currentStep.field) {
|
|
updateFormData(currentStep.field as keyof OnboardingData, value);
|
|
}
|
|
}}
|
|
onBack={handleBack}
|
|
onNext={handleNext}
|
|
sectionPosition={sectionInfo.sectionPosition}
|
|
totalInSection={sectionInfo.totalInSection}
|
|
sectionName={sectionInfo.sectionName}
|
|
canSkip={!currentStep.required}
|
|
/>
|
|
);
|
|
|
|
default:
|
|
return (
|
|
<div className="text-center">
|
|
<p>Step type "{currentStep.type}" not implemented</p>
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
// Show a loading state while generating report
|
|
if (isGeneratingReport) {
|
|
return (
|
|
<div className="min-h-screen bg-[--Neutrals-NeutralSlate0] flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-medium text-[--Neutrals-NeutralSlate950] mb-4">
|
|
Completing your setup...
|
|
</div>
|
|
<div className="text-base text-[--Neutrals-NeutralSlate500]">
|
|
Please wait while we process your information.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[--Neutrals-NeutralSlate0] flex items-center justify-center overflow-hidden">
|
|
{renderStepContent()}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Onboarding; |