prjct-cli 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/CLAUDE.md +74 -211
- package/core/agentic/prompt-builder.ts +3 -7
- package/core/command-registry/optional-commands.ts +0 -20
- package/core/infrastructure/command-installer/command-installer.ts +8 -1
- package/core/infrastructure/command-installer/global-config.ts +31 -1
- package/core/infrastructure/command-installer/index.ts +1 -1
- package/core/infrastructure/setup.ts +3 -0
- package/package.json +3 -17
- package/templates/commands/done.md +57 -258
- package/templates/commands/now.md +72 -277
- package/templates/commands/ship.md +55 -261
- package/templates/commands/test.md +328 -21
- package/templates/global/CLAUDE.md +40 -205
- package/templates/global/docs/agents.md +88 -0
- package/templates/global/docs/architecture.md +103 -0
- package/templates/global/docs/commands.md +98 -0
- package/templates/global/docs/validation.md +95 -0
- package/templates/mcp-config.json +36 -0
- package/bin/dev.js +0 -216
- package/bin/serve.js +0 -361
- package/packages/web/README.md +0 -36
- package/packages/web/app/api/claude/sessions/route.ts +0 -44
- package/packages/web/app/api/claude/status/route.ts +0 -34
- package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
- package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
- package/packages/web/app/api/projects/[id]/route.ts +0 -29
- package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
- package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
- package/packages/web/app/api/projects/route.ts +0 -16
- package/packages/web/app/api/sessions/current/route.ts +0 -132
- package/packages/web/app/api/sessions/history/route.ts +0 -204
- package/packages/web/app/error.tsx +0 -34
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +0 -198
- package/packages/web/app/layout.tsx +0 -53
- package/packages/web/app/loading.tsx +0 -7
- package/packages/web/app/not-found.tsx +0 -25
- package/packages/web/app/page.tsx +0 -12
- package/packages/web/app/project/[id]/code/layout.tsx +0 -18
- package/packages/web/app/project/[id]/code/page.tsx +0 -408
- package/packages/web/app/project/[id]/error.tsx +0 -41
- package/packages/web/app/project/[id]/loading.tsx +0 -9
- package/packages/web/app/project/[id]/not-found.tsx +0 -27
- package/packages/web/app/project/[id]/page.tsx +0 -384
- package/packages/web/app/project/[id]/reports/page.tsx +0 -59
- package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
- package/packages/web/app/sessions/page.tsx +0 -165
- package/packages/web/app/settings/page.tsx +0 -151
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
- package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
- package/packages/web/components/ActivityTimeline/index.ts +0 -2
- package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
- package/packages/web/components/AgentsCard/index.ts +0 -2
- package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
- package/packages/web/components/AppSidebar/index.ts +0 -1
- package/packages/web/components/BackLink/BackLink.tsx +0 -18
- package/packages/web/components/BackLink/BackLink.types.ts +0 -5
- package/packages/web/components/BackLink/index.ts +0 -2
- package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
- package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
- package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
- package/packages/web/components/BentoCard/index.ts +0 -2
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
- package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
- package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
- package/packages/web/components/BentoGrid/index.ts +0 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
- package/packages/web/components/BlockersCard/index.ts +0 -2
- package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
- package/packages/web/components/CommandBar/index.ts +0 -1
- package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
- package/packages/web/components/CommandButton/index.ts +0 -1
- package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
- package/packages/web/components/ConnectionStatus/index.ts +0 -1
- package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
- package/packages/web/components/DashboardContent/index.ts +0 -1
- package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
- package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
- package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
- package/packages/web/components/DateGroup/index.ts +0 -2
- package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
- package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
- package/packages/web/components/EmptyState/index.ts +0 -2
- package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
- package/packages/web/components/EventRow/EventRow.tsx +0 -49
- package/packages/web/components/EventRow/EventRow.types.ts +0 -7
- package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
- package/packages/web/components/EventRow/index.ts +0 -2
- package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
- package/packages/web/components/ExpandButton/index.ts +0 -2
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
- package/packages/web/components/HealthGradientBackground/index.ts +0 -2
- package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
- package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
- package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
- package/packages/web/components/HeroSection/hooks/index.ts +0 -2
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
- package/packages/web/components/HeroSection/index.ts +0 -2
- package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
- package/packages/web/components/IdeasCard/index.ts +0 -2
- package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
- package/packages/web/components/InsightMessage/index.ts +0 -2
- package/packages/web/components/Logo/Logo.tsx +0 -65
- package/packages/web/components/Logo/index.ts +0 -1
- package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
- package/packages/web/components/MarkdownContent/index.ts +0 -1
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
- package/packages/web/components/MasonryGrid/index.ts +0 -1
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
- package/packages/web/components/MomentumWidget/index.ts +0 -2
- package/packages/web/components/NowCard/NowCard.tsx +0 -118
- package/packages/web/components/NowCard/NowCard.types.ts +0 -16
- package/packages/web/components/NowCard/index.ts +0 -2
- package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
- package/packages/web/components/PageHeader/index.ts +0 -1
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
- package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
- package/packages/web/components/ProgressRing/index.ts +0 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
- package/packages/web/components/ProjectAvatar/index.ts +0 -1
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
- package/packages/web/components/ProjectColorDot/index.ts +0 -1
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
- package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
- package/packages/web/components/Providers/Providers.tsx +0 -48
- package/packages/web/components/Providers/index.ts +0 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
- package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
- package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
- package/packages/web/components/QueueCard/index.ts +0 -2
- package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
- package/packages/web/components/RecoverCard/index.ts +0 -2
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
- package/packages/web/components/RoadmapCard/index.ts +0 -2
- package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
- package/packages/web/components/ShipsCard/index.ts +0 -2
- package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
- package/packages/web/components/SparklineChart/index.ts +0 -2
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
- package/packages/web/components/StatsMasonry/index.ts +0 -1
- package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
- package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
- package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
- package/packages/web/components/StreakCard/index.ts +0 -2
- package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
- package/packages/web/components/TasksCounter/index.ts +0 -2
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
- package/packages/web/components/TechStackBadges/index.ts +0 -1
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
- package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
- package/packages/web/components/TerminalDock/index.ts +0 -2
- package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
- package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
- package/packages/web/components/TerminalTabs/index.ts +0 -1
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
- package/packages/web/components/VelocityBadge/index.ts +0 -2
- package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
- package/packages/web/components/VelocityCard/index.ts +0 -2
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
- package/packages/web/components/WeeklyReports/index.ts +0 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
- package/packages/web/components/WeeklySparkline/index.ts +0 -2
- package/packages/web/components/charts/SessionsChart.tsx +0 -175
- package/packages/web/components/ui/alert-dialog.tsx +0 -157
- package/packages/web/components/ui/badge.tsx +0 -46
- package/packages/web/components/ui/button.tsx +0 -60
- package/packages/web/components/ui/card.tsx +0 -92
- package/packages/web/components/ui/chart.tsx +0 -385
- package/packages/web/components/ui/dialog.tsx +0 -143
- package/packages/web/components/ui/drawer.tsx +0 -135
- package/packages/web/components/ui/dropdown-menu.tsx +0 -257
- package/packages/web/components/ui/input.tsx +0 -21
- package/packages/web/components/ui/scroll-area.tsx +0 -58
- package/packages/web/components/ui/select.tsx +0 -187
- package/packages/web/components/ui/sheet.tsx +0 -139
- package/packages/web/components/ui/tabs.tsx +0 -66
- package/packages/web/components/ui/tooltip.tsx +0 -61
- package/packages/web/components.json +0 -22
- package/packages/web/context/GlobalTerminalContext.tsx +0 -538
- package/packages/web/context/TerminalContext.tsx +0 -45
- package/packages/web/context/TerminalTabsContext.tsx +0 -181
- package/packages/web/eslint.config.mjs +0 -18
- package/packages/web/hooks/useClaudeTerminal.ts +0 -425
- package/packages/web/hooks/useProjectStats.ts +0 -93
- package/packages/web/hooks/useProjects.ts +0 -73
- package/packages/web/lib/actions/projects.ts +0 -15
- package/packages/web/lib/commands.ts +0 -81
- package/packages/web/lib/format.ts +0 -23
- package/packages/web/lib/generate-week-report.ts +0 -285
- package/packages/web/lib/parse-prjct-files.ts +0 -1123
- package/packages/web/lib/project-colors.ts +0 -58
- package/packages/web/lib/projects.ts +0 -506
- package/packages/web/lib/pty.ts +0 -101
- package/packages/web/lib/query-config.ts +0 -44
- package/packages/web/lib/services/index.ts +0 -9
- package/packages/web/lib/services/projects.server.ts +0 -66
- package/packages/web/lib/services/stats.server.ts +0 -562
- package/packages/web/lib/unified-loader.ts +0 -396
- package/packages/web/lib/utils.ts +0 -6
- package/packages/web/next-env.d.ts +0 -6
- package/packages/web/next.config.ts +0 -7
- package/packages/web/package.json +0 -57
- package/packages/web/postcss.config.mjs +0 -7
- package/packages/web/public/file.svg +0 -1
- package/packages/web/public/globe.svg +0 -1
- package/packages/web/public/next.svg +0 -1
- package/packages/web/public/vercel.svg +0 -1
- package/packages/web/public/window.svg +0 -1
- package/packages/web/server.ts +0 -312
- package/packages/web/tsconfig.json +0 -34
- package/templates/commands/serve.md +0 -121
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import Link from 'next/link'
|
|
4
|
-
import { Play, FileText, Flame } from 'lucide-react'
|
|
5
|
-
import { ProgressRing } from '@/components/ProgressRing'
|
|
6
|
-
import { TasksCounter } from '@/components/TasksCounter'
|
|
7
|
-
import { InsightMessage } from '@/components/InsightMessage'
|
|
8
|
-
import { WeeklySparkline } from '@/components/WeeklySparkline'
|
|
9
|
-
import { HealthGradientBackground } from '@/components/HealthGradientBackground'
|
|
10
|
-
import { useCountUp, useWeeklyActivity } from './hooks'
|
|
11
|
-
import { getCompletionColor } from './HeroSection.utils'
|
|
12
|
-
import type { HeroProps } from './HeroSection.types'
|
|
13
|
-
|
|
14
|
-
export function HeroSection({
|
|
15
|
-
projectId,
|
|
16
|
-
projectName,
|
|
17
|
-
projectVersion,
|
|
18
|
-
totalShips,
|
|
19
|
-
completionRate,
|
|
20
|
-
streak,
|
|
21
|
-
insightMessage,
|
|
22
|
-
timeline = [],
|
|
23
|
-
}: HeroProps) {
|
|
24
|
-
const animatedCount = useCountUp(totalShips)
|
|
25
|
-
const weeklyData = useWeeklyActivity(timeline)
|
|
26
|
-
const completionColor = getCompletionColor(completionRate)
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="relative mb-6 md:mb-8">
|
|
30
|
-
<HealthGradientBackground score={completionRate} />
|
|
31
|
-
|
|
32
|
-
<div className="relative z-10 flex items-center justify-between my-16">
|
|
33
|
-
<div className="flex items-baseline gap-3">
|
|
34
|
-
<h1 className="text-5xl md:text-6xl font-bold tracking-tight">{projectName}</h1>
|
|
35
|
-
{projectVersion && (
|
|
36
|
-
<span className="text-lg md:text-xl font-mono text-muted-foreground">v{projectVersion}</span>
|
|
37
|
-
)}
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div className="flex items-center gap-3">
|
|
41
|
-
{/* Reports Link */}
|
|
42
|
-
<Link
|
|
43
|
-
href={`/project/${projectId}/reports`}
|
|
44
|
-
className="inline-flex items-center gap-2 px-4 py-3 bg-muted hover:bg-muted/80 text-foreground font-medium rounded-xl transition-all duration-200 hover:scale-105"
|
|
45
|
-
>
|
|
46
|
-
<FileText className="h-5 w-5" />
|
|
47
|
-
<span>Reports</span>
|
|
48
|
-
</Link>
|
|
49
|
-
|
|
50
|
-
{/* Start Working CTA */}
|
|
51
|
-
<Link
|
|
52
|
-
href={`/project/${projectId}/code`}
|
|
53
|
-
className="group relative inline-flex items-center gap-2 px-6 py-3 bg-emerald-500 hover:bg-emerald-600 text-white font-semibold rounded-xl shadow-lg shadow-emerald-500/25 hover:shadow-emerald-500/40 transition-all duration-200 hover:scale-105"
|
|
54
|
-
>
|
|
55
|
-
<Play className="h-5 w-5 fill-current" />
|
|
56
|
-
<span>Start Working</span>
|
|
57
|
-
<div className="absolute inset-0 rounded-xl bg-gradient-to-r from-emerald-400 to-emerald-600 opacity-0 group-hover:opacity-100 transition-opacity -z-10" />
|
|
58
|
-
</Link>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div className="relative flex flex-col md:flex-row md:items-start md:justify-between gap-4 md:gap-0">
|
|
63
|
-
<div className="flex items-center md:items-start gap-4 md:gap-6">
|
|
64
|
-
<ProgressRing
|
|
65
|
-
value={completionRate}
|
|
66
|
-
size="xl"
|
|
67
|
-
accentColor={completionColor}
|
|
68
|
-
className="shrink-0"
|
|
69
|
-
/>
|
|
70
|
-
|
|
71
|
-
<div className="flex-1 min-w-0">
|
|
72
|
-
<TasksCounter count={animatedCount} />
|
|
73
|
-
{/* Streak badge */}
|
|
74
|
-
{streak > 0 && (
|
|
75
|
-
<div className="flex items-center gap-1.5 mt-1">
|
|
76
|
-
<Flame className="h-4 w-4 text-muted-foreground" />
|
|
77
|
-
<span className="text-sm font-medium text-muted-foreground">
|
|
78
|
-
{streak} day streak
|
|
79
|
-
</span>
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
<InsightMessage message={insightMessage} />
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<div className="flex flex-col items-start md:items-end gap-3 md:gap-4">
|
|
87
|
-
<WeeklySparkline data={weeklyData} size="lg" />
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { TimelineEvent } from '@/lib/parse-prjct-files'
|
|
2
|
-
|
|
3
|
-
export type { TimelineEvent }
|
|
4
|
-
|
|
5
|
-
export interface HeroProps {
|
|
6
|
-
projectId: string
|
|
7
|
-
projectName: string
|
|
8
|
-
projectVersion?: string | null
|
|
9
|
-
totalShips: number
|
|
10
|
-
completionRate: number
|
|
11
|
-
streak: number
|
|
12
|
-
insightMessage: string
|
|
13
|
-
timeline?: TimelineEvent[]
|
|
14
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type AccentColor = 'default' | 'success' | 'warning' | 'destructive'
|
|
2
|
-
|
|
3
|
-
// Completion rate color: traffic light based on project progress
|
|
4
|
-
// Green >= 75% (almost done!)
|
|
5
|
-
// Yellow 25-74% (good progress)
|
|
6
|
-
// Red < 25% (just getting started)
|
|
7
|
-
export function getCompletionColor(rate: number): AccentColor {
|
|
8
|
-
if (rate >= 75) return 'success'
|
|
9
|
-
if (rate >= 25) return 'warning'
|
|
10
|
-
return 'destructive'
|
|
11
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useState } from 'react'
|
|
4
|
-
|
|
5
|
-
export function useCountUp(target: number, duration: number = 800): number {
|
|
6
|
-
const [count, setCount] = useState(0)
|
|
7
|
-
const prevTargetRef = useRef(target)
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
if (target === 0) {
|
|
11
|
-
// Only reset if target changed to 0 (not if it was already 0)
|
|
12
|
-
if (prevTargetRef.current !== 0) {
|
|
13
|
-
// Defer state update to avoid synchronous setState in effect
|
|
14
|
-
const timeoutId = setTimeout(() => setCount(0), 0)
|
|
15
|
-
prevTargetRef.current = target
|
|
16
|
-
return () => clearTimeout(timeoutId)
|
|
17
|
-
}
|
|
18
|
-
prevTargetRef.current = target
|
|
19
|
-
return
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
prevTargetRef.current = target
|
|
23
|
-
|
|
24
|
-
const startTime = performance.now()
|
|
25
|
-
|
|
26
|
-
const animate = (currentTime: number) => {
|
|
27
|
-
const elapsed = currentTime - startTime
|
|
28
|
-
const progress = Math.min(elapsed / duration, 1)
|
|
29
|
-
const easeOut = 1 - Math.pow(1 - progress, 3)
|
|
30
|
-
const current = Math.floor(easeOut * target)
|
|
31
|
-
|
|
32
|
-
setCount(current)
|
|
33
|
-
|
|
34
|
-
if (progress < 1) {
|
|
35
|
-
requestAnimationFrame(animate)
|
|
36
|
-
} else {
|
|
37
|
-
setCount(target)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
requestAnimationFrame(animate)
|
|
42
|
-
}, [target, duration])
|
|
43
|
-
|
|
44
|
-
return count
|
|
45
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useMemo } from 'react'
|
|
4
|
-
import type { TimelineEvent } from '../HeroSection.types'
|
|
5
|
-
|
|
6
|
-
export function useWeeklyActivity(timeline: TimelineEvent[]): number[] {
|
|
7
|
-
return useMemo(() => {
|
|
8
|
-
const today = new Date()
|
|
9
|
-
|
|
10
|
-
return Array.from({ length: 7 }, (_, i) => {
|
|
11
|
-
const date = new Date(today)
|
|
12
|
-
date.setDate(date.getDate() - (6 - i))
|
|
13
|
-
const dateStr = date.toISOString().split('T')[0]
|
|
14
|
-
|
|
15
|
-
return timeline.filter(e => e.ts?.startsWith(dateStr)).length
|
|
16
|
-
})
|
|
17
|
-
}, [timeline])
|
|
18
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react'
|
|
4
|
-
import Link from 'next/link'
|
|
5
|
-
import { EmptyState } from '@/components/EmptyState'
|
|
6
|
-
import { ExpandButton } from '@/components/ExpandButton'
|
|
7
|
-
import { Lightbulb, Sparkles, Rocket, X } from 'lucide-react'
|
|
8
|
-
import { cn } from '@/lib/utils'
|
|
9
|
-
import type { IdeasCardProps } from './IdeasCard.types'
|
|
10
|
-
|
|
11
|
-
const COLLAPSED_LIMIT = 8
|
|
12
|
-
const EXPANDED_LIMIT = 30
|
|
13
|
-
|
|
14
|
-
export function IdeasCard({ ideas, codeHref, className }: IdeasCardProps) {
|
|
15
|
-
const [expanded, setExpanded] = useState(false)
|
|
16
|
-
const limit = expanded ? EXPANDED_LIMIT : COLLAPSED_LIMIT
|
|
17
|
-
const displayIdeas = ideas.slice(0, limit)
|
|
18
|
-
const highImpactCount = ideas.filter(i => i.impact === 'HIGH').length
|
|
19
|
-
const hasMore = ideas.length > limit
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div className={cn(
|
|
23
|
-
'relative overflow-hidden rounded-xl border bg-card p-4',
|
|
24
|
-
className
|
|
25
|
-
)}>
|
|
26
|
-
<div className="flex items-center justify-between mb-3">
|
|
27
|
-
<div className="flex items-center gap-2">
|
|
28
|
-
<Lightbulb className="h-4 w-4 text-muted-foreground" />
|
|
29
|
-
<span className="text-xs font-bold uppercase tracking-[0.15em] text-muted-foreground">
|
|
30
|
-
Ideas
|
|
31
|
-
</span>
|
|
32
|
-
</div>
|
|
33
|
-
<div className="flex items-center gap-2">
|
|
34
|
-
{highImpactCount > 0 && (
|
|
35
|
-
<span className="text-xs font-medium text-muted-foreground bg-muted px-1.5 py-0.5 rounded">
|
|
36
|
-
{highImpactCount} high impact
|
|
37
|
-
</span>
|
|
38
|
-
)}
|
|
39
|
-
<span className="text-xs font-medium text-muted-foreground tabular-nums">
|
|
40
|
-
{ideas.length}
|
|
41
|
-
</span>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
{ideas.length === 0 ? (
|
|
46
|
-
<EmptyState
|
|
47
|
-
icon={Lightbulb}
|
|
48
|
-
title="No ideas yet"
|
|
49
|
-
command="/p:idea"
|
|
50
|
-
href={codeHref}
|
|
51
|
-
compact
|
|
52
|
-
/>
|
|
53
|
-
) : (
|
|
54
|
-
<div className="space-y-1">
|
|
55
|
-
{displayIdeas.map((idea, i) => {
|
|
56
|
-
const featureHref = codeHref
|
|
57
|
-
? `${codeHref}?cmd=${encodeURIComponent(`p. feature "${idea.title}"`)}`
|
|
58
|
-
: undefined
|
|
59
|
-
const deleteHref = codeHref
|
|
60
|
-
? `${codeHref}?cmd=${encodeURIComponent(`p. idea remove ${i + 1}`)}`
|
|
61
|
-
: undefined
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div
|
|
65
|
-
key={i}
|
|
66
|
-
className="flex items-start gap-2 py-1.5 group hover:bg-muted/50 rounded px-1 -mx-1"
|
|
67
|
-
>
|
|
68
|
-
{idea.impact === 'HIGH' ? (
|
|
69
|
-
<Sparkles className="h-3 w-3 mt-0.5 shrink-0 text-foreground" />
|
|
70
|
-
) : (
|
|
71
|
-
<Lightbulb className="h-3 w-3 mt-0.5 shrink-0 text-muted-foreground" />
|
|
72
|
-
)}
|
|
73
|
-
<p className={cn(
|
|
74
|
-
'text-sm flex-1 min-w-0',
|
|
75
|
-
idea.impact === 'HIGH' && 'font-medium'
|
|
76
|
-
)}>
|
|
77
|
-
{idea.title}
|
|
78
|
-
</p>
|
|
79
|
-
{/* Always visible action buttons */}
|
|
80
|
-
<div className="flex items-center gap-1 shrink-0">
|
|
81
|
-
{featureHref && (
|
|
82
|
-
<Link
|
|
83
|
-
href={featureHref}
|
|
84
|
-
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
85
|
-
title="Convert to feature"
|
|
86
|
-
>
|
|
87
|
-
<Rocket className="h-3.5 w-3.5" />
|
|
88
|
-
</Link>
|
|
89
|
-
)}
|
|
90
|
-
{deleteHref && (
|
|
91
|
-
<Link
|
|
92
|
-
href={deleteHref}
|
|
93
|
-
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
94
|
-
title="Delete idea"
|
|
95
|
-
>
|
|
96
|
-
<X className="h-3.5 w-3.5" />
|
|
97
|
-
</Link>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
)
|
|
102
|
-
})}
|
|
103
|
-
{(hasMore || expanded) && ideas.length > COLLAPSED_LIMIT && (
|
|
104
|
-
<ExpandButton
|
|
105
|
-
expanded={expanded}
|
|
106
|
-
totalCount={ideas.length}
|
|
107
|
-
collapsedLimit={COLLAPSED_LIMIT}
|
|
108
|
-
onToggle={() => setExpanded(!expanded)}
|
|
109
|
-
/>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
</div>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { cn } from '@/lib/utils'
|
|
2
|
-
|
|
3
|
-
interface LogoProps {
|
|
4
|
-
showText?: boolean
|
|
5
|
-
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
6
|
-
rounded?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Logo({ showText = true, size = 'md', rounded = false }: LogoProps) {
|
|
10
|
-
const logoSize = {
|
|
11
|
-
xs: 'size-7',
|
|
12
|
-
sm: 'size-10',
|
|
13
|
-
md: 'size-12',
|
|
14
|
-
lg: 'size-14',
|
|
15
|
-
xl: 'size-16',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const textSize = {
|
|
19
|
-
xs: 'text-sm',
|
|
20
|
-
sm: 'text-lg',
|
|
21
|
-
md: 'text-lg',
|
|
22
|
-
lg: 'text-2xl',
|
|
23
|
-
xl: 'text-3xl',
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const containerTextSize = {
|
|
27
|
-
xs: 'text-sm',
|
|
28
|
-
sm: 'text-base',
|
|
29
|
-
md: 'text-xl',
|
|
30
|
-
lg: 'text-2xl',
|
|
31
|
-
xl: 'text-3xl',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const brandTextSize = {
|
|
35
|
-
xs: 'text-xs',
|
|
36
|
-
sm: 'text-sm',
|
|
37
|
-
md: 'text-lg',
|
|
38
|
-
lg: 'text-xl',
|
|
39
|
-
xl: 'text-2xl',
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
className={cn('flex items-center gap-2', containerTextSize[size])}
|
|
45
|
-
data-testid="prjct-logo"
|
|
46
|
-
>
|
|
47
|
-
<div className="relative isolate overflow-visible">
|
|
48
|
-
<div className={cn("fancy-border pointer-events-none", rounded && "rounded-full")}></div>
|
|
49
|
-
<div
|
|
50
|
-
className={cn(
|
|
51
|
-
'relative z-10 flex items-center justify-center border border-border bg-foreground text-background shadow-sm',
|
|
52
|
-
rounded ? 'rounded-full' : 'rounded-lg',
|
|
53
|
-
logoSize[size]
|
|
54
|
-
)}
|
|
55
|
-
data-testid="prjct-logo-icon"
|
|
56
|
-
>
|
|
57
|
-
<p className={cn('mb-0.5 inline-block font-bold leading-none', textSize[size])}>p/</p>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
{showText && (
|
|
61
|
-
<span className={cn(brandTextSize[size], 'font-bold text-foreground')}>prjct</span>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Logo } from './Logo'
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import ReactMarkdown from 'react-markdown'
|
|
4
|
-
import remarkGfm from 'remark-gfm'
|
|
5
|
-
import { cn } from '@/lib/utils'
|
|
6
|
-
|
|
7
|
-
interface MarkdownContentProps {
|
|
8
|
-
content: string
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
13
|
-
if (!content || !content.trim()) {
|
|
14
|
-
return (
|
|
15
|
-
<p className="text-sm text-muted-foreground italic">No content</p>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className={cn('prose prose-sm dark:prose-invert max-w-none', className)}>
|
|
21
|
-
<ReactMarkdown
|
|
22
|
-
remarkPlugins={[remarkGfm]}
|
|
23
|
-
components={{
|
|
24
|
-
// Headers
|
|
25
|
-
h1: ({ children }) => (
|
|
26
|
-
<h1 className="text-xl font-bold mt-4 mb-2 first:mt-0">{children}</h1>
|
|
27
|
-
),
|
|
28
|
-
h2: ({ children }) => (
|
|
29
|
-
<h2 className="text-lg font-semibold mt-4 mb-2 border-b pb-1">{children}</h2>
|
|
30
|
-
),
|
|
31
|
-
h3: ({ children }) => (
|
|
32
|
-
<h3 className="text-base font-medium mt-3 mb-1">{children}</h3>
|
|
33
|
-
),
|
|
34
|
-
// Lists
|
|
35
|
-
ul: ({ children }) => (
|
|
36
|
-
<ul className="list-disc list-inside space-y-1 my-2">{children}</ul>
|
|
37
|
-
),
|
|
38
|
-
ol: ({ children }) => (
|
|
39
|
-
<ol className="list-decimal list-inside space-y-1 my-2">{children}</ol>
|
|
40
|
-
),
|
|
41
|
-
li: ({ children }) => (
|
|
42
|
-
<li className="text-sm">{children}</li>
|
|
43
|
-
),
|
|
44
|
-
// Checkbox lists (GFM)
|
|
45
|
-
input: ({ checked, ...props }) => (
|
|
46
|
-
<input
|
|
47
|
-
type="checkbox"
|
|
48
|
-
checked={checked}
|
|
49
|
-
readOnly
|
|
50
|
-
className="mr-2 rounded"
|
|
51
|
-
{...props}
|
|
52
|
-
/>
|
|
53
|
-
),
|
|
54
|
-
// Paragraphs
|
|
55
|
-
p: ({ children }) => (
|
|
56
|
-
<p className="text-sm my-1.5 leading-relaxed">{children}</p>
|
|
57
|
-
),
|
|
58
|
-
// Code
|
|
59
|
-
code: ({ className, children, ...props }) => {
|
|
60
|
-
const isInline = !className
|
|
61
|
-
if (isInline) {
|
|
62
|
-
return (
|
|
63
|
-
<code className="bg-muted px-1 py-0.5 rounded text-xs font-mono" {...props}>
|
|
64
|
-
{children}
|
|
65
|
-
</code>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
return (
|
|
69
|
-
<code className={cn('block bg-muted p-2 rounded text-xs font-mono overflow-x-auto', className)} {...props}>
|
|
70
|
-
{children}
|
|
71
|
-
</code>
|
|
72
|
-
)
|
|
73
|
-
},
|
|
74
|
-
pre: ({ children }) => (
|
|
75
|
-
<pre className="bg-muted p-3 rounded-md overflow-x-auto my-2 text-xs">
|
|
76
|
-
{children}
|
|
77
|
-
</pre>
|
|
78
|
-
),
|
|
79
|
-
// Tables
|
|
80
|
-
table: ({ children }) => (
|
|
81
|
-
<div className="overflow-x-auto my-2">
|
|
82
|
-
<table className="min-w-full text-xs border-collapse">{children}</table>
|
|
83
|
-
</div>
|
|
84
|
-
),
|
|
85
|
-
thead: ({ children }) => (
|
|
86
|
-
<thead className="bg-muted">{children}</thead>
|
|
87
|
-
),
|
|
88
|
-
th: ({ children }) => (
|
|
89
|
-
<th className="px-2 py-1 text-left font-medium border-b">{children}</th>
|
|
90
|
-
),
|
|
91
|
-
td: ({ children }) => (
|
|
92
|
-
<td className="px-2 py-1 border-b">{children}</td>
|
|
93
|
-
),
|
|
94
|
-
// Blockquotes
|
|
95
|
-
blockquote: ({ children }) => (
|
|
96
|
-
<blockquote className="border-l-2 border-muted-foreground/30 pl-3 italic text-muted-foreground my-2">
|
|
97
|
-
{children}
|
|
98
|
-
</blockquote>
|
|
99
|
-
),
|
|
100
|
-
// Horizontal rules
|
|
101
|
-
hr: () => (
|
|
102
|
-
<hr className="my-4 border-border" />
|
|
103
|
-
),
|
|
104
|
-
// Strong/em
|
|
105
|
-
strong: ({ children }) => (
|
|
106
|
-
<strong className="font-semibold">{children}</strong>
|
|
107
|
-
),
|
|
108
|
-
em: ({ children }) => (
|
|
109
|
-
<em className="italic">{children}</em>
|
|
110
|
-
),
|
|
111
|
-
// Links
|
|
112
|
-
a: ({ href, children }) => (
|
|
113
|
-
<a href={href} className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">
|
|
114
|
-
{children}
|
|
115
|
-
</a>
|
|
116
|
-
),
|
|
117
|
-
}}
|
|
118
|
-
>
|
|
119
|
-
{content}
|
|
120
|
-
</ReactMarkdown>
|
|
121
|
-
</div>
|
|
122
|
-
)
|
|
123
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { MarkdownContent } from './MarkdownContent'
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { cn } from '@/lib/utils'
|
|
2
|
-
|
|
3
|
-
interface MasonryGridProps {
|
|
4
|
-
children: React.ReactNode
|
|
5
|
-
className?: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function MasonryGrid({ children, className }: MasonryGridProps) {
|
|
9
|
-
return (
|
|
10
|
-
<div className={cn(
|
|
11
|
-
'columns-1 sm:columns-2 xl:columns-3 gap-4 max-w-full overflow-x-hidden',
|
|
12
|
-
'[&>*]:mb-4 [&>*]:break-inside-avoid',
|
|
13
|
-
className
|
|
14
|
-
)}>
|
|
15
|
-
{children}
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { MasonryGrid } from './MasonryGrid'
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
-
import { SparklineChart } from '@/components/SparklineChart'
|
|
5
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
|
6
|
-
import { Flame, TrendingUp, Activity, Heart } from 'lucide-react'
|
|
7
|
-
import { cn } from '@/lib/utils'
|
|
8
|
-
import type { MomentumWidgetProps, MomentumData, MomentumStatus } from './MomentumWidget.types'
|
|
9
|
-
|
|
10
|
-
const statusConfig: Record<MomentumStatus, {
|
|
11
|
-
color: string
|
|
12
|
-
bgColor: string
|
|
13
|
-
textColor: string
|
|
14
|
-
icon: typeof Flame
|
|
15
|
-
}> = {
|
|
16
|
-
// Growing - you're killing it!
|
|
17
|
-
hot: {
|
|
18
|
-
color: '#22c55e',
|
|
19
|
-
bgColor: 'bg-green-500/10',
|
|
20
|
-
textColor: 'text-green-500',
|
|
21
|
-
icon: Flame
|
|
22
|
-
},
|
|
23
|
-
// Normal activity - neutral tones
|
|
24
|
-
active: {
|
|
25
|
-
color: '#a1a1aa',
|
|
26
|
-
bgColor: 'bg-muted',
|
|
27
|
-
textColor: 'text-muted-foreground',
|
|
28
|
-
icon: Activity
|
|
29
|
-
},
|
|
30
|
-
// Slight slowdown - still neutral
|
|
31
|
-
cooling: {
|
|
32
|
-
color: '#a1a1aa',
|
|
33
|
-
bgColor: 'bg-muted',
|
|
34
|
-
textColor: 'text-muted-foreground',
|
|
35
|
-
icon: TrendingUp
|
|
36
|
-
},
|
|
37
|
-
// Abandoned (7+ days) - red alert
|
|
38
|
-
cold: {
|
|
39
|
-
color: '#ef4444',
|
|
40
|
-
bgColor: 'bg-red-500/10',
|
|
41
|
-
textColor: 'text-red-500',
|
|
42
|
-
icon: Heart
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function MomentumWidget({ projectId }: MomentumWidgetProps) {
|
|
47
|
-
const [data, setData] = useState<MomentumData | null>(null)
|
|
48
|
-
const [loading, setLoading] = useState(true)
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
async function fetchMomentum() {
|
|
52
|
-
try {
|
|
53
|
-
const res = await fetch(`/api/projects/${projectId}/momentum`)
|
|
54
|
-
const json = await res.json()
|
|
55
|
-
if (json.success) {
|
|
56
|
-
setData(json.data)
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.error('Failed to fetch momentum:', error)
|
|
60
|
-
} finally {
|
|
61
|
-
setLoading(false)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
fetchMomentum()
|
|
66
|
-
}, [projectId])
|
|
67
|
-
|
|
68
|
-
if (loading) {
|
|
69
|
-
return (
|
|
70
|
-
<div className="flex items-center gap-2 h-10 animate-pulse">
|
|
71
|
-
<div className="h-10 w-32 bg-muted rounded" />
|
|
72
|
-
<div className="h-4 w-16 bg-muted rounded" />
|
|
73
|
-
</div>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!data) return null
|
|
78
|
-
|
|
79
|
-
const config = statusConfig[data.status]
|
|
80
|
-
const Icon = config.icon
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<Tooltip>
|
|
84
|
-
<TooltipTrigger asChild>
|
|
85
|
-
<div className={cn(
|
|
86
|
-
'flex items-center gap-2 px-2 py-1 rounded-md cursor-default transition-colors',
|
|
87
|
-
config.bgColor
|
|
88
|
-
)}>
|
|
89
|
-
{/* Mini sparkline - matching button width for visual weight */}
|
|
90
|
-
<div className="w-32 h-10">
|
|
91
|
-
<SparklineChart
|
|
92
|
-
data={data.dailyTasks}
|
|
93
|
-
color={config.color}
|
|
94
|
-
height={40}
|
|
95
|
-
showArea={true}
|
|
96
|
-
/>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
{/* Status badge */}
|
|
100
|
-
<div className={cn('flex items-center gap-1.5', config.textColor)}>
|
|
101
|
-
<Icon className="w-4 h-4" />
|
|
102
|
-
<span className="text-sm font-medium whitespace-nowrap">
|
|
103
|
-
{data.message}
|
|
104
|
-
</span>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
</TooltipTrigger>
|
|
108
|
-
<TooltipContent side="bottom" className="text-xs">
|
|
109
|
-
<div className="space-y-1">
|
|
110
|
-
<p className="font-medium">7-day activity</p>
|
|
111
|
-
<p>{data.totalTasks} tasks, {data.totalShips} ships</p>
|
|
112
|
-
{data.streak > 0 && (
|
|
113
|
-
<p className="text-foreground font-medium">{data.streak} day streak!</p>
|
|
114
|
-
)}
|
|
115
|
-
</div>
|
|
116
|
-
</TooltipContent>
|
|
117
|
-
</Tooltip>
|
|
118
|
-
)
|
|
119
|
-
}
|