Initial project setup for PhaseFlow

Set up Next.js 16 project with TypeScript for a training decision app
that integrates menstrual cycle phases with Garmin biometrics for
Hashimoto's thyroiditis management.

Stack: Next.js 16, React 19, Tailwind/shadcn, PocketBase, Drizzle,
Zod, Resend, Vitest, Biome, Lefthook, Nix dev environment.

Includes:
- 7 page routes (dashboard, login, settings, calendar, history, plan)
- 12 API endpoints (garmin, user, cycle, calendar, overrides, cron)
- Core lib utilities (decision engine, cycle phases, nutrition, ICS)
- Type definitions and component scaffolding
- Python script for Garmin token bootstrapping
- Initial unit tests for cycle utilities

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 16:50:39 +00:00
commit f15e093254
63 changed files with 6061 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
// ABOUTME: Dashboard panel showing biometric data.
// ABOUTME: Displays body battery, HRV, and intensity minutes.
interface DataPanelProps {
bodyBatteryCurrent: number | null;
bodyBatteryYesterdayLow: number | null;
hrvStatus: string;
weekIntensity: number;
phaseLimit: number;
remainingMinutes: number;
}
export function DataPanel({
bodyBatteryCurrent,
bodyBatteryYesterdayLow,
hrvStatus,
weekIntensity,
phaseLimit,
remainingMinutes,
}: DataPanelProps) {
return (
<div className="rounded-lg border p-4">
<h3 className="font-semibold mb-4">YOUR DATA</h3>
<ul className="space-y-2 text-sm">
<li>Body Battery: {bodyBatteryCurrent ?? "N/A"}</li>
<li>Yesterday Low: {bodyBatteryYesterdayLow ?? "N/A"}</li>
<li>HRV: {hrvStatus}</li>
<li>
Week: {weekIntensity}/{phaseLimit} min
</li>
<li>Remaining: {remainingMinutes} min</li>
</ul>
</div>
);
}

View File

@@ -0,0 +1,17 @@
// ABOUTME: Dashboard card displaying today's training decision.
// ABOUTME: Shows decision status, icon, and reason prominently.
import type { Decision } from "@/types";
interface DecisionCardProps {
decision: Decision;
}
export function DecisionCard({ decision }: DecisionCardProps) {
return (
<div className="rounded-lg border p-6">
<div className="text-4xl mb-2">{decision.icon}</div>
<h2 className="text-2xl font-bold">{decision.status}</h2>
<p className="text-gray-600">{decision.reason}</p>
</div>
);
}

View File

@@ -0,0 +1,29 @@
// ABOUTME: Compact calendar widget for the dashboard.
// ABOUTME: Shows current month with color-coded cycle phases.
interface MiniCalendarProps {
currentDate: Date;
cycleDay: number;
phase: string;
}
export function MiniCalendar({
currentDate,
cycleDay,
phase,
}: MiniCalendarProps) {
return (
<div className="rounded-lg border p-4">
<h3 className="font-semibold mb-4">
Day {cycleDay} {phase}
</h3>
<p className="text-sm text-gray-500">
{currentDate.toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})}
</p>
{/* Full calendar grid will be implemented here */}
<p className="text-gray-400 text-xs mt-4">Calendar grid placeholder</p>
</div>
);
}

View File

@@ -0,0 +1,20 @@
// ABOUTME: Dashboard panel showing nutrition guidance.
// ABOUTME: Displays seed cycling and macro recommendations.
import type { NutritionGuidance } from "@/types";
interface NutritionPanelProps {
nutrition: NutritionGuidance;
}
export function NutritionPanel({ nutrition }: NutritionPanelProps) {
return (
<div className="rounded-lg border p-4">
<h3 className="font-semibold mb-4">NUTRITION TODAY</h3>
<ul className="space-y-2 text-sm">
<li>🌱 {nutrition.seeds}</li>
<li>🍽 Carbs: {nutrition.carbRange}</li>
<li>🥑 Keto: {nutrition.ketoGuidance}</li>
</ul>
</div>
);
}

View File

@@ -0,0 +1,41 @@
// ABOUTME: Dashboard component for emergency training overrides.
// ABOUTME: Provides toggles for flare, stress, sleep, and PMS modes.
import type { OverrideType } from "@/types";
interface OverrideTogglesProps {
activeOverrides: OverrideType[];
onToggle: (override: OverrideType) => void;
}
const OVERRIDE_OPTIONS: { type: OverrideType; label: string }[] = [
{ type: "flare", label: "Flare Mode" },
{ type: "stress", label: "High Stress" },
{ type: "sleep", label: "Poor Sleep" },
{ type: "pms", label: "PMS Symptoms" },
];
export function OverrideToggles({
activeOverrides,
onToggle,
}: OverrideTogglesProps) {
return (
<div className="rounded-lg border p-4">
<h3 className="font-semibold mb-4">OVERRIDES</h3>
<ul className="space-y-2">
{OVERRIDE_OPTIONS.map(({ type, label }) => (
<li key={type}>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={activeOverrides.includes(type)}
onChange={() => onToggle(type)}
className="rounded"
/>
{label}
</label>
</li>
))}
</ul>
</div>
);
}