Transform your health journey with cutting-edge AI that analyzes food, debunks myths, and delivers science-backed nutrition insightsβall in a beautiful, offline-first Progressive Web App.
π Live Demo: HealthyME App | π§ Developer: Salugu Harshita Bhanu
View Demo β’ Features β’ Tech Stack β’ Getting Started
- π― Project Highlights
- β¨ Key Features
- πΈ Live Screenshots
- π οΈ Tech Stack
- ποΈ Architecture
- π Getting Started
- π§ͺ Testing & Quality
- π Firebase Integration
- π¨ UI/UX Design
- π Security
- π Performance
- π’ Deployment
- π¨βπ» Developer
|
|
|
AI-Powered Food Intelligence |
Science-Backed Truth Serum |
Your Health Command Center |
|
Real-time analytics and quick actions |
Detailed nutritional insights |
|
Complete macro & micronutrient breakdown |
Smart recommendations & insights |
|
Browse popular nutrition myths |
AI-powered myth analysis with sources |
|
Explore verified myths |
Detailed myth analysis |
- Multi-Food Detection: Recognize multiple foods in a single image
- Confidence Scoring: AI provides confidence levels for each detection
- Nutritional Estimates: Get approximate nutrition based on visual portion sizes
- Recipe Recognition: Upload recipe photos and get ingredient-wise breakdown
|
Quick access to saved foods |
Complete search timeline |
Personalization options |
Framework: Next.js 16.0 (App Router)
Language: TypeScript 5.0
Styling: Tailwind CSS 4.x
Components: shadcn/ui (40+ components)
Forms: React Hook Form + Zod
Charts: Recharts 2.15
Animation: Framer Motion
State: React Hooks + Context APIWhy This Stack?
|
Database: Firebase Firestore
Authentication: Firebase Auth (Email + OAuth)
Storage: Firebase Storage
AI Engine: Google Gemini 2.0 API
ML Platform: Vertex AI
Vision: Google Cloud Vision API
Hosting: Firebase Hosting + CDNWhy Firebase?
|
|
All Tests Passing β
|
# Run all tests
npm test
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverageTechnologies:
|
GitHub Actions Workflow:
β
Automated Testing β Runs on every PR
β
Code Quality Checks β ESLint + TypeScript
β
Build Verification β Next.js production build
β
Firebase Deployment β Auto-deploy on main branch
β
Coverage Reports β Codecov integrationResult: Zero downtime deployments with automated rollback on failure π
|
Firestore Collections:
|
Real-time Insights:
|
// Firestore Rules - User data isolation
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own data
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
// Myths are public read, authenticated write
match /myths/{mythId} {
allow read: if true;
allow write: if request.auth != null;
}
}
}- Interactive Charts: Recharts-powered visualizations for macro breakdown
- Daily Tracking: Line charts showing calorie intake over time
- Goal Progress: Visual indicators for nutrition goals (protein, calories, etc.)
- Comparative Analysis: Compare foods side-by-side with bar charts
- Weekly Summaries: Aggregate nutrition data with trend analysis
- Personal Dashboard: Centralized view of all nutrition activities
- Search History: Quick access to previously searched foods
- Favorites System: Save frequently consumed foods
- Custom Goals: Set personalized calorie and macro targets
- Achievements: Unlock badges for consistent tracking
- Meal Plans: AI-suggested meal plans based on goals (future feature)
- Firebase Authentication: Secure email/password and social logins
- Email Verification: Mandatory email verification for new accounts
- Password Reset: Secure password recovery flow
- Session Management: Automatic logout on inactivity
- Role-Based Access: User/Admin roles with different permissions
- Installable: Add to home screen on mobile and desktop
- Offline Support: Service worker caching for offline functionality
- Push Notifications: Reminders for meal logging (if enabled)
- Background Sync: Queue requests when offline, sync when back online
- App-like Experience: Native app feel with smooth animations
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENT SIDE β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Next.js 14 (App Router) β β
β β ββββββββββββββ ββββββββββββββ ββββββββββββββ β β
β β β Pages β β Components β β Hooks β β β
β β ββββββββββββββ ββββββββββββββ ββββββββββββββ β β
β β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Service Worker (PWA) β β β
β β β - Offline caching β β β
β β β - Background sync β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β HTTPS
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FIREBASE BACKEND β
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Firestore β β Firebase Authβ βFirebase Storageβ β
β β (Database) β β(Authentication)β β(Image Storage) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β API Calls
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GOOGLE CLOUD AI STACK β
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Gemini API β β Vertex AI β β Vision API β β
β β(Text Analysis)β β(Advanced ML) β β(Image Recog) β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- User Input β Next.js Frontend
- Frontend β Firebase Auth (if authentication needed)
- Frontend β Gemini API / Vision API (for AI processing)
- AI Response β Frontend (display results)
- Frontend β Firestore (save to user history/favorites)
- Service Worker β Cache responses for offline access
healthy-me/
βββ src/
β βββ app/ # Next.js 14 App Router
β β βββ (auth)/ # Authentication group
β β β βββ login/ # Login page
β β β βββ register/ # Registration page
β β β βββ reset-password/ # Password reset
β β βββ (dashboard)/ # Protected routes group
β β β βββ dashboard/ # User dashboard
β β β βββ nutrition/ # Nutrition search
β β β βββ myths/ # Myth-busting interface
β β β βββ history/ # Search history
β β β βββ favorites/ # Saved favorites
β β βββ api/ # API routes
β β β βββ analyze/ # Food analysis endpoint
β β β βββ myth-bust/ # Myth verification endpoint
β β β βββ image-upload/ # Image processing endpoint
β β βββ layout.tsx # Root layout
β β βββ page.tsx # Landing page
β β βββ globals.css # Global styles
β β
β βββ components/ # React components
β β βββ ui/ # shadcn/ui base components
β β β βββ button.tsx
β β β βββ card.tsx
β β β βββ input.tsx
β β β βββ dialog.tsx
β β β βββ ... (other shadcn components)
β β βββ features/ # Feature-specific components
β β β βββ nutrition/
β β β β βββ NutritionCard.tsx
β β β β βββ MacroChart.tsx
β β β β βββ SearchBar.tsx
β β β βββ myths/
β β β β βββ MythCard.tsx
β β β β βββ SourceCitation.tsx
β β β βββ dashboard/
β β β βββ RecentSearches.tsx
β β β βββ StatsOverview.tsx
β β βββ forms/ # Form components
β β β βββ LoginForm.tsx
β β β βββ RegisterForm.tsx
β β β βββ FoodSearchForm.tsx
β β βββ layout/ # Layout components
β β βββ Header.tsx
β β βββ Sidebar.tsx
β β βββ Footer.tsx
β β
β βββ lib/ # Core utilities
β β βββ firebase/ # Firebase configuration
β β β βββ config.ts # Firebase initialization
β β β βββ auth.ts # Auth helpers
β β β βββ firestore.ts # Firestore helpers
β β β βββ storage.ts # Storage helpers
β β βββ ai/ # AI integrations
β β β βββ gemini.ts # Gemini API client
β β β βββ vertex.ts # Vertex AI client
β β β βββ vision.ts # Vision API client
β β βββ utils.ts # General utilities
β β βββ constants.ts # App constants
β β
β βββ hooks/ # Custom React hooks
β β βββ useAuth.ts # Authentication hook
β β βββ useNutrition.ts # Nutrition data hook
β β βββ useMyths.ts # Myth-busting hook
β β βββ useFirestore.ts # Firestore operations hook
β β βββ useImageUpload.ts # Image upload hook
β β
β βββ types/ # TypeScript definitions
β β βββ nutrition.ts # Nutrition data types
β β βββ user.ts # User types
β β βββ myth.ts # Myth types
β β βββ api.ts # API response types
β β
β βββ contexts/ # React contexts
β β βββ AuthContext.tsx # Auth context provider
β β βββ ThemeContext.tsx # Theme context (dark mode)
β β
β βββ middleware.ts # Next.js middleware (auth protection)
β βββ utils/ # Helper functions
β βββ validation.ts # Zod schemas
β βββ formatting.ts # Data formatting
β βββ api-helpers.ts # API utilities
β
βββ public/ # Static assets
β βββ icons/ # App icons for PWA
β βββ images/ # Static images
β βββ manifest.json # PWA manifest
β βββ sw.js # Service worker
β
βββ docs/ # Documentation
β βββ screenshots/ # App screenshots
β βββ API.md # API documentation
β βββ DEPLOYMENT.md # Deployment guide
β
βββ .env.local.example # Environment variables template
βββ .eslintrc.json # ESLint configuration
βββ .prettierrc # Prettier configuration
βββ next.config.js # Next.js configuration
βββ tailwind.config.ts # Tailwind configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json # Dependencies
βββ firebase.json # Firebase configuration
βββ README.md # This file
Before you begin, ensure you have:
- Node.js 18.0 or higher (Download)
- npm or yarn package manager
- Firebase account (Sign up)
- Google Cloud account with billing enabled (Sign up)
- Git for version control
git clone https://github.com/yourusername/healthy-me.git
cd healthy-menpm install
# or
yarn installCreate Firebase Project:
- Go to Firebase Console
- Click "Add Project" and follow the wizard
- Enable Firestore, Authentication, and Storage
Get Firebase Configuration:
- Go to Project Settings β General
- Scroll to "Your apps" section
- Click "Add app" β Web
- Copy the configuration object
Set Firestore Rules:
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users collection - users can only read/write their own data
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Nutrition history - private to user
match /nutrition_history/{userId}/entries/{entryId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Myths - public read, authenticated write
match /myths/{mythId} {
allow read: if true;
allow write: if request.auth != null;
}
}
}Deploy rules:
firebase deploy --only firestore:rulesEnable APIs:
- Go to Google Cloud Console
- Enable these APIs:
- Gemini API (Generative AI)
- Vertex AI API
- Cloud Vision API
Create API Keys:
- Go to APIs & Services β Credentials
- Click "Create Credentials" β API Key
- Restrict the key to your specific APIs
- Copy the API key
Create .env.local file in root directory:
# ===================================
# Firebase Configuration
# ===================================
NEXT_PUBLIC_FIREBASE_API_KEY=your_firebase_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
# ===================================
# Google Cloud AI Configuration
# ===================================
GEMINI_API_KEY=your_gemini_api_key
NEXT_PUBLIC_GEMINI_API_KEY=your_gemini_api_key # For client-side calls
VERTEX_AI_PROJECT_ID=your-vertex-project-id
VERTEX_AI_LOCATION=us-central1
GOOGLE_CLOUD_VISION_API_KEY=your_vision_api_key
# ===================================
# Application Configuration
# ===================================
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=development
# ===================================
# Optional: Analytics & Monitoring
# ===================================
NEXT_PUBLIC_GA_ID=your_google_analytics_id # OptionalCreate .env.local.example (for GitHub - without sensitive values):
# HealthyME - Complete Documentation
## Environment Configuration
### Copy this file to .env.local and fill in your actual values
```bash
# Firebase Configuration
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
# Google Cloud AI
GEMINI_API_KEY=
NEXT_PUBLIC_GEMINI_API_KEY=
VERTEX_AI_PROJECT_ID=
VERTEX_AI_LOCATION=us-central1
GOOGLE_CLOUD_VISION_API_KEY=
# App Configuration
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=developmentnpm run dev
# or
yarn devOpen http://localhost:3000 in your browser.
npm run build
npm start- User enters food name (e.g., "Grilled Chicken Breast")
- Frontend sends request to /api/analyze with food name
- API route calls Gemini API with structured prompt
- Gemini returns detailed nutritional breakdown
- Data is parsed, validated with Zod, and returned
- Frontend displays results with Recharts visualization
- User can save to history or favorites (stored in Firestore)
import { GoogleGenerativeAI } from "@google/generative-ai";
import { NextResponse } from "next/server";
import { z } from "zod";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
const NutritionSchema = z.object({
name: z.string(),
servingSize: z.string(),
calories: z.number(),
macros: z.object({
protein: z.number(),
carbs: z.number(),
fats: z.number(),
fiber: z.number(),
}),
vitamins: z.array(z.object({
name: z.string(),
amount: z.string(),
dailyValue: z.number(),
})),
minerals: z.array(z.object({
name: z.string(),
amount: z.string(),
dailyValue: z.number(),
})),
});
export async function POST(request: Request) {
try {
const { foodName, servingSize } = await request.json();
const model = genAI.getGenerativeModel({ model: "gemini-pro" });
const prompt = `
Provide detailed nutritional information for: ${foodName}
Serving size: ${servingSize || "100g"}
Return ONLY a valid JSON object with this exact structure:
{
"name": "food name",
"servingSize": "serving size",
"calories": number,
"macros": {
"protein": number (in grams),
"carbs": number (in grams),
"fats": number (in grams),
"fiber": number (in grams)
},
"vitamins": [
{"name": "Vitamin A", "amount": "500 IU", "dailyValue": 10}
],
"minerals": [
{"name": "Iron", "amount": "2mg", "dailyValue": 11}
]
}
Be accurate and use USDA food database values when possible.
`;
const result = await model.generateContent(prompt);
const response = await result.response;
const text = response.text();
// Clean response (remove markdown code blocks if present)
const cleanedText = text.replace(/```json\n?|\n?```/g, "").trim();
const nutritionData = JSON.parse(cleanedText);
// Validate with Zod
const validatedData = NutritionSchema.parse(nutritionData);
return NextResponse.json(validatedData);
} catch (error) {
console.error("Nutrition analysis error:", error);
return NextResponse.json(
{ error: "Failed to analyze nutrition" },
{ status: 500 }
);
}
}"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function SearchBar({ onResults }) {
const [foodName, setFoodName] = useState("");
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
setLoading(true);
try {
const response = await fetch("/api/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ foodName, servingSize: "100g" }),
});
const data = await response.json();
onResults(data);
} catch (error) {
console.error("Search error:", error);
} finally {
setLoading(false);
}
};
return (
<div className="flex gap-2">
<Input
placeholder="Enter food name..."
value={foodName}
onChange={(e) => setFoodName(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
/>
<Button onClick={handleSearch} disabled={loading}>
{loading ? "Analyzing..." : "Search"}
</Button>
</div>
);
}- User submits a myth/question (e.g., "Does eating fat make you fat?")
- Frontend sends to /api/myth-bust
- Gemini API analyzes claim against scientific consensus
- Returns verdict (True/False/Partially True) with explanation
- Includes 3-5 credible sources with citations
- Frontend displays with proper source formatting
- Saved to Firestore for community browsing
import { GoogleGenerativeAI } from "@google/generative-ai";
import { NextResponse } from "next/server";
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
export async function POST(request: Request) {
try {
const { myth } = await request.json();
const model = genAI.getGenerativeModel({ model: "gemini-pro" });
const prompt = `
As a nutrition science expert, analyze this claim: "${myth}"
Provide a response in this JSON format:
{
"verdict": "TRUE" | "FALSE" | "PARTIALLY_TRUE" | "INCONCLUSIVE",
"explanation": "Detailed 2-3 paragraph explanation",
"keyPoints": ["point 1", "point 2", "point 3"],
"sources": [
{
"title": "Study or article title",
"authors": "Author names",
"publication": "Journal or institution",
"year": 2023,
"url": "https://link-to-source.com",
"summary": "Brief summary of findings"
}
],
"recommendation": "Practical advice based on evidence"
}
Base your analysis on peer-reviewed research and scientific consensus.
Include at least 3-5 credible sources.
`;
const result = await model.generateContent(prompt);
const response = await result.response;
const text = response.text();
const cleanedText = text.replace(/```json\n?|\n?```/g, "").trim();
const mythData = JSON.parse(cleanedText);
return NextResponse.json(mythData);
} catch (error) {
console.error("Myth-busting error:", error);
return NextResponse.json(
{ error: "Failed to verify myth" },
{ status: 500 }
);
}
}- User uploads/captures food image
- Image is resized and converted to base64
- Sent to /api/image-upload
- Vision API detects food items in image
- Each detected item is sent to Gemini for nutrition analysis
- Combined results returned to frontend
- Image stored in Firebase Storage (optional)
import vision from "@google-cloud/vision";
import { GoogleGenerativeAI } from "@google/generative-ai";
import { NextResponse } from "next/server";
const visionClient = new vision.ImageAnnotatorClient({
keyFilename: process.env.GOOGLE_CLOUD_KEY_PATH,
});
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY!);
export async function POST(request: Request) {
try {
const { image } = await request.json(); // base64 image
// Step 1: Detect food items with Vision API
const [result] = await visionClient.labelDetection({
image: { content: image },
});
const labels = result.labelAnnotations || [];
const foodLabels = labels
.filter(label => label.score! > 0.7)
.map(label => label.description)
.slice(0, 5); // Top 5 detected items
// Step 2: Get nutrition for each detected item
const nutritionPromises = foodLabels.map(async (foodName) => {
const model = genAI.getGenerativeModel({ model: "gemini-pro" });
const prompt = `Provide nutritional information for: ${foodName}`;
const result = await model.generateContent(prompt);
const text = await result.response.text();
return JSON.parse(text.replace(/```json\n?|\n?```/g, "").trim());
});
const nutritionData = await Promise.all(nutritionPromises);
return NextResponse.json({
detectedFoods: foodLabels,
nutrition: nutritionData,
});
} catch (error) {
console.error("Image analysis error:", error);
return NextResponse.json(
{ error: "Failed to analyze image" },
{ status: 500 }
);
}
}"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export function ImageUpload({ onResults }) {
const [loading, setLoading] = useState(false);
const [preview, setPreview] = useState<string | null>(null);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// Show preview
const reader = new FileReader();
reader.onloadend = () => setPreview(reader.result as string);
reader.readAsDataURL(file);
// Convert to base64 for API
setLoading(true);
const base64 = await fileToBase64(file);
try {
const response = await fetch("/api/image-upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ image: base64 }),
});
const data = await response.json();
onResults(data);
} catch (error) {
console.error("Upload error:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="file"
accept="image/*"
onChange={handleFileChange}
className="hidden"
id="image-upload"
/>
<label htmlFor="image-upload">
<Button as="span" disabled={loading}>
{loading ? "Analyzing..." : "Upload Image"}
</Button>
</label>
{preview && (
<img src={preview} alt="Preview" className="mt-4 max-w-sm" />
)}
</div>
);
}
function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const base64 = (reader.result as string).split(",")[1];
resolve(base64);
};
reader.onerror = reject;
});
}"use client";
import { PieChart, Pie, Cell, ResponsiveContainer, Legend } from "recharts";
interface MacroChartProps {
protein: number;
carbs: number;
fats: number;
}
export function MacroChart({ protein, carbs, fats }: MacroChartProps) {
const data = [
{ name: "Protein", value: protein, color: "#3b82f6" },
{ name: "Carbs", value: carbs, color: "#10b981" },
{ name: "Fats", value: fats, color: "#f59e0b" },
];
return (
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={data}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, percent }) =>
`${name}: ${(percent * 100).toFixed(0)}%`
}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Legend />
</PieChart>
</ResponsiveContainer>
);
}{
"name": "HealthyME - Nutrition & Wellness",
"short_name": "HealthyME",
"description": "AI-powered nutrition analysis and myth-busting platform",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#10b981",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
],
"screenshots": [
{
"src": "/screenshots/screenshot-wide.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/screenshot-narrow.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
"categories": ["health", "lifestyle", "food"],
"shortcuts": [
{
"name": "Search Food",
"short_name": "Search",
"description": "Quickly search for nutrition info",
"url": "/nutrition",
"icons": [{ "src": "/icons/search-icon.png", "sizes": "96x96" }]
},
{
"name": "Bust a Myth",
"short_name": "Myths",
"description": "Verify nutrition myths",
"url": "/myths",
"icons": [{ "src": "/icons/myth-icon.png", "sizes": "96x96" }]
}
]
}const CACHE_NAME = "healthyme-v1";
const urlsToCache = [
"/",
"/nutrition",
"/myths",
"/dashboard",
"/offline",
"/styles/globals.css",
"/icons/icon-192x192.png",
"/icons/icon-512x512.png",
];
// Install event - cache resources
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
console.log("Opened cache");
return cache.addAll(urlsToCache);
})
);
});
// Fetch event - serve from cache, fallback to network
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then((response) => {
// Check if valid response
if (!response || response.status !== 200 || response.type !== "basic") {
return response;
}
// Clone response
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
}).catch(() => {
// If both cache and network fail, show offline page
return caches.match("/offline");
})
);
});
// Activate event - clean up old caches
self.addEventListener("activate", (event) => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});"use client";
import { useEffect } from "react";
export default function RootLayout({ children }) {
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("SW registered:", registration);
})
.catch((error) => {
console.log("SW registration failed:", error);
});
}
}, []);
return (
<html lang="en">
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#10b981" />
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
</head>
<body>{children}</body>
</html>
);
}{
uid: string; // Firebase Auth UID
email: string;
displayName: string;
createdAt: Timestamp;
preferences: {
dailyCalories: number;
macroGoals: {
protein: number; // in grams
carbs: number;
fats: number;
};
dietType: "omnivore" | "vegetarian" | "vegan" | "pescatarian";
allergies: string[];
};
stats: {
totalSearches: number;
favoritesCount: number;
mythsBusted: number;
};
}users/{userId}/nutrition_history/{entryId}
{
foodName: string;
servingSize: string;
calories: number;
macros: {
protein: number;
carbs: number;
fats: number;
fiber: number;
};
searchedAt: Timestamp;
source: "manual_search" | "image_upload" | "barcode_scan";
}users/{userId}/favorites/{foodId}
{
foodName: string;
nutritionData: { ... }; // Same structure as nutrition_history
addedAt: Timestamp;
notes: string; // User notes
}{
mythId: string;
question: string;
verdict: "TRUE" | "FALSE" | "PARTIALLY_TRUE" | "INCONCLUSIVE";
explanation: string;
keyPoints: string[];
sources: Array<{
title: string;
authors: string;
publication: string;
year: number;
url: string;
summary: string;
}>;
recommendation: string;
askedBy: string; // userId
askedAt: Timestamp;
upvotes: number;
downvotes: number;
views: number;
}users/{userId}/meal_logs/{logId}
{
date: Timestamp;
mealType: "breakfast" | "lunch" | "dinner" | "snack";
foods: Array<{
name: string;
calories: number;
macros: { ... };
}>;
totalCalories: number;
totalMacros: { ... };
}- User submits email/password
- Firebase Auth creates account
- Email verification sent
- User document created in Firestore
- Redirect to dashboard after email verification
- Email/password submitted
- Firebase Auth validates credentials
- JWT token generated
- Token stored in httpOnly cookie
- User redirected to dashboard
- Next.js middleware checks for valid token
- Redirects to login if unauthorized
- Allows access if token valid
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("authToken");
// Protected routes
const protectedPaths = ["/dashboard", "/nutrition", "/myths", "/favorites"];
const isProtectedPath = protectedPaths.some((path) =>
request.nextUrl.pathname.startsWith(path)
);
if (isProtectedPath && !token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/nutrition/:path*", "/myths/:path*", "/favorites/:path*"],
};Key points:
- Users can only access their own data
- Myths collection is publicly readable
- All writes require authentication
- Admin operations require admin role verification
- Next.js
<Image>component for automatic optimization - WebP format with fallback to JPEG
- Lazy loading for below-the-fold images
- Responsive images with srcSet
- Automatic code splitting with Next.js App Router
- Dynamic imports for heavy components
- Route-based splitting
- API responses cached in Firestore for repeated queries
- Service Worker caches static assets
- Stale-while-revalidate for data fetching
- Tree-shaking with ES modules
- Dynamic imports for large libraries
- Remove unused dependencies
import dynamic from "next/dynamic";
const MacroChart = dynamic(() => import("@/components/MacroChart"), {
loading: () => <p>Loading chart...</p>,
ssr: false,
});- Firestore indexes for common queries
- Pagination for large datasets
- Limit query results to 20 items per page
npm install -g firebase-tools
firebase loginfirebase init hosting
# Select:
# - Use existing project
# - Build directory: out
# - Configure as single-page app: Yes
# - Set up automatic builds with GitHub: Yes (optional)npm run build
firebase deploy{
"hosting": {
"public": "out",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=31536000"
}
]
}
]
}
}npm install -g vercelvercel
# Follow prompts- Go to Vercel Dashboard β Project Settings β Environment Variables
- Add all .env.local variables
name: Deploy to Firebase
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "18"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
# Add all environment variables
- name: Deploy to Firebase
uses: w9jds/firebase-action@master
with:
args: deploy --only hosting
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SearchBar } from "./SearchBar";
describe("SearchBar", () => {
it("renders search input and button", () => {
render(<SearchBar onResults={jest.fn()} />);
expect(screen.getByPlaceholderText("Enter food name...")).toBeInTheDocument();
expect(screen.getByText("Search")).toBeInTheDocument();
});
it("calls onResults with API data on search", async () => {
const mockOnResults = jest.fn();
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: "Apple", calories: 95 }),
})
);
render(<SearchBar onResults={mockOnResults} />);
const input = screen.getByPlaceholderText("Enter food name...");
const button = screen.getByText("Search");
fireEvent.change(input, { target: { value: "Apple" } });
fireEvent.click(button);
await waitFor(() => {
expect(mockOnResults).toHaveBeenCalledWith({
name: "Apple",
calories: 95,
});
});
});
});npm test
npm run test:coverage
|
|
Full-Stack Developer | AI Enthusiast | Cloud Architecture Specialist
| Domain | Skills Showcased |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS, Responsive Design |
| Backend | Firebase (Firestore, Auth, Storage), RESTful APIs, Real-time Data |
| AI/ML | Google Gemini AI, Vertex AI, Cloud Vision, Prompt Engineering |
| DevOps | GitHub Actions CI/CD, Automated Testing, Firebase Hosting |
| Testing | Jest, React Testing Library, Unit & Integration Tests |
| Security | Firebase Security Rules, Authentication, Data Encryption |
| Architecture | Microservices, PWA, Service Workers, Caching Strategies |
This project welcomes contributions! Whether you're fixing bugs, adding features, or improving documentation, your input is valuable.
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- β Write TypeScript with full type coverage
- β Add tests for new features
- β Follow existing code style (Prettier + ESLint)
- β Update documentation
- β Keep commits atomic and descriptive
This project is licensed under the MIT License - see the LICENSE file for details.
Built with cutting-edge technologies and powered by:
Special thanks to:
- Google Cloud AI Team for Gemini API and Vertex AI
- Firebase Team for robust backend infrastructure
- Next.js Team for the incredible React framework
- shadcn for the beautiful UI component library
- The open-source community for continuous inspiration
Built with β€οΈ and β by Salugu Harshita Bhanu
Making nutrition information accessible, accurate, and actionable through AI
π Live Demo β’ π§ Get in Touch β’ πΌ View Portfolio
Last Updated: December 2025 | Status: Production Ready β
π§ Contact & Support Developer: Salugu Harshita Bhanu Email: [email protected] GitHub: @Git-brintsi20 LinkedIn: Salugu Harshita Bhanu Project Repository: https://github.com/Git-brintsi20/healthy-me Issues & Bug Reports: GitHub Issues
π Additional Resources Documentation
Next.js 14 Documentation Firebase Documentation Google Gemini API Documentation Tailwind CSS Documentation PWA Best Practices
Tutorials Used
Building with Gemini API Next.js App Router Guide Firebase with Next.js

















