From ef1a60cc9b328c2f3ce286f3cafb42b713d680ed Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 30 Jul 2025 18:07:37 +0700 Subject: [PATCH 01/69] feat: implement product validation framework with market research and competitor analysis components Co-authored-by: issac-anhpdm --- .../specs/product-launch-essentials/tasks.md | 2 +- .../components/LaunchEssentialsDashboard.tsx | 242 ++++++++++++++ .../components/NextStepsPanel.tsx | 307 ++++++++++++++++++ .../components/OverviewCard.tsx | 211 ++++++++++++ .../components/PhaseProgress.tsx | 224 +++++++++++++ 5 files changed, 985 insertions(+), 1 deletion(-) create mode 100644 app/launch-essentials/components/LaunchEssentialsDashboard.tsx create mode 100644 app/launch-essentials/components/NextStepsPanel.tsx create mode 100644 app/launch-essentials/components/OverviewCard.tsx create mode 100644 app/launch-essentials/components/PhaseProgress.tsx diff --git a/.kiro/specs/product-launch-essentials/tasks.md b/.kiro/specs/product-launch-essentials/tasks.md index a76ab34..0a61afc 100644 --- a/.kiro/specs/product-launch-essentials/tasks.md +++ b/.kiro/specs/product-launch-essentials/tasks.md @@ -28,7 +28,7 @@ - Write comprehensive tests for progress tracking logic and edge cases - _Requirements: 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1_ -- [ ] 5. Build launch essentials dashboard and overview +- [x] 5. Build launch essentials dashboard and overview - Create LaunchEssentialsDashboard component with phase overview and progress visualization - Implement OverviewCard, PhaseProgress, and NextStepsPanel components - Add interactive elements for navigating between different frameworks and phases diff --git a/app/launch-essentials/components/LaunchEssentialsDashboard.tsx b/app/launch-essentials/components/LaunchEssentialsDashboard.tsx new file mode 100644 index 0000000..0855183 --- /dev/null +++ b/app/launch-essentials/components/LaunchEssentialsDashboard.tsx @@ -0,0 +1,242 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useAuth } from '@/contexts/AuthContext'; +import { toast } from '@/hooks/use-toast'; +import { ProjectDataService } from '@/lib/launch-essentials-firestore'; +import { progressTracker } from '@/lib/progress-tracker'; +import { LaunchPhase, ProjectData, UserProgress } from '@/types/launch-essentials'; +import { Loader2, Plus } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { NextStepsPanel } from './NextStepsPanel'; +import { OverviewCard } from './OverviewCard'; +import { PhaseProgress } from './PhaseProgress'; + +interface LaunchEssentialsDashboardProps { + className?: string; +} + +export function LaunchEssentialsDashboard({ className }: LaunchEssentialsDashboardProps) { + const { user } = useAuth(); + const [userProgress, setUserProgress] = useState(null); + const [projectData, setProjectData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + if (!user) { + setLoading(false); + return; + } + + loadUserData(); + }, [user]); + + const loadUserData = async () => { + if (!user) return; + + try { + setLoading(true); + setError(null); + + // Get user's projects + const projects = await ProjectDataService.getUserProjects(user.uid); + + if (projects.length === 0) { + // No projects yet - show empty state + setUserProgress(null); + setProjectData(null); + } else { + // Use the first project for now (in future, allow project selection) + const project = projects[0]; + setProjectData(project); + + // Get progress for this project + const progress = await progressTracker.getProgress(user.uid, project.id); + setUserProgress(progress); + } + } catch (err) { + console.error('Error loading user data:', err); + setError('Failed to load your progress. Please try again.'); + toast({ + title: 'Error', + description: 'Failed to load your progress. Please try again.', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }; + + const handleCreateProject = async () => { + if (!user) return; + + try { + // Create a new project + const newProject = await ProjectDataService.createProject( + user.uid, + 'My Product Launch', + 'A new product launch project' + ); + + // Initialize progress for the new project + const progress = await progressTracker.initializeProgress( + user.uid, + newProject.id, + 'validation' + ); + + setProjectData(newProject); + setUserProgress(progress); + + toast({ + title: 'Project Created', + description: 'Your new product launch project has been created!', + }); + } catch (err) { + console.error('Error creating project:', err); + toast({ + title: 'Error', + description: 'Failed to create project. Please try again.', + variant: 'destructive', + }); + } + }; + + const handlePhaseClick = (phase: LaunchPhase) => { + // Navigate to specific phase (implement routing logic) + console.log('Navigate to phase:', phase); + // TODO: Implement navigation to specific phase + }; + + if (!user) { + return ( +
+ + + Sign In Required + + Please sign in to access your product launch dashboard + + + +
+ ); + } + + if (loading) { + return ( +
+
+ + Loading your dashboard... +
+
+ ); + } + + if (error) { + return ( +
+ + + Error + {error} + + + + + +
+ ); + } + + if (!projectData || !userProgress) { + return ( +
+ + + Welcome to Launch Essentials + + Get started by creating your first product launch project + + + + + + +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ {projectData.name} +

+

+ {projectData.description} +

+
+
+ + +
+
+ + {/* Overview Cards */} +
+ + + +
+ + {/* Phase Progress Grid */} +
+ + { + console.log('Navigate to step:', stepId); + // TODO: Implement navigation to specific step + }} + /> +
+
+ ); +} diff --git a/app/launch-essentials/components/NextStepsPanel.tsx b/app/launch-essentials/components/NextStepsPanel.tsx new file mode 100644 index 0000000..bf761b0 --- /dev/null +++ b/app/launch-essentials/components/NextStepsPanel.tsx @@ -0,0 +1,307 @@ +'use client'; + +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { cn } from '@/lib/utils'; +import { LaunchPhase, ProjectData, UserProgress } from '@/types/launch-essentials'; +import { + AlertTriangle, + ArrowRight, + CheckCircle, + Clock, + Lightbulb, + Target, + TrendingUp +} from 'lucide-react'; + +interface NextStepsPanelProps { + userProgress: UserProgress; + projectData: ProjectData; + onStepClick: (stepId: string) => void; + className?: string; +} + +const phaseLabels: Record = { + validation: 'Product Validation', + definition: 'Product Definition', + technical: 'Technical Architecture', + marketing: 'Go-to-Market Strategy', + operations: 'Operational Readiness', + financial: 'Financial Planning', + risk: 'Risk Management', + optimization: 'Post-Launch Optimization' +}; + +export function NextStepsPanel({ + userProgress, + projectData, + onStepClick, + className +}: NextStepsPanelProps) { + // Calculate overall progress + const calculateOverallProgress = (): number => { + const phases = Object.values(userProgress.phases); + if (phases.length === 0) return 0; + + const totalSteps = phases.reduce((sum, phase) => sum + phase.steps.length, 0); + const completedSteps = phases.reduce( + (sum, phase) => sum + phase.steps.filter(step => step.status === 'completed').length, + 0 + ); + + return totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0; + }; + + // Get next incomplete steps across all phases + const getNextSteps = () => { + const nextSteps: Array<{ + stepId: string; + phase: LaunchPhase; + status: string; + priority: 'high' | 'medium' | 'low'; + }> = []; + + // Current phase steps first + const currentPhase = userProgress.phases[userProgress.currentPhase]; + if (currentPhase) { + const incompleteSteps = currentPhase.steps.filter( + step => step.status === 'not_started' || step.status === 'in_progress' + ); + + incompleteSteps.slice(0, 2).forEach(step => { + nextSteps.push({ + stepId: step.stepId, + phase: userProgress.currentPhase, + status: step.status, + priority: 'high' + }); + }); + } + + // Add steps from other phases if we need more + if (nextSteps.length < 3) { + const phaseOrder: LaunchPhase[] = [ + 'validation', 'definition', 'technical', 'marketing', + 'operations', 'financial', 'risk', 'optimization' + ]; + + for (const phase of phaseOrder) { + if (phase === userProgress.currentPhase) continue; + + const phaseProgress = userProgress.phases[phase]; + if (phaseProgress) { + const incompleteSteps = phaseProgress.steps.filter( + step => step.status === 'not_started' || step.status === 'in_progress' + ); + + incompleteSteps.slice(0, 3 - nextSteps.length).forEach(step => { + nextSteps.push({ + stepId: step.stepId, + phase, + status: step.status, + priority: 'medium' + }); + }); + + if (nextSteps.length >= 3) break; + } + } + } + + return nextSteps; + }; + + // Generate recommendations based on progress + const getRecommendations = () => { + const overallProgress = calculateOverallProgress(); + const recommendations: Array<{ + type: 'warning' | 'suggestion' | 'achievement'; + title: string; + description: string; + action?: string; + }> = []; + + // Progress-based recommendations + if (overallProgress < 25) { + recommendations.push({ + type: 'suggestion', + title: 'Focus on Validation', + description: 'Complete market validation before moving to other phases', + action: 'Start validation framework' + }); + } else if (overallProgress < 50) { + recommendations.push({ + type: 'suggestion', + title: 'Define Your Product', + description: 'Clearly define your product vision and core features', + action: 'Continue product definition' + }); + } else if (overallProgress < 75) { + recommendations.push({ + type: 'suggestion', + title: 'Plan Your Launch', + description: 'Start planning your go-to-market strategy and operations', + action: 'Begin marketing planning' + }); + } else { + recommendations.push({ + type: 'achievement', + title: 'Almost Ready!', + description: 'You\'re close to launch. Review all phases for completeness', + action: 'Final review' + }); + } + + // Phase-specific warnings + const validationCompletion = userProgress.phases.validation?.completionPercentage || 0; + if (validationCompletion < 80 && userProgress.currentPhase !== 'validation') { + recommendations.push({ + type: 'warning', + title: 'Validation Incomplete', + description: 'Low market validation may lead to product-market fit issues', + action: 'Complete validation' + }); + } + + return recommendations.slice(0, 2); // Limit to 2 recommendations + }; + + const nextSteps = getNextSteps(); + const recommendations = getRecommendations(); + const overallProgress = calculateOverallProgress(); + + return ( + + + + + Next Steps & Recommendations + + + + {/* Progress Summary */} +
+
+ Overall Progress + {overallProgress}% +
+
+
+
+
+ + {/* Next Steps */} +
+

+ + Immediate Next Steps +

+ + {nextSteps.length > 0 ? ( +
+ {nextSteps.map((step, index) => ( +
onStepClick(step.stepId)} + > +
+
+ {index + 1} +
+
+ +
+
+ + {step.stepId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + + {phaseLabels[step.phase]} + + {step.status === 'in_progress' && ( + + + In Progress + + )} +
+

+ Continue working on this step in the {phaseLabels[step.phase].toLowerCase()} phase +

+
+ + +
+ ))} +
+ ) : ( +
+ +

All Steps Complete!

+

+ Great job! You've completed all current steps. +

+
+ )} +
+ + {/* Recommendations */} + {recommendations.length > 0 && ( +
+

+ + Recommendations +

+ +
+ {recommendations.map((rec, index) => ( +
+
+
+ {rec.type === 'warning' ? ( + + ) : rec.type === 'achievement' ? ( + + ) : ( + + )} +
+ +
+

{rec.title}

+

+ {rec.description} +

+ {rec.action && ( + + )} +
+
+
+ ))} +
+
+ )} + + + ); +} diff --git a/app/launch-essentials/components/OverviewCard.tsx b/app/launch-essentials/components/OverviewCard.tsx new file mode 100644 index 0000000..59eef4a --- /dev/null +++ b/app/launch-essentials/components/OverviewCard.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { cn } from '@/lib/utils'; +import { LaunchPhase, ProjectData, UserProgress } from '@/types/launch-essentials'; +import { + AlertCircle, + ArrowRight, + CheckCircle, + Clock, + Target, + TrendingUp +} from 'lucide-react'; + +interface ProgressCalculation { + phaseCompletion: Record; + overallCompletion: number; + completedSteps: number; + totalSteps: number; + nextStep: any; + nextPhase: LaunchPhase | null; +} + +interface OverviewCardProps { + title: string; + progress: ProgressCalculation; + userProgress: UserProgress; + projectData: ProjectData; + variant?: 'default' | 'current-phase' | 'next-steps'; + className?: string; +} + +const phaseLabels: Record = { + validation: 'Product Validation', + definition: 'Product Definition', + technical: 'Technical Architecture', + marketing: 'Go-to-Market Strategy', + operations: 'Operational Readiness', + financial: 'Financial Planning', + risk: 'Risk Management', + optimization: 'Post-Launch Optimization' +}; + +const phaseColors: Record = { + validation: 'bg-blue-500', + definition: 'bg-green-500', + technical: 'bg-purple-500', + marketing: 'bg-orange-500', + operations: 'bg-teal-500', + financial: 'bg-yellow-500', + risk: 'bg-red-500', + optimization: 'bg-indigo-500' +}; + +export function OverviewCard({ + title, + progress, + userProgress, + projectData, + variant = 'default', + className +}: OverviewCardProps) { + const renderDefaultCard = () => ( + + + {title} + + + +
+
+ + {progress.overallCompletion}% + + 75 ? 'default' : 'secondary'}> + {progress.completedSteps} of {progress.totalSteps} steps + +
+ +
+ + {Object.values(progress.phaseCompletion).filter(p => p === 100).length} of 8 phases complete +
+
+
+
+ ); + + const renderCurrentPhaseCard = () => { + const currentPhase = userProgress.currentPhase; + const currentPhaseCompletion = progress.phaseCompletion[currentPhase] || 0; + const currentPhaseSteps = userProgress.phases[currentPhase]?.steps || []; + const completedStepsInPhase = currentPhaseSteps.filter(s => s.status === 'completed').length; + + return ( + + + {title} +
+ + +
+
+

+ {phaseLabels[currentPhase]} +

+

+ {completedStepsInPhase} of {currentPhaseSteps.length} steps completed +

+
+ +
+ + {currentPhaseCompletion}% complete + + {currentPhaseCompletion === 100 ? ( + + + Complete + + ) : ( + + + In Progress + + )} +
+
+
+ + ); + }; + + const renderNextStepsCard = () => { + const nextStep = progress.nextStep; + const nextPhase = progress.nextPhase; + const hasNextStep = nextStep !== null; + const hasNextPhase = nextPhase !== null && nextPhase !== userProgress.currentPhase; + + return ( + + + {title} + + + +
+ {hasNextStep ? ( +
+
+ + Next Step +
+

+ Complete: {nextStep.stepId.replace(/-/g, ' ')} +

+
+ ) : ( +
+
+ + All Steps Complete! +
+

+ Great job! You've completed all current steps. +

+
+ )} + + {hasNextPhase && ( +
+
+
+ Recommended Phase +
+

+ {phaseLabels[nextPhase]} +

+
+ )} + + {progress.overallCompletion < 25 && ( +
+ +
+

+ Focus on Validation +

+

+ Complete market validation before moving forward +

+
+
+ )} +
+ + + ); + }; + + switch (variant) { + case 'current-phase': + return renderCurrentPhaseCard(); + case 'next-steps': + return renderNextStepsCard(); + default: + return renderDefaultCard(); + } +} diff --git a/app/launch-essentials/components/PhaseProgress.tsx b/app/launch-essentials/components/PhaseProgress.tsx new file mode 100644 index 0000000..23603db --- /dev/null +++ b/app/launch-essentials/components/PhaseProgress.tsx @@ -0,0 +1,224 @@ +'use client'; + +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { cn } from '@/lib/utils'; +import { LaunchPhase, UserProgress } from '@/types/launch-essentials'; +import { + ArrowRight, + CheckCircle, + Clock, + Play, + Target, + TrendingUp +} from 'lucide-react'; + +interface PhaseProgressProps { + userProgress: UserProgress; + onPhaseClick: (phase: LaunchPhase) => void; + className?: string; +} + +const phaseLabels: Record = { + validation: 'Product Validation', + definition: 'Product Definition', + technical: 'Technical Architecture', + marketing: 'Go-to-Market Strategy', + operations: 'Operational Readiness', + financial: 'Financial Planning', + risk: 'Risk Management', + optimization: 'Post-Launch Optimization' +}; + +const phaseDescriptions: Record = { + validation: 'Validate your product idea with market research and user feedback', + definition: 'Define your product vision, features, and success metrics', + technical: 'Plan your technology stack, architecture, and infrastructure', + marketing: 'Develop pricing strategy, marketing channels, and launch timeline', + operations: 'Set up team structure, processes, and customer support', + financial: 'Create financial projections and business model planning', + risk: 'Identify and mitigate potential risks and challenges', + optimization: 'Plan for post-launch analytics, feedback, and improvements' +}; + +const phaseColors: Record = { + validation: 'bg-blue-500', + definition: 'bg-green-500', + technical: 'bg-purple-500', + marketing: 'bg-orange-500', + operations: 'bg-teal-500', + financial: 'bg-yellow-500', + risk: 'bg-red-500', + optimization: 'bg-indigo-500' +}; + +const phaseOrder: LaunchPhase[] = [ + 'validation', + 'definition', + 'technical', + 'marketing', + 'operations', + 'financial', + 'risk', + 'optimization' +]; + +export function PhaseProgress({ userProgress, onPhaseClick, className }: PhaseProgressProps) { + const calculatePhaseCompletion = (phase: LaunchPhase): number => { + const phaseProgress = userProgress.phases[phase]; + if (!phaseProgress || phaseProgress.steps.length === 0) return 0; + + const completedSteps = phaseProgress.steps.filter(step => step.status === 'completed').length; + return Math.round((completedSteps / phaseProgress.steps.length) * 100); + }; + + const getPhaseStatus = (phase: LaunchPhase): 'not-started' | 'in-progress' | 'completed' => { + const completion = calculatePhaseCompletion(phase); + if (completion === 0) return 'not-started'; + if (completion === 100) return 'completed'; + return 'in-progress'; + }; + + const isPhaseAccessible = (phase: LaunchPhase): boolean => { + const phaseIndex = phaseOrder.indexOf(phase); + const currentPhaseIndex = phaseOrder.indexOf(userProgress.currentPhase); + + // Allow access to current phase and previous phases, plus one phase ahead + return phaseIndex <= currentPhaseIndex + 1; + }; + + return ( + + + + + Phase Progress + + + +
+ {phaseOrder.map((phase, index) => { + const completion = calculatePhaseCompletion(phase); + const status = getPhaseStatus(phase); + const isAccessible = isPhaseAccessible(phase); + const isCurrent = phase === userProgress.currentPhase; + const phaseSteps = userProgress.phases[phase]?.steps || []; + const completedSteps = phaseSteps.filter(s => s.status === 'completed').length; + + return ( +
isAccessible && onPhaseClick(phase)} + > + {/* Phase indicator line */} + {index < phaseOrder.length - 1 && ( +
+ )} + +
+ {/* Phase icon/indicator */} +
+
+ {status === 'completed' ? ( + + ) : status === 'in-progress' ? ( + + ) : ( + index + 1 + )} +
+
+ + {/* Phase content */} +
+
+
+

+ {phaseLabels[phase]} +

+ {isCurrent && ( + + Current + + )} + {status === 'completed' && ( + + + Complete + + )} +
+
+ + {completion}% + + {isAccessible && ( + + )} +
+
+ +

+ {phaseDescriptions[phase]} +

+ +
+ +
+ + {completedSteps} of {phaseSteps.length} steps completed + + {status === 'not-started' && isAccessible && ( + + )} + {status === 'in-progress' && ( + + )} +
+
+
+
+
+ ); + })} +
+ + + ); +} From b9af89d6a6c77ff60d3d5d70dc69fe8b66371add Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 30 Jul 2025 18:08:18 +0700 Subject: [PATCH 02/69] feat: add validation components for market research, interview guide and validation report Co-authored-by: issac-anhpdm --- .../specs/product-launch-essentials/tasks.md | 2 +- .../components/ProductDefinition.tsx | 354 +++++++++ .../components/ValidationFramework.tsx | 373 +++++++++ .../definition/FeaturePrioritization.tsx | 476 ++++++++++++ .../definition/MetricsDefinition.tsx | 519 +++++++++++++ .../definition/ValueProposition.tsx | 353 +++++++++ .../components/definition/VisionMission.tsx | 209 +++++ .../validation/CompetitorAnalysis.tsx | 511 ++++++++++++ .../components/validation/InterviewGuide.tsx | 641 ++++++++++++++++ .../components/validation/MarketResearch.tsx | 334 ++++++++ .../validation/TargetAudienceValidation.tsx | 724 ++++++++++++++++++ .../validation/ValidationReport.tsx | 526 +++++++++++++ 12 files changed, 5021 insertions(+), 1 deletion(-) create mode 100644 app/launch-essentials/components/ProductDefinition.tsx create mode 100644 app/launch-essentials/components/ValidationFramework.tsx create mode 100644 app/launch-essentials/components/definition/FeaturePrioritization.tsx create mode 100644 app/launch-essentials/components/definition/MetricsDefinition.tsx create mode 100644 app/launch-essentials/components/definition/ValueProposition.tsx create mode 100644 app/launch-essentials/components/definition/VisionMission.tsx create mode 100644 app/launch-essentials/components/validation/CompetitorAnalysis.tsx create mode 100644 app/launch-essentials/components/validation/InterviewGuide.tsx create mode 100644 app/launch-essentials/components/validation/MarketResearch.tsx create mode 100644 app/launch-essentials/components/validation/TargetAudienceValidation.tsx create mode 100644 app/launch-essentials/components/validation/ValidationReport.tsx diff --git a/.kiro/specs/product-launch-essentials/tasks.md b/.kiro/specs/product-launch-essentials/tasks.md index 0a61afc..3a7ab96 100644 --- a/.kiro/specs/product-launch-essentials/tasks.md +++ b/.kiro/specs/product-launch-essentials/tasks.md @@ -36,7 +36,7 @@ - Create responsive design that works across desktop, tablet, and mobile devices - _Requirements: 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1_ -- [ ] 6. Implement product validation framework +- [x] 6. Implement product validation framework - Create ValidationFramework component with market research templates and competitor analysis tools - Build interactive forms for target audience validation and user persona creation - Implement validation logic for market research data and competitor comparison frameworks diff --git a/app/launch-essentials/components/ProductDefinition.tsx b/app/launch-essentials/components/ProductDefinition.tsx new file mode 100644 index 0000000..4f3ab1b --- /dev/null +++ b/app/launch-essentials/components/ProductDefinition.tsx @@ -0,0 +1,354 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useAuth } from '@/contexts/AuthContext'; +import { toast } from '@/hooks/use-toast'; +import { ProjectDataService, UserProgressService } from '@/lib/launch-essentials-firestore'; +import { ProjectData, UserProgress, ProductDefinitionData } from '@/types/launch-essentials'; +import { AlertCircle, CheckCircle, Save, Target, Lightbulb, Star, BarChart3 } from 'lucide-react'; +import t, useState } from 'react'; +import { VisionMission } from './definition/VisionMission'; +import { ValueProposition } from './definition/ValueProposition'; +import { FeaturePrioritization } from './definition/FeaturePrioritization'; +import { MetricsDefinition } from './definition/MetricsDefinition'; + +interface ProductDefinitionProps { + projectData: ProjectData; + userProgress: UserProgress; + onProgressUpdate?: (progress: UserProgress) => void; +} + +type DefinitionStep = + | 'vision-mission' + | 'value-proposition' + | 'feature-prioritization' + | 'metrics-definition'; + +interface DefinitionStepConfig { + id: DefinitionStep; + title: string; + description: string; + icon: React.ComponentType<{ className?: string }>; + required: boolean; +} + +const DEFINITION_STEPS: DefinitionStepConfig[] = [ + { + id: 'vision-mission', + title: 'Vision & Mission', + description: 'Define your product vision and align with mission', + icon: Target, + required: true + }, + { + id: 'value-proposition', + title: 'Value Proposition', + description: 'Create compelling value proposition using proven frameworks', + icon: Lightbulb, + required: true + }, + { + id: 'feature-prioritization', + title: 'Feature Prioritization', + description: 'Prioritize features using MoSCoW and Kano methodologies', + icon: Star, + required: true + }, + { + id: 'metrics-definition', + title: 'Success Metrics', + description: 'Define KPIs and success criteria for measurement', + icon: BarChart3, + required: true + } +]; + +export function ProductDefinition({ + projectData, + userProgress, + onProgressUpdate +}: ProductDefinitionProps) { + const { user } = useAuth(); + const [currentStep, setCurrentStep] = useState('vision-mission'); + const [definitionData, setDefinitionData] = useState(() => { + return projectData.data.definition || { + vision: { + statement: '', + missionAlignment: '' + }, + valueProposition: { + canvas: { + customerJobs: [], + painPoints: [], + gainCreators: [], + painRelievers: [], + productsServices: [] + }, + uniqueValue: '' + }, + features: { + coreFeatures: [], + prioritization: { + method: 'moscow', + results: [] + } + }, + metrics: { + kpis: [], + successCriteria: [] + } + }; + }); + const [saving, setSaving] = useState(false); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + useEffect(() => { + // Load existing definition data if available + if (projectData.data.definition) { + setDefinitionData(projectData.data.definition); + } + }, [projectData]); + + const handleDataChange = (stepData: Partial) => { + setDefinitionData(prev => ({ ...prev, ...stepData })); + setHasUnsavedChanges(true); + }; + + const handleSave = async () => { + if (!user) return; + + try { + setSaving(true); + + // Update project data with definition data + await ProjectDataService.updateProjectPhaseData( + projectData.id, + 'definition', + definitionData + ); + + // Update step progress + await UserProgressService.updateStepProgress( + user.uid, + projectData.id, + 'definition', + currentStep, + 'completed', + definitionData + ); + + setHasUnsavedChanges(false); + + toast({ + title: 'Progress Saved', + description: 'Your product definition progress has been saved successfully.', + }); + + // Refresh progress if callback provided + if (onProgressUpdate) { + const updatedProgress = await UserProgressService.getUserProgress(user.uid, projectData.id); + if (updatedProgress) { + onProgressUpdate(updatedProgress); + } + } + + } catch (error) { + console.error('Error saving definition data:', error); + toast({ + title: 'Save Failed', + description: 'Failed to save your progress. Please try again.', + variant: 'destructive', + }); + } finally { + setSaving(false); + } + }; + + const getStepStatus = (stepId: DefinitionStep): 'completed' | 'in-progress' | 'not-started' => { + const definitionPhase = userProgress.phases.definition; + const step = definitionPhase.steps.find(s => s.stepId === stepId); + + if (step?.status === 'completed') return 'completed'; + if (stepId === currentStep) return 'in-progress'; + return 'not-started'; + }; + + const isStepComplete = (stepId: DefinitionStep): boolean => { + switch (stepId) { + case 'vision-mission': + return definitionData.vision.statement.length > 0 && + definitionData.vision.missionAlignment.length > 0; + case 'value-proposition': + return definitionData.valueProposition.canvas.customerJobs.length > 0 && + definitionData.valueProposition.canvas.painPoints.length > 0 && + definitionData.valueProposition.uniqueValue.length > 0; + case 'feature-prioritization': + return definitionData.features.coreFeatures.length > 0 && + definitionData.features.prioritization.results.length > 0; + case 'metrics-definition': + return definitionData.metrics.kpis.length > 0 && + definitionData.metrics.successCriteria.length > 0; + default: + return false; + } + }; + + const renderStepContent = () => { + switch (currentStep) { + case 'vision-mission': + return ( + handleDataChange({ vision })} + /> + ); + case 'value-proposition': + return ( + handleDataChange({ valueProposition })} + /> + ); + case 'feature-prioritization': + return ( + handleDataChange({ features })} + /> + ); + case 'metrics-definition': + return ( + handleDataChange({ metrics })} + /> + ); + default: + return null; + } + }; + + return ( +
+ {/* Header */} +
+
+

+ Product Definition Framework +

+

+ Define what you're building and why it matters +

+
+
+ {hasUnsavedChanges && ( +
+ + Unsaved changes +
+ )} + +
+
+ +
+ {/* Step Navigation */} +
+ + + Definition Steps + + Complete each step to define your product clearly + + + + {DEFINITION_STEPS.map((step) => { + const status = getStepStatus(step.id); + const isComplete = isStepComplete(step.id); + const Icon = step.icon; + + return ( + + ); + })} + + +
+ + {/* Step Content */} +
+ + +
+
+ + {DEFINITION_STEPS.find(s => s.id === currentStep)?.title} + + + {DEFINITION_STEPS.find(s => s.id === currentStep)?.description} + +
+ {isStepComplete(currentStep) && ( +
+ + Complete +
+ )} +
+
+ + {renderStepContent()} + +
+
+
+
+ ); +} diff --git a/app/launch-essentials/components/ValidationFramework.tsx b/app/launch-essentials/components/ValidationFramework.tsx new file mode 100644 index 0000000..28eec48 --- /dev/null +++ b/app/launch-essentials/components/ValidationFramework.tsx @@ -0,0 +1,373 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useAuth } from '@/contexts/AuthContext'; +import { toast } from '@/hooks/use-toast'; +import { ProjectDataService, UserProgressService } from '@/lib/launch-essentials-firestore'; +import { ProjectData, UserProgress, ValidationData } from '@/types/launch-essentials'; +import { AlertCircle, CheckCircle, Save, TrendingUp, Users } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { CompetitorAnalysis } from './validation/CompetitorAnalysis'; +import { InterviewGuide } from './validation/InterviewGuide'; +import { MarketResearch } from './validation/MarketResearch'; +import { TargetAudienceValidation } from './validation/TargetAudienceValidation'; +import { ValidationReport } from './validation/ValidationReport'; + +interface ValidationFrameworkProps { + projectData: ProjectData; + userProgress: UserProgress; + onProgressUpdate?: (progress: UserProgress) => void; +} + +type ValidationStep = + | 'market-research' + | 'competitor-analysis' + | 'target-audience' + | 'interview-guide' + | 'validation-report'; + +interface ValidationStepConfig { + id: ValidationStep; + title: string; + description: string; + icon: React.ComponentType<{ className?: string }>; + required: boolean; +} + +const VALIDATION_STEPS: ValidationStepConfig[] = [ + { + id: 'market-research', + title: 'Market Research', + description: 'Analyze market size, growth trends, and opportunities', + icon: TrendingUp, + required: true + }, + { + id: 'competitor-analysis', + title: 'Competitor Analysis', + description: 'Identify and analyze direct and indirect competitors', + icon: Users, + required: true + }, + { + id: 'target-audience', + title: 'Target Audience', + description: 'Define user personas and validate target segments', + icon: Users, + required: true + }, + { + id: 'interview-guide', + title: 'User Interviews', + description: 'Conduct structured interviews with potential users', + icon: Users, + required: false + }, + { + id: 'validation-report', + title: 'Validation Report', + description: 'Generate go/no-go recommendation based on findings', + icon: CheckCircle, + required: true + } +]; + +export function ValidationFramework({ + projectData, + userProgress, + onProgressUpdate +}: ValidationFrameworkProps) { + const { user } = useAuth(); + const [currentStep, setCurrentStep] = useState('market-research'); + const [validationData, setValidationData] = useState(() => { + return projectData.data.validation || { + marketResearch: { + marketSize: 0, + growthRate: 0, + trends: [], + sources: [] + }, + competitorAnalysis: { + competitors: [], + competitiveAdvantage: '', + marketGap: '' + }, + targetAudience: { + personas: [], + interviewResults: [], + validationScore: 0 + }, + validationReport: { + recommendation: 'go', + reasoning: '', + nextSteps: [] + } + }; + }); + const [saving, setSaving] = useState(false); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + useEffect(() => { + // Load existing validation data if available + if (projectData.data.validation) { + setValidationData(projectData.data.validation); + } + }, [projectData]); + + const handleDataChange = (stepData: Partial) => { + setValidationData(prev => ({ ...prev, ...stepData })); + setHasUnsavedChanges(true); + }; + + const handleSave = async () => { + if (!user) return; + + try { + setSaving(true); + + // Update project data with validation data + await ProjectDataService.updateProjectPhaseData( + projectData.id, + 'validation', + validationData + ); + + // Update step progress + await UserProgressService.updateStepProgress( + user.uid, + projectData.id, + 'validation', + currentStep, + 'completed', + validationData + ); + + setHasUnsavedChanges(false); + + toast({ + title: 'Progress Saved', + description: 'Your validation progress has been saved successfully.', + }); + + // Refresh progress if callback provided + if (onProgressUpdate) { + const updatedProgress = await UserProgressService.getUserProgress(user.uid, projectData.id); + if (updatedProgress) { + onProgressUpdate(updatedProgress); + } + } + + } catch (error) { + console.error('Error saving validation data:', error); + toast({ + title: 'Save Failed', + description: 'Failed to save your progress. Please try again.', + variant: 'destructive', + }); + } finally { + setSaving(false); + } + }; + + const getStepStatus = (stepId: ValidationStep): 'completed' | 'in-progress' | 'not-started' => { + const validationPhase = userProgress.phases.validation; + const step = validationPhase.steps.find(s => s.stepId === stepId); + + if (step?.status === 'completed') return 'completed'; + if (stepId === currentStep) return 'in-progress'; + return 'not-started'; + }; + + const isStepComplete = (stepId: ValidationStep): boolean => { + switch (stepId) { + case 'market-research': + return validationData.marketResearch.marketSize > 0 && + validationData.marketResearch.trends.length > 0; + case 'competitor-analysis': + return validationData.competitorAnalysis.competitors.length > 0 && + validationData.competitorAnalysis.competitiveAdvantage.length > 0; + case 'target-audience': + return validationData.targetAudience.personas.length > 0; + case 'interview-guide': + return validationData.targetAudience.interviewResults.length > 0; + case 'validation-report': + return validationData.validationReport.reasoning.length > 0; + default: + return false; + } + }; + + const renderStepContent = () => { + switch (currentStep) { + case 'market-research': + return ( + handleDataChange({ marketResearch })} + /> + ); + case 'competitor-analysis': + return ( + handleDataChange({ competitorAnalysis })} + /> + ); + case 'target-audience': + return ( + handleDataChange({ targetAudience })} + /> + ); + case 'interview-guide': + return ( + + handleDataChange({ + targetAudience: { + ...validationData.targetAudience, + interviewResults + } + }) + } + /> + ); + case 'validation-report': + return ( + handleDataChange({ validationReport })} + /> + ); + default: + return null; + } + }; + + return ( +
+ {/* Header */} +
+
+

+ Product Validation Framework +

+

+ Validate your product idea before investing in development +

+
+
+ {hasUnsavedChanges && ( +
+ + Unsaved changes +
+ )} + +
+
+ +
+ {/* Step Navigation */} +
+ + + Validation Steps + + Complete each step to validate your product idea + + + + {VALIDATION_STEPS.map((step) => { + const status = getStepStatus(step.id); + const isComplete = isStepComplete(step.id); + const Icon = step.icon; + + return ( + + ); + })} + + +
+ + {/* Step Content */} +
+ + +
+
+ + {VALIDATION_STEPS.find(s => s.id === currentStep)?.title} + + + {VALIDATION_STEPS.find(s => s.id === currentStep)?.description} + +
+ {isStepComplete(currentStep) && ( +
+ + Complete +
+ )} +
+
+ + {renderStepContent()} + +
+
+
+
+ ); +} diff --git a/app/launch-essentials/components/definition/FeaturePrioritization.tsx b/app/launch-essentials/components/definition/FeaturePrioritization.tsx new file mode 100644 index 0000000..7f3918e --- /dev/null +++ b/app/launch-essentials/components/definition/FeaturePrioritization.tsx @@ -0,0 +1,476 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Textarea } from '@/components/ui/textarea'; +import { Feature, ProductDefinitionData } from '@/types/launch-essentials'; +import { Calculator, HelpCircle, Plus, X } from 'lucide-react'; +import { useState } from 'react'; + +interface FeaturePrioritizationProps { + data: ProductDefinitionData['features']; + onChange: (data: ProductDefinitionData['features']) => void; +} + +const PRIORITIZATION_METHODS = [ + { + value: 'moscow', + label: 'MoSCoW Method', + description: 'Must have, Should have, Could have, Won\'t have' + }, + { + value: 'kano', + label: 'Kano Model', + description: 'Basic, Performance, Excitement features' + }, + { + value: 'rice', + label: 'RICE Framework', + description: 'Reach, Impact, Confidence, Effort scoring' + }, + { + value: 'value-effort', + label: 'Value vs Effort', + description: 'Simple value and effort matrix' + } +]; + +const MOSCOW_PRIORITIES = [ + { value: 'must-have', label: 'Must Have', color: 'bg-red-100 text-red-800' }, + { value: 'should-have', label: 'Should Have', color: 'bg-orange-100 text-orange-800' }, + { value: 'could-have', label: 'Could Have', color: 'bg-yellow-100 text-yellow-800' }, + { value: 'wont-have', label: 'Won\'t Have', color: 'bg-gray-100 text-gray-800' } +]; + +const EFFORT_LEVELS = [ + { value: 'low', label: 'Low', description: '1-2 weeks' }, + { value: 'medium', label: 'Medium', description: '1-2 months' }, + { value: 'high', label: 'High', description: '3+ months' } +]; + +const IMPACT_LEVELS = [ + { value: 'low', label: 'Low', description: 'Nice to have' }, + { value: 'medium', label: 'Medium', description: 'Valuable improvement' }, + { value: 'high', label: 'High', description: 'Game changer' } +]; + +export function FeaturePrioritization({ data, onChange }: FeaturePrioritizationProps) { + const [showAddFeature, setShowAddFeature] = useState(false); + const [showMethodHelp, setShowMethodHelp] = useState(false); + const [newFeature, setNewFeature] = useState>({ + name: '', + description: '', + priority: 'must-have', + effort: 'medium', + impact: 'medium', + dependencies: [] + }); + + const handleMethodChange = (method: string) => { + onChange({ + ...data, + prioritization: { + ...data.prioritization, + method: method as any, + results: [] // Reset results when method changes + } + }); + }; + + const addFeature = () => { + if (!newFeature.name || !newFeature.description) return; + + const feature: Feature = { + id: Date.now().toString(), + name: newFeature.name, + description: newFeature.description, + priority: newFeature.priority as any, + effort: newFeature.effort as any, + impact: newFeature.impact as any, + dependencies: newFeature.dependencies || [] + }; + + onChange({ + ...data, + coreFeatures: [...data.coreFeatures, feature] + }); + + setNewFeature({ + name: '', + description: '', + priority: 'must-have', + effort: 'medium', + impact: 'medium', + dependencies: [] + }); + setShowAddFeature(false); + }; + + const removeFeature = (id: string) => { + onChange({ + ...data, + coreFeatures: data.coreFeatures.filter(f => f.id !== id) + }); + }; + + const updateFeature = (id: string, updates: Partial) => { + onChange({ + ...data, + coreFeatures: data.coreFeatures.map(f => + f.id === id ? { ...f, ...updates } : f + ) + }); + }; + + const calculatePrioritization = () => { + const results = data.coreFeatures.map((feature, index) => { + let score = 0; + + switch (data.prioritization.method) { + case 'moscow': + const priorityScores = { 'must-have': 4, 'should-have': 3, 'could-have': 2, 'wont-have': 1 }; + score = priorityScores[feature.priority]; + break; + + case 'value-effort': + const impactScores = { high: 3, medium: 2, low: 1 }; + const effortScores = { low: 3, medium: 2, high: 1 }; // Inverse for effort + score = impactScores[feature.impact] * effortScores[feature.effort]; + break; + + case 'rice': + // Simplified RICE calculation + const reach = 100; // Default reach + const impact = { high: 3, medium: 2, low: 1 }[feature.impact]; + const confidence = 0.8; // Default confidence + const effort = { low: 1, medium: 2, high: 3 }[feature.effort]; + score = (reach * impact * confidence) / effort; + break; + + case 'kano': + // Simplified Kano scoring based on impact + score = { high: 3, medium: 2, low: 1 }[feature.impact]; + break; + } + + return { + featureId: feature.id, + score, + ranking: 0 // Will be set after sorting + }; + }); + + // Sort by score and assign rankings + results.sort((a, b) => b.score - a.score); + results.forEach((result, index) => { + result.ranking = index + 1; + }); + + onChange({ + ...data, + prioritization: { + ...data.prioritization, + results + } + }); + }; + + const getSortedFeatures = () => { + if (data.prioritization.results.length === 0) { + return data.coreFeatures; + } + + return data.coreFeatures.sort((a, b) => { + const aResult = data.prioritization.results.find(r => r.featureId === a.id); + const bResult = data.prioritization.results.find(r => r.featureId === b.id); + + if (!aResult || !bResult) return 0; + return aResult.ranking - bResult.ranking; + }); + }; + + return ( +
+ {/* Prioritization Method Selection */} + + +
+
+ Prioritization Method + + Choose a framework to prioritize your features + +
+ +
+
+ + {showMethodHelp && ( +
+

Prioritization Methods

+
+ {PRIORITIZATION_METHODS.map((method) => ( +
+ {method.label}: {method.description} +
+ ))} +
+
+ )} + +
+ {PRIORITIZATION_METHODS.map((method) => ( + + ))} +
+
+
+ + {/* Feature Management */} + + +
+
+ Core Features + + Define and prioritize your product's core features + +
+
+ {data.coreFeatures.length > 0 && ( + + )} + +
+
+
+ + {/* Add Feature Form */} + {showAddFeature && ( + + + Add New Feature + + +
+
+ + setNewFeature({ ...newFeature, name: e.target.value })} + /> +
+
+ + +
+
+ +
+ +