# Project export: Sync

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: Sync your fitness plans to your menstrual cycle
- Devpost: https://devpost.com/software/sync-79r18p
- GitHub: https://github.com/isakaii/sync
- Demo: https://sync-zeta.vercel.app/health
- Video: https://www.youtube.com/embed/Cb9o4UBcL0k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Isabella He (12 commits), William Gu (3 commits), liu-meina (2 commits)

## Devpost submission (written by the team)

### Inspiration

For female athletes, such as professional competitors and military personnel, workout efficiency is crucial to their performance. Despite this, research has shown that 75% of elite female athletes report their menstrual cycle affects their performance, with over 50% identifying the mid-follicular phase (just after menstruation) as their optimal performance window. However, there is currently no seamless way for women to tailor their fitness plans to their menstrual cycles. Coaches and military supervisors often lack the necessary understanding of female physiology to optimize training regimens, resulting in inefficient workouts and increased injury risk. This gap in sports science and training inspired us to build Sync—a platform that empowers female athletes by syncing their workouts with their menstrual cycles. By leveraging health data and AI-driven insights, we provide an intuitive, science-backed solution that optimizes training for female physiology. Customer Insights Our extensive market research revealed compelling data about the need for menstrual cycle-based training: While 87% of athletes reported their menstrual cycle impacted their performance and training, only 35% had ever discussed it with their coaches. 76.8% of elite track and field athletes described their menstrual cycle as negatively affecting their performance Customer Interviews We interviewed 12 female athletes on campus about Sync, and heard "I mean, I love my coach but he'd never think to tailor my workout plan to my menstrual cycle on his own - but I feel my body perform differently throughout the month so it'd be great if he did. But it takes so much manual work getting that data and tweaking workout plans."

### What it does

Sync is a comprehensive fitness platform that revolutionizes workout planning for female athletes through several key components: Health Data Syncing: Using Terra API, we seamlessly import health data from wearables like Oura and Clue, ensuring real-time synchronization of menstrual cycle data. AI-Powered Workout Plans: We integrate with Gemini AI to generate customized fitness regimens tailored to different menstrual cycle phases, optimizing training intensity and recovery. Voice-Assisted AI Coach: Powered by ElevenLabs, our AI coach provides spoken workout guidance, making workouts more engaging and accessible. Performance Insights: Utilizing Perplexity AI, we analyze historical trends in readiness scores, sleep data, and period levels to assess Sync’s effectiveness and refine future recommendations. Data Export & Integration: Users can export workout and health data to spreadsheets, enabling researchers and platforms like Paradigm to analyze large-scale fitness trends.

### How we built it

From the outset, we knew that integrating multiple APIs and ensuring smooth data flow would be a major challenge, so we carefully designed our approach to maintain both technical feasibility and usability. We started by defining our problem statement: empowering female athletes with personalized, cycle-aware fitness plans. From there, we broke down our implementation strategy into key milestones, ensuring that each component aligned with our overarching goal. One of our biggest decisions was how to handle health data synchronization. Instead of manually integrating with multiple wearable platforms, we chose Terra API as our bridge to pull data from Oura, Clue, and other wearables -- pulling the data into Supabase to then query from. This decision streamlined our development and ensured broader compatibility with existing health tracking systems. Once we had access to accurate menstrual cycle and biometric data, we focused on integrating Gemini AI to generate workout plans based on that information, ensuring our AI-generated insights aligned with scientific training recommendations. We also wanted Sync to be more than just a passive data platform—it needed to engage users actively. This led us to incorporate ElevenLabs’ AI-powered voice assistant, allowing the app to act as a virtual workout coach, guiding users in real-time through their personalized training regimens. To provide meaningful performance insights, we then integrated Perplexity AI, which allowed us to analyze trends in readiness scores, sleep quality, and workout effectiveness over time. Finally, we built robust export functionality, allowing users to share their health data in spreadsheet format, opening possibilities for integration with platforms like Paradigm for further analysis. On the frontend, we leveraged Next.js, React, and TypeScript to ensure a seamless and interactive experience. We chose Supabase as our backend due to its flexibility and ability to store large amounts of user data efficiently. Finally, we hosted and deployed Sync on Vercel, ensuring continuous integration and rapid iteration.

### Challenges we ran into

We started off with ambitious goals given that this is our entire team's very first hackathon, and faced a lot of challenges along the way. One of the toughest hurdles was ensuring smooth data integration across multiple APIs. The Terra API provided a powerful interface for syncing menstrual cycle and fitness data, but handling data logic across different APIs from Terra to Gemini to ElevenLabs, etc, and reading/writing to the database throughout proved challenging. Another challenge arose with the AI-generated workout plans. Initially, Gemini AI’s recommendations were too generalized and did not fully account for the nuances of different menstrual cycle phases. We had to refine our prompts and implement custom logic to ensure that the AI-generated plans were truly personalized and effective. This took multiple iterations, but in the end, we were able to fine-tune the AI to produce actionable, phase-specific workout recommendations. Another challenge was that ElevenLabs provided an excellent text-to-speech interface, but making it feel like a real personal coach required thoughtful UX design and testing. We experimented with different pacing and voice styles to make the guidance more engaging and intuitive.

### Accomplishments we're proud of

We are incredibly proud of building an end-to-end solution that seamlessly guides users from health data collection to actionable fitness plans. By integrating AI-driven insights, real-time coaching, and performance tracking, we’ve developed a tool that addresses a previously overlooked gap in athletic training for women. Beyond that, we’re proud of our ability to leverage multiple advanced technologies and integrate them into a single, cohesive platform. The synchronization between Terra API, Gemini AI, ElevenLabs, and Perplexity AI creates a unique, data-driven approach that truly prioritizes female athletes’ needs.

### What we learned

Throughout this process, we gained valuable insights into health data synchronization, AI-powered recommendations, and real-time voice interaction. We learned how to tune world-class AI models to apply them to real-world use cases and that designing for female athletes involves a level of personalization that is often ignored in mainstream fitness solutions. Additionally, this project reinforced the importance of designing for seamless user experience, particularly in the realm of health and fitness, where engagement is crucial to success.

### What's next

We see enormous potential in expanding Sync beyond elite athletes to support all women looking to optimize their fitness: Expanding Wearable Integrations: Adding support for Natural Cycles, Apple Watch, and Google Pixel for a more holistic health view. Nutrition & Recovery Insights: Extending our AI-driven recommendations to include nutrition plans and recovery protocols. Coach & Military Supervisor Tools: Creating dashboards that allow trainers to tailor workouts for female athletes under their supervision. Our vision is to be at the forefront of female health technology, empowering women to train smarter, perform better, and understand their bodies in ways never before possible. If Sync can improve women’s health and fitness outcomes, we consider that success.

## README (from the GitHub repository)

# Sync
Treehacks 2025

# Deployment
Go to https://sync-zeta.vercel.app/health to see this in action


## Detected evidence (automated analysis)

Indexed codebase: 53 recognized source files, 103 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (57 of 57)

```
README.md
sync-app/.gitignore
sync-app/app/(auth-pages)/forgot-password/page.tsx
sync-app/app/(auth-pages)/layout.tsx
sync-app/app/(auth-pages)/sign-in/page.tsx
sync-app/app/(auth-pages)/sign-up/page.tsx
sync-app/app/(auth-pages)/smtp-message.tsx
sync-app/app/actions.ts
sync-app/app/ai-assistant/AiAssistantClient.tsx
sync-app/app/ai-assistant/page.tsx
sync-app/app/analytics/page.tsx
sync-app/app/api/elevenlabs-speak/route.ts
sync-app/app/api/generate-insights/route.ts
sync-app/app/auth/callback/route.ts
sync-app/app/globals.css
sync-app/app/health/page.tsx
sync-app/app/layout.tsx
sync-app/app/page.tsx
sync-app/app/protected/page.tsx
sync-app/app/protected/reset-password/page.tsx
sync-app/components.json
sync-app/components/deploy-button.tsx
sync-app/components/env-var-warning.tsx
sync-app/components/fitness-plan.tsx
sync-app/components/form-message.tsx
sync-app/components/header-auth.tsx
sync-app/components/hero.tsx
sync-app/components/next-logo.tsx
sync-app/components/submit-button.tsx
sync-app/components/supabase-logo.tsx
sync-app/components/theme-switcher.tsx
sync-app/components/tutorial/code-block.tsx
sync-app/components/tutorial/connect-supabase-steps.tsx
sync-app/components/tutorial/fetch-data-steps.tsx
sync-app/components/tutorial/sign-up-user-steps.tsx
sync-app/components/tutorial/tutorial-step.tsx
sync-app/components/typography/inline-code.tsx
sync-app/components/ui/badge.tsx
sync-app/components/ui/button.tsx
sync-app/components/ui/card.tsx
sync-app/components/ui/checkbox.tsx
sync-app/components/ui/dropdown-menu.tsx
sync-app/components/ui/input.tsx
sync-app/components/ui/label.tsx
sync-app/lib/utils.ts
sync-app/middleware.ts
sync-app/next.config.ts
sync-app/package.json
sync-app/postcss.config.js
sync-app/README.md
sync-app/tailwind.config.ts
sync-app/tsconfig.json
sync-app/utils/supabase/check-env-vars.ts
sync-app/utils/supabase/client.ts
sync-app/utils/supabase/middleware.ts
sync-app/utils/supabase/server.ts
sync-app/utils/utils.ts
```

### Dependencies

- sync-app/package.json: @radix-ui/react-checkbox@^1.1.1, @radix-ui/react-dropdown-menu@^2.1.1, @radix-ui/react-label@^2.1.0, @radix-ui/react-slot@^1.1.0, @supabase/auth-helpers-nextjs@^0.10.0, @supabase/ssr@latest, @supabase/supabase-js@^2.48.1, @types/node@22.10.2, @types/react@^19.0.2, @types/react-dom@19.0.2, autoprefixer@10.4.20, axios@^1.7.9, class-variance-authority@^0.7.0, clsx@^2.1.1, lucide-react@^0.468.0, next@latest, next-themes@^0.4.3, postcss@8.4.49, prettier@^3.3.3, react@19.0.0, react-dom@19.0.0, recharts@^2.15.1, tailwind-merge@^2.5.2, tailwindcss@3.4.17, tailwindcss-animate@^1.0.7, typescript@5.7.2

### Recent commits (newest first)

- Update README.md
- Merge pull request #5 from isakaii/wg-changes
- Changes
- Merge pull request #4 from isakaii/ih-changes
- add nav
- Merge pull request #3 from isakaii/ih-changes
- add data syncing
- graph
- Merge pull request #2 from isakaii/ml-changes
- meina chat
- Merge pull request #1 from isakaii/ih-changes
- update from isabella
- add code from isabella
- add page
- add page
- download next
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### sync-app/package.json

```
{
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@radix-ui/react-checkbox": "^1.1.1",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/ssr": "latest",
    "@supabase/supabase-js": "^2.48.1",
    "autoprefixer": "10.4.20",
    "axios": "^1.7.9",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "lucide-react": "^0.468.0",
    "next": "latest",
    "next-themes": "^0.4.3",
    "prettier": "^3.3.3",
    "react": "19.0.0",
    "react-dom": "19.0.0",
    "recharts": "^2.15.1"
  },
  "devDependencies": {
    "@types/node": "22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "19.0.2",
    "postcss": "8.4.49",
    "tailwind-merge": "^2.5.2",
    "tailwindcss": "3.4.17",
    "tailwindcss-animate": "^1.0.7",
    "typescript": "5.7.2"
  }
}

```

### sync-app/app/page.tsx

```typescript
import Hero from "@/components/hero";
import ConnectSupabaseSteps from "@/components/tutorial/connect-supabase-steps";
import SignUpUserSteps from "@/components/tutorial/sign-up-user-steps";
import { hasEnvVars } from "@/utils/supabase/check-env-vars";

export default async function Home() {
  return (
    <>
      <Hero />
      <main className="flex-1 flex flex-col gap-6 px-4">
        <h2 className="font-medium text-xl mb-4">Next steps</h2>
        {hasEnvVars ? <SignUpUserSteps /> : <ConnectSupabaseSteps />}
      </main>
    </>
  );
}

```

### sync-app/app/layout.tsx

```typescript
import DeployButton from "@/components/deploy-button";
import { EnvVarWarning } from "@/components/env-var-warning";
import HeaderAuth from "@/components/header-auth";
import { ThemeSwitcher } from "@/components/theme-switcher";
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
import { Geist } from "next/font/google";
import { ThemeProvider } from "next-themes";
import Link from "next/link";
import "./globals.css";

const defaultUrl = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : "http://localhost:3000";

export const metadata = {
  metadataBase: new URL(defaultUrl),
  title: "Next.js and Supabase Starter Kit",
  description: "The fastest way to build apps with Next.js and Supabase",
};

const geistSans = Geist({
  display: "swap",
  subsets: ["latin"],
});

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" className={geistSans.className} suppressHydrationWarning>
      <body className="bg-background text-foreground">
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          <main className="min-h-screen flex flex-col items-center">
            <div className="flex-1 w-full flex flex-col gap-20 items-center">
              <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
                <div className="w-full max-w-5xl flex justify-between items-center p-3 px-5 text-sm">
                  <div className="flex gap-5 items-center font-semibold">
                    <Link href={"/"}>Next.js Supabase Starter</Link>
                    <div className="flex items-center gap-2">
                      <DeployButton />
                    </div>
                  </div>
                  {!hasEnvVars ? <EnvVarWarning /> : <HeaderAuth />}
                </div>
              </nav>
              <div className="flex flex-col gap-20 max-w-5xl p-5">
                {children}
              </div>

              <footer className="w-full flex items-center justify-center border-t mx-auto text-center text-xs gap-8 py-16">
                <p>
                  Powered by{" "}
                  <a
                    href="https://supabase.com/?utm_source=create-next-app&utm_medium=template&utm_term=nextjs"
                    target="_blank"
                    className="font-bold hover:underline"
                    rel="noreferrer"
                  >
                    Supabase
                  </a>
                </p>
                <ThemeSwitcher />
              </footer>
            </div>
          </main>
        </ThemeProvider>
      </body>
    </html>
  );
}

```

### sync-app/app/(auth-pages)/layout.tsx

```typescript
export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="max-w-7xl flex flex-col gap-12 items-start">{children}</div>
  );
}

```

### sync-app/utils/supabase/server.ts

```typescript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export const createClient = async () => {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) => {
              cookieStore.set(name, value, options);
            });
          } catch (error) {
            // The `set` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    },
  );
};
```

### sync-app/app/health/page.tsx

```typescript
import { createClient } from "@/utils/supabase/server"
import FitnessPlan from "@/components/fitness-plan"
import Link from "next/link"

export default async function HealthPage() {
  const supabase = createClient()
  const { data: healthData, error } = await (await supabase)
    .from("health_data")
    .select("*")
    .order("date", { ascending: true }) // Fetch all data in chronological order

  if (error) {
    console.error("Error fetching health data:", error)
    return <p>Error loading health data.</p>
  }

  return (
    <div className="container mx-auto p-4">
      <FitnessPlan healthData={healthData || []} />

      {/* Navigation Button */}
      <div className="flex justify-end mt-6">
        <Link href="/ai-assistant">
          <button className="bg-blue-500 text-white px-6 py-3 rounded shadow-lg hover:bg-blue-600">
            Meet your Workout Buddy →
          </button>
        </Link>
      </div>
    </div>
  )
}

```

### sync-app/app/protected/page.tsx

```typescript
import FetchDataSteps from "@/components/tutorial/fetch-data-steps";
import { createClient } from "@/utils/supabase/server";
import { InfoIcon } from "lucide-react";
import { redirect } from "next/navigation";

export default async function ProtectedPage() {
  const supabase = await createClient();

  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    return redirect("/sign-in");
  }

  return (
    <div className="flex-1 w-full flex flex-col gap-12">
      <div className="w-full">
        <div className="bg-accent text-sm p-3 px-5 rounded-md text-foreground flex gap-3 items-center">
          <InfoIcon size="16" strokeWidth={2} />
          This is a protected page that you can only see as an authenticated
          user
        </div>
      </div>
      <div className="flex flex-col gap-2 items-start">
        <h2 className="font-bold text-2xl mb-4">Your user details</h2>
        <pre className="text-xs font-mono p-3 rounded border max-h-32 overflow-auto">
          {JSON.stringify(user, null, 2)}
        </pre>
      </div>
      <div>
        <h2 className="font-bold text-2xl mb-4">Next steps</h2>
        <FetchDataSteps />
      </div>
    </div>
  );
}

```

### sync-app/app/ai-assistant/page.tsx

```typescript
import { createClient } from "@/utils/supabase/server"
import AiAssistantClient from "./AiAssistantClient"
import Link from "next/link"

export default async function AiAssistantPage() {
  const supabase = await createClient()

  const { data, error } = await supabase
    .from("gemini_output")
    .select("workout_plan")
    .order("generated_at", { ascending: false })
    .limit(1)
    .single()

  if (error) {
    console.error("Error fetching from Supabase:", error)
    return <div>Error loading Gemini output</div>
  }

  const workoutPlan = data?.workout_plan?.days ?? []
  const formattedInstructions = workoutPlan.map(
    (day: any) =>
      `🗓️ ${day.day}: ${day.focus}\n💡 ${day.notes}\n🏋️ ${day.workouts
        .map((w: any) => `${w.name} (${w.duration})`)
        .join(", ")}`
  )

  return (
    <div className="container mx-auto p-4">
      <AiAssistantClient workoutInstructions={formattedInstructions} />

      {/* Navigation Buttons */}
      <div className="flex justify-between mt-6">
        <Link href="/health">
          <button className="bg-gray-500 text-white px-6 py-3 rounded shadow-lg hover:bg-gray-600">
            ← Back
          </button>
        </Link>

        <Link href="/analytics">
          <button className="bg-blue-500 text-white px-6 py-3 rounded shadow-lg hover:bg-blue-600">
            See your insights →
          </button>
        </Link>
      </div>
    </div>
  )
}
```

### sync-app/app/analytics/page.tsx

```typescript
"use client"

import { useState, useEffect } from "react"
import { createClient } from "@/utils/supabase/client"
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  Tooltip,
  CartesianGrid,
  Legend,
  ResponsiveContainer,
  ReferenceLine
} from "recharts"
import { Download } from "lucide-react"

type HealthData = {
  id: number
  date: string
  period_level: number
  readiness_score: number
  sleep_score: number
}

export default function AnalyticsPage() {
  const [combinedData, setCombinedData] = useState<HealthData[]>([])
  const [insights, setInsights] = useState("")
  const [loadingInsights, setLoadingInsights] = useState(false)

  useEffect(() => {
    async function fetchData() {
      try {
        const supabase = createClient()
        
        // 1) Fetch from health_data
        const { data: healthData, error: healthError } = await supabase
          .from("health_data")
          .select("*")

        if (healthError) {
          console.error("Error fetching health_data:", healthError)
        }

        // 2) Fetch from new_health_data
        const { data: newHealthData, error: newHealthError } = await supabase
          .from("new_health_data")
          .select("*")

        if (newHealthError) {
          console.error("Error fetching new_health_data:", newHealthError)
        }

        // 3) Merge & sort
        const combined = [...(healthData || []), ...(newHealthData || [])]
        combined.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())

        setCombinedData(combined)
      } catch (error) {
        console.error("Error fetching health data:", error)
      }
    }
    fetchData()
  }, [])

  // Mark 3rd data point => "Started using Sync"
  const syncStartIndex = 2
  const syncStartDate = combinedData[syncStartIndex]?.date

  const handleGenerateInsights = async () => {
    setLoadingInsights(true)
    setInsights("")

    try {
      console.log("[handleGenerateInsights] Sending data:", combinedData)
      const response = await fetch("/api/generate-insights", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ data: combinedData }),
      })

      if (!response.ok) {
        const errorRes = await response.json()
        console.error("Error from /api/generate-insights:", errorRes)
        setInsights(`Error generating insights: ${errorRes.error || "unknown error"}`)
      } else {
        const json = await response.json()
        console.log("[handleGenerateInsights] Received:", json)
        setInsights(json.insights || "No insights returned")
      }
    } catch (error) {
      console.error("Error generating insights:", error)
      setInsights("Error generating insights.")
    } finally {
      setLoadingInsights(false)
    }
  }

  const handleExportCSV = () => {
    const header = ["Date", "Period Level", "Readiness Score", "Sleep Score"]
    const rows = combinedData.map(d => [
      d.date,
      d.period_level.toString(),
      d.readiness_score.toString(),
      d.sleep_score.toString(),
    ])
    const csvContent = [header, ...rows].map(r => r.join(",")).join("\n")

    const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" })
    const url = URL.createObjectURL(blob)

    const link = document.createElement("a")
    link.href = url
    link.setAttribute("download", "health_data.csv")
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  }

  return (
    <div className="container mx-auto p-4 text-white">
      {/* Top Explanation */}
      <div className="text-center mb-8">
        <h1 className="text-5xl font-extrabold text-blue-400 mb-4">Sync Insights</h1>
        <p className="text-lg mb-2">
          Discover how <span className="font-bold text-blue-300">Sync</span> tailors
          your workouts to your menstrual cycle, boosting your overall readiness and performance. 
        </p>
        <p className="text-lg mb-2">
          The data below shows how your readiness score has evolved 
          since you started using <strong>Sync</strong>. Keep going!
        </p>
        <div className="flex justify-center space-x-8 mt-4">
          <div className="p-2 bg-gray-900 rounded-lg">
            <p className="font-semibold">Workouts Completed</p>
            <p>21</p>
          </div>
          <div className="p-2 bg-gray-900 rounded-lg">
            <p className="font-semibold">Cycle Synced Sessions</p>
            <p>15</p>
          </div>
        </div>
      </div>

      {/* Chart Section */}
      <div className="bg-white p-4 rounded-lg mb-6 text-black shadow-md">
        <h2 className="text-xl font-bold mb-4">Historical Trends</h2>
        {combinedData.length === 0 ? (
          <p>No data available.</p>
        ) : (
          <ResponsiveContainer width="100%" height={350}>
            <LineChart
              data={combinedData.map(d => ({
                ...d,
                readiness_score: Number(d.readiness_score),
                sleep_score: Number(d.sleep_score),
                period_level: Number(d.period_level),
              }))}
              margin={{ top: 5, right: 10, left: 10, bottom: 5 }}
            >
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis dataKey="date" />
              <YAxis />
              <Tooltip />
              <Legend />
              <Line
                type="monotone"
                dataKey="readiness_score"
                stroke="#36A2EB"
                name="Readiness Score"
                strokeWidth={2}
              />
              <Line
                type="monotone"
                dataKey="sleep_score"
                stroke="#4BC0C0"
                name="Sleep Score"
                strokeWidth={2}
              />
              <Line
                type="monotone"
                dataKey="period_level"
                stroke="#FF6384"
                name="Period Level"
                strokeWidth={2}
      
[truncated — 1373 more characters]
```

### sync-app/app/auth/callback/route.ts

```typescript
import { createClient } from "@/utils/supabase/server";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  // The `/auth/callback` route is required for the server-side auth flow implemented
  // by the SSR package. It exchanges an auth code for the user's session.
  // https://supabase.com/docs/guides/auth/server-side/nextjs
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get("code");
  const origin = requestUrl.origin;
  const redirectTo = requestUrl.searchParams.get("redirect_to")?.toString();

  if (code) {
    const supabase = await createClient();
    await supabase.auth.exchangeCodeForSession(code);
  }

  if (redirectTo) {
    return NextResponse.redirect(`${origin}${redirectTo}`);
  }

  // URL to redirect to after sign up process completes
  return NextResponse.redirect(`${origin}/protected`);
}

```

[42 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]