zazzy
Blocks

Active sessions card

A settings card listing the account's live sessions with a friendly device label and per-device revoke, wired to the user router.

An account-settings card listing every live session for the signed-in user. Each row shows a friendly device label (derived from the raw User-Agent by a pure helper), the IP and sign-in time, and a revoke button — except the session making the request, which is badged This device and can't self-revoke (signing out is the app shell's job). Revoking invalidates the query so the row disappears without a reload. It's composed over the user router's listSessions / revokeSession surface — the same surface the reference app's account settings use.

Requires: auth

Preview

Active sessions
Devices currently signed in to your account. Revoke any you don't recognise.
  • Chrome on macOS

    203.0.113.7 · Signed in 7/18/2026, 9:12:00 AM

    This device
  • Safari on iOS

    198.51.100.22 · Signed in 7/16/2026, 8:41:00 PM

  • Firefox on Windows

    Signed in 7/11/2026, 8:03:00 AM

"use client";import { Button } from "@/components/ui/button";import {  Card,  CardContent,  CardDescription,  CardHeader,  CardTitle,} from "@/components/ui/card";import { trpc } from "@/lib/trpc";import { deviceLabel } from "./device-label";/** Human-readable timestamp; dates cross the tRPC boundary as ISO strings. */function formatWhen(value: string | Date): string {  return new Date(value).toLocaleString();}/** * An account-settings card listing every live session for the signed-in user, * composed over the user router's `listSessions` / `revokeSession` surface (the * same surface `features/auth/components/sessions-list.tsx` uses). The session * making this request is badged "This device" and has no revoke button — * signing out is the app shell's job, not a self-revoke here. Revoking any other * session invalidates the query so its row disappears without a manual reload. * The raw `User-Agent` is rendered through the pure `deviceLabel` helper. */export function ActiveSessionsCard() {  const utils = trpc.useUtils();  const sessions = trpc.user.listSessions.useQuery();  const revoke = trpc.user.revokeSession.useMutation({    // Router-level invalidation per GUIDELINES §4 (no measured hotspot here).    onSuccess: () => utils.user.invalidate(),  });  return (    <Card>      <CardHeader>        <CardTitle>Active sessions</CardTitle>        <CardDescription>          Devices currently signed in to your account. Revoke any you don&apos;t          recognise.        </CardDescription>      </CardHeader>      <CardContent>        {sessions.isPending ? (          <p className="text-sm text-muted-foreground">Loading sessions…</p>        ) : sessions.isError ? (          <p role="alert" className="text-sm text-destructive">            Could not load your sessions: {sessions.error.message}          </p>        ) : (          <ul className="flex flex-col gap-3">            {sessions.data.map((session) => (              <li                key={session.id}                className="flex items-center justify-between gap-4 rounded-md border px-4 py-3"              >                <div className="min-w-0 text-sm">                  <p className="truncate font-medium">                    {deviceLabel(session.userAgent)}                  </p>                  <p className="text-muted-foreground">                    {session.ipAddress ? `${session.ipAddress} · ` : ""}                    Signed in {formatWhen(session.createdAt)}                  </p>                </div>                {session.current ? (                  <span className="shrink-0 rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700 dark:bg-green-900/40 dark:text-green-400">                    This device                  </span>                ) : (                  <Button                    variant="secondary"                    className="h-9 shrink-0"                    onClick={() => revoke.mutate({ token: session.token })}                    disabled={revoke.isPending}                  >                    Revoke                  </Button>                )}              </li>            ))}            {revoke.isError && (              <p role="alert" className="text-sm text-destructive">                Could not revoke that session: {revoke.error.message}              </p>            )}          </ul>        )}      </CardContent>    </Card>  );}

Offline preview

The docs site has no backend, so the preview seeds three representative sessions (one current, two revocable) to show the device labels and the revoke affordance. In an app the data comes from the user router.

Install

npx create-saas-starter add active-sessions-card

Run it from your app's root. add only writes new files — wiring is up to you:

import { ActiveSessionsCard } from "@/components/blocks/active-sessions-card/active-sessions-card";

On this page