TodayActivityCard
Shows today's Move, Exercise and Steps rings, with every state covered.
Overview
TodayActivityCard renders three activity rings (Move in kcal, Exercise in minutes and Steps) with real data from your Supabase database. It also handles every state the component can be in, from loading to fully synced.
How to install it
There is no CLI. You copy the files below into your project:
- The seven files in the bundle further down this page go into
components/wristkit/today-activity-card/andlib/wristkit/(paths are shown on each tab) - The route handler at Installation goes into
app/api/wristkit-sync/route.ts - The SQL migrations from the same page go into Supabase
Make sure you have these peer dependencies in your project: drizzle-orm, postgres, zod.
Usage
import { TodayActivityCard, loadTodayActivity } from "@/components/wristkit/today-activity-card";
export default async function Page() {
const state = await loadTodayActivity({ tz: "America/Sao_Paulo" });
return <TodayActivityCard state={state} />;
}loadTodayActivity() is a server side helper. It reads from your Supabase database and returns the right state. Pass it straight to the component, so you never have to think about the state machine yourself.
Options
loadTodayActivity({
tz?: string, // IANA timezone for the day boundary and label. Default "UTC".
goals?: {
kcal?: number, // default 600
exerciseMinutes?: number, // default 30
steps?: number, // default 8000
},
});Props
type Props = {
state: TodayState;
className?: string;
};States
The component covers every state below. For the reasoning behind the contract, see Component states.
ok
All three metrics arrived for today. Full rings, real numbers and a synced status.
loading
First fetch is in flight. The rings pulse and the metric values show as shimmering skeletons. The footer shows that the request is in progress.
empty
Nothing has been synced yet. The ring tracks are dotted. Values show as —. The footer invites the user to install the Shortcut.
iPhone hasn't checked in. Install the shortcut.
stale
Data exists but it is older than 24 hours. Numbers are a bit washed out and the status pill shows stale · Nh.
Six hours ago. Shortcut may be paused.
error
Something went wrong during sync. The card shows a short, generic message — it never echoes the underlying error or code so you don't leak details about your auth or your database.
We couldn't load today's activity. Check that the sync ran and try again later.
Rings only
A compact variant with no card chrome. Handy for a hero block or a small status corner.
Customization
The component file uses literal hex colors (entrepta violet, emerald, amber) so nothing breaks if you do not have wristkit's CSS variables in your project. To match a different palette, edit colors at the top of states.tsx.
· Files to copy
Drop each file in its destination path. Copy with the button on the right.
import type * as React from "react";
import type { TodayState } from "./load";
import { loadTodayActivity } from "./load";
import {
TodayActivityCardEmpty,
TodayActivityCardError,
TodayActivityCardLoading,
TodayActivityCardOk,
TodayActivityCardStale,
} from "./states";
export { loadTodayActivity };
export type { TodayData, TodayState } from "./load";
export function TodayActivityCard({
state,
className,
}: {
state: TodayState;
className?: string;
}): React.JSX.Element {
switch (state.kind) {
case "loading":
return <TodayActivityCardLoading className={className} />;
case "empty":
return <TodayActivityCardEmpty className={className} />;
case "error":
return <TodayActivityCardError className={className} />;
case "stale":
return <TodayActivityCardStale className={className} data={state.data} />;
case "ok":
return <TodayActivityCardOk className={className} data={state.data} />;
}
}