wristkit.
· docs

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/ and lib/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

tsx
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

ts
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

ts
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.

today / activitysynced
MOVE/ 600 kcal544
EXERCISE/ 30 min80
STEPS/ 8,000 6,480
updated 21:14

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.

today / activitysyncing…00:01
MOVE/ 600 kcal
EXERCISE/ 30 min
STEPS/ 8,000
syncing…

empty

Nothing has been synced yet. The ring tracks are dotted. Values show as . The footer invites the user to install the Shortcut.

today / activitynot connected0 snapshots
no data yet

iPhone hasn't checked in. Install the shortcut.

MOVE/ 600 kcal
EXERCISE/ 30 min
STEPS/ 8,000
run shortcut on iPhone · or POST /api/wristkit-syncinstall shortcut →

stale

Data exists but it is older than 24 hours. Numbers are a bit washed out and the status pill shows stale · Nh.

today / activitystale · 6hcached
last sync · 15:08

Six hours ago. Shortcut may be paused.

MOVE/ 600 kcal412
EXERCISE/ 30 min18
STEPS/ 8,000 4,280
showing cached values · automation due 23:00

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.

today / activityerror
Something went wrong.

We couldn't load today's activity. Check that the sync ran and try again later.

showing nothing rather than guessing

Rings only

A compact variant with no card chrome. Handy for a hero block or a small status corner.

today
3/3
rings closed
MOVE544 / 600kcal
EXERCISE80 / 30min ✓
STEPS6480 / 8000

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.

components/wristkit/today-activity-card/index.tsx
35 linestsx
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} />;
  }
}
Active theme: Ivy, dark mode.