feat(web): tracking page — weight entries with sparkline + history (PRD §5.2)
New /tracking route in the editorial language:
- "CAPITOLO VI / Pesata e misure." masthead
- 7/5 split: form left, STORICO right
Form (left, "NUOVA RILEVAZIONE"):
- Date + weight required, with the latest weight pre-filled to encourage
consistency between weeks
- Optional misure: girovita / fianchi / coscia / braccio (cm), grouped under
a "MISURE (CM) — OPZIONALI" eyebrow
- Subjective indicators 1–10: energia / sonno / fame
- Italic "Registrato." (oliva) confirmation after a successful POST
- Server action calls POST /me/weight-entries through API_URL with the
user's session cookie; sparkline + history revalidate via
revalidatePath('/tracking')
History (right, "STORICO"):
- Inline SVG sparkline of the user's weight series with a terracotta dot
marking the latest reading; ink-soft date axis labels under the line
- Two big mono stats: "PESO ATTUALE" and "VARIAZIONE" (oliva when negative,
pomodoro when positive, ink at zero)
- Newest 8 entries listed with hairline rules between rows
- Italian italic empty-state copy when no entries exist yet
Home gains a third NavItem ("Tracking — Pesata & misure").
Verified manually with a fresh user signup → disclaimer → profile → tracking:
sparkline / "PESO ATTUALE 76.0 kg" / "VARIAZIONE 0.0 kg" / list row "29 apr 76.0 kg".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
59a311cfc9
commit
4e00c131b9
6 files changed
+462
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import { prisma } from '@ketopath/db';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
|
||||
import { Masthead } from '@/components/masthead';
|
||||
import { getServerSession } from '@/lib/auth';
|
||||
|
||||
import { fetchWeightEntries } from './actions';
|
||||
import { WeightEntryForm } from './weight-entry-form';
|
||||
import { WeightHistory } from './weight-history';
|
||||
|
||||
export default async function TrackingPage({ params: { locale } }: { params: { locale: string } }) {
|
||||
setRequestLocale(locale);
|
||||
const session = await getServerSession();
|
||||
if (!session?.user) redirect('/sign-in');
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
select: { disclaimerAcceptedAt: true, profile: { select: { id: true } } },
|
||||
});
|
||||
if (!user?.disclaimerAcceptedAt) redirect('/welcome');
|
||||
if (!user.profile) redirect('/profile');
|
||||
|
||||
const entries = await fetchWeightEntries();
|
||||
|
||||
return <TrackingPageContent entries={entries} />;
|
||||
}
|
||||
|
||||
function TrackingPageContent({
|
||||
entries,
|
||||
}: {
|
||||
entries: Awaited<ReturnType<typeof fetchWeightEntries>>;
|
||||
}) {
|
||||
const t = useTranslations('Tracking');
|
||||
const latest = entries[0] ?? null;
|
||||
|
||||
return (
|
||||
<div className="mx-auto min-h-screen max-w-5xl px-6 sm:px-10">
|
||||
<Masthead issueLabel="N. 06 — Tracking" />
|
||||
<main className="py-12 sm:py-16">
|
||||
<p className="editorial-eyebrow animate-fade-up">Capitolo VI</p>
|
||||
<h1 className="font-display text-display text-ink animate-fade-up mt-3 font-medium leading-[0.95] tracking-tight [animation-delay:120ms]">
|
||||
{t('title')}
|
||||
<span className="text-pomodoro">.</span>
|
||||
</h1>
|
||||
<p className="font-display text-ink-soft animate-fade-up mt-6 max-w-2xl text-xl italic leading-snug [animation-delay:240ms]">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
|
||||
<div className="rule animate-rule-in my-10 [animation-delay:360ms]" />
|
||||
|
||||
<div className="grid gap-12 md:grid-cols-12">
|
||||
<section className="animate-fade-up [animation-delay:300ms] md:col-span-7">
|
||||
<p className="editorial-eyebrow mb-6">{t('newEntry')}</p>
|
||||
<WeightEntryForm latest={latest} />
|
||||
</section>
|
||||
<aside className="md:border-ink/15 animate-fade-up [animation-delay:420ms] md:col-span-5 md:border-l md:pl-10">
|
||||
<p className="editorial-eyebrow mb-6">{t('history')}</p>
|
||||
<WeightHistory entries={entries} />
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user