fix: move fitness goals to Settings, clean up mobile nav
Some checks failed
Security Checks / dependency-audit (push) Has been cancelled
Security Checks / secret-scanning (push) Has been cancelled
Security Checks / dockerfile-lint (push) Has been cancelled

- Removed fitness sub-pages from mobile More sheet (Food Library and
  Quick Meals are already tabs on the fitness page)
- Added Fitness Goals section to Settings page with inline editing
- Goals show current values with Edit Goals button
- Edit mode: 2x2 grid with calorie/protein/carbs/fat inputs
- Save calls PUT /api/fitness/goals
- Works for both users independently
This commit is contained in:
Yusuf Suleman
2026-03-29 14:51:34 -05:00
parent e8d1cd1681
commit 792d89a377
2 changed files with 116 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import { LayoutDashboard, DollarSign, Package, Activity, MoreVertical, MapPin, BookOpen, Library, Settings, Target, UtensilsCrossed, Zap } from '@lucide/svelte'; import { LayoutDashboard, DollarSign, Package, Activity, MoreVertical, MapPin, BookOpen, Library, Settings } from '@lucide/svelte';
interface Props { interface Props {
visibleApps?: string[]; visibleApps?: string[];
@@ -66,20 +66,6 @@
Trips Trips
</a> </a>
{/if} {/if}
{#if showApp('fitness')}
<a href="/fitness/foods" class="more-sheet-item" onclick={closeMore}>
<UtensilsCrossed size={20} />
Food Library
</a>
<a href="/fitness/goals" class="more-sheet-item" onclick={closeMore}>
<Target size={20} />
Fitness Goals
</a>
<a href="/fitness/templates" class="more-sheet-item" onclick={closeMore}>
<Zap size={20} />
Quick Meals
</a>
{/if}
{#if showApp('reader')} {#if showApp('reader')}
<a href="/reader" class="more-sheet-item" onclick={closeMore}> <a href="/reader" class="more-sheet-item" onclick={closeMore}>
<BookOpen size={20} /> <BookOpen size={20} />

View File

@@ -14,6 +14,15 @@
let loading = $state(true); let loading = $state(true);
let darkMode = $state(false); let darkMode = $state(false);
// Fitness goals
let goalCal = $state('');
let goalProtein = $state('');
let goalCarbs = $state('');
let goalFat = $state('');
let goalsLoaded = $state(false);
let editingGoals = $state(false);
let savingGoals = $state(false);
// Connect form state // Connect form state
let showConnectForm = $state(''); let showConnectForm = $state('');
let connectToken = $state(''); let connectToken = $state('');
@@ -46,6 +55,21 @@
} }
} catch { /* silent */ } } catch { /* silent */ }
loading = false; loading = false;
// Load fitness goals
try {
const n = new Date();
const today = `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, '0')}-${String(n.getDate()).padStart(2, '0')}`;
const gRes = await fetch(`/api/fitness/goals/for-date?date=${today}`, { credentials: 'include' });
if (gRes.ok) {
const g = await gRes.json();
goalCal = String(Math.round(g.calories || 2000));
goalProtein = String(Math.round(g.protein || 150));
goalCarbs = String(Math.round(g.carbs || 200));
goalFat = String(Math.round(g.fat || 65));
goalsLoaded = true;
}
} catch { /* silent */ }
}); });
async function connectService(serviceId: string) { async function connectService(serviceId: string) {
@@ -99,6 +123,27 @@
document.documentElement.classList.toggle('dark', darkMode); document.documentElement.classList.toggle('dark', darkMode);
} }
async function saveGoals() {
savingGoals = true;
try {
const n = new Date();
const today = `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, '0')}-${String(n.getDate()).padStart(2, '0')}`;
const res = await fetch('/api/fitness/goals', {
method: 'PUT', credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
calories: parseFloat(goalCal) || 2000,
protein: parseFloat(goalProtein) || 150,
carbs: parseFloat(goalCarbs) || 200,
fat: parseFloat(goalFat) || 65,
start_date: today,
}),
});
if (res.ok) editingGoals = false;
} catch { /* silent */ }
savingGoals = false;
}
async function logout() { async function logout() {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
window.location.href = '/login'; window.location.href = '/login';
@@ -158,6 +203,63 @@
</div> </div>
</section> </section>
<!-- Fitness Goals -->
<section class="settings-section">
<div class="section-title">Fitness Goals</div>
<div class="settings-card">
{#if editingGoals}
<div class="setting-row">
<div class="goal-edit-grid">
<div class="goal-edit-field">
<label class="goal-edit-label">Calories</label>
<input class="goal-edit-input" type="number" bind:value={goalCal} />
</div>
<div class="goal-edit-field">
<label class="goal-edit-label">Protein (g)</label>
<input class="goal-edit-input" type="number" bind:value={goalProtein} />
</div>
<div class="goal-edit-field">
<label class="goal-edit-label">Carbs (g)</label>
<input class="goal-edit-input" type="number" bind:value={goalCarbs} />
</div>
<div class="goal-edit-field">
<label class="goal-edit-label">Fat (g)</label>
<input class="goal-edit-input" type="number" bind:value={goalFat} />
</div>
</div>
</div>
<div class="setting-row">
<div></div>
<div class="goal-actions">
<button class="btn-text" onclick={() => editingGoals = false}>Cancel</button>
<button class="btn-save-goals" onclick={saveGoals} disabled={savingGoals}>{savingGoals ? 'Saving...' : 'Save'}</button>
</div>
</div>
{:else}
<div class="setting-row">
<div class="setting-label">Calories</div>
<div class="setting-value">{goalsLoaded ? goalCal : '...'} kcal</div>
</div>
<div class="setting-row">
<div class="setting-label">Protein</div>
<div class="setting-value">{goalsLoaded ? goalProtein : '...'}g</div>
</div>
<div class="setting-row">
<div class="setting-label">Carbs</div>
<div class="setting-value">{goalsLoaded ? goalCarbs : '...'}g</div>
</div>
<div class="setting-row">
<div class="setting-label">Fat</div>
<div class="setting-value">{goalsLoaded ? goalFat : '...'}g</div>
</div>
<div class="setting-row">
<div></div>
<button class="btn-edit-goals" onclick={() => editingGoals = true}>Edit Goals</button>
</div>
{/if}
</div>
</section>
<!-- Service Connections --> <!-- Service Connections -->
<section class="settings-section"> <section class="settings-section">
<div class="section-title">Service Connections</div> <div class="section-title">Service Connections</div>
@@ -321,6 +423,18 @@
.toggle-thumb svg { width: 14px; height: 14px; color: var(--text-3); } .toggle-thumb svg { width: 14px; height: 14px; color: var(--text-3); }
.theme-toggle.dark .toggle-thumb svg { color: var(--accent); } .theme-toggle.dark .toggle-thumb svg { color: var(--accent); }
.goal-edit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); width: 100%; }
.goal-edit-field { display: flex; flex-direction: column; gap: var(--sp-1); }
.goal-edit-label { font-size: var(--text-sm); color: var(--text-3); }
.goal-edit-input { padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface-secondary); color: var(--text-1); font-size: var(--text-md); font-family: var(--mono); }
.goal-edit-input:focus { outline: none; border-color: var(--accent); }
.goal-actions { display: flex; gap: var(--sp-2); }
.btn-text { background: none; border: none; color: var(--text-3); font-size: var(--text-sm); cursor: pointer; font-family: var(--font); }
.btn-text:hover { color: var(--text-1); }
.btn-save-goals { padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-md); background: var(--accent); color: white; border: none; font-size: var(--text-sm); font-weight: 600; cursor: pointer; font-family: var(--font); }
.btn-save-goals:disabled { opacity: 0.5; }
.btn-edit-goals { padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-md); background: var(--accent); color: white; border: none; font-size: var(--text-sm); font-weight: 600; cursor: pointer; font-family: var(--font); }
@media (max-width: 768px) { @media (max-width: 768px) {
.page-subtitle { font-size: var(--text-xl); } .page-subtitle { font-size: var(--text-xl); }
.setting-row { flex-wrap: wrap; gap: var(--sp-2); } .setting-row { flex-wrap: wrap; gap: var(--sp-2); }