AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning

This commit is contained in:
Krilly
2026-03-04 13:29:22 +00:00
parent 29a98137a7
commit 57dd294675
13706 changed files with 2114953 additions and 237629 deletions

View File

@@ -0,0 +1,62 @@
# Treatment Milestone Tracker Implementation
## Database Schema Changes
Add to prisma/schema.prisma:
```prisma
model TreatmentPlan {
id String @id @default(cuid())
workspaceId String @unique
title String // e.g., "Grace's Chemotherapy Plan"
totalCycles Int
currentCycle Int @default(0)
startDate DateTime?
estimatedEnd DateTime?
status String @default("ACTIVE") // ACTIVE, PAUSED, COMPLETED
cycleType String @default("WEEKLY") // WEEKLY, BIWEEKLY, MONTHLY, CUSTOM
cycleDays Int @default(7) // Days between cycles
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdById String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation(fields: [createdById], references: [id])
milestones TreatmentMilestone[]
}
model TreatmentMilestone {
id String @id @default(cuid())
planId String
cycleNumber Int // Which cycle this milestone represents
date DateTime // When it happened (or estimated)
status String @default("UPCOMING") // UPCOMING, COMPLETED, SKIPPED
notes String? // Personal reflection
sideEffects String? // What was experienced
celebratedAt DateTime? // When we showed the celebration
createdAt DateTime @default(now())
plan TreatmentPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
}
```
## API Routes
### GET /api/workspaces/[id]/treatment-plan
Get the treatment plan for a workspace
### POST /api/workspaces/[id]/treatment-plan
Create or update treatment plan
### POST /api/workspaces/[id]/treatment-plan/milestones/[cycleNumber]/complete
Mark a milestone as completed
### GET /api/workspaces/[id]/treatment-plan/progress
Get progress stats
## Components
- TreatmentProgress - Main progress widget
- MilestoneCelebration - Celebration modal
- TreatmentCalendar - Timeline view
- CycleDetailView - Individual cycle details

View File

@@ -0,0 +1,111 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/db/prisma'
import { getCurrentUser } from '@/lib/auth'
import { canAccessWorkspace } from '@/lib/db/workspace-access'
// POST /api/workspaces/[id]/treatment-plan/complete-cycle
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const workspaceId = params.id
const access = await canAccessWorkspace(user.id, workspaceId)
if (!access || access.role === 'VIEWER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
// Get current plan
const plan = await prisma.treatmentPlan.findUnique({
where: { workspaceId },
include: { milestones: true }
})
if (!plan) {
return NextResponse.json(
{ error: 'No treatment plan found' },
{ status: 404 }
)
}
if (plan.status !== 'ACTIVE') {
return NextResponse.json(
{ error: 'Treatment plan is not active' },
{ status: 400 }
)
}
if (plan.currentCycle >= plan.totalCycles) {
return NextResponse.json(
{ error: 'All cycles already completed' },
{ status: 400 }
)
}
const nextCycle = plan.currentCycle + 1
// Update milestone
const milestone = plan.milestones.find(m => m.cycleNumber === nextCycle)
if (milestone) {
await prisma.treatmentMilestone.update({
where: { id: milestone.id },
data: {
status: 'COMPLETED',
date: new Date()
}
})
}
// Update plan
const isComplete = nextCycle >= plan.totalCycles
const updatedPlan = await prisma.treatmentPlan.update({
where: { workspaceId },
data: {
currentCycle: nextCycle,
status: isComplete ? 'COMPLETED' : 'ACTIVE'
},
include: {
milestones: {
orderBy: { cycleNumber: 'asc' }
}
}
})
// Log audit
await prisma.auditLog.create({
data: {
workspaceId,
userId: user.id,
action: 'COMPLETE_CYCLE',
entityType: 'TREATMENT_PLAN',
entityId: plan.id,
details: {
cycleCompleted: nextCycle,
totalCycles: plan.totalCycles,
isComplete
}
}
})
return NextResponse.json({
plan: updatedPlan,
milestone: milestone,
celebration: {
cycleNumber: nextCycle,
isHalfway: nextCycle === Math.floor(plan.totalCycles / 2),
isFinal: isComplete
}
})
} catch (error) {
console.error('Failed to complete cycle:', error)
return NextResponse.json(
{ error: 'Failed to complete cycle' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,134 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/db/prisma'
import { getCurrentUser } from '@/lib/auth'
import { canAccessWorkspace } from '@/lib/db/workspace-access'
import { addDays } from 'date-fns'
// GET /api/workspaces/[id]/treatment-plan
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const workspaceId = params.id
const access = await canAccessWorkspace(user.id, workspaceId)
if (!access) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const plan = await prisma.treatmentPlan.findUnique({
where: { workspaceId },
include: {
milestones: {
orderBy: { cycleNumber: 'asc' }
}
}
})
if (!plan) {
return NextResponse.json({ plan: null })
}
return NextResponse.json({ plan })
} catch (error) {
console.error('Failed to fetch treatment plan:', error)
return NextResponse.json(
{ error: 'Failed to fetch treatment plan' },
{ status: 500 }
)
}
}
// POST /api/workspaces/[id]/treatment-plan
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const workspaceId = params.id
const access = await canAccessWorkspace(user.id, workspaceId)
if (!access || access.role === 'VIEWER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json()
const { title, totalCycles, startDate, cycleType, cycleDays } = body
// Validate required fields
if (!title || !totalCycles || !startDate) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
)
}
// Calculate estimated end date
const start = new Date(startDate)
const estimatedEnd = addDays(start, cycleDays * totalCycles)
// Create or update treatment plan
const plan = await prisma.treatmentPlan.upsert({
where: { workspaceId },
create: {
workspaceId,
title,
totalCycles,
startDate: start,
estimatedEnd,
cycleType,
cycleDays,
createdById: user.id,
milestones: {
create: Array.from({ length: totalCycles }, (_, i) => ({
cycleNumber: i + 1,
date: addDays(start, cycleDays * i),
status: i === 0 ? 'UPCOMING' : 'UPCOMING'
}))
}
},
update: {
title,
totalCycles,
startDate: start,
estimatedEnd,
cycleType,
cycleDays,
},
include: {
milestones: {
orderBy: { cycleNumber: 'asc' }
}
}
})
// Log audit
await prisma.auditLog.create({
data: {
workspaceId,
userId: user.id,
action: 'CREATE',
entityType: 'TREATMENT_PLAN',
entityId: plan.id,
details: { title, totalCycles }
}
})
return NextResponse.json({ plan })
} catch (error) {
console.error('Failed to create treatment plan:', error)
return NextResponse.json(
{ error: 'Failed to create treatment plan' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,34 @@
'use client'
import { useRouter } from 'next/navigation'
import { ChevronLeft } from 'lucide-react'
import { Header, PageContainer } from '@/components/layout/header'
import { TreatmentPlanForm } from '@/components/treatment/TreatmentPlanForm'
import { useApp } from '../provider'
export default function NewTreatmentPlanPage() {
const router = useRouter()
const { currentWorkspace } = useApp()
return (
<>
<Header
title="Create Treatment Plan"
leftAction={{
icon: <ChevronLeft className="w-6 h-6" />,
label: 'Back',
onClick: () => router.push('/treatment')
}}
/>
<PageContainer className="pt-4">
<div className="mb-6">
<p className="text-secondary-500">
Set up your treatment schedule to track progress and celebrate milestones.
</p>
</div>
<TreatmentPlanForm workspaceId={currentWorkspace.id} />
</PageContainer>
</>
)
}

View File

@@ -0,0 +1,86 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { Plus, Target, ChevronLeft } from 'lucide-react'
import { Card, LoadingState, Button } from '@/components/ui'
import { Header, PageContainer } from '@/components/layout/header'
import { TreatmentProgress } from '@/components/treatment/TreatmentProgress'
import { useApp } from '../provider'
export default function TreatmentPage() {
const router = useRouter()
const { currentWorkspace } = useApp()
const [plan, setPlan] = useState(null)
const [loading, setLoading] = useState(true)
const fetchPlan = async () => {
try {
const response = await fetch(`/api/workspaces/${currentWorkspace.id}/treatment-plan`)
if (response.ok) {
const data = await response.json()
setPlan(data.plan)
}
} catch (err) {
console.error('Failed to fetch plan:', err)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchPlan()
}, [currentWorkspace.id])
if (loading) {
return (
<>
<Header title="Treatment Plan" />
<PageContainer>
<LoadingState message="Loading treatment plan..." />
</PageContainer>
</>
)
}
return (
<>
<Header
title="Treatment Plan"
leftAction={{
icon: <ChevronLeft className="w-6 h-6" />,
label: 'Back',
onClick: () => router.push('/today')
}}
/>
<PageContainer className="pt-4 space-y-6">
{!plan ? (
<Card variant="outline" className="text-center py-12">
<div className="w-16 h-16 rounded-full bg-primary-100 flex items-center justify-center mx-auto mb-4">
<Target className="w-8 h-8 text-primary-600" />
</div>
<h2 className="text-lg font-semibold text-secondary-900 mb-2">
No Treatment Plan Yet
</h2>
<p className="text-secondary-500 mb-6 max-w-xs mx-auto">
Create a treatment plan to track your progress through chemotherapy and celebrate milestones.
</p>
{currentWorkspace.role !== 'VIEWER' && (
<Button href="/treatment/new">
<Plus className="w-4 h-4 mr-2" />
Create Treatment Plan
</Button>
)}
</Card>
) : (
<TreatmentProgress
plan={plan}
workspaceId={currentWorkspace.id}
onUpdate={fetchPlan}
/>
)}
</PageContainer>
</>
)
}

View File

@@ -0,0 +1,228 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { Calendar, Clock, Target } from 'lucide-react'
import { Card, Button, showToast } from '@/components/ui'
interface TreatmentPlanFormProps {
workspaceId: string
initialData?: {
title: string
totalCycles: number
startDate: string
cycleType: string
cycleDays: number
}
}
const CYCLE_TYPES = [
{ value: 'WEEKLY', label: 'Weekly', days: 7 },
{ value: 'BIWEEKLY', label: 'Every 2 weeks', days: 14 },
{ value: 'MONTHLY', label: 'Monthly', days: 30 },
{ value: 'CUSTOM', label: 'Custom', days: 0 },
]
export function TreatmentPlanForm({ workspaceId, initialData }: TreatmentPlanFormProps) {
const router = useRouter()
const [title, setTitle] = useState(initialData?.title || '')
const [totalCycles, setTotalCycles] = useState(initialData?.totalCycles || 12)
const [startDate, setStartDate] = useState(initialData?.startDate || '')
const [cycleType, setCycleType] = useState(initialData?.cycleType || 'WEEKLY')
const [customDays, setCustomDays] = useState(initialData?.cycleDays || 14)
const [saving, setSaving] = useState(false)
const selectedCycleType = CYCLE_TYPES.find(t => t.value === cycleType)
const cycleDays = cycleType === 'CUSTOM' ? customDays : (selectedCycleType?.days || 7)
// Calculate estimated end date
const estimatedEndDate = startDate
? new Date(new Date(startDate).getTime() + (cycleDays * totalCycles * 24 * 60 * 60 * 1000))
: null
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!title.trim()) {
showToast('Please enter a treatment name', 'error')
return
}
if (!startDate) {
showToast('Please select a start date', 'error')
return
}
setSaving(true)
try {
const response = await fetch(`/api/workspaces/${workspaceId}/treatment-plan`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title.trim(),
totalCycles,
startDate,
cycleType,
cycleDays,
}),
})
if (!response.ok) throw new Error('Failed to create plan')
showToast('Treatment plan created!', 'success')
router.push('/treatment')
} catch {
showToast('Failed to create plan', 'error')
} finally {
setSaving(false)
}
}
return (
<Card>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Treatment Name */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
Treatment Name
</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="e.g., Grace's Chemotherapy Plan"
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
required
/>
</div>
{/* Total Cycles */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Target className="w-4 h-4 inline mr-1" />
Total Cycles/Sessions
</label>
<div className="flex items-center gap-4">
<input
type="range"
min="1"
max="50"
value={totalCycles}
onChange={(e) => setTotalCycles(parseInt(e.target.value))}
className="flex-1"
/>
<input
type="number"
min="1"
max="50"
value={totalCycles}
onChange={(e) => setTotalCycles(parseInt(e.target.value) || 1)}
className="w-20 px-3 py-2 border border-border rounded-lg text-center"
/>
</div>
<p className="text-xs text-secondary-500 mt-1">
Number of chemo sessions or treatment cycles
</p>
</div>
{/* Start Date */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Calendar className="w-4 h-4 inline mr-1" />
Start Date
</label>
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
required
/>
</div>
{/* Cycle Frequency */}
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
<Clock className="w-4 h-4 inline mr-1" />
How Often?
</label>
<div className="grid grid-cols-2 gap-2">
{CYCLE_TYPES.map((type) => (
<button
key={type.value}
type="button"
onClick={() => setCycleType(type.value)}
className={`p-3 rounded-lg border-2 text-left transition-all ${
cycleType === type.value
? 'border-primary-500 bg-primary-50'
: 'border-border hover:border-secondary-300'
}`}
>
<span className="font-medium text-secondary-900">{type.label}</span>
{type.value !== 'CUSTOM' && (
<span className="text-xs text-secondary-500 block">
Every {type.days} days
</span>
)}
</button>
))}
</div>
</div>
{/* Custom Days Input */}
{cycleType === 'CUSTOM' && (
<div>
<label className="block text-sm font-medium text-secondary-700 mb-2">
Days Between Cycles
</label>
<input
type="number"
min="1"
max="90"
value={customDays}
onChange={(e) => setCustomDays(parseInt(e.target.value) || 1)}
className="w-full px-3 py-2 border border-border rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-primary-200 focus:border-primary-500"
/>
</div>
)}
{/* Estimated End Date */}
{estimatedEndDate && (
<div className="p-4 bg-primary-50 rounded-lg">
<p className="text-sm text-secondary-600">
Estimated completion:
</p>
<p className="font-semibold text-primary-700">
{estimatedEndDate.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</p>
</div>
)}
{/* Submit */}
<div className="flex gap-3">
<Button
type="button"
variant="secondary"
href="/treatment"
fullWidth
>
Cancel
</Button>
<Button
type="submit"
loading={saving}
fullWidth
>
Create Plan
</Button>
</div>
</form>
</Card>
)
}

View File

@@ -0,0 +1,266 @@
'use client'
import { useState, useEffect } from 'react'
import { Trophy, Calendar, TrendingUp, ChevronRight, Flag, PartyPopper } from 'lucide-react'
import { format, addDays, differenceInDays } from 'date-fns'
import { Card, Button } from '@/components/ui'
import { showToast } from '@/components/ui'
interface Milestone {
id: string
cycleNumber: number
date: string
status: 'UPCOMING' | 'COMPLETED' | 'SKIPPED'
notes?: string
celebratedAt?: string
}
interface TreatmentPlan {
id: string
title: string
totalCycles: number
currentCycle: number
startDate: string | null
estimatedEnd: string | null
status: 'ACTIVE' | 'PAUSED' | 'COMPLETED'
cycleType: string
cycleDays: number
milestones: Milestone[]
}
interface TreatmentProgressProps {
plan: TreatmentPlan | null
workspaceId: string
onUpdate?: () => void
}
export function TreatmentProgress({ plan, workspaceId, onUpdate }: TreatmentProgressProps) {
const [showCelebration, setShowCelebration] = useState(false)
const [celebratingMilestone, setCelebratingMilestone] = useState<Milestone | null>(null)
useEffect(() => {
// Check for uncelebrated completed milestones
if (plan?.milestones) {
const uncelebrated = plan.milestones.find(
m => m.status === 'COMPLETED' && !m.celebratedAt
)
if (uncelebrated) {
setCelebratingMilestone(uncelebrated)
setShowCelebration(true)
// Mark as celebrated
markCelebrated(uncelebrated.id)
}
}
}, [plan])
const markCelebrated = async (milestoneId: string) => {
try {
await fetch(`/api/workspaces/${workspaceId}/treatment-plan/milestones/${milestoneId}/celebrate`, {
method: 'POST'
})
} catch (err) {
console.error('Failed to mark celebration:', err)
}
}
const handleCompleteCycle = async () => {
if (!plan) return
try {
const response = await fetch(`/api/workspaces/${workspaceId}/treatment-plan/complete-cycle`, {
method: 'POST'
})
if (!response.ok) throw new Error('Failed to complete cycle')
showToast('Cycle completed! Great job!', 'success')
onUpdate?.()
} catch {
showToast('Failed to complete cycle', 'error')
}
}
if (!plan) {
return (
<Card variant="outline" className="text-center py-8">
<Trophy className="w-12 h-12 text-secondary-300 mx-auto mb-3" />
<h3 className="font-semibold text-secondary-900 mb-1">No Treatment Plan</h3>
<p className="text-secondary-500 text-sm mb-4">
Set up a treatment plan to track your progress
</p>
<Button href={`/treatment/new`}>Create Plan</Button>
</Card>
)
}
const progress = (plan.currentCycle / plan.totalCycles) * 100
const daysRemaining = plan.estimatedEnd
? differenceInDays(new Date(plan.estimatedEnd), new Date())
: null
const milestones = plan.milestones || []
const completedCount = milestones.filter(m => m.status === 'COMPLETED').length
return (
<>
<Card className="overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-primary-500 to-primary-600 text-white p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-lg">{plan.title}</h3>
<p className="text-primary-100 text-sm">
Cycle {plan.currentCycle} of {plan.totalCycles}
</p>
</div>
<div className="w-14 h-14 rounded-full bg-white/20 flex items-center justify-center">
<Trophy className="w-7 h-7" />
</div>
</div>
</div>
{/* Progress Bar */}
<div className="p-4">
<div className="flex items-center justify-between text-sm mb-2">
<span className="text-secondary-600">{completedCount} completed</span>
<span className="font-semibold text-primary-600">{Math.round(progress)}%</span>
</div>
<div className="h-3 bg-secondary-100 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-primary-500 to-primary-400 transition-all duration-500"
style={{ width: `${progress}%` }}
/>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-4 mt-4 pt-4 border-t border-border">
<div className="text-center">
<p className="text-2xl font-bold text-secondary-900">{plan.currentCycle}</p>
<p className="text-xs text-secondary-500">Current</p>
</div>
<div className="text-center border-x border-border">
<p className="text-2xl font-bold text-secondary-900">{plan.totalCycles - plan.currentCycle}</p>
<p className="text-xs text-secondary-500">Remaining</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-secondary-900">
{daysRemaining !== null ? Math.max(0, daysRemaining) : '?'}
</p>
<p className="text-xs text-secondary-500">Days Left</p>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="px-4 pb-4 space-y-2">
{plan.status === 'ACTIVE' && plan.currentCycle < plan.totalCycles && (
<Button
onClick={handleCompleteCycle}
fullWidth
className="bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700"
>
<PartyPopper className="w-4 h-4 mr-2" />
Complete Cycle {plan.currentCycle + 1}
</Button>
)}
<Button
variant="secondary"
href="/treatment/details"
fullWidth
>
View Details
<ChevronRight className="w-4 h-4 ml-1" />
</Button>
</div>
</Card>
{/* Milestone Preview */}
{milestones.length > 0 && (
<Card className="mt-4">
<h4 className="font-semibold text-secondary-900 mb-3">Upcoming Milestones</h4>
<div className="space-y-2">
{milestones
.filter(m => m.status === 'UPCOMING')
.slice(0, 3)
.map(milestone => (
<div
key={milestone.id}
className="flex items-center gap-3 p-2 rounded-lg bg-secondary-50"
>
<div className="w-8 h-8 rounded-full bg-primary-100 flex items-center justify-center flex-shrink-0">
<Flag className="w-4 h-4 text-primary-600" />
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-secondary-900">
Cycle {milestone.cycleNumber}
</p>
<p className="text-xs text-secondary-500">
{format(new Date(milestone.date), 'MMM d, yyyy')}
</p>
</div>
</div>
))}
</div>
</Card>
)}
{/* Celebration Modal */}
{showCelebration && celebratingMilestone && (
<MilestoneCelebration
milestone={celebratingMilestone}
plan={plan}
onClose={() => setShowCelebration(false)}
/>
)}
</>
)
}
interface MilestoneCelebrationProps {
milestone: Milestone
plan: TreatmentPlan
onClose: () => void
}
function MilestoneCelebration({ milestone, plan, onClose }: MilestoneCelebrationProps) {
const messages = [
"You're doing amazing!",
"One step closer to the finish line!",
"Your strength is inspiring!",
"Keep going, you've got this!",
"Another milestone conquered!"
]
const randomMessage = messages[Math.floor(Math.random() * messages.length)]
const isHalfway = milestone.cycleNumber === Math.floor(plan.totalCycles / 2)
const isFinal = milestone.cycleNumber === plan.totalCycles
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl p-6 max-w-sm w-full text-center animate-in fade-in zoom-in duration-300">
<div className="w-20 h-20 rounded-full bg-gradient-to-r from-yellow-400 to-orange-500 flex items-center justify-center mx-auto mb-4 animate-bounce">
<PartyPopper className="w-10 h-10 text-white" />
</div>
<h2 className="text-2xl font-bold text-secondary-900 mb-2">
{isFinal ? '🎉 Treatment Complete!' : isHalfway ? '🌟 Halfway There!' : 'Milestone Reached!'}
</h2>
<p className="text-secondary-600 mb-2">
Cycle {milestone.cycleNumber} of {plan.totalCycles} completed!
</p>
<p className="text-primary-600 font-medium mb-6">
{isFinal ? "You've done it! What an incredible journey." : randomMessage}
</p>
<Button onClick={onClose} fullWidth>
Continue
</Button>
</div>
</div>
)
}