# Project export: curato

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: CruzHacks 2025
- Tagline: Doctors spend too much time charting and not enough time connecting. In fact, Physicians spend nearly twice as much time on EHR/desk work as patients. Curato fixes that with smart EHR automation.
- Devpost: https://devpost.com/software/curato
- GitHub: https://github.com/xntle/curato
- Demo: https://curato-six.vercel.app/
- Video: https://www.youtube.com/embed/BZjFyVjrdX0?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Health Hacks)
- Team: 2 GitHub contributor(s) — An Le (50 commits), bri (6 commits)

## Devpost submission (written by the team)

### Inspiration

The healthcare system is facing a critical shortage of skilled professionals, with projected deficits of over 78,000 nurses and 94,000 physicians in 2025, leading to workflow inefficiencies and staff burnout. In evaluation of the process variations, we discovered clinicians spend 2x more time on paperwork than with patients. There needs to be a smarter, faster way to document medical visits.

### What it does

Curato is an AI assistant that listens to doctor-patient conversations, then generates a full SOAP note, auto-fills the EHR, and lets both parties review and share the summaries. Key Features: 🎙️ One-click voice recording of consultations 🧠 AI-generated SOAP notes (Subjective, Objective, Assessment, Plan) 📋 Auto-filled EHR forms with edit options 📄 PDF export of the final report 🔍 QR-based patient info intake 💬 Patient-side EHR viewer with clean summaries and transcripts

### How we built it

We built Curato using: Next.js with React and Tailwind CSS for the frontend Supabase as our backend and authentication provider Gemini for transcription and summarization jsPDF for exporting the final EHR documents html5-qrcode to scan patient QR data into the form Typescript throughout for safer development

### Challenges we ran into

Balancing clean UX for both doctors and patients in a single platform Transforming our transcribed EHR data to be stored in our Postgres Database Converting our QR code into a JSON payload to store in our Postgres Database

### Accomplishments we're proud of

Seamless voice-to-EHR generation from scratch in < 30 hours Real-time QR scanning to preload patient data Built a multi-step data pipeline involving AI, Supabase, and client rendering with minimal latency Learned how to use and implement new technologies - Supabase & Gemini were new to us!

### What we learned

This weekend was a wonderful learning experience for us! We got to use new tools and technologies like Gemini AI and Supabase for the first time. We even got to use Google Cloud to add users into our database. We are proud to say that this weekend at CruzHacks we were able to expand on our skills and gain a deeper understanding of Fullstack development.

### What's next

Expanding and refining our database design. Cross-provider communication so patients can share their data with different clinics and/or providers. Improve scalability.

## README (from the GitHub repository)

# 🏥 Curato: AI-Powered Clinical Assistant

**Curato** is an AI-powered tool designed to alleviate the growing administrative burden on healthcare providers. By combining audio transcription, structured EHR generation, and document sharing, Curato streamlines workflows so clinicians can focus on what matters most: patient care.

---

## 🔍 Problem

### 1. **Documentation & Record-Keeping**

> Clinicians spend **2x more time** on paperwork than with patients.

- Manual updates to EHRs are **time-consuming and error-prone**.
- **Example**: Paper-based cognitive tests require manual scoring and data entry.
- **Impact**: Leads to **delayed diagnoses**, **billing mistakes**, and **patient safety issues**.

---

### 2. **Appointment Scheduling & Communication**

> **24.4% of patients** report delays due to inefficient communication.

- Scheduling via phone/email lacks automation and follow-up.
- **Example**: Missed appointments due to no reminders or confirmations.
- **Impact**: Strains staff and results in **delayed care**.

---

### 3. **Insurance & Billing**

> **30.9% of patients** experience care delays due to insurance issues.

- Verifying coverage, resolving denials, and prior authorizations are time-intensive.
- **Example**: Incorrect billing leads to **financial strain** and **denied claims**.

---

### 4. **Regulatory Compliance**

> A single 161-bed hospital may spend **$7.6 million annually** on compliance.

- Requires nearly **59 full-time staff** to manage documentation and oversight.
- **Example**: Post-acute care facilities must comply with **288+ extra regulations**.
- **Impact**: Significant **financial and operational burden**.

---

### 5. **Care Coordination**

> Fragmented systems hinder cross-provider communication.

- Referral and handoff processes are often **manual and redundant**.
- **Impact**: Leads to **miscommunication**, **duplicate tests**, and **disrupted care**.

---

### 📚 Sources

- [1] [Creyos](https://creyos.com/blog/administrative-burden-in-healthcare)
- [2] [Agiliti Health](https://www.agilitihealth.com/caregivers/focus-on-patients-not-hospital-admin-tasks/)
- [3] [PMC Article](https://pmc.ncbi.nlm.nih.gov/articles/PMC8522562/)
- [6] [SLCHC Blog](https://slchc.edu/uncategorized/blogs/key-challenges-facing-healthcare-administrators/)

---

## ✅ Solution: Curato

Curato addresses these challenges through a modular, AI-driven system:

### 1. **Smart Recording & Transcription**

Doctors can record patient conversations directly in the platform. Curato transcribes the audio in real time using the Gemini API, generating accurate, editable text.

### 2. **EHR Autofill Assistant**

Transcripts are analyzed by an AI model that extracts structured data for Electronic Health Records (EHR), auto-filling critical sections like:

- Chief Complaint
- Allergies & Medications
- SOAP Notes (Subjective, Objective, Assessment, Plan)
- Chaperone documentation, vitals, and more

### 3. **Secure Record Sharing**

Patients can selectively share their health information with providers via QR code. This helps reduce repetitive intake questions and improves continuity of care.

### 4. **Patient-Facing Portal**

Patients can:

- View upcoming and past appointments
- Chat with the assistant to ask about medications, appointments, summaries, etc.
- Control what data to share with clinicians and when

---

## 🛠️ Tech Stack

- **Next.js 14** + App Router
- **TypeScript**
- **Tailwind CSS**
- **Google Gemini API** (for transcription & EHR RAG)
- **QRCode generation**
- **Optional Vectara (RAG platform) support** for longitudinal health document search

---

## 📦 Future Directions

- 🧠 Integrate Vectara for document-level semantic search
- 🔒 End-to-end encrypted patient-provider messaging
- 🗂️ FHIR-compliant health data export
- 🌍 Multilingual and accessibility support

---

## 🤝 Contributors

- Thai An Le – Developer, Designer
- Britney Nguyen - Developer

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

# curato


## Detected evidence (automated analysis)

Indexed codebase: 31 recognized source files, 240 KB.
- CSS (language) — detected in the code
- Google Gemini (technology) — 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
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- SQL (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (36 of 36)

```
.gitignore
components.json
eslint.config.mjs
next.config.ts
package.json
postcss.config.mjs
README.md
src/app/doctor/home/page.tsx
src/app/doctor/page.tsx
src/app/doctor/sidebar.tsx
src/app/doctor/start/page.tsx
src/app/gemini/blobToBase64.ts
src/app/gemini/ehr.ts
src/app/gemini/transcribe.ts
src/app/globals.css
src/app/layout.tsx
src/app/login/doctors/page.tsx
src/app/login/patient/page.tsx
src/app/page.tsx
src/app/patient/appointments/page.tsx
src/app/patient/ehr/page.tsx
src/app/patient/page.tsx
src/app/patient/shareRecord/page.tsx
src/app/patient/sidebar.tsx
src/components/ui/button.tsx
src/components/ui/checkbox.tsx
src/components/ui/heartbeat.tsx
src/components/ui/input.tsx
src/components/ui/separator.tsx
src/components/ui/sheet.tsx
src/components/ui/sidebar.tsx
src/components/ui/skeleton.tsx
src/components/ui/tooltip.tsx
src/hooks/use-mobile.ts
src/lib/utils.ts
tsconfig.json
```

### Dependencies

- package.json: @eslint/eslintrc@^3, @google/genai@^0.8.0, @google/generative-ai@^0.24.0, @radix-ui/react-checkbox@^1.1.5, @radix-ui/react-dialog@^1.1.7, @radix-ui/react-separator@^1.1.3, @radix-ui/react-slot@^1.2.0, @radix-ui/react-tooltip@^1.2.0, @supabase/auth-helpers-nextjs@^0.10.0, @supabase/ssr@^0.6.1, @supabase/supabase-js@^2.49.4, @tailwindcss/postcss@^4.1.3, @types/node@^20, @types/react@^19, @types/react-dom@^19, @vectara/react-chatbot@^3.1.0, @vectara/react-search@^1.2.0, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.0, framer-motion@^12.6.5, html2canvas@^1.4.1, html2pdf.js@^0.10.3, html5-qrcode@^2.3.8, jspdf@^3.0.1, lucide-react@^0.487.0, mime-types@^3.0.1, next@^15.3.0, postcss@^8.5.3, qrcode.react@^4.2.0, react@^18.3.1, react-dom@^18.3.1, react-icons@^5.5.0, react-qr-code@^2.0.15, tailwind-merge@^3.2.0, tailwindcss@^4.1.3, tw-animate-css@^1.2.5, typescript@^5

### Recent commits (newest first)

- woo
- hhdhdh
- Merge pull request #3 from xntle/supabase
- Merge branch 'main' into supabase
- woohoo
- yay
- yay
- save and retrieve ehr apis.
- yay
- yay
- phew
- Merge branch 'main' of https://github.com/xntle/curato
- wawawawa
- created an update request for ehr.
- Merge pull request #2 from xntle/supabase
- Update page.tsx
- Merge branch 'main' into supabase
- weawe
- retrieved transcript for patient.
- awaw

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

### package.json

```
{
  "name": "curato",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@google/genai": "^0.8.0",
    "@google/generative-ai": "^0.24.0",
    "@radix-ui/react-checkbox": "^1.1.5",
    "@radix-ui/react-dialog": "^1.1.7",
    "@radix-ui/react-separator": "^1.1.3",
    "@radix-ui/react-slot": "^1.2.0",
    "@radix-ui/react-tooltip": "^1.2.0",
    "@supabase/auth-helpers-nextjs": "^0.10.0",
    "@supabase/ssr": "^0.6.1",
    "@supabase/supabase-js": "^2.49.4",
    "@vectara/react-chatbot": "^3.1.0",
    "@vectara/react-search": "^1.2.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.6.5",
    "html2canvas": "^1.4.1",
    "html2pdf.js": "^0.10.3",
    "html5-qrcode": "^2.3.8",
    "jspdf": "^3.0.1",
    "lucide-react": "^0.487.0",
    "mime-types": "^3.0.1",
    "next": "^15.3.0",
    "postcss": "^8.5.3",
    "qrcode.react": "^4.2.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-icons": "^5.5.0",
    "react-qr-code": "^2.0.15",
    "tailwind-merge": "^3.2.0",
    "tw-animate-css": "^1.2.5"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4.1.3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.0",
    "tailwindcss": "^4.1.3",
    "typescript": "^5"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "curato",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### src/app/page.tsx

```typescript
"use client";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import Heartbeat from "@/components/ui/heartbeat";
import { Mic, FileText, Share2 } from "lucide-react"; // You can swap these icons out with your own set
export default function Home() {
  const router = useRouter();
  return (
    <main className=" font-sans text-gray-800">
      {/* Hero Section */}
      <section className="h-screen py-24 px-6 flex flex-col justify-center text-center">
        <Heartbeat></Heartbeat>
        <h1 className="text-4xl md:text-6xl font-bold mb-4">curato</h1>
        <p className="max-w-2xl mx-auto text-lg text-gray-600">
          Doctors spend more time on charts than with patients — we fix that
          with smart EHR automation.
        </p>
        <div className="space-x-4 mt-8">
          <Button onClick={() => router.push("/login/patient")}>
            I&apos;m a patient
          </Button>
          <Button onClick={() => router.push("/login/doctors")}>
            I&apos;m a doctor
          </Button>
        </div>
      </section>

      <section className="py-24 px-6 bg-white flex flex-col justify-center text-center min-h-screen">
        <h2 className="text-4xl md:text-5xl font-bold mb-6 text-gray-800">
          From Small Clinics to Big Hospitals
        </h2>
        <p className="max-w-3xl mx-auto text-lg md:text-xl text-gray-700 leading-relaxed mb-12">
          <span className="font-semibold text-blue-700">Doctors</span> spend too
          much time charting and not enough time connecting. Whether you’re a
          solo provider or a full care team,
          <span className="font-semibold text-blue-700"> Curato</span> helps
          shift that balance — giving you time back for what truly matters:{" "}
          <span className="italic">your patients</span>.
        </p>
        {/* Steps */}
        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto text-left">
          {/* Step 1 */}
          <div className="bg-gray-50 rounded-xl shadow-md p-6 flex flex-col items-center text-center">
            <Mic size={48} className="text-blue-600 mb-4" />
            <h4 className="text-lg font-bold mb-2">AI-Powered Transcription</h4>
            <p className="text-sm text-gray-600">
              Record visits and get accurate transcripts automatically—no more
              typing while talking.
            </p>
          </div>

          {/* Step 2 */}
          <div className="bg-gray-50 rounded-xl shadow-md p-6 flex flex-col items-center text-center">
            <FileText size={48} className="text-purple-600 mb-4" />
            <h4 className="text-lg font-bold mb-2">SOAP Note Generation</h4>
            <p className="text-sm text-gray-600">
              Instantly draft Subjective, Objective, Assessment, and Plan
              notes—streamlined and structured.
            </p>
          </div>

          {/* Step 3 */}
          <div className="bg-gray-50 rounded-xl shadow-md p-6 flex flex-col items-center text-center">
            <Share2 size={48} className="text-rose-600 mb-4" />
            <h4 className="text-lg font-bold mb-2">Secure Record Sharing</h4>
            <p className="text-sm text-gray-600">
              Patients can share their visit summaries via QR codes or
              links—with full consent.
            </p>
          </div>
        </div>

        <div className="mt-16">
          <svg
            className="mx-auto text-blue-600 animate-pulse"
            xmlns="http://www.w3.org/2000/svg"
            width="40"
            height="40"
            fill="currentColor"
            viewBox="0 0 24 24"
          >
            <path d="M12 4.5l8.66 7.5H3.34L12 4.5zM3 18h18v-2H3v2z" />
          </svg>
        </div>
      </section>

      {/* The Mission */}
      <section className="py-20 px-6 bg-white flex flex-col justify-center text-center h-screen">
        <Heartbeat></Heartbeat>
        <p className="max-w-2xl mx-auto text-lg text-gray-600">
          We&apos;re here to bring the humanity back to healthcare. One voice,
          one note, one conversation at a time.
        </p>
        <div className="space-x-4 mt-8">
          <p className="mb-2">Start Today</p>
          <Button onClick={() => router.push("/login/patient")}>
            I&apos;m a patient
          </Button>
          <Button onClick={() => router.push("/login/doctors")}>
            I&apos;m a doctor
          </Button>
        </div>
      </section>
    </main>
  );
}

```

### src/app/doctor/page.tsx

```typescript
import PatientSidebar from "./sidebar";

export default function PatientPage() {
  return (
    <>
      <PatientSidebar></PatientSidebar>
    </>
  );
}

```

### src/app/patient/page.tsx

```typescript
import PatientSidebar from "./sidebar";

export default function PatientPage() {
  return (
    <>
      <PatientSidebar></PatientSidebar>
    </>
  );
}

```

### src/app/doctor/home/page.tsx

```typescript
"use client";

import { useRouter } from "next/navigation";
import DoctorSidebar from "../sidebar";

const patientsToday = [
  { name: "John Smith", time: "9:00 AM", reason: "Annual Physical" },
  { name: "Emily Chen", time: "10:30 AM", reason: "Follow-up: Hypertension" },
  { name: "Carlos Rodriguez", time: "1:00 PM", reason: "Lab Results Review" },
  { name: "Aisha Patel", time: "3:15 PM", reason: "New Patient Consultation" },
];

export default function HomePage() {
  const router = useRouter();

  return (
    <div className="flex min-h-screen">
      {/* Sidebar */}
      <div className="w-64">
        <DoctorSidebar />
      </div>

      {/* Page content */}
      <div className="flex-1 p-6">
        <h1 className="text-2xl font-bold mb-4">Welcome Back, Doc</h1>

        <div className="mt-6">
          <h2 className="text-xl font-semibold mb-3">
            Today&apos;s Appointments
          </h2>
          <div className="bg-white shadow rounded-lg divide-y">
            {patientsToday.map((patient, index) => (
              <div
                key={index}
                className="flex justify-between items-center p-4 hover:bg-gray-50 cursor-pointer"
                onClick={() => router.push("/doctor/start")}
              >
                <div>
                  <p className="font-medium text-gray-800">{patient.name}</p>
                  <p className="text-sm text-gray-500">{patient.reason}</p>
                </div>
                <span className="text-sm font-semibold text-blue-600">
                  {patient.time}
                </span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

```

### src/app/login/doctors/page.tsx

```typescript
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { FcGoogle } from "react-icons/fc";
import { ArrowLeft } from "lucide-react";

export default function LoginPage() {
  const router = useRouter();
  const supabase = createClientComponentClient();

  const handleGoogleSignIn = async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        redirectTo: `${window.location.origin}/doctor/home`,
      },
    });
    if (error) {
      console.error("OAuth error:", error.message);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center px-4">
      <div className="bg-white shadow-xl rounded-xl p-10 max-w-md w-full text-center space-y-2 border border-1">
        {/* Back Button */}
        <div className="flex justify-start">
          <Button
            variant="ghost"
            size="sm"
            className="flex items-center gap-2 text-sm text-gray-600"
            onClick={handleGoogleSignIn}
          >
            <ArrowLeft className="w-4 h-4" />
            Back
          </Button>
        </div>

        <h1 className="text-3xl font-bold text-gray-800">Welcome to Curato</h1>
        <p className="text-gray-500">Sign in to continue</p>

        <Button
          variant="outline"
          className="w-full flex items-center justify-center gap-2 border-gray-300 text-gray-700 hover:bg-gray-100"
          onClick={handleGoogleSignIn}
        >
          <FcGoogle className="text-xl" />
          Sign in with Google
        </Button>

        <div className="items-top flex space-x-2 mt-8 text-left">
          <Checkbox id="terms1" />
          <div className="grid gap-1.5 leading-none">
            <label
              htmlFor="terms1"
              className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
            >
              Accept terms and conditions
            </label>
            <p className="text-sm text-muted-foreground">
              You agree to our Terms of Service and Privacy Policy.
            </p>
          </div>
        </div>
        <div>
          <Button
            variant="ghost"
            size="sm"
            className="flex items-center gap-2 text-sm text-gray-600"
            onClick={() => router.push("/login/patient")}
          >
            I&apos;m a patient
          </Button>
        </div>
      </div>
    </div>
  );
}

```

### src/app/login/patient/page.tsx

```typescript
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { FcGoogle } from "react-icons/fc";
import { ArrowLeft } from "lucide-react";

export default function LoginPage() {
  const router = useRouter();
  const supabase = createClientComponentClient();

  const handleGoogleSignIn = async () => {
    const { error } = await supabase.auth.signInWithOAuth({
      provider: "google",
      options: {
        redirectTo: `${window.location.origin}/patient/appointments`,
      },
    });
    if (error) {
      console.error("OAuth error:", error.message);
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center px-4">
      <div className="bg-white shadow-xl rounded-xl p-10 max-w-md w-full text-center space-y-2 border border-1">
        {/* Back Button */}
        <div className="flex justify-start">
          <Button
            variant="ghost"
            size="sm"
            className="flex items-center gap-2 text-sm text-gray-600"
            onClick={handleGoogleSignIn}
          >
            <ArrowLeft className="w-4 h-4" />
            Back
          </Button>
        </div>

        <h1 className="text-3xl font-bold text-gray-800">Welcome to Curato</h1>
        <p className="text-gray-500">Sign in to continue</p>

        <Button
          variant="outline"
          className="w-full flex items-center justify-center gap-2 border-gray-300 text-gray-700 hover:bg-gray-100"
          onClick={handleGoogleSignIn}
        >
          <FcGoogle className="text-xl" />
          Sign in with Google
        </Button>

        <div className="items-top flex space-x-2 mt-8 text-left">
          <Checkbox id="terms1" />
          <div className="grid gap-1.5 leading-none">
            <label
              htmlFor="terms1"
              className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
            >
              Accept terms and conditions
            </label>
            <p className="text-sm text-muted-foreground">
              You agree to our Terms of Service and Privacy Policy.
            </p>
          </div>
        </div>
        <div>
          <Button
            variant="ghost"
            size="sm"
            className="flex items-center gap-2 text-sm text-gray-600"
            onClick={() => router.push("/login/doctors")}
          >
            I&apos;m a doctor
          </Button>
        </div>
      </div>
    </div>
  );
}

```

### src/app/patient/appointments/page.tsx

```typescript
"use client";

import Link from "next/link";
import PatientSidebar from "../sidebar";

export default function AppointmentsPage() {
  const upcomingAppointments = [
    {
      date: "April 30, 2025",
      time: "10:00 AM",
      provider: "Dr. Emily Carter",
      location: "UC Davis Clinic, Room 204",
      reason: "Follow-up for lab results",
    },
    {
      date: "May 5, 2025",
      time: "2:30 PM",
      provider: "Dr. Michael Nguyen",
      location: "Dermatology Center",
      reason: "Skin rash consultation",
    },
  ];

  const pastAppointments = [
    {
      date: "March 14, 2025",
      time: "2:20 PM",
      provider: "Dr. Eileen Reilly",
      location: "Urgent Care",
      reason: "Rash all over",
    },
  ];

  return (
    <div className="flex min-h-screen">
      <div className="w-64">
        <PatientSidebar />
      </div>

      <div className="flex-1 p-6 space-y-8">
        <div className="bg-white p-4 rounded-lg shadow border">
          <h1 className="text-2xl font-bold text-gray-800 mb-1">
            Welcome back 👋
          </h1>
          <p className="text-gray-600 text-sm">
            Here’s a quick look at your appointments.
          </p>
        </div>

        <section>
          <h2 className="text-xl font-semibold mb-4">Upcoming Appointments</h2>
          <div className="space-y-4">
            {upcomingAppointments.map((appt, index) => (
              <div
                key={index}
                className="p-4 border rounded-md shadow-sm bg-white space-y-1"
              >
                <p className="text-gray-700 font-medium">
                  {appt.date} at {appt.time}
                </p>
                <p className="text-gray-600">With {appt.provider}</p>
                <p className="text-gray-600">{appt.location}</p>
                <p className="text-gray-500 text-sm">Reason: {appt.reason}</p>
              </div>
            ))}
          </div>
        </section>

        <section>
          <h2 className="text-xl font-semibold mb-4">Past Appointments</h2>
          <div className="space-y-4">
            {pastAppointments.map((appt, index) => (
              <Link
                key={index}
                href={{
                  pathname: "/patient/ehr",
                  query: {
                    date: appt.date,
                    time: appt.time,
                    provider: appt.provider,
                    location: appt.location,
                    reason: appt.reason,
                  },
                }}
              >
                <div className="p-4 border rounded-md shadow-sm bg-gray-50 space-y-1 hover:bg-gray-100 transition cursor-pointer">
                  <p className="text-gray-700 font-medium">
                    {appt.date} at {appt.time}
                  </p>
                  <p className="text-gray-600">With {appt.provider}</p>
                  <p className="text-gray-600">{appt.location}</p>
                  <p className="text-gray-500 text-sm">Reason: {appt.reason}</p>
                </div>
              </Link>
            ))}
          </div>
        </section>
      </div>
    </div>
  );
}

```

### src/app/patient/ehr/page.tsx

```typescript
"use client";

import { Suspense } from "react";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import PatientSidebar from "../sidebar";

function PatientEHRPage() {
  const searchParams = useSearchParams();
  const supabase = createClientComponentClient();

  const [transcript, setTranscript] = useState("");
  const [loading, setLoading] = useState(true);

  const [ehr, setEHR] = useState({
    chaperoneDocumentation: "",
    vitalsAndSmokingStatus: "",
    chiefComplaint: "",
    allergies: "",
    currentMedications: "",
    problemListAndHistory: "",
    physicalExam: "",
    subjective: "",
    objective: "",
    assessment: "",
    plan: "",
  });

  const appointment = {
    date: searchParams.get("date") || "N/A",
    time: searchParams.get("time") || "N/A",
    provider: searchParams.get("provider") || "N/A",
    location: searchParams.get("location") || "N/A",
    reason: searchParams.get("reason") || "N/A",
  };

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);

      const [{ data: ehrData }, { data: transcriptData }] = await Promise.all([
        supabase
          .from("ehr_forms")
          .select("*")
          .order("created_at", { ascending: false })
          .limit(1)
          .single(),
        supabase
          .from("transcriptions")
          .select("*")
          .order("created_at", { ascending: false })
          .limit(1),
      ]);

      if (ehrData) {
        setEHR({
          chaperoneDocumentation: ehrData.chaperone_documentation || "",
          vitalsAndSmokingStatus: ehrData.vitals_and_smoking_status || "",
          chiefComplaint: ehrData.chief_complaint || "",
          allergies: ehrData.allergies || "",
          currentMedications: ehrData.current_medications || "",
          problemListAndHistory: ehrData.problem_list_history || "",
          physicalExam: ehrData.physical_exam || "",
          subjective: ehrData.subjective || "",
          objective: ehrData.objective || "",
          assessment: ehrData.assessment || "",
          plan: ehrData.plan || "",
        });
      }

      if (transcriptData?.length) {
        setTranscript(transcriptData[0].transcription_text || "");
      }

      setLoading(false);
    };

    fetchData();
  }, [supabase]);

  return (
    <div className="flex min-h-screen">
      <div className="w-64">
        <PatientSidebar />
      </div>
      <div className="flex-1 p-6 space-y-6">
        <h1 className="text-2xl font-bold text-gray-800">
          Appointment Details
        </h1>

        <div className="bg-white rounded-lg shadow border p-6 space-y-4 max-w-3xl">
          <section>
            <h2 className="text-lg font-semibold">Transcript</h2>
            <div className="p-4 border rounded bg-gray-50 whitespace-pre-wrap text-gray-800">
              {transcript || "No transcript found."}
            </div>
          </section>
          <section>
            <h2 className="text-lg font-semibold">Visit Info</h2>
            <p>
              <strong>Date & Time:</strong> {appointment.date} at{" "}
              {appointment.time}
            </p>
            <p>
              <strong>Provider:</strong> {appointment.provider}
            </p>
            <p>
              <strong>Location:</strong> {appointment.location}
            </p>
            <p>
              <strong>Reason:</strong> {appointment.reason}
            </p>
          </section>

          <section>
            <h2 className="text-lg font-semibold">EHR Summary</h2>
            {loading ? (
              <p className="text-gray-500">Loading EHR...</p>
            ) : (
              <>
                {Object.entries(ehr).map(([label, value]) => (
                  <div key={label}>
                    <h3 className="text-sm font-medium text-gray-600 capitalize">
                      {label.replace(/([A-Z])/g, " $1")}
                    </h3>
                    <p className="p-3 border rounded bg-gray-50 text-gray-800 whitespace-pre-wrap">
                      {value || "No data found."}
                    </p>
                  </div>
                ))}
              </>
            )}
          </section>
        </div>
      </div>
    </div>
  );
}

export default function EHRPage() {
  return (
    <Suspense fallback={<p>Loading appointment details...</p>}>
      <PatientEHRPage />
    </Suspense>
  );
}

```

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