import React, { useState, useEffect, useRef } from ‘react’;
import {
Plus,
Trash2,
Camera,
ShoppingCart,
Utensils,
History,
ChevronRight,
Loader2,
Scale,
Zap,
Flame,
Search,
CheckCircle2,
AlertCircle
} from ‘lucide-react’;
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
AreaChart,
Area
} from ‘recharts’;
// — Configuration & API Setup —
const apiKey = “”; // Managed by environment
const GEMINI_MODEL = “gemini-2.5-flash-preview-09-2025”;
// — Utility Functions —
const fetchGemini = async (prompt, imageBase64 = null) => {
const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
const payload = {
contents: [{
parts: [
{ text: prompt },
…(imageBase64 ? [{ inlineData: { mimeType: “image/png”, data: imageBase64 } }] : [])
]
}]
};
let delay = 1000;
for (let i = 0; i < 5; i++) {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) throw new Error('API request failed');
const data = await response.json();
return data.candidates?.[0]?.content?.parts?.[0]?.text;
} catch (err) {
if (i === 4) throw err;
await new Promise(res => setTimeout(res, delay));
delay *= 2;
}
}
};
const App = () => {
// — State —
const [activeTab, setActiveTab] = useState(‘dashboard’);
const [inventory, setInventory] = useState(() => JSON.parse(localStorage.getItem(‘dt_inventory’) || ‘[]’));
const [entries, setEntries] = useState(() => JSON.parse(localStorage.getItem(‘dt_entries’) || ‘[]’));
const [isScanning, setIsScanning] = useState(false);
const [aiLoading, setAiLoading] = useState(false);
const [error, setError] = useState(null);
// Persistence
useEffect(() => {
localStorage.setItem(‘dt_inventory’, JSON.stringify(inventory));
}, [inventory]);
useEffect(() => {
localStorage.setItem(‘dt_entries’, JSON.stringify(entries));
}, [entries]);
// — Actions —
const addInventoryItem = (item) => {
setInventory(prev => […prev, { …item, id: Date.now(), dateAdded: new Date().toISOString() }]);
};
const removeInventoryItem = (id) => {
setInventory(prev => prev.filter(item => item.id !== id));
};
const logMeal = (entry) => {
setEntries(prev => […prev, { …entry, id: Date.now() }]);
};
const deleteEntry = (id) => {
setEntries(prev => prev.filter(e => e.id !== id));
};
// — Receipt Scanning Logic —
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
setAiLoading(true);
setError(null);
const reader = new FileReader();
reader.onloadend = async () => {
const base64Data = reader.result.split(‘,’)[1];
const prompt = `
Analyze this receipt or grocery list.
Return a JSON array of objects representing food items.
Focus on Pescatarian, High-Protein, Low-Carb dietary goals.
For each item, provide:
– “name”: string
– “quantity”: string (e.g., “500g”, “1 pack”)
– “protein”: number (estimated grams per serving)
– “carbs”: number (estimated grams per serving)
– “fat”: number (estimated grams per serving)
– “calories”: number (estimated total)
– “category”: one of [“Fridge”, “Pantry”, “Produce”, “Seafood”]
Strict JSON only, no markdown markers.
`;
try {
const result = await fetchGemini(prompt, base64Data);
const cleanedResult = result.replace(/“`json|“`/g, “”).trim();
const parsedItems = JSON.parse(cleanedResult);
parsedItems.forEach(item => addInventoryItem(item));
setActiveTab(‘inventory’);
} catch (err) {
setError(“Failed to process image. Please try again with a clearer photo.”);
console.error(err);
} finally {
setAiLoading(false);
}
};
reader.readAsDataURL(file);
};
// — Derived Data for Charts —
const chartData = entries.reduce((acc, entry) => {
const date = entry.date;
const existing = acc.find(d => d.date === date);
if (existing) {
existing.protein += Number(entry.protein || 0);
existing.carbs += Number(entry.carbs || 0);
existing.calories += Number(entry.calories || 0);
} else {
acc.push({
date,
protein: Number(entry.protein || 0),
carbs: Number(entry.carbs || 0),
calories: Number(entry.calories || 0)
});
}
return acc;
}, []).sort((a, b) => new Date(a.date) – new Date(b.date)).slice(-7);
// — UI Components —
const Navbar = () => (
{[
{ id: ‘dashboard’, icon: Zap, label: ‘Dash’ },
{ id: ‘inventory’, icon: ShoppingCart, label: ‘Pantry’ },
{ id: ‘log’, icon: Utensils, label: ‘Meals’ },
{ id: ‘receipt’, icon: Camera, label: ‘Scan’ }
].map(tab => (
))}
);
return (
{/* Header */}
{error && (
)}
{/* Tab Content */}
{activeTab === ‘dashboard’ && (
{/* Quick Stats */}
Protein Avg
{chartData.length ? (chartData.reduce((a,b) => a+b.protein, 0)/chartData.length).toFixed(0) : 0}g
Today’s Carbs
{entries.filter(e => e.date === new Date().toISOString().split(‘T’)[0]).reduce((a,b) => a+b.carbs, 0).toFixed(0)}g
{/* Chart */}
7-Day Macronutrient Trend
{/* AI Suggestion Section */}
Smart Suggestions
{inventory.length > 0 ? (
Based on your {inventory.length} pantry items, you could make:
{inventory.slice(0, 2).map((item, i) => (
-
{item.name.includes(‘Salmon’) ? ‘Grilled Salmon with Greens’ : `High-Protein ${item.name} Bowl`}
))}
) : (
Scan a grocery receipt to get personalized meal suggestions!
)}
)}
{activeTab === ‘inventory’ && (
Your Pantry
{inventory.length === 0 && (
)}
{inventory.map((item) => (
{item.name.charAt(0)}
{item.name}
{item.quantity} • {item.category}
Protein
{item.protein || 0}g
))}
)}
{activeTab === ‘log’ && (
Recent Logs
{[…entries].reverse().map(entry => (
{entry.food}
{entry.date}
P: {entry.protein}g
C: {entry.carbs}g
))}
)}
{activeTab === ‘receipt’ && (
{aiLoading ? (
) : (
)}
{aiLoading && (
AI SCANNING…
)}
Receipt Scanner
Upload a photo of your receipt to automatically add items and nutrition info to your pantry.
Supports Walmart, Whole Foods, Instacart & More
)}
);
};
export default App;