Control posture
One card per compliance framework with its passing, failing and not started controls, then the overdue evidence tasks. It loads its data on the server through the block's client.
pnpm dev:example) to see it.Add the components
terminal npx shadcn add alert badge button card empty progress skeleton tableInstall the glue
Install the block with
npx better-supabase add controls. The glue items are copied into your app, so edit them as you need.None: the page calls the block's client on the server.
Write the component
React and shadcn
View in the examplecontrol-posture.tsx // oxlint-disable-next-line typescript/require-await -- a Server Action must be async, and refresh() returns nothing to await async function retry() { "use server"; refresh(); } /** * Render inside `<Suspense>`: one card per compliance framework with how many * of its controls pass, then the evidence tasks that are past their due date. */ export async function ControlPosture() { const [state, t, format] = await Promise.all([ getPosture(), getExtracted("compliance"), getFormatter(), ]); if (state.kind === "no-organization") { return null; } if (state.kind === "error") { return ( <Alert variant="destructive" data-recipe="control-posture"> <TriangleAlertIcon /> <AlertTitle>{t("The control posture didn't load")}</AlertTitle> <AlertDescription>{t("Try again in a moment.")}</AlertDescription> <AlertAction> <form action={retry}> <Button size="sm" variant="outline" type="submit"> {t("Retry")} </Button> </form> </AlertAction> </Alert> ); } const { frameworks, overdue } = state.data; if (frameworks.length === 0) { return ( <Empty className="border" data-recipe="control-posture"> <EmptyHeader> <EmptyMedia variant="icon"> <ShieldCheckIcon /> </EmptyMedia> <EmptyTitle>{t("No frameworks yet")}</EmptyTitle> <EmptyDescription> {t( "Add a framework such as SOC 2 or ISO 27001 with the controls block, then link evidence to each control.", )} </EmptyDescription> </EmptyHeader> <EmptyContent> <a className="text-sm underline underline-offset-4" href="https://bettersupabase.com/docs/blocks/controls" > {t("Read the controls guide")} </a> </EmptyContent> </Empty> ); } const day = (date: string) => format.dateTime(new Date(date), { dateStyle: "medium", timeZone: "UTC" }); return ( <div className="flex flex-col gap-6" data-recipe="control-posture"> <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3"> {frameworks.map((framework) => { const applicable = framework.total - framework.notApplicable; const percent = applicable === 0 ? 100 : Math.round((framework.passing / applicable) * 100); return ( <Card key={framework.frameworkId}> <CardHeader> <CardTitle>{framework.name}</CardTitle> <CardDescription> {t( "{passing, number} of {applicable, number} controls passing", { passing: framework.passing, applicable }, )} </CardDescription> </CardHeader> <CardContent className="flex flex-col gap-3"> <Progress value={percent} aria-label={t("Controls passing")} /> <div className="flex flex-wrap gap-2"> <Badge variant="success-light"> {t("{count, number} passing", { count: framework.passing })} </Badge> {framework.failing > 0 ? ( <Badge variant="destructive-light"> {t("{count, number} failing", { count: framework.failing, })} </Badge> ) : null} {framework.notStarted > 0 ? ( <Badge variant="secondary"> {t("{count, number} not started", { count: framework.notStarted, })} </Badge> ) : null} {framework.overdueTasks > 0 ? ( <Badge variant="warning-light"> {t("{count, number} overdue tasks", { count: framework.overdueTasks, })} </Badge> ) : null} </div> </CardContent> </Card> ); })} </div> <Card> <CardHeader> <CardTitle>{t("Overdue evidence")}</CardTitle> <CardDescription> {t( "Recurring tasks whose evidence is past due. Collecting evidence moves the task to its next due date.", )} </CardDescription> </CardHeader> <CardContent> {overdue.length === 0 ? ( <p className="text-sm text-muted-foreground"> {t("Every evidence task is on schedule.")} </p> ) : ( <Table> <TableHeader> <TableRow> <TableHead>{t("Task")}</TableHead> <TableHead>{t("Control")}</TableHead> <TableHead>{t("Due")}</TableHead> </TableRow> </TableHeader> <TableBody> {overdue.map((task) => ( <TableRow key={task.id}> <TableCell className="font-medium">{task.title}</TableCell> <TableCell className="font-mono text-xs"> {task.controlCode ?? "–"} </TableCell> <TableCell> <Badge variant="destructive-light"> {day(task.dueOn)} </Badge> </TableCell> </TableRow> ))} </TableBody> </Table> )} </CardContent> </Card> </div> ); } export function ControlPostureSkeleton() { return ( <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3" aria-busy="true"> <Skeleton className="h-36 rounded-xl" /> <Skeleton className="h-36 rounded-xl" /> <Skeleton className="h-36 rounded-xl" /> </div> ); }