Build an AI Postpartum Health Companion
Build a Bengali-aware AI health app with Gemini, Supabase, and Next.js.
Introduction
30 Second Summary
New mothers need personalized health guidance during their postpartum recovery, but access to nutritionists and mental health professionals can be limited or expensive. What if AI could provide culturally-relevant advice using local foods and compassionate support?
In this project, you will build MaaCare AI, a web application that uses AI to provide postpartum nutrition planning with Bangladeshi foods, mental health chatbot support, and daily health tracking for new mothers.
What You'll Build
A full-stack Next.js web application deployed on Vercel that uses the Google Gemini API for AI-powered features and Supabase for persistent data storage.
By the end of this project, you'll have:
- An AI Nutrition Planner that generates personalized meal plans using local Bangladeshi foods based on a mother's health profile.
- A Mental Health Chatbot that provides compassionate, culturally-aware emotional support and can detect signs of postpartum distress.
- A Health Tracker dashboard that logs weight, sleep, and mood data to a Supabase database and displays trends over time.
- Secret Mission: Add Bengali voice input using the Web Speech API so mothers can speak their queries instead of typing.
Are there any prerequisites?
You should have basic familiarity with JavaScript and web development concepts. No prior experience with AI APIs, databases, or Next.js is required.
Set Up Your Development Environment
MaaCare AI is built with Next.js, a full-stack framework that lets you write your frontend and backend in one project. To get started, you need a JavaScript runtime and a project scaffold on your machine.
In this step, you will install Node.js, create a new Next.js project with Tailwind CSS for styling, and install the SDKs for Google Gemini and Supabase.
In this step, get ready to:
- Install and verify Node.js on your machine.
- Create a new Next.js project with App Router and Tailwind CSS.
- Install the Google GenAI and Supabase client SDKs.
Install and verify Node.js
Node.js is a JavaScript runtime that lets you execute JavaScript outside the browser. Next.js needs it to build and serve your application.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), then type Terminal and press Enter.
- Check if Node.js is already installed by running this command:
node --version
✔️ I see version 18 or higher
You are good to go. Your Node.js version is compatible with this project.
ⓧ I see an older version
You need Node.js 18 or higher to run this project. The current LTS versions are 22 and 24.
- Visit the official Node.js downloads page at https://nodejs.org.
- Download and install the latest LTS version.
- Close and reopen your terminal, then run node --version again to confirm it shows 18 or higher.
ⓧ Command not found
Node.js is not installed yet. The current LTS versions are 22 and 24.
- Visit the official Node.js downloads page at https://nodejs.org.
- Download and install the latest LTS version for your operating system.
- Close and reopen your terminal after the installation completes.
- Run node --version to verify you see a version number of 18 or higher.
Create the Next.js project
Next.js provides a scaffolding tool called create-next-app that generates a ready-to-use project structure with all the configuration you need.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the MaaCare AI project by running this command:
npx create-next-app@latest maacare-ai
What will this command ask me?
The create-next-app wizard will first ask if you want to use the recommended defaults. Select Yes, use recommended defaults. This automatically sets up TypeScript, ESLint, Tailwind CSS, and the App Router for you.
- When prompted, select Yes, use recommended defaults and press Enter.
After the wizard finishes, it creates a maacare-ai folder on your Desktop with all the starter files installed.
- Move into your new project folder by running:
cd maacare-ai
- Open the project in your code editor. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code (or Cursor if you use Cursor) and press Enter to open it.
- In your editor, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the maacare-ai folder, and click Open.
Don't see the maacare-ai folder?
Make sure the npx create-next-app@latest maacare-ai command finished without errors. Look for a success message in your terminal output. If you see a permissions error, try running the command again.
help me troubleshoot why my Next.js project was not created
Install dependencies and set up initial files
The scaffolded project already has Next.js and React installed. You also need the Google GenAI SDK for AI features and the Supabase client for database access.
- Install both SDKs by running this command in your terminal (make sure you are inside the maacare-ai folder):
npm install @google/genai @supabase/supabase-js
What are these packages?
@google/genai is the official Google GenAI SDK that lets you call the Gemini API from your server code. @supabase/supabase-js is the client library for Supabase, an open-source database platform.
- Verify the install succeeded by running:
ls node_modules/@google/genai
You should see files listed in the output, confirming the package installed correctly.
Seeing permission errors or install failures?
If npm install fails, make sure you are inside the maacare-ai folder (run pwd to check). Also confirm your Node.js version is 18 or higher with node --version.
help me fix my npm install error in the maacare-ai project
Now you need to customize the scaffolded files. The default create-next-app template includes demo content that you will replace with the MaaCare AI homepage.
- In your editor's sidebar, expand the app folder and open globals.css.
- Delete all the existing content in globals.css and replace it with the following:
@import "tailwindcss";
Why only one line?
Tailwind CSS 4 uses the @import "tailwindcss" syntax to load all of Tailwind's utility classes. The default scaffold includes extra demo styles you do not need.
- Open app/layout.tsx in your editor.
- Delete all the existing content and replace it with the following code:
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "MaaCare AI - Postpartum Health Companion",
description: "AI-powered postpartum health support for new mothers in Bangladesh",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="bg-pink-50 min-h-screen">
{children}
</body>
</html>
);
}
What does this layout do?
The root layout wraps every page in your app. It sets a soft pink background (bg-pink-50) and ensures the page fills the full screen height (min-h-screen).
The metadata export tells search engines and browser tabs what your app is called.
- Open app/page.tsx in your editor.
- Delete all the existing content and replace it with the following code:
import Link from "next/link";
export default function Home() {
return (
<main className="max-w-4xl mx-auto p-6">
<header className="text-center py-8">
<h1 className="text-4xl font-bold text-pink-700">🤱 MaaCare AI</h1>
<p className="text-lg text-gray-600 mt-2">
Your AI-powered postpartum health companion
</p>
</header>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
<Link
href="/nutrition"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-green-700">🥗 Nutrition Planner</h2>
<p className="text-gray-600 mt-2">
Get personalized meal plans with local Bangladeshi foods
</p>
</Link>
<Link
href="/chat"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-purple-700">🧠 Mental Health Support</h2>
<p className="text-gray-600 mt-2">
Chat with an AI companion for emotional support
</p>
</Link>
<Link
href="/tracker"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-blue-700">📊 Health Tracker</h2>
<p className="text-gray-600 mt-2">
Log and track your daily health metrics
</p>
</Link>
</div>
</main>
);
}
What does this homepage do?
This is the main landing page for MaaCare AI. It displays a header with the app title and three navigation cards that link to the features you will build in upcoming steps: the Nutrition Planner, Mental Health Chatbot, and Health Tracker.
The Link component from Next.js handles client-side navigation between pages without a full page reload.
- Save all three files (globals.css, layout.tsx, page.tsx).
- Start the development server by running:
npm run dev
- Open your browser and navigate to http://localhost:3000.
You should see a light pink page with the MaaCare AI title and three white cards for Nutrition Planner, Mental Health Support, and Health Tracker.
Not seeing the homepage?
Make sure npm run dev is still running in your terminal without errors. Check that you saved all three files. If you see a compilation error, look at the terminal output for the file name and line number mentioned.
help me fix my Next.js dev server error
✔️ Awesome, I've got everything!
Great. Double check you have saved all your files before moving on.
ⓧ I'd like to double check the full code
Here is the full code for each file at this point. Make sure yours matches exactly.
@import "tailwindcss";
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "MaaCare AI - Postpartum Health Companion",
description: "AI-powered postpartum health support for new mothers in Bangladesh",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="bg-pink-50 min-h-screen">
{children}
</body>
</html>
);
}
import Link from "next/link";
export default function Home() {
return (
<main className="max-w-4xl mx-auto p-6">
<header className="text-center py-8">
<h1 className="text-4xl font-bold text-pink-700">🤱 MaaCare AI</h1>
<p className="text-lg text-gray-600 mt-2">
Your AI-powered postpartum health companion
</p>
</header>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
<Link
href="/nutrition"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-green-700">🥗 Nutrition Planner</h2>
<p className="text-gray-600 mt-2">
Get personalized meal plans with local Bangladeshi foods
</p>
</Link>
<Link
href="/chat"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-purple-700">🧠 Mental Health Support</h2>
<p className="text-gray-600 mt-2">
Chat with an AI companion for emotional support
</p>
</Link>
<Link
href="/tracker"
className="block p-6 bg-white rounded-xl shadow-md hover:shadow-lg transition-shadow"
>
<h2 className="text-xl font-semibold text-blue-700">📊 Health Tracker</h2>
<p className="text-gray-600 mt-2">
Log and track your daily health metrics
</p>
</Link>
</div>
</main>
);
}
Your development environment is ready and the MaaCare AI homepage is live on your machine. Next up, you will get your free API keys from Google AI Studio and Supabase so the AI features have something to talk to.
Get Your Free API Keys
Your Next.js project is scaffolded and ready to go. Before you build the AI features, you need credentials that connect your app to external services.
In this step, you'll create free API keys for Google Gemini (the AI engine) and Supabase (the database). Then you'll wire them into your project as environment variables and helper files.
In this step, get ready to:
- Create a Google AI Studio API key for Gemini access.
- Set up a Supabase project and get your database credentials.
- Configure environment variables and create helper files.
Create a Google AI Studio API key
Google AI Studio gives you free access to the Gemini API. No credit card is required, and the free tier provides 1,500 requests per day.
- Open your browser and navigate to aistudio.google.com.
- Sign in with your Google account (any personal Gmail works).
- In the left sidebar, click Get API Key.
- Click Create API Key.
- If prompted to select a project, choose the default project that Google AI Studio created for you.
- Copy your new API key and paste it somewhere safe (a text file or notes app). You will need it shortly.
Why is this free?
Google offers the Gemini API free tier with 1,500 requests per day, 15 requests per minute, and 1 million tokens per minute. This project stays well within those limits.
Seeing a permissions error?
If you see a message about not having permission to create a key, make sure you are signed in with a personal Google account (not a work or school account). Organization-managed accounts may not have the required permissions.
Try creating a new project that is not associated with an organization to generate your keys.
help me fix my Google AI Studio API key permissions error
Create a Supabase project
Supabase provides a free hosted PostgreSQL database with a generous free plan (500 MB storage, 50,000 monthly active users). Your health tracker data will live here.
- Open your browser and navigate to supabase.com.
- Click Start your project and sign up with your GitHub account (or email).
- Once signed in, click New Project.
- Enter maacare-ai as the project name.
- Set a strong database password (save this somewhere safe).
- Choose the region closest to you.
- Click Create new project.
- Wait for your project to finish setting up (this takes about 1 minute).
Once your project is ready, you need to grab the project URL and anon key.
- In your Supabase project dashboard, go to Settings in the left sidebar.
- Click API Keys.
- Under the Legacy API Keys tab, find and copy the anon key. Paste it somewhere safe.
- Your project URL is displayed at the top of the page. It looks like https://abcdefg.supabase.co. Copy it as well.
What is Supabase?
Supabase is an open-source backend-as-a-service built on PostgreSQL. It gives you a real database, authentication, and auto-generated APIs. The free plan includes 500 MB of database storage and pauses after 1 week of inactivity.
Can't find your API keys?
You can also find your project URL and keys from the project's Connect dialog (the button near the top of your project dashboard).
help me find my Supabase project URL and anon key
Set up environment variables and helper files
Your app needs to read these credentials at runtime without exposing them in source code. Next.js uses a .env.local file for this purpose.
- In VS Code, create a new file called .env.local in the root of your maacare-ai project folder (the same level as package.json).
- Add the following environment variables, replacing each placeholder with your actual credentials:
GEMINI_API_KEY=your-api-key-here
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url-here
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key-here
What does this file do?
.env.local stores secrets that Next.js loads at runtime. Variables prefixed with NEXT_PUBLIC_ are exposed to the browser (safe for the Supabase client). Variables without the prefix (like GEMINI_API_KEY) are only available on the server side.
This file is already in your .gitignore by default, so your secrets won't be committed to Git.
- Replace your-api-key-here with the Gemini API key you copied earlier.
- Replace your-supabase-url-here with your Supabase project URL.
- Replace your-supabase-anon-key-here with your Supabase anon key.
- Save .env.local.
Now you'll create helper files that initialize the Gemini and Supabase clients. These keep your setup code in one place so every feature can import them.
- Create a new folder called lib in the root of your project (same level as the app folder).
- Inside lib, create a file called gemini.ts and add the following code:
import { GoogleGenAI } from "@google/genai";
// Initialize the Gemini AI client with the server-side API key
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
export { ai };
What does this code do?
This imports the GoogleGenAI class from the @google/genai package and creates a single client instance using your API key. Every AI feature in the app will import ai from this file instead of duplicating the setup.
- Inside lib, create another file called supabase.ts and add the following code:
import { createClient } from "@supabase/supabase-js";
// Read Supabase credentials from environment variables
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
What does this code do?
This creates a Supabase client using your project URL and anon key. The ! after each variable tells TypeScript these values will always be defined (since they come from your .env.local). All database operations will use this single client instance.
- Save both lib/gemini.ts and lib/supabase.ts.
- Confirm both files exist by checking your file explorer in VS Code. You should see a lib folder containing gemini.ts and supabase.ts.
Seeing red squiggly lines in the imports?
If TypeScript shows errors on the import lines, make sure you ran npm install in the previous step. The packages @google/genai and @supabase/supabase-js should both be listed in your package.json dependencies.
help me fix TypeScript import errors for @google/genai or @supabase/supabase-js
✔️ Awesome, I've got everything!
Great. Double check that you have saved all three files: .env.local, lib/gemini.ts, and lib/supabase.ts.
ⓧ I'd like to double check the full code
Here is the full content of each new file:
GEMINI_API_KEY=your-api-key-here
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url-here
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key-here
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
export { ai };
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
Your credentials are wired up and your helper files are ready. Next up, you'll build the Supabase database schema that stores the health tracking data.
Build the Supabase Database Schema
Your app needs a place to store health data that persists between sessions. Without a database, any logged weight, sleep, or mood information disappears the moment the browser closes.
In this step, you will create two tables in your Supabase database using SQL. You will also enable Row Level Security to protect the data.
In this step, get ready to:
- Create the health_logs table for daily tracking data.
- Create the meal_plans table for storing AI-generated nutrition plans.
- Enable Row Level Security and configure access policies.
Create the health_logs table
The health_logs table stores daily entries for weight, sleep hours, and mood. Each row represents one day of tracked health data.
- Return to the Supabase dashboard in your browser.
- Click SQL Editor in the left sidebar.
- Clear any existing text in the editor, then paste the following SQL:
-- Create a table to store daily health metrics
CREATE TABLE health_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
date DATE NOT NULL,
weight NUMERIC,
sleep_hours NUMERIC,
mood INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
- Click Run (or press Cmd+Enter on macOS, Ctrl+Enter on Windows) to execute the query.
What does this SQL do?
This creates a health_logs table with a unique ID for each entry, a required date, numeric fields for weight and sleep hours, an integer mood rating, and an automatic timestamp for when the record was created.
The gen_random_uuid() function generates a unique identifier for each row automatically. NOW() captures the exact moment the record is inserted.
- You should see a success message at the bottom of the SQL Editor confirming the query ran.
Seeing an error?
Make sure you copied the entire SQL block including the closing semicolon. If you see a "relation already exists" error, the table was already created from a previous attempt and you can move on.
Need more help? Why is my CREATE TABLE query failing in Supabase?
Create the meal_plans table
The meal_plans table gives you a place to store AI-generated nutrition plans for future reference.
- Clear the SQL Editor, then paste this SQL:
-- Create a table to store AI-generated meal plans
CREATE TABLE meal_plans (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW()
);
- Click Run to execute the query.
- You should see a success message confirming the table was created.
Why is this table so simple?
For this project, the meal_plans table stores a minimal record with a unique ID and timestamp. The AI generates meal plans on the fly via the API route, so you only need a lightweight table to track when plans were created.
Enable Row Level Security and add policies
Row Level Security (RLS) controls who can read or write data in your tables. Without it, your tables are wide open through the Supabase API.
- Clear the SQL Editor, then paste this SQL to enable RLS and create access policies:
-- Enable Row Level Security on both tables
ALTER TABLE health_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE meal_plans ENABLE ROW LEVEL SECURITY;
-- Allow public read access to health_logs
CREATE POLICY "Allow public select" ON health_logs FOR SELECT USING (true);
-- Allow public insert access to health_logs
CREATE POLICY "Allow public insert" ON health_logs FOR INSERT WITH CHECK (true);
-- Allow public read access to meal_plans
CREATE POLICY "Allow public select" ON meal_plans FOR SELECT USING (true);
-- Allow public insert access to meal_plans
CREATE POLICY "Allow public insert" ON meal_plans FOR INSERT WITH CHECK (true);
- Click Run to execute the query.
- You should see a success message confirming all policies were created.
What is Row Level Security?
RLS is a PostgreSQL feature that lets you define rules for who can access each row in a table. Once enabled, all access is denied by default until you create policies that explicitly allow it.
The policies above allow anyone to read and insert data. In a production app, you would restrict these to authenticated users only. For this demo project, public access keeps things simple.
- Click Table Editor in the left sidebar to verify your tables.
- You should see both health_logs and meal_plans listed in the table list.
Tables not showing in Table Editor?
Make sure you are viewing the public schema (this is the default). If you accidentally created the tables in a different schema, re-run the SQL above.
Still stuck? Help me find my tables in the Supabase Table Editor
Your database is ready to store health data. Next up, you will build the AI Nutrition Planner that generates personalized meal plans using Gemini.
Build the AI Nutrition Planner
Your database is ready to store health data. Now it is time to build the first AI-powered feature of MaaCare AI.
The Nutrition Planner takes a mother's health profile and sends it to Gemini with a carefully crafted system instruction. The system instruction tells Gemini to act as a Bangladeshi postpartum nutritionist and respond with locally available foods.
In this step, get ready to:
- Create a server-side API route that calls Gemini with a postpartum nutrition system instruction.
- Build a frontend form where mothers input their health profile.
- Display the AI-generated meal plan with local Bangladeshi food recommendations.
Create the nutrition API route
In Next.js App Router, API routes live inside the app/api/ directory. Each folder becomes an endpoint, and the route.ts file inside it handles HTTP requests.
- In your code editor, create a new folder at app/api/nutrition/ inside your maacare-ai project.
- Create a new file called route.ts inside the app/api/nutrition/ folder.
- Add the imports and system instruction by pasting this code into route.ts:
import { ai } from "@/lib/gemini";
import { NextRequest, NextResponse } from "next/server";
const SYSTEM_INSTRUCTION = `You are a certified nutritionist specializing in postpartum care for mothers in Bangladesh.
Your role is to create personalized meal plans using locally available Bangladeshi foods.
Guidelines:
- Recommend foods rich in iron, calcium, protein, and omega-3 for recovery and breastfeeding
- Use familiar Bangladeshi foods: rice, dal, fish (ilish, rui, pangash), chicken, eggs, seasonal vegetables (pui shak, lal shak, palang shak), fruits (banana, papaya, guava)
- Include traditional postpartum foods like holud doodh (turmeric milk), ajwain water, and ginger tea
- Consider dietary restrictions and allergies
- Provide meals for breakfast, lunch, dinner, and 2 snacks
- Include approximate calorie counts
- Write food names in both Bengali and English
- Be encouraging and supportive in tone
Respond in a structured format with clear meal sections.`;
What is a system instruction?
A system instruction tells the AI model how to behave before the user even sends a message. It sets the persona, rules, and output format.
Here, the SYSTEM_INSTRUCTION constant tells Gemini to act as a Bangladeshi nutritionist, use local foods, and structure the response as a full day's meal plan.
- Add the POST handler function below the SYSTEM_INSTRUCTION constant in the same file:
export async function POST(request: NextRequest) {
try {
const { weeksPostpartum, isBreastfeeding, restrictions, weight } =
await request.json();
const prompt = `Create a daily meal plan for a mother who is ${weeksPostpartum} weeks postpartum.
Breastfeeding: ${isBreastfeeding ? "Yes" : "No"}.
Current weight: ${weight} kg.
Dietary restrictions: ${restrictions || "None"}.
Please provide a complete day's meal plan with local Bangladeshi foods.`;
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: prompt,
config: {
systemInstruction: SYSTEM_INSTRUCTION,
},
});
return NextResponse.json({ mealPlan: response.text });
} catch (error) {
console.error("Nutrition API error:", error);
return NextResponse.json(
{ error: "Failed to generate meal plan" },
{ status: 500 }
);
}
}
What does this code do?
The POST function extracts the mother's health profile from the request body and builds a prompt string that includes her specific details.
It then calls ai.models.generateContent with the gemini-2.5-flash model, passing both the prompt and the system instruction. The response text is returned as JSON.
- Save app/api/nutrition/route.ts.
Seeing a red underline on the import?
Make sure lib/gemini.ts exists from the previous step. The @/lib/gemini path uses the @ alias that Next.js configures automatically for the project root.
help me fix my import error in route.ts
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
import { ai } from "@/lib/gemini";
import { NextRequest, NextResponse } from "next/server";
const SYSTEM_INSTRUCTION = `You are a certified nutritionist specializing in postpartum care for mothers in Bangladesh.
Your role is to create personalized meal plans using locally available Bangladeshi foods.
Guidelines:
- Recommend foods rich in iron, calcium, protein, and omega-3 for recovery and breastfeeding
- Use familiar Bangladeshi foods: rice, dal, fish (ilish, rui, pangash), chicken, eggs, seasonal vegetables (pui shak, lal shak, palang shak), fruits (banana, papaya, guava)
- Include traditional postpartum foods like holud doodh (turmeric milk), ajwain water, and ginger tea
- Consider dietary restrictions and allergies
- Provide meals for breakfast, lunch, dinner, and 2 snacks
- Include approximate calorie counts
- Write food names in both Bengali and English
- Be encouraging and supportive in tone
Respond in a structured format with clear meal sections.`;
export async function POST(request: NextRequest) {
try {
const { weeksPostpartum, isBreastfeeding, restrictions, weight } =
await request.json();
const prompt = `Create a daily meal plan for a mother who is ${weeksPostpartum} weeks postpartum.
Breastfeeding: ${isBreastfeeding ? "Yes" : "No"}.
Current weight: ${weight} kg.
Dietary restrictions: ${restrictions || "None"}.
Please provide a complete day's meal plan with local Bangladeshi foods.`;
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: prompt,
config: {
systemInstruction: SYSTEM_INSTRUCTION,
},
});
return NextResponse.json({ mealPlan: response.text });
} catch (error) {
console.error("Nutrition API error:", error);
return NextResponse.json(
{ error: "Failed to generate meal plan" },
{ status: 500 }
);
}
}
Build the nutrition page with a health profile form
The API route is ready. Now you need a frontend page where mothers can enter their health details and see the generated meal plan.
- Create a new folder at app/nutrition/ inside your maacare-ai project.
- Create a new file called page.tsx inside the app/nutrition/ folder.
- Add the full nutrition page component by pasting this code into page.tsx:
"use client";
import { useState } from "react";
import Link from "next/link";
export default function NutritionPage() {
const [weeksPostpartum, setWeeksPostpartum] = useState("");
const [isBreastfeeding, setIsBreastfeeding] = useState(true);
const [restrictions, setRestrictions] = useState("");
const [weight, setWeight] = useState("");
const [mealPlan, setMealPlan] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setMealPlan("");
try {
const response = await fetch("/api/nutrition", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
weeksPostpartum: Number(weeksPostpartum),
isBreastfeeding,
restrictions,
weight: Number(weight),
}),
});
const data = await response.json();
if (data.mealPlan) {
setMealPlan(data.mealPlan);
} else {
setMealPlan("Sorry, could not generate a meal plan. Please try again.");
}
} catch {
setMealPlan("An error occurred. Please try again.");
} finally {
setLoading(false);
}
}
return (
<main className="max-w-2xl mx-auto p-6">
<Link href="/" className="text-pink-600 hover:underline">
\u2190 Back to Home
</Link>
<h1 className="text-3xl font-bold text-green-700 mt-4">
\ud83e\udd57 AI Nutrition Planner
</h1>
<p className="text-gray-600 mt-2">
Get a personalized meal plan with local Bangladeshi foods for your postpartum recovery.
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Weeks Postpartum
</label>
<input
type="number"
value={weeksPostpartum}
onChange={(e) => setWeeksPostpartum(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 4"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Current Weight (kg)
</label>
<input
type="number"
value={weight}
onChange={(e) => setWeight(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 65"
required
/>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={isBreastfeeding}
onChange={(e) => setIsBreastfeeding(e.target.checked)}
className="rounded"
/>
<span className="text-sm text-gray-700">Currently breastfeeding</span>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Dietary Restrictions (optional)
</label>
<input
type="text"
value={restrictions}
onChange={(e) => setRestrictions(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., no shellfish, lactose intolerant"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
{loading ? "Generating meal plan..." : "Generate Meal Plan"}
</button>
</form>
{mealPlan && (
<div className="mt-6 p-4 bg-white rounded-xl shadow">
<h2 className="text-lg font-semibold text-green-700 mb-2">
Your Personalized Meal Plan
</h2>
<div className="whitespace-pre-wrap text-gray-700">{mealPlan}</div>
</div>
)}
</main>
);
}
What does this page do?
The "use client" directive marks this as a client component so it can use React state and event handlers.
The form collects four inputs: weeks postpartum, weight, breastfeeding status, and dietary restrictions. When submitted, handleSubmit sends these to your /api/nutrition endpoint and displays the returned meal plan below the form.
- Save app/nutrition/page.tsx.
- Return to your terminal and start the dev server by running:
npm run dev
- Open your browser and navigate to http://localhost:3000/nutrition.
- You should see the AI Nutrition Planner form with fields for weeks postpartum, weight, a breastfeeding checkbox, and dietary restrictions.
- Fill in the form with sample values (e.g. 4 weeks, 65 kg, breastfeeding checked) and click Generate Meal Plan.
- After a few seconds, a personalized meal plan with Bangladeshi foods should appear below the form.
Not seeing a meal plan?
Check that your GEMINI_API_KEY in .env.local is correct. You may need to restart the dev server after editing environment variables.
Also confirm that the file is saved at app/api/nutrition/route.ts (not app/nutrition/route.ts). The api folder is required for API routes.
help me debug why my nutrition API is not returning a meal plan
✔️ Awesome, I've got everything!
Great. Double check you have saved both files before moving on.
ⓧ I'd like to double check the full code
"use client";
import { useState } from "react";
import Link from "next/link";
export default function NutritionPage() {
const [weeksPostpartum, setWeeksPostpartum] = useState("");
const [isBreastfeeding, setIsBreastfeeding] = useState(true);
const [restrictions, setRestrictions] = useState("");
const [weight, setWeight] = useState("");
const [mealPlan, setMealPlan] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setMealPlan("");
try {
const response = await fetch("/api/nutrition", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
weeksPostpartum: Number(weeksPostpartum),
isBreastfeeding,
restrictions,
weight: Number(weight),
}),
});
const data = await response.json();
if (data.mealPlan) {
setMealPlan(data.mealPlan);
} else {
setMealPlan("Sorry, could not generate a meal plan. Please try again.");
}
} catch {
setMealPlan("An error occurred. Please try again.");
} finally {
setLoading(false);
}
}
return (
<main className="max-w-2xl mx-auto p-6">
<Link href="/" className="text-pink-600 hover:underline">
\u2190 Back to Home
</Link>
<h1 className="text-3xl font-bold text-green-700 mt-4">
\ud83e\udd57 AI Nutrition Planner
</h1>
<p className="text-gray-600 mt-2">
Get a personalized meal plan with local Bangladeshi foods for your postpartum recovery.
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Weeks Postpartum
</label>
<input
type="number"
value={weeksPostpartum}
onChange={(e) => setWeeksPostpartum(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 4"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Current Weight (kg)
</label>
<input
type="number"
value={weight}
onChange={(e) => setWeight(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 65"
required
/>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={isBreastfeeding}
onChange={(e) => setIsBreastfeeding(e.target.checked)}
className="rounded"
/>
<span className="text-sm text-gray-700">Currently breastfeeding</span>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Dietary Restrictions (optional)
</label>
<input
type="text"
value={restrictions}
onChange={(e) => setRestrictions(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., no shellfish, lactose intolerant"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
{loading ? "Generating meal plan..." : "Generate Meal Plan"}
</button>
</form>
{mealPlan && (
<div className="mt-6 p-4 bg-white rounded-xl shadow">
<h2 className="text-lg font-semibold text-green-700 mb-2">
Your Personalized Meal Plan
</h2>
<div className="whitespace-pre-wrap text-gray-700">{mealPlan}</div>
</div>
)}
</main>
);
}
Your first AI feature is live. Next up, you will build the Mental Health Chatbot so mothers can have a compassionate conversation with MaaCare.
Build the Mental Health Chatbot
Your nutrition planner is generating personalized meal plans using Gemini. Now it is time to build the second AI feature: a mental health chatbot that provides compassionate support for postpartum mothers.
Postpartum mental health challenges are common but often go unaddressed. Your chatbot will use Gemini's multi-turn conversation API to maintain context across messages, gently screen for signs of distress, and respond with culturally sensitive guidance.
In this step, get ready to:
- Create an API route that manages multi-turn chat conversations with Gemini.
- Build a real-time chat interface with message history.
- Add mood assessment screening through the AI's system instruction.
Create the chat API route
Unlike the nutrition planner which handles single requests, the chatbot needs to remember previous messages in the conversation. Gemini's chat API handles this by accepting a history of past messages alongside each new one.
- Create a new file at app/api/chat/route.ts inside your maacare-ai project.
- Add the imports and system instruction by pasting this code:
import { ai } from "@/lib/gemini";
import { NextRequest, NextResponse } from "next/server";
const SYSTEM_INSTRUCTION = `You are a compassionate and supportive AI health companion for new mothers in Bangladesh.
Your name is MaaCare.
Your role:
- Provide emotional support and encouragement for postpartum mothers
- Listen with empathy and validate their feelings
- Gently screen for signs of postpartum depression (persistent sadness, loss of interest, difficulty bonding with baby, severe anxiety, thoughts of self-harm)
- If you detect concerning signs, kindly suggest seeking professional help and provide the Bangladesh mental health helpline: Kaan Pete Roi (1800-599-0019)
- Share practical coping tips for common postpartum challenges (sleep deprivation, body image concerns, breastfeeding difficulties)
- Respond in English but understand Bengali text input
- Never diagnose or prescribe medication
- Be warm, non-judgmental, and culturally sensitive to Bangladeshi family dynamics
- Keep responses concise (2-3 paragraphs max)
Important: You are NOT a replacement for professional mental health care. Always encourage mothers to speak with a doctor if they are struggling.`;
What does this system instruction do?
The system instruction defines MaaCare's personality and boundaries. It tells Gemini to be empathetic, culturally aware, and concise.
The instruction also includes mood screening guidelines. If a mother describes persistent sadness or thoughts of self-harm, MaaCare will gently suggest professional help and provide the Kaan Pete Roi helpline number. This is prompt engineering in action.
- Below the system instruction, add the Message interface and POST handler:
interface Message {
role: "user" | "model";
parts: { text: string }[];
}
export async function POST(request: NextRequest) {
try {
const { message, history } = await request.json();
// Convert frontend message format to Gemini's expected format
const chatHistory: Message[] = (history || []).map(
(msg: { role: string; content: string }) => ({
role: msg.role === "user" ? "user" : "model",
parts: [{ text: msg.content }],
})
);
// Create a chat session with conversation history
const chat = ai.chats.create({
model: "gemini-2.5-flash",
history: chatHistory,
config: {
systemInstruction: SYSTEM_INSTRUCTION,
},
});
// Send the new message and get a response
const response = await chat.sendMessage({ message });
return NextResponse.json({ reply: response.text });
} catch (error) {
console.error("Chat API error:", error);
return NextResponse.json(
{ error: "Failed to get response" },
{ status: 500 }
);
}
}
What does this code do?
- The Message interface defines the shape Gemini expects: each message has a role and parts array.
- The handler maps the frontend's simpler format (role + content) into Gemini's format (role + parts array).
- The ai.chats.create method creates a multi-turn chat session. Gemini uses the history to understand context from earlier messages.
- The chat.sendMessage({ message }) call sends the user's new message and returns the AI's response.
Why multi-turn chat instead of single requests?
The nutrition planner uses single-shot requests because each meal plan is independent. But a mental health chatbot needs to remember what the mother said earlier in the conversation. Multi-turn chat lets MaaCare build rapport and notice patterns across multiple messages.
- Save app/api/chat/route.ts.
Seeing a red underline on the import?
If your editor shows an error on @/lib/gemini, make sure your dev server is running. The @/ path alias is configured by Next.js in tsconfig.json and resolves when the project is active.
Also confirm that lib/gemini.ts exists from the earlier step.
Need more help? Why is my @/lib/gemini import showing an error in my chat route?
✔️ Awesome, I've got everything!
Great. Double check you have saved app/api/chat/route.ts before moving on.
ⓧ I'd like to double check the full code
import { ai } from "@/lib/gemini";
import { NextRequest, NextResponse } from "next/server";
const SYSTEM_INSTRUCTION = `You are a compassionate and supportive AI health companion for new mothers in Bangladesh.
Your name is MaaCare.
Your role:
- Provide emotional support and encouragement for postpartum mothers
- Listen with empathy and validate their feelings
- Gently screen for signs of postpartum depression (persistent sadness, loss of interest, difficulty bonding with baby, severe anxiety, thoughts of self-harm)
- If you detect concerning signs, kindly suggest seeking professional help and provide the Bangladesh mental health helpline: Kaan Pete Roi (1800-599-0019)
- Share practical coping tips for common postpartum challenges (sleep deprivation, body image concerns, breastfeeding difficulties)
- Respond in English but understand Bengali text input
- Never diagnose or prescribe medication
- Be warm, non-judgmental, and culturally sensitive to Bangladeshi family dynamics
- Keep responses concise (2-3 paragraphs max)
Important: You are NOT a replacement for professional mental health care. Always encourage mothers to speak with a doctor if they are struggling.`;
interface Message {
role: "user" | "model";
parts: { text: string }[];
}
export async function POST(request: NextRequest) {
try {
const { message, history } = await request.json();
const chatHistory: Message[] = (history || []).map(
(msg: { role: string; content: string }) => ({
role: msg.role === "user" ? "user" : "model",
parts: [{ text: msg.content }],
})
);
const chat = ai.chats.create({
model: "gemini-2.5-flash",
history: chatHistory,
config: {
systemInstruction: SYSTEM_INSTRUCTION,
},
});
const response = await chat.sendMessage({ message });
return NextResponse.json({ reply: response.text });
} catch (error) {
console.error("Chat API error:", error);
return NextResponse.json(
{ error: "Failed to get response" },
{ status: 500 }
);
}
}
Build the chat interface
The API route is ready. Now you need a chat interface where mothers can type messages and see MaaCare's responses in real time.
- Create a new file at app/chat/page.tsx inside your maacare-ai project.
- Paste the following code to build the full chat page:
"use client";
import { useState } from "react";
import Link from "next/link";
// Define the shape of each chat message
interface ChatMessage {
role: "user" | "model";
content: string;
}
export default function ChatPage() {
// Track all messages, the current input, and loading state
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
async function handleSend(e: React.FormEvent) {
e.preventDefault();
if (!input.trim() || loading) return;
// Add the user's message to the chat immediately
const userMessage: ChatMessage = { role: "user", content: input };
const updatedMessages = [...messages, userMessage];
setMessages(updatedMessages);
setInput("");
setLoading(true);
try {
// Send the message and full history to the API
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: input,
history: messages,
}),
});
const data = await response.json();
const botMessage: ChatMessage = {
role: "model",
content: data.reply || "Sorry, I could not respond. Please try again.",
};
setMessages([...updatedMessages, botMessage]);
} catch {
setMessages([
...updatedMessages,
{ role: "model", content: "An error occurred. Please try again." },
]);
} finally {
setLoading(false);
}
}
return (
<main className="max-w-2xl mx-auto p-6 flex flex-col h-screen">
<Link href="/" className="text-pink-600 hover:underline">
\u2190 Back to Home
</Link>
<h1 className="text-3xl font-bold text-purple-700 mt-4">
\ud83e\udde0 Mental Health Support
</h1>
<p className="text-gray-600 mt-2">
Chat with MaaCare for emotional support and guidance.
</p>
{/* Message display area */}
<div className="flex-1 overflow-y-auto mt-4 space-y-3">
{messages.length === 0 && (
<div className="text-center text-gray-400 mt-8">
<p>Hi! I am MaaCare, your postpartum health companion. \ud83d\udc9c</p>
<p className="mt-2">How are you feeling today?</p>
</div>
)}
{messages.map((msg, i) => (
<div
key={i}
className={`p-3 rounded-lg max-w-[80%] ${
msg.role === "user"
? "bg-purple-100 ml-auto text-right"
: "bg-white shadow"
}`}
>
<p className="text-sm text-gray-700 whitespace-pre-wrap">
{msg.content}
</p>
</div>
))}
{loading && (
<div className="bg-white shadow p-3 rounded-lg max-w-[80%]">
<p className="text-sm text-gray-400">MaaCare is typing...</p>
</div>
)}
</div>
{/* Input form */}
<form onSubmit={handleSend} className="mt-4 flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 p-3 border rounded-lg"
placeholder="Type your message..."
disabled={loading}
/>
<button
type="submit"
disabled={loading || !input.trim()}
className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
Send
</button>
</form>
</main>
);
}
What does this code do?
- The ChatMessage interface tracks each message's role (user or model) and content text.
- The handleSend function adds the user's message to the display immediately, then sends both the new message and the full history to the API.
- Passing history: messages lets Gemini see the full conversation so it can respond in context.
- The JSX renders messages in a scrollable area with different styles for user messages (purple, right-aligned) and MaaCare responses (white, left-aligned).
- A loading indicator shows "MaaCare is typing..." while waiting for the AI response.
- Save app/chat/page.tsx.
- Open your browser and navigate to http://localhost:3000/chat.
- You should see a purple-themed chat page with a welcome message from MaaCare and a text input at the bottom.
Seeing a 404 page?
Make sure you created the file at exactly app/chat/page.tsx (not app/chat/page.ts or pages/chat.tsx). Next.js App Router uses the app/ directory and requires .tsx for files with JSX.
Also confirm your dev server is running with npm run dev.
Still stuck? My chat page at /chat shows a 404. How do I fix this?
✔️ Awesome, I've got everything!
Great. Make sure you have saved the file before testing the chat.
ⓧ I'd like to double check the full code
"use client";
import { useState } from "react";
import Link from "next/link";
interface ChatMessage {
role: "user" | "model";
content: string;
}
export default function ChatPage() {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
async function handleSend(e: React.FormEvent) {
e.preventDefault();
if (!input.trim() || loading) return;
const userMessage: ChatMessage = { role: "user", content: input };
const updatedMessages = [...messages, userMessage];
setMessages(updatedMessages);
setInput("");
setLoading(true);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: input,
history: messages,
}),
});
const data = await response.json();
const botMessage: ChatMessage = {
role: "model",
content: data.reply || "Sorry, I could not respond. Please try again.",
};
setMessages([...updatedMessages, botMessage]);
} catch {
setMessages([
...updatedMessages,
{ role: "model", content: "An error occurred. Please try again." },
]);
} finally {
setLoading(false);
}
}
return (
<main className="max-w-2xl mx-auto p-6 flex flex-col h-screen">
<Link href="/" className="text-pink-600 hover:underline">
← Back to Home
</Link>
<h1 className="text-3xl font-bold text-purple-700 mt-4">
🧠 Mental Health Support
</h1>
<p className="text-gray-600 mt-2">
Chat with MaaCare for emotional support and guidance.
</p>
<div className="flex-1 overflow-y-auto mt-4 space-y-3">
{messages.length === 0 && (
<div className="text-center text-gray-400 mt-8">
<p>Hi! I am MaaCare, your postpartum health companion. 💜</p>
<p className="mt-2">How are you feeling today?</p>
</div>
)}
{messages.map((msg, i) => (
<div
key={i}
className={`p-3 rounded-lg max-w-[80%] ${
msg.role === "user"
? "bg-purple-100 ml-auto text-right"
: "bg-white shadow"
}`}
>
<p className="text-sm text-gray-700 whitespace-pre-wrap">
{msg.content}
</p>
</div>
))}
{loading && (
<div className="bg-white shadow p-3 rounded-lg max-w-[80%]">
<p className="text-sm text-gray-400">MaaCare is typing...</p>
</div>
)}
</div>
<form onSubmit={handleSend} className="mt-4 flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 p-3 border rounded-lg"
placeholder="Type your message..."
disabled={loading}
/>
<button
type="submit"
disabled={loading || !input.trim()}
className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
Send
</button>
</form>
</main>
);
}
Test the mood assessment feature
The mood assessment is built into MaaCare's system instruction. You do not need a separate UI component for it. The AI will gently screen for distress signals based on what the mother shares in conversation.
- In the chat interface, type a message like "I have been feeling really sad and can't sleep" and press Send.
- Observe how MaaCare responds with empathy and practical coping tips.
- Send a follow-up message to test that MaaCare remembers the context of your earlier message.
How does the mood screening work?
The system instruction tells Gemini to watch for specific signs: persistent sadness, loss of interest, difficulty bonding with baby, severe anxiety, and thoughts of self-harm. When MaaCare detects these patterns, it gently suggests seeking professional help.
This approach uses prompt engineering rather than code-level sentiment analysis. The AI model handles the nuance of human language, which is especially important for sensitive mental health conversations.
- Try sending a more concerning message like "I feel like I can't bond with my baby and nothing makes me happy anymore."
- MaaCare should respond with compassion and mention the Kaan Pete Roi helpline (1800-599-0019) as a professional resource.
Multi-turn context in action
Send 3-4 messages in a row. Notice how MaaCare references things you said earlier in the conversation. This is because your handleSend function passes the full history array to the API on every request.
Your mental health chatbot is live and responding with empathy. Next up, you will build the health tracker dashboard where mothers can log and visualize their daily health data.
Build the Health Tracker Dashboard
You have two AI-powered features working. Mothers can get personalized nutrition plans and chat with MaaCare for emotional support.
Now you need a way for mothers to track their recovery over time. In this step, you will build a health tracker that logs weight, sleep, and mood data directly to your Supabase database and displays recent entries.
In this step, get ready to:
- Create a health logging form that saves data to Supabase.
- Build a recent logs visualization showing health trends.
- Connect the tracker data to the AI nutrition planner for personalized recommendations.
Create the health tracker page
The health tracker is a client-side page that talks directly to Supabase. It reads and writes to the health_logs table you created earlier.
- Create a new file at app/tracker/page.tsx inside your maacare-ai project folder.
- Paste the following code into the file:
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { supabase } from "@/lib/supabase";
// Define the shape of a health log entry from Supabase
interface HealthLog {
id: string;
date: string;
weight: number;
sleep_hours: number;
mood: number;
created_at: string;
}
export default function TrackerPage() {
// State for recent log entries
const [logs, setLogs] = useState<HealthLog[]>([]);
// Form input state
const [weight, setWeight] = useState("");
const [sleepHours, setSleepHours] = useState("");
const [mood, setMood] = useState("3");
const [saving, setSaving] = useState(false);
// Fetch recent logs when the page loads
useEffect(() => {
fetchLogs();
}, []);
// Query the last 7 days of health logs from Supabase
async function fetchLogs() {
const { data } = await supabase
.from("health_logs")
.select("*")
.order("date", { ascending: false })
.limit(7);
if (data) setLogs(data);
}
// Insert a new health log entry for today
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
const today = new Date().toISOString().split("T")[0];
const { error } = await supabase.from("health_logs").insert({
date: today,
weight: Number(weight),
sleep_hours: Number(sleepHours),
mood: Number(mood),
});
if (!error) {
setWeight("");
setSleepHours("");
setMood("3");
fetchLogs();
}
setSaving(false);
}
// Emoji representations for mood levels 1-5
const moodEmojis = ["\ud83d\ude22", "\ud83d\ude1f", "\ud83d\ude10", "\ud83d\ude42", "\ud83d\ude0a"];
return (
<main className="max-w-2xl mx-auto p-6">
<Link href="/" className="text-pink-600 hover:underline">
\u2190 Back to Home
</Link>
<h1 className="text-3xl font-bold text-blue-700 mt-4">
\ud83d\udcca Health Tracker
</h1>
<p className="text-gray-600 mt-2">
Log your daily health metrics to track your recovery.
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4 bg-white p-4 rounded-xl shadow">
<div>
<label className="block text-sm font-medium text-gray-700">
Weight (kg)
</label>
<input
type="number"
step="0.1"
value={weight}
onChange={(e) => setWeight(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 65.5"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Sleep (hours)
</label>
<input
type="number"
step="0.5"
value={sleepHours}
onChange={(e) => setSleepHours(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 6"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Mood (1-5)
</label>
<div className="flex gap-2 mt-1">
{moodEmojis.map((emoji, i) => (
<button
key={i}
type="button"
onClick={() => setMood(String(i + 1))}
className={`text-2xl p-2 rounded-lg ${
mood === String(i + 1) ? "bg-blue-100 ring-2 ring-blue-400" : ""
}`}
>
{emoji}
</button>
))}
</div>
</div>
<button
type="submit"
disabled={saving}
className="w-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{saving ? "Saving..." : "Log Today's Health"}
</button>
</form>
<div className="mt-6">
<h2 className="text-lg font-semibold text-gray-700">Recent Logs</h2>
{logs.length === 0 ? (
<p className="text-gray-400 mt-2">No logs yet. Start tracking today!</p>
) : (
<div className="mt-2 space-y-2">
{logs.map((log) => (
<div
key={log.id}
className="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm"
>
<span className="text-sm text-gray-600">{log.date}</span>
<span className="text-sm">{log.weight} kg</span>
<span className="text-sm">{log.sleep_hours}h sleep</span>
<span className="text-xl">{moodEmojis[log.mood - 1]}</span>
</div>
))}
</div>
)}
</div>
</main>
);
}
What does this code do?
This page has two main responsibilities: logging new health data and displaying recent entries.
- The HealthLog interface defines the shape of each row from the health_logs table in Supabase.
- The fetchLogs function queries the 7 most recent entries, ordered newest-first. It runs automatically when the page loads via useEffect.
- The handleSubmit function inserts a new row with today's date, resets the form, and refreshes the logs list.
- The moodEmojis array maps mood levels 1-5 to clickable emoji buttons, giving mothers a quick visual way to select their mood.
- Save app/tracker/page.tsx.
- In your browser, navigate to http://localhost:3000/tracker. You should see a form with weight, sleep, and mood inputs, plus an empty "Recent Logs" section below.
Page not loading or seeing an error?
- Make sure your dev server is still running. If you stopped it, run npm run dev again from your maacare-ai folder.
- Check that lib/supabase.ts exists and your .env.local has NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY set correctly.
- If you see a TypeScript error about the supabase import, confirm the file path is @/lib/supabase (not a relative path).
Why is my tracker page not loading?
Test the health logging form
The form writes directly to your Supabase database. Each entry gets today's date automatically.
- In the Weight (kg) field, enter a test value like 65.
- In the Sleep (hours) field, enter a test value like 6.
- Click one of the mood emoji buttons to select a mood level.
- Click Log Today's Health.
Your entry should appear instantly in the "Recent Logs" section below the form. The form fields reset after a successful save.
Log not appearing after submitting?
- Open your browser's DevTools console and check for any network errors. A 403 error means your Row Level Security policies are not configured to allow inserts.
- Return to your Supabase dashboard and verify the health_logs table has an "Allow all" policy for INSERT and SELECT operations.
Why is my Supabase insert failing with a permissions error?
Connect the tracker to your nutrition planner
Your health tracker now stores weight data over time. This is the same weight value the AI nutrition planner uses to generate personalized meal plans.
How does the tracker connect to nutrition planning?
When a mother logs her weight in the tracker, she can see her trend over the past 7 days. She can then use her most recent tracked weight when requesting a meal plan from the nutrition planner.
Both features read from and write to the same Supabase database. In a production app, you could auto-fill the nutrition planner's weight field with the most recent tracker entry. For now, the connection is manual but the shared database makes this straightforward to add later.
✔️ Awesome, I've got everything!
Great. Double check you have saved app/tracker/page.tsx and that you can log and view health entries in your browser.
ⓧ I'd like to double check the full code
Here is the complete app/tracker/page.tsx file:
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { supabase } from "@/lib/supabase";
interface HealthLog {
id: string;
date: string;
weight: number;
sleep_hours: number;
mood: number;
created_at: string;
}
export default function TrackerPage() {
const [logs, setLogs] = useState<HealthLog[]>([]);
const [weight, setWeight] = useState("");
const [sleepHours, setSleepHours] = useState("");
const [mood, setMood] = useState("3");
const [saving, setSaving] = useState(false);
useEffect(() => {
fetchLogs();
}, []);
async function fetchLogs() {
const { data } = await supabase
.from("health_logs")
.select("*")
.order("date", { ascending: false })
.limit(7);
if (data) setLogs(data);
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSaving(true);
const today = new Date().toISOString().split("T")[0];
const { error } = await supabase.from("health_logs").insert({
date: today,
weight: Number(weight),
sleep_hours: Number(sleepHours),
mood: Number(mood),
});
if (!error) {
setWeight("");
setSleepHours("");
setMood("3");
fetchLogs();
}
setSaving(false);
}
const moodEmojis = ["\ud83d\ude22", "\ud83d\ude1f", "\ud83d\ude10", "\ud83d\ude42", "\ud83d\ude0a"];
return (
<main className="max-w-2xl mx-auto p-6">
<Link href="/" className="text-pink-600 hover:underline">
\u2190 Back to Home
</Link>
<h1 className="text-3xl font-bold text-blue-700 mt-4">
\ud83d\udcca Health Tracker
</h1>
<p className="text-gray-600 mt-2">
Log your daily health metrics to track your recovery.
</p>
<form onSubmit={handleSubmit} className="mt-6 space-y-4 bg-white p-4 rounded-xl shadow">
<div>
<label className="block text-sm font-medium text-gray-700">
Weight (kg)
</label>
<input
type="number"
step="0.1"
value={weight}
onChange={(e) => setWeight(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 65.5"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Sleep (hours)
</label>
<input
type="number"
step="0.5"
value={sleepHours}
onChange={(e) => setSleepHours(e.target.value)}
className="mt-1 w-full p-2 border rounded-lg"
placeholder="e.g., 6"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">
Mood (1-5)
</label>
<div className="flex gap-2 mt-1">
{moodEmojis.map((emoji, i) => (
<button
key={i}
type="button"
onClick={() => setMood(String(i + 1))}
className={`text-2xl p-2 rounded-lg ${
mood === String(i + 1) ? "bg-blue-100 ring-2 ring-blue-400" : ""
}`}
>
{emoji}
</button>
))}
</div>
</div>
<button
type="submit"
disabled={saving}
className="w-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{saving ? "Saving..." : "Log Today's Health"}
</button>
</form>
<div className="mt-6">
<h2 className="text-lg font-semibold text-gray-700">Recent Logs</h2>
{logs.length === 0 ? (
<p className="text-gray-400 mt-2">No logs yet. Start tracking today!</p>
) : (
<div className="mt-2 space-y-2">
{logs.map((log) => (
<div
key={log.id}
className="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm"
>
<span className="text-sm text-gray-600">{log.date}</span>
<span className="text-sm">{log.weight} kg</span>
<span className="text-sm">{log.sleep_hours}h sleep</span>
<span className="text-xl">{moodEmojis[log.mood - 1]}</span>
</div>
))}
</div>
)}
</div>
</main>
);
}
All three core features of MaaCare AI are now functional locally. Next up, you will push your code to GitHub and deploy the live app to Vercel.
Deploy to Vercel
Your MaaCare AI app is fully functional on your local machine. The next step is getting it live on the internet so it has a real URL anyone can visit.
Vercel is a hosting platform built by the same team behind Next.js. It auto-detects your Next.js project, handles the build, and deploys it to a global CDN. You will push your code to GitHub first, then connect that repository to Vercel for automatic deployments.
In this step, get ready to:
- Push your code to a GitHub repository.
- Connect the repository to Vercel and configure environment variables.
- Deploy and verify all features work on the live URL.
Push your code to a GitHub repository
Your project needs to live in a GitHub repository so Vercel can pull the code and build it. The create-next-app command already initialized a Git repository in your project folder. You just need to commit your changes and push to GitHub.
- Return to your terminal and stop the development server by pressing Ctrl+C.
- Stage and commit all your project files by running these commands:
git add .
git commit -m "Add MaaCare AI features"
What do these commands do?
- git add . stages all new and modified files in your project for the next commit.
- git commit -m saves a snapshot of your staged changes with a descriptive message.
What about my API keys?
Your .env.local file is already in .gitignore by default. Next.js excludes it from Git so your secrets never get pushed to GitHub.
- Open GitHub in your browser and navigate to github.com/new to create a new repository.
- Enter maacare-ai as the repository name.
- Leave the repository set to Public (or choose Private if you prefer).
- Do NOT check any boxes for README, .gitignore, or license. You already have code to push.
- Click Create repository.
- GitHub will show you setup instructions. Copy the repository URL from the page (it looks like https://github.com/YOUR_USERNAME/maacare-ai.git).
- Back in your terminal, connect your local repository to GitHub and push your code by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/maacare-ai.git"]]
git branch -M main
git push -u origin main
What do these commands do?
- git remote add origin tells Git where your GitHub repository lives.
- git branch -M main ensures your branch is named main (the default branch Vercel deploys from).
- git push -u origin main uploads all your code to GitHub.
- Refresh your GitHub repository page in the browser. You should see all your project files listed (app/, lib/, package.json, etc.).
Push failed or asking for authentication?
GitHub requires authentication to push. If prompted, sign in with your GitHub credentials or set up a personal access token. If you see a "Permission denied" error, make sure you are signed into the correct GitHub account in your terminal.
Help me fix my GitHub push error
Connect your repository to Vercel and configure environment variables
Now that your code is on GitHub, Vercel can pull it, build it, and deploy it automatically. You will also need to add your API keys as environment variables since .env.local was not pushed to GitHub.
- Open vercel.com in your browser.
- Click Sign Up (or Log In if you already have an account). Choose Continue with GitHub to connect your GitHub account.
- Once signed in, click Add New... and select Project.
- Find maacare-ai in the repository list and click Import next to it.
What if I don't see my repository?
If your repository does not appear in the list, you may need to grant Vercel access to it. Click the link to adjust GitHub app permissions and allow access to the maacare-ai repository.
Vercel auto-detects that this is a Next.js project and pre-fills the build settings. Before deploying, you need to add the environment variables that your app depends on.
- On the project configuration page, expand the Environment Variables section.
- Add the first environment variable: enter GEMINI_API_KEY as the name and paste your actual Gemini API key as the value.
- Add a second variable: enter NEXT_PUBLIC_SUPABASE_URL as the name and paste your Supabase project URL as the value.
- Add a third variable: enter NEXT_PUBLIC_SUPABASE_ANON_KEY as the name and paste your Supabase anon key as the value.
- Click Deploy.
Where to find your keys
Your API keys are in the .env.local file you created earlier. Open it in your code editor and copy each value into the matching Vercel environment variable field.
Vercel will now build and deploy your project. You can watch the build logs stream in real time on the deployment page.
Build failing?
The most common cause is a missing or mistyped environment variable. Double-check that all three variable names match exactly (they are case-sensitive). Also ensure you pasted the values without any extra spaces.
Help me debug my Vercel build failure
Verify all features work on the live URL
Once the build succeeds, Vercel gives you a live production URL. Time to confirm everything works in the real deployment, not just on your local machine.
- On the Vercel deployment page, click Visit (or copy the production URL that looks like https://maacare-ai.vercel.app).
- You should see the MaaCare AI home page with three cards: Nutrition Planner, Mental Health Support, and Health Tracker.
- Click Nutrition Planner. Fill in some test data (e.g., 4 weeks postpartum, 65 kg) and click the generate button. Confirm the AI returns a meal plan.
- Click Back to Home, then open Mental Health Support. Type a message like "How are you?" and send it. Confirm the chatbot responds.
- Click Back to Home, then open Health Tracker. Enter a weight, sleep hours, and mood. Click the log button and confirm the entry appears in the recent logs.
Why test all three features?
Each feature uses a different service. The Nutrition Planner and Chatbot call the Gemini API (testing your GEMINI_API_KEY). The Health Tracker reads and writes to Supabase (testing your NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY). If any feature fails, you know exactly which variable to check.
Feature not working on the live site?
If the Nutrition Planner or Chatbot returns an error, go to your Vercel project settings, navigate to Environment Variables, and verify GEMINI_API_KEY is correct. If the Health Tracker fails, check NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. After fixing a variable, redeploy by clicking Redeploy from the deployments page.
Help me fix my deployed MaaCare AI features
Your MaaCare AI app is live on the internet. Anyone with the URL can access it. Every time you push new code to GitHub, Vercel automatically rebuilds and redeploys your app.
Secret mission
Bengali Voice Input
Your chatbot works great with typed messages. But many new mothers in Bangladesh may find it easier to speak than type, especially when holding a baby. Can you add a Bengali voice input feature using the Web Speech API?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses entirely free-tier services, so there are no ongoing costs.
Resources you used:
- Supabase free project (health_logs and meal_plans tables).
- Vercel Hobby deployment (your live MaaCare AI URL).
- Google AI Studio API key (used by the Gemini API).
- Local maacare-ai project folder on your Desktop.
- GitHub repository.
Keep everything running
No action needed. Choose this if you are still actively building or want to show off your project.
- All three services (Supabase, Vercel, Google AI Studio) remain on their free tiers with no charges.
- Your Supabase project will automatically pause after 1 week of inactivity. You can unpause it from the Supabase dashboard at any time.
- Your Vercel deployment stays live on the Hobby plan for non-commercial personal use.
- Your Google AI Studio API key does not expire and has no ongoing cost.
Pause - I'll come back to this later
Shut down running processes to free up local memory. Your cloud resources will remain available when you return.
- In your terminal, press Ctrl+C to stop the Next.js dev server if it is still running.
- Your Supabase project will auto-pause after 1 week of inactivity. No manual action needed.
- Your Vercel deployment and Google AI Studio key will remain active and free.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to fully clean up.
- Delete your Vercel project: open the Vercel dashboard, select your MaaCare AI project, click Settings in the sidebar, scroll to the bottom of the General page, and click Delete in the Delete Project section. Confirm by entering the project name.
- Delete your Supabase project: open the Supabase dashboard, navigate to your project's Settings page, and click Delete project. This permanently removes your database and all data.
- Delete your Google AI Studio API key: open the Google Cloud API Credentials page, find your Gemini API key, click the Actions icon next to it, and select Delete API key. Confirm deletion in the modal.
- Delete your local project files by running these commands in your terminal:
cd ~/Desktop
rm -rf maacare-ai
- Optionally, delete the GitHub repository: go to your repository page on GitHub, click Settings, scroll to the bottom, and click Delete this repository.
Nice Work!
Nice Work!
You just built and deployed an AI-powered postpartum health companion from scratch, at zero cost. MaaCare AI is live on the internet, helping new mothers with personalized nutrition planning, mental health support, and daily health tracking.
You've learned how to:
- Built a full-stack Next.js web application with two AI-powered features: a nutrition planner and a mental health chatbot.
- Integrated the Google Gemini API with culturally-relevant system instructions designed for Bangladeshi postpartum care.
- Set up a Supabase database for persistent health tracking and deployed the entire application live to Vercel.
- Secret Mission: Added Bengali voice input using the Web Speech API so mothers can speak their queries instead of typing.
Ready to quiz yourself?