mission-control/app/mission/page.tsx

437 lines
18 KiB
TypeScript

import { DashboardLayout } from "@/components/layout/sidebar";
import { PageHeader } from "@/components/layout/page-header";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Target,
TrendingUp,
Heart,
Plane,
DollarSign,
Briefcase,
Smartphone,
Rocket,
CheckCircle2,
ArrowRight,
Flame,
Clock,
ExternalLink,
} from "lucide-react";
import {
fetchMissionProgress,
ProgressMetric,
Milestone,
NextStep
} from "@/lib/data/mission";
import { siteUrls } from "@/lib/config/sites";
// Revalidate every 5 minutes
export const revalidate = 300;
// ============================================================================
// Progress Bar Component
// ============================================================================
function ProgressBar({ metric }: { metric: ProgressMetric }) {
const IconComponent = {
Target,
Smartphone,
DollarSign,
Plane,
Briefcase,
Heart,
TrendingUp,
}[metric.icon] || Target;
const formattedCurrent = metric.unit === "MRR" || metric.unit === "saved"
? new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
}).format(metric.current)
: metric.current;
const formattedTarget = metric.unit === "MRR" || metric.unit === "saved"
? new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
}).format(metric.target)
: metric.target;
return (
<Card className="relative overflow-hidden group hover:shadow-md transition-all duration-300">
{/* Background glow effect */}
<div className={`absolute inset-0 bg-gradient-to-br ${metric.color} opacity-5 group-hover:opacity-10 transition-opacity`} />
<CardContent className="p-4 sm:p-6 relative z-10">
<div className="flex items-start justify-between gap-3 mb-4">
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
<div className={`w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-gradient-to-br ${metric.color} flex items-center justify-center shadow-lg shrink-0`}>
<IconComponent className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
</div>
<div className="min-w-0 flex-1">
<h3 className="font-bold text-base sm:text-lg truncate">{metric.label}</h3>
<p className="text-xs sm:text-sm text-muted-foreground">
{formattedCurrent} / {formattedTarget} {metric.unit}
</p>
</div>
</div>
<div className="text-right shrink-0">
<span className={`text-2xl sm:text-3xl font-bold bg-gradient-to-r ${metric.color} bg-clip-text text-transparent`}>
{metric.percentage}%
</span>
</div>
</div>
{/* Progress bar */}
<div className="relative">
<div className="h-3 sm:h-4 bg-secondary/50 rounded-full overflow-hidden">
<div
className={`h-full bg-gradient-to-r ${metric.color} rounded-full transition-all duration-1000 ease-out relative`}
style={{ width: `${metric.percentage}%` }}
>
{/* Shine effect */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent animate-shimmer" />
</div>
</div>
{/* Milestone markers - hidden on mobile */}
<div className="hidden sm:flex justify-between mt-2 text-xs text-muted-foreground">
<span>Start</span>
<span>25%</span>
<span>50%</span>
<span>75%</span>
<span>Goal</span>
</div>
</div>
</CardContent>
</Card>
);
}
// ============================================================================
// Milestone Timeline Component
// ============================================================================
function MilestoneTimeline({ milestones }: { milestones: Milestone[] }) {
if (milestones.length === 0) {
return (
<Card className="bg-muted/50">
<CardContent className="p-6 sm:p-8 text-center">
<Rocket className="w-10 h-10 sm:w-12 sm:h-12 mx-auto text-muted-foreground mb-4" />
<h3 className="font-semibold text-base sm:text-lg mb-2">No Milestones Yet</h3>
<p className="text-sm text-muted-foreground">
Complete tasks tagged with &quot;milestone&quot; or &quot;mission&quot; to see your achievements here!
</p>
</CardContent>
</Card>
);
}
const categoryColors: Record<Milestone["category"], string> = {
ios: "bg-blue-500",
financial: "bg-amber-500",
travel: "bg-purple-500",
family: "bg-pink-500",
business: "bg-emerald-500",
};
const categoryLabels: Record<Milestone["category"], string> = {
ios: "iOS",
financial: "Financial",
travel: "Travel",
family: "Family",
business: "Business",
};
const IconComponents: Record<string, React.ComponentType<{ className?: string }>> = {
Smartphone,
DollarSign,
Plane,
Heart,
Briefcase,
};
return (
<div className="relative">
{/* Timeline line - hidden on mobile */}
<div className="hidden sm:block absolute left-5 top-0 bottom-0 w-0.5 bg-gradient-to-b from-primary via-primary/50 to-transparent" />
<div className="space-y-3 sm:space-y-4">
{milestones.map((milestone, index) => {
const IconComponent = IconComponents[milestone.icon] || CheckCircle2;
const completedDate = new Date(milestone.completedAt).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
return (
<div
key={milestone.id}
className="relative flex gap-3 sm:gap-4 group animate-fade-in"
style={{ animationDelay: `${index * 100}ms` }}
>
{/* Timeline dot - hidden on mobile */}
<div className={`hidden sm:flex w-10 h-10 rounded-full ${categoryColors[milestone.category]} items-center justify-center shrink-0 shadow-lg z-10 group-hover:scale-110 transition-transform`}>
<IconComponent className="w-5 h-5 text-white" />
</div>
{/* Mobile icon */}
<div className={`sm:hidden w-8 h-8 rounded-full ${categoryColors[milestone.category]} flex items-center justify-center shrink-0`}>
<IconComponent className="w-4 h-4 text-white" />
</div>
{/* Content */}
<Card className="flex-1 group-hover:shadow-md transition-shadow">
<CardContent className="p-3 sm:p-4">
<div className="flex flex-wrap items-center gap-2 mb-1">
<Badge className={`${categoryColors[milestone.category]}/10 text-${categoryColors[milestone.category].replace("bg-", "")} text-xs`}>
{categoryLabels[milestone.category]}
</Badge>
<span className="text-xs text-muted-foreground flex items-center gap-1">
<CheckCircle2 className="w-3 h-3" />
{completedDate}
</span>
</div>
<h4 className="font-semibold text-sm sm:text-base">{milestone.title}</h4>
{milestone.description && (
<p className="text-xs sm:text-sm text-muted-foreground mt-1 line-clamp-2">
{milestone.description}
</p>
)}
</CardContent>
</Card>
</div>
);
})}
</div>
</div>
);
}
// ============================================================================
// Next Steps Component
// ============================================================================
function NextSteps({ steps }: { steps: NextStep[] }) {
if (steps.length === 0) {
return (
<Card className="bg-muted/50">
<CardContent className="p-6 sm:p-8 text-center">
<CheckCircle2 className="w-10 h-10 sm:w-12 sm:h-12 mx-auto text-green-500 mb-4" />
<h3 className="font-semibold text-base sm:text-lg mb-2">All Caught Up!</h3>
<p className="text-sm text-muted-foreground">
No high priority tasks. Time to set new goals!
</p>
</CardContent>
</Card>
);
}
return (
<div className="space-y-2 sm:space-y-3">
{steps.map((step, index) => (
<a
key={step.id}
href={step.ganttBoardUrl}
target="_blank"
rel="noopener noreferrer"
className="block group animate-fade-in"
style={{ animationDelay: `${index * 100}ms` }}
>
<Card className="group-hover:border-primary/50 group-hover:shadow-md transition-all">
<CardContent className="p-3 sm:p-4">
<div className="flex items-start gap-2 sm:gap-3">
{/* Priority indicator */}
<div className={`w-1 h-full min-h-[32px] sm:min-h-[40px] rounded-full ${
step.priority === "urgent" ? "bg-red-500" : "bg-orange-500"
}`} />
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2 mb-1">
<Badge
variant={step.priority === "urgent" ? "destructive" : "default"}
className="text-xs"
>
{step.priority === "urgent" ? (
<><Flame className="w-3 h-3 mr-1" /> Urgent</>
) : (
"High Priority"
)}
</Badge>
{step.projectName && (
<span className="text-xs text-muted-foreground truncate">
{step.projectName}
</span>
)}
</div>
<h4 className="font-medium text-sm sm:text-base truncate group-hover:text-primary transition-colors">
{step.title}
</h4>
{step.dueDate && (
<p className="text-xs text-muted-foreground mt-1 flex items-center gap-1">
<Clock className="w-3 h-3" />
Due {new Date(step.dueDate).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})}
</p>
)}
</div>
<ExternalLink className="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0 mt-1" />
</div>
</CardContent>
</Card>
</a>
))}
</div>
);
}
// ============================================================================
// Main Page Component
// ============================================================================
export default async function MissionPage() {
const progress = await fetchMissionProgress();
return (
<DashboardLayout>
<div className="space-y-6 sm:space-y-8">
{/* Header */}
<div className="text-center sm:text-left">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight">
<span className="bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent">
The Mission
</span>
</h1>
<p className="text-muted-foreground mt-2 text-sm sm:text-base md:text-lg">
Build an iOS empire retire on our terms travel with Heidi 53 is just the start
</p>
</div>
{/* Mission Statement Card */}
<Card className="relative overflow-hidden hover:shadow-lg transition-shadow">
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 via-purple-500/10 to-pink-500/10" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-500/20 via-transparent to-transparent" />
<CardContent className="p-4 sm:p-6 md:p-8 relative z-10">
<div className="flex flex-col sm:flex-row items-center gap-4 sm:gap-6">
<div className="w-16 h-16 sm:w-20 sm:h-20 rounded-2xl bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center shadow-xl shrink-0">
<Rocket className="w-8 h-8 sm:w-10 sm:h-10 text-white" />
</div>
<div className="text-center sm:text-left">
<h2 className="text-lg sm:text-xl md:text-2xl font-bold mb-2">Why We Do What We Do</h2>
<p className="text-muted-foreground text-sm sm:text-base md:text-lg leading-relaxed max-w-2xl">
Build a sustainable side hustle through iOS apps to achieve financial independence,
travel with Heidi, take care of family, and retire on my own terms all while
staying healthy and having fun.
</p>
</div>
</div>
</CardContent>
</Card>
{/* Progress Dashboard */}
<section>
<div className="flex items-center gap-3 mb-4 sm:mb-6">
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center shrink-0">
<TrendingUp className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
</div>
<div>
<h2 className="text-xl sm:text-2xl font-bold">Progress Dashboard</h2>
<p className="text-xs sm:text-sm text-muted-foreground">
Tracking the journey to freedom
</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-4">
<ProgressBar metric={progress.retirement} />
<ProgressBar metric={progress.iosPortfolio} />
<ProgressBar metric={progress.sideHustleRevenue} />
<ProgressBar metric={progress.travelFund} />
</div>
</section>
{/* Two Column Layout: Milestones & Next Steps */}
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6 sm:gap-8">
{/* Milestones Timeline */}
<section>
<div className="flex items-center gap-3 mb-4 sm:mb-6">
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 flex items-center justify-center shrink-0">
<CheckCircle2 className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-bold">Milestones Achieved</h2>
<p className="text-xs sm:text-sm text-muted-foreground">
{progress.milestones.length} completed
</p>
</div>
</div>
<MilestoneTimeline milestones={progress.milestones} />
</section>
{/* Next Steps */}
<section>
<div className="flex items-center gap-3 mb-4 sm:mb-6">
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-red-500 to-pink-600 flex items-center justify-center shrink-0">
<Flame className="w-4 h-4 sm:w-5 sm:h-5 text-white" />
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-bold">Next Steps</h2>
<p className="text-xs sm:text-sm text-muted-foreground">
High priority mission tasks
</p>
</div>
<Button variant="outline" size="sm" asChild className="shrink-0">
<a href={siteUrls.ganttBoard} target="_blank" rel="noopener noreferrer">
View All <ArrowRight className="w-4 h-4 ml-1" />
</a>
</Button>
</div>
<NextSteps steps={progress.nextSteps} />
</section>
</div>
{/* Core Values */}
<section>
<h2 className="text-lg sm:text-xl font-bold mb-4">Core Values</h2>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
{[
{ name: "Family", icon: Heart, color: "from-pink-500 to-rose-500", desc: "Priority #1" },
{ name: "Health", icon: Target, color: "from-green-500 to-emerald-500", desc: "Stay strong" },
{ name: "Fun", icon: Rocket, color: "from-blue-500 to-cyan-500", desc: "Enjoy the ride" },
{ name: "Adventure", icon: Plane, color: "from-purple-500 to-violet-500", desc: "Explore more" },
].map((value) => (
<Card key={value.name} className="group hover:shadow-md hover:scale-105 transition-all">
<CardContent className="p-3 sm:p-4 text-center">
<div className={`w-10 h-10 sm:w-12 sm:h-12 rounded-full bg-gradient-to-br ${value.color} flex items-center justify-center mx-auto mb-2 sm:mb-3 shadow-lg group-hover:shadow-xl transition-shadow`}>
<value.icon className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
</div>
<h3 className="font-bold text-sm sm:text-base">{value.name}</h3>
<p className="text-xs text-muted-foreground">{value.desc}</p>
</CardContent>
</Card>
))}
</div>
</section>
{/* Quote */}
<Card className="bg-gradient-to-r from-slate-800 to-slate-900 border-0 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-blue-500/10 via-transparent to-transparent" />
<CardContent className="p-6 sm:p-8 text-center relative z-10">
<p className="text-lg sm:text-xl md:text-2xl italic text-slate-300 font-light">
&ldquo;53 is just the start of the best chapter.&rdquo;
</p>
<p className="text-xs sm:text-sm text-slate-500 mt-3"> The Mission</p>
</CardContent>
</Card>
</div>
</DashboardLayout>
);
}