Direct debit mandates · Vektos
Tasks / Direct debit mandates A company's direct debit mandates with their last collection and status, a warning for failed collections, and cancellation after a confirmation.
The brief What the agent reads, word for word.
What a good answer has The page must show these texts, or the run is not done. A good answer uses these components.
Best and worst runs By design-system violations.
By context and model Mean design-system violations per run on this task.
The reference answer Written by hand, graded with zero violations, rendered by the browser check in every build. The agent never sees it.
8.7
13 Table,
14 TableBody,
15 TableCell,
16 TableHead,
17 TableHeader,
18 TableRow,
19 Toaster,
20 toast,
21 } from "@vektos/ds";
22 import { useState } from "react";
23 import { css } from "styled-system/css";
24 import data from "./fixture.json";
25
26 type Mandate = (typeof data.mandates)[number];
27
28 const money = new Intl.NumberFormat("en-IE", { style: "currency", currency: data.currency });
29 const date = new Intl.DateTimeFormat("en-GB", { dateStyle: "medium", timeZone: "UTC" });
30
31 const TONE = { active: "success", failed: "danger", cancelled: "neutral" } as const;
32 const LABEL = { active: "Active", failed: "Collection failed", cancelled: "Cancelled" } as const;
33
34 function isStatus(value: string): value is keyof typeof TONE {
35 return Object.hasOwn(TONE, value);
36 }
37
38 function CancelMandate({ mandate, onConfirm }: { mandate: Mandate; onConfirm: () => void }) {
39 return (
40 <Dialog>
41 <DialogTrigger asChild>
42 <Button variant="outline" size="sm">
43 Cancel mandate
44 </Button>
45 </DialogTrigger>
46 <DialogContent
47 title={`Cancel the mandate for ${mandate.creditor}?`}
48 description={`${mandate.creditor} will no longer collect from this account. Reference ${mandate.id}.`}
49 >
50 <DialogFooter>
51 <DialogClose asChild>
52 <Button variant="outline">Keep it</Button>
53 </DialogClose>
54 <DialogClose asChild>
55 <Button onClick={onConfirm}>Cancel mandate</Button>
56 </DialogClose>
57 </DialogFooter>
58 </DialogContent>
59 </Dialog>
60 );
61 }
62
63 export default function DirectDebitsPage() {
64 const [statuses, setStatuses] = useState<Record<string, string>>(
65 Object.fromEntries(data.mandates.map((mandate) => [mandate.id, mandate.status])),
66 );
67 const failed = data.mandates.filter((mandate) => statuses[mandate.id] === "failed").length;
68
69 const cancel = (mandate: Mandate) => {
70 setStatuses((current) => ({ ...current, [mandate.id]: "cancelled" }));
71 toast({ title: `Mandate for ${mandate.creditor} cancelled`, tone: "success" });
72 };
73
74 return (
75 <main className={css({ p: "6" })}>
76 <Stack gap="6">
77 <Stack gap="1">
78 <h1 className={css({ fontSize: "xl", fontWeight: "semibold" })}>Direct debits</h1>
79 <p className={css({ color: "fg.muted" })}>{data.company}</p>
80 </Stack>
81
82 {failed > 0 ? (
83 <Alert
84 tone="danger"
85 title={`${failed} ${failed === 1 ? "collection" : "collections"} failed`}
86 >
87 Each creditor will try again in {data.retryDays} working days. Make sure the account has
88 enough funds.
89 </Alert>
90 ) : null}
91
92 <Table aria-label="Direct debit mandates">
93 <TableHead>
94 <TableRow>
95 <TableHeader>Creditor</TableHeader>
96 <TableHeader>Reference</TableHeader>
97 <TableHeader>Last collection</TableHeader>
98 <TableHeader numeric>Amount</TableHeader>
99 <TableHeader>Status</TableHeader>
100 <TableHeader>
101 <span className={css({ srOnly: true })}>Actions</span>
102 </TableHeader>
103 </TableRow>
104 </TableHead>
105 <TableBody>
106 {data.mandates.map((mandate) => {
107 const status = statuses[mandate.id] ?? mandate.status;
108 return (
109 <TableRow key={mandate.id}>
110 <TableCell>{mandate.creditor}</TableCell>
111 <TableCell>{mandate.id}</TableCell>
112 <TableCell>{date.format(new Date(mandate.lastCollection.date))}</TableCell>
113 <TableCell numeric>{money.format(mandate.lastCollection.amount)}</TableCell>
114 <TableCell>
115 {isStatus(status) ? <Badge tone={TONE[status]}>{LABEL[status]}</Badge> : status}
116 </TableCell>
117 <TableCell>
118 {status === "cancelled" ? null : (
119 <CancelMandate mandate={mandate} onConfirm={() => cancel(mandate)} />
120 )}
121 </TableCell>
122 </TableRow>
123 );
124 })}
125 </TableBody>
126 </Table>
127 </Stack>
128 <Toaster />
129 </main>
130 );
131 }
132