import { getStore } from "@netlify/blobs";
const store = getStore("checkins");
const headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
};
export default async (req) => {
const token = req.headers
.get("authorization")
.replace("Bearer ", "");
const payload = JSON.parse(
atob(token.split(".")[1])
);
const userId = payload.sub;
const storeKey = `user-${userId}`;
if (req.method === "GET") {
const result = await store.get(
storeKey, { type: "json" }
);
return new Response(
JSON.stringify(result || []),
{ status: 200, headers }
);
}
if (req.method === "POST") {
const newEntry = await req.json();
const existing = await store.get(
storeKey, { type: "json" }
) || [];
existing.unshift(newEntry);
await store.setJSON(storeKey, existing);
return new Response(
JSON.stringify({ success: true }),
{ status: 200, headers }
);
}
};
import { getStore } from "@netlify/blobs";
const store = getStore("checkins");
const headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
};
export default async (req) => {
const token = req.headers
.get("authorization")
.replace("Bearer ", "");
const payload = JSON.parse(
atob(token.split(".")[1])
);
const userId = payload.sub;
const storeKey = `user-${userId}`;
};
const rings = [
{ key: "calories", color: "#22d3ee" },
{ key: "protein", color: "#f472b6" },
{ key: "carbs", color: "#fbbf24" },
{ key: "fat", color: "#a78bfa" },
{ key: "water", color: "#60a5fa" },
{ key: "activity", color: "#4ade80" },
];
function drawRings(ctx, data) {
rings.forEach((ring, i) => {
const radius = 80 - (i * 12);
const pct = data[ring.key] || 0;
const angle = (pct / 100) * Math.PI * 2;
ctx.beginPath();
ctx.arc(120, 120, radius,
-Math.PI / 2,
angle - Math.PI / 2
);
ctx.strokeStyle = ring.color;
ctx.lineWidth = 8;
ctx.lineCap = "round";
ctx.stroke();
});
}
function updateDashboard() {
const canvas = document
.getElementById("ringChart");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, 240, 240);
drawRings(ctx, dailyState);
}
const rings = [
{ key: "calories", color: "#22d3ee" },
{ key: "protein", color: "#f472b6" },
{ key: "carbs", color: "#fbbf24" },
{ key: "fat", color: "#a78bfa" },
{ key: "water", color: "#60a5fa" },
{ key: "activity", color: "#4ade80" },
];
async function saveCheckin(data) {
const user = netlifyIdentity
.currentUser();
const token = await user
.jwt(true);
const res = await fetch(
"/.netlify/functions/checkins",
{
method: "POST",
headers: {
"Content-Type":
"application/json",
Authorization:
`Bearer ${token}`,
},
body: JSON.stringify({
date: data.date,
weight: data.weight,
bodyFat: data.bodyFat,
notes: data.notes,
photo: data.photo,
}),
}
);
if (res.ok) {
showToast("Check-in saved!");
loadCheckins();
}
}
netlifyIdentity.on("login", () => {
loadDailyState();
loadCheckins();
updateUI();
});
async function saveCheckin(data) {
const user = netlifyIdentity
.currentUser();
const token = await user
.jwt(true);
};
const chart = new Chart(ctx, {
type: "bar",
data: {
labels: weekLabels,
datasets: [{
data: weeklyHits,
backgroundColor:
"rgba(244, 114, 182, 0.6)",
borderRadius: 8,
}],
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 7,
grid: {
color: "rgba(255,255,255,.05)"
},
},
x: {
grid: { display: false },
},
},
plugins: {
legend: { display: false },
},
},
});
function updateWeeklyChart() {
chart.data.datasets[0].data =
getWeeklyHits(dailyHistory);
chart.update();
}
const chart = new Chart(ctx, {
type: "bar",
data: {
labels: weekLabels,
datasets: [{
data: weeklyHits,
backgroundColor:
"rgba(244, 114, 182, 0.6)",
borderRadius: 8,
}],
},
});
function switchTab(tabId) {
document.querySelectorAll(
".tab-content"
).forEach(t =>
t.classList.remove("active")
);
document.querySelectorAll(
".nav-tab"
).forEach(n =>
n.classList.remove("active")
);
document.getElementById(tabId)
.classList.add("active");
event.currentTarget
.classList.add("active");
}
const supplements = [
"Omega-3",
"Vitamin D",
"Creatine",
"Magnesium",
"Zinc",
"Ashwagandha",
];
function renderSupplements() {
return supplements.map(s => `
<label class="toggle-switch">
<input type="checkbox"
onchange="toggleSupplement(
'${s}'
)"
/>
${s}
</label>
`).join("");
}
function switchTab(tabId) {
document.querySelectorAll(
".tab-content"
).forEach(t =>
t.classList.remove("active")
);
};
FitFuel runs entirely on Netlify's serverless stack. User authentication is handled through Netlify Identity with JWT tokens. All persistent data — daily logs, weekly check-ins, measurements — is stored in Netlify Blobs via two serverless functions in Node.js. No traditional database, no backend server, no DevOps overhead.
The frontend is a single HTML file — nearly 3,000 lines of code, all generated by Claude and directed through iterative creative prompting. Chart.js powers the progress visualizations. Everything is designed mobile-first with a native-app feel: bottom navigation, card-based UI, glassmorphism, and smooth transitions. A brief for something like this would normally take weeks to see a first draft. We hit v6 in one session.