Files
auditly/pages/Onboarding.tsx
Ra 1a9e92d7bd Implement comprehensive report system with detailed viewing and AI enhancements
- Add detailed report viewing with full-screen ReportDetail component for both company and employee reports
- Fix company wiki to display onboarding Q&A in card format matching Figma designs
- Exclude company owners from employee submission counts (owners contribute to wiki, not employee data)
- Fix employee report generation to include company context (wiki + company report + employee answers)
- Fix company report generation to use filtered employee submissions only
- Add proper error handling for submission data format variations
- Update Firebase functions to use gpt-4o model instead of deprecated gpt-4.1
- Fix UI syntax errors and improve report display functionality
- Add comprehensive logging for debugging report generation flow

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-18 19:08:29 -07:00

727 lines
38 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useOrg } from '../contexts/OrgContext';
import { Card, Button } from '../components/UiKit';
import { FigmaProgress } from '../components/figma/FigmaProgress';
import { FigmaInput } from '../components/figma/FigmaInput';
import { FigmaAlert } from '../components/figma/FigmaAlert';
interface OnboardingData {
// Step 1: Company Basics
companyName: string;
industry: string;
size: string;
description: string;
// Step 2: Mission & Vision
mission: string;
vision: string;
values: string[];
// Step 3: Company Evolution & History
foundingYear: string;
evolution: string;
majorMilestones: string;
// Step 4: Competitive Landscape
advantages: string;
vulnerabilities: string;
competitors: string;
marketPosition: string;
// Step 5: Current Challenges & Goals
currentChallenges: string[];
shortTermGoals: string;
longTermGoals: string;
keyMetrics: string;
// Step 6: Team & Culture
cultureDescription: string;
workEnvironment: string;
leadershipStyle: string;
communicationStyle: string;
// Step 7: Final Review
additionalContext: string;
}
const Onboarding: React.FC = () => {
const { org, upsertOrg, generateCompanyWiki } = useOrg();
const navigate = useNavigate();
useEffect(() => {
if (org?.onboardingCompleted) {
navigate('/reports', { replace: true });
}
}, [org, navigate]);
const [step, setStep] = useState(0);
const [isGeneratingReport, setIsGeneratingReport] = useState(false);
const [formData, setFormData] = useState<OnboardingData>({
companyName: org?.name || '',
industry: '',
size: '',
description: '',
mission: '',
vision: '',
values: [],
foundingYear: '',
evolution: '',
majorMilestones: '',
advantages: '',
vulnerabilities: '',
competitors: '',
marketPosition: '',
currentChallenges: [],
shortTermGoals: '',
longTermGoals: '',
keyMetrics: '',
cultureDescription: '',
workEnvironment: '',
leadershipStyle: '',
communicationStyle: '',
additionalContext: ''
});
const steps = [
{
title: 'Company Basics',
description: 'Tell us about your company fundamentals'
},
{
title: 'Mission & Vision',
description: 'Define your purpose and direction'
},
{
title: 'Evolution & History',
description: 'Share your company\'s journey'
},
{
title: 'Competitive Position',
description: 'Understand your market position'
},
{
title: 'Goals & Challenges',
description: 'Current objectives and obstacles'
},
{
title: 'Team & Culture',
description: 'Describe your work environment'
},
{
title: 'Final Review',
description: 'Complete your company profile'
}
];
const handleNext = async () => {
// Prevent re-entry during generation
if (isGeneratingReport) return;
if (step < steps.length - 1) {
setStep(prev => prev + 1);
return;
}
// Final step: persist org & generate report
setIsGeneratingReport(true);
console.log('Starting onboarding completion...', { step });
try {
const newOrgData = {
name: formData.companyName,
industry: formData.industry,
size: formData.size,
description: formData.description,
mission: formData.mission,
vision: formData.vision,
values: formData.values.join(','),
foundingYear: formData.foundingYear,
evolution: formData.evolution,
majorMilestones: formData.majorMilestones,
advantages: formData.advantages,
vulnerabilities: formData.vulnerabilities,
competitors: formData.competitors,
marketPosition: formData.marketPosition,
currentChallenges: formData.currentChallenges.join(','),
shortTermGoals: formData.shortTermGoals,
longTermGoals: formData.longTermGoals,
keyMetrics: formData.keyMetrics,
cultureDescription: formData.cultureDescription,
workEnvironment: formData.workEnvironment,
leadershipStyle: formData.leadershipStyle,
communicationStyle: formData.communicationStyle,
additionalContext: formData.additionalContext,
onboardingCompleted: true
};
console.log('Saving org data...', newOrgData);
await upsertOrg(newOrgData);
console.log('Org data saved successfully');
console.log('Generating company wiki...');
await generateCompanyWiki({ ...newOrgData, orgId: org!.orgId });
console.log('Company wiki generated successfully');
// Small delay to ensure states are updated, then redirect
console.log('Redirecting to reports...');
setTimeout(() => {
console.log('Navigation executing...');
navigate('/reports', { replace: true });
console.log('Navigation called successfully');
}, 100);
} catch (error) {
console.error('Error completing onboarding:', error);
// Show detailed error to user for debugging
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 (step > 0) setStep(step - 1);
};
const addToArray = (field: 'values' | 'currentChallenges', value: string) => {
if (value.trim()) {
setFormData(prev => ({
...prev,
[field]: [...prev[field], value.trim()]
}));
}
};
const removeFromArray = (field: 'values' | 'currentChallenges', index: number) => {
setFormData(prev => ({
...prev,
[field]: prev[field].filter((_, i) => i !== index)
}));
};
const renderStep = () => {
switch (step) {
case 0:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Name *
</label>
<input
type="text"
value={formData.companyName}
onChange={(e) => setFormData(prev => ({ ...prev, companyName: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter your company name"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Industry *
</label>
<select
value={formData.industry}
onChange={(e) => setFormData(prev => ({ ...prev, industry: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select industry</option>
<option value="Technology">Technology</option>
<option value="Healthcare">Healthcare</option>
<option value="Finance">Finance</option>
<option value="Manufacturing">Manufacturing</option>
<option value="Retail">Retail</option>
<option value="Professional Services">Professional Services</option>
<option value="Education">Education</option>
<option value="Media & Entertainment">Media & Entertainment</option>
<option value="Other">Other</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Size *
</label>
<select
value={formData.size}
onChange={(e) => setFormData(prev => ({ ...prev, size: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select size</option>
<option value="1-10">1-10 employees</option>
<option value="11-50">11-50 employees</option>
<option value="51-200">51-200 employees</option>
<option value="201-1000">201-1000 employees</option>
<option value="1000+">1000+ employees</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Description *
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Describe what your company does, its products/services, and target market"
/>
</div>
</div>
);
case 1:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Mission Statement *
</label>
<textarea
value={formData.mission}
onChange={(e) => setFormData(prev => ({ ...prev, mission: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="What is your company's purpose? Why does it exist?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Vision Statement *
</label>
<textarea
value={formData.vision}
onChange={(e) => setFormData(prev => ({ ...prev, vision: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Where do you see your company in the future? What impact do you want to make?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Core Values
</label>
<div className="space-y-2">
{formData.values.map((value, index) => (
<div key={index} className="flex items-center space-x-2">
<span className="flex-1 px-3 py-2 bg-[--background-tertiary] rounded-lg text-[--text-primary]">
{value}
</span>
<Button
size="sm"
variant="danger"
onClick={() => removeFromArray('values', index)}
>
Remove
</Button>
</div>
))}
<div className="flex space-x-2">
<input
type="text"
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Add a core value"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addToArray('values', e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
<Button
size="sm"
onClick={(e) => {
const input = (e.target as HTMLElement).parentElement?.querySelector('input');
if (input) {
addToArray('values', input.value);
input.value = '';
}
}}
>
Add
</Button>
</div>
</div>
</div>
</div>
);
case 2:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Founding Year
</label>
<input
type="text"
value={formData.foundingYear}
onChange={(e) => setFormData(prev => ({ ...prev, foundingYear: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="When was your company founded?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Evolution *
</label>
<textarea
value={formData.evolution}
onChange={(e) => setFormData(prev => ({ ...prev, evolution: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="How has your company evolved since its founding? What major changes or pivots have occurred?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Major Milestones
</label>
<textarea
value={formData.majorMilestones}
onChange={(e) => setFormData(prev => ({ ...prev, majorMilestones: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="List key achievements, product launches, funding rounds, or other significant milestones"
/>
</div>
</div>
);
case 3:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Competitive Advantages *
</label>
<textarea
value={formData.advantages}
onChange={(e) => setFormData(prev => ({ ...prev, advantages: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What gives your company a competitive edge? What are your unique strengths?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Vulnerabilities & Weaknesses *
</label>
<textarea
value={formData.vulnerabilities}
onChange={(e) => setFormData(prev => ({ ...prev, vulnerabilities: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What are your company's current weaknesses or areas of vulnerability?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Key Competitors
</label>
<textarea
value={formData.competitors}
onChange={(e) => setFormData(prev => ({ ...prev, competitors: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Who are your main competitors? How do you differentiate from them?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Market Position
</label>
<textarea
value={formData.marketPosition}
onChange={(e) => setFormData(prev => ({ ...prev, marketPosition: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How do you position yourself in the market? What's your market share or standing?"
/>
</div>
</div>
);
case 4:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Current Challenges
</label>
<div className="space-y-2">
{formData.currentChallenges.map((challenge, index) => (
<div key={index} className="flex items-center space-x-2">
<span className="flex-1 px-3 py-2 bg-[--background-tertiary] rounded-lg text-[--text-primary]">
{challenge}
</span>
<Button
size="sm"
variant="danger"
onClick={() => removeFromArray('currentChallenges', index)}
>
Remove
</Button>
</div>
))}
<div className="flex space-x-2">
<input
type="text"
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Add a current challenge"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addToArray('currentChallenges', e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
<Button
size="sm"
onClick={(e) => {
const input = (e.target as HTMLElement).parentElement?.querySelector('input');
if (input) {
addToArray('currentChallenges', input.value);
input.value = '';
}
}}
>
Add
</Button>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Short-term Goals (6-12 months) *
</label>
<textarea
value={formData.shortTermGoals}
onChange={(e) => setFormData(prev => ({ ...prev, shortTermGoals: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What are your immediate priorities and goals for the next 6-12 months?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Long-term Goals (1-3 years) *
</label>
<textarea
value={formData.longTermGoals}
onChange={(e) => setFormData(prev => ({ ...prev, longTermGoals: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What are your strategic objectives for the next 1-3 years?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Key Metrics & Success Indicators
</label>
<textarea
value={formData.keyMetrics}
onChange={(e) => setFormData(prev => ({ ...prev, keyMetrics: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How do you measure success? What are your key performance indicators?"
/>
</div>
</div>
);
case 5:
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Culture *
</label>
<textarea
value={formData.cultureDescription}
onChange={(e) => setFormData(prev => ({ ...prev, cultureDescription: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Describe your company culture. What's it like to work at your company?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Work Environment *
</label>
<select
value={formData.workEnvironment}
onChange={(e) => setFormData(prev => ({ ...prev, workEnvironment: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select work environment</option>
<option value="Remote">Fully Remote</option>
<option value="Hybrid">Hybrid (Remote + Office)</option>
<option value="In-office">In-office</option>
<option value="Flexible">Flexible/Varies by role</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Leadership Style *
</label>
<textarea
value={formData.leadershipStyle}
onChange={(e) => setFormData(prev => ({ ...prev, leadershipStyle: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Describe the leadership approach and management style in your organization"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Communication Style *
</label>
<textarea
value={formData.communicationStyle}
onChange={(e) => setFormData(prev => ({ ...prev, communicationStyle: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How does your team communicate? What tools and processes do you use?"
/>
</div>
</div>
);
case 6:
return (
<div className="space-y-6">
<div className="text-center mb-6">
<div className="text-4xl mb-4">📋</div>
<h3 className="text-xl font-semibold text-[--text-primary] mb-2">
Review Your Information
</h3>
<p className="text-[--text-secondary]">
Please review the information below and add any additional context
</p>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg space-y-3">
<div><strong>Company:</strong> {formData.companyName}</div>
<div><strong>Industry:</strong> {formData.industry}</div>
<div><strong>Size:</strong> {formData.size}</div>
<div><strong>Mission:</strong> {formData.mission.substring(0, 100)}{formData.mission.length > 100 ? '...' : ''}</div>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Additional Context
</label>
<textarea
value={formData.additionalContext}
onChange={(e) => setFormData(prev => ({ ...prev, additionalContext: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Is there anything else important about your company that Auditly should know to provide better insights?"
/>
</div>
<div className="text-center pt-4">
<div className="text-4xl mb-4">🎉</div>
<h3 className="text-xl font-semibold text-[--text-primary]">
Ready to Complete Setup!
</h3>
<p className="text-[--text-secondary]">
{isGeneratingReport
? 'Generating your personalized company insights...'
: 'Once you complete this step, you\'ll have access to all Auditly features and your personalized company wiki will be generated.'
}
</p>
{isGeneratingReport && (
<div className="mt-4 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span className="ml-3 text-[--text-secondary]">Creating your company profile...</span>
</div>
)}
</div>
</div>
);
default:
return null;
}
};
const canProceed = () => {
switch (step) {
case 0:
return formData.companyName.trim().length > 0 && formData.industry && formData.size && formData.description.trim().length > 0;
case 1:
return formData.mission.trim().length > 0 && formData.vision.trim().length > 0;
case 2:
return formData.evolution.trim().length > 0;
case 3:
return formData.advantages.trim().length > 0 && formData.vulnerabilities.trim().length > 0;
case 4:
return formData.shortTermGoals.trim().length > 0 && formData.longTermGoals.trim().length > 0;
case 5:
return formData.cultureDescription.trim().length > 0 && formData.workEnvironment && formData.leadershipStyle.trim().length > 0 && formData.communicationStyle.trim().length > 0;
case 6:
return true;
default:
return false;
}
};
return (
<div className="min-h-screen bg-[--background-primary] flex items-center justify-center p-4">
<div className="max-w-4xl w-full">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-[--text-primary] mb-2">
Welcome to Auditly
</h1>
<p className="text-[--text-secondary]">
Let's build a comprehensive profile of your organization to provide the best insights
</p>
</div>
{/* Progress indicator */}
<div className="mb-8">
<FigmaProgress
currentStep={step + 1}
steps={steps.map((s, i) => ({ number: i + 1, title: s.title }))}
/>
</div>
<Card className="max-w-none">
<div className="mb-6">
<h2 className="text-xl font-semibold text-[--text-primary] mb-2">
{steps[step].title}
</h2>
<p className="text-[--text-secondary]">
{steps[step].description}
</p>
</div>
{renderStep()}
<div className="flex justify-between mt-8">
<Button
variant="secondary"
onClick={handleBack}
disabled={step === 0}
>
Back
</Button>
<Button
onClick={handleNext}
disabled={!canProceed() || isGeneratingReport}
>
{isGeneratingReport
? 'Generating Wiki...'
: step === steps.length - 1 ? 'Complete Setup & Generate Wiki' : 'Next'
}
</Button>
</div>
</Card>
</div>
</div>
);
};
export default Onboarding;