@eifi1/ui-kit 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/README.md +65 -6
  2. package/dist/account/account-parts.d.ts +21 -0
  3. package/dist/account/account-parts.js +40 -0
  4. package/dist/account/account-parts.js.map +1 -0
  5. package/dist/account/data-export-setting.d.ts +73 -0
  6. package/dist/account/data-export-setting.js +114 -0
  7. package/dist/account/data-export-setting.js.map +1 -0
  8. package/dist/account/delete-account-setting.d.ts +95 -0
  9. package/dist/account/delete-account-setting.js +200 -0
  10. package/dist/account/delete-account-setting.js.map +1 -0
  11. package/dist/account/email-change-setting.d.ts +136 -0
  12. package/dist/account/email-change-setting.js +278 -0
  13. package/dist/account/email-change-setting.js.map +1 -0
  14. package/dist/account/sessions-setting.d.ts +110 -0
  15. package/dist/account/sessions-setting.js +147 -0
  16. package/dist/account/sessions-setting.js.map +1 -0
  17. package/dist/admin/admin-action-confirm.d.ts +95 -0
  18. package/dist/admin/admin-action-confirm.js +276 -0
  19. package/dist/admin/admin-action-confirm.js.map +1 -0
  20. package/dist/admin/admin-action-log.d.ts +105 -0
  21. package/dist/admin/admin-action-log.js +153 -0
  22. package/dist/admin/admin-action-log.js.map +1 -0
  23. package/dist/admin/admin-parts.d.ts +40 -0
  24. package/dist/admin/admin-parts.js +45 -0
  25. package/dist/admin/admin-parts.js.map +1 -0
  26. package/dist/admin/invitations-panel.d.ts +95 -0
  27. package/dist/admin/invitations-panel.js +290 -0
  28. package/dist/admin/invitations-panel.js.map +1 -0
  29. package/dist/admin/reviewer-scope-editor.d.ts +95 -0
  30. package/dist/admin/reviewer-scope-editor.js +134 -0
  31. package/dist/admin/reviewer-scope-editor.js.map +1 -0
  32. package/dist/admin/role-select.d.ts +95 -0
  33. package/dist/admin/role-select.js +128 -0
  34. package/dist/admin/role-select.js.map +1 -0
  35. package/dist/admin/transfer-ownership-dialog.d.ts +95 -0
  36. package/dist/admin/transfer-ownership-dialog.js +83 -0
  37. package/dist/admin/transfer-ownership-dialog.js.map +1 -0
  38. package/dist/admin/user-roster.d.ts +95 -0
  39. package/dist/admin/user-roster.js +285 -0
  40. package/dist/admin/user-roster.js.map +1 -0
  41. package/dist/auth/auth-errors.d.ts +103 -6
  42. package/dist/auth/auth-errors.js +68 -2
  43. package/dist/auth/auth-errors.js.map +1 -1
  44. package/dist/auth/forgot-password-form.d.ts +10 -3
  45. package/dist/auth/forgot-password-form.js +6 -3
  46. package/dist/auth/forgot-password-form.js.map +1 -1
  47. package/dist/auth/register-form.d.ts +21 -12
  48. package/dist/auth/register-form.js +6 -4
  49. package/dist/auth/register-form.js.map +1 -1
  50. package/dist/auth/reset-password-form.d.ts +10 -3
  51. package/dist/auth/reset-password-form.js +6 -3
  52. package/dist/auth/reset-password-form.js.map +1 -1
  53. package/dist/auth/sign-in-form.d.ts +75 -9
  54. package/dist/auth/sign-in-form.js +105 -15
  55. package/dist/auth/sign-in-form.js.map +1 -1
  56. package/dist/auth/verify-email.d.ts +7 -2
  57. package/dist/auth/verify-email.js +44 -8
  58. package/dist/auth/verify-email.js.map +1 -1
  59. package/dist/chart.d.ts +20 -11
  60. package/dist/components/account-chips.d.ts +47 -7
  61. package/dist/components/account-chips.js +19 -3
  62. package/dist/components/account-chips.js.map +1 -1
  63. package/dist/components/account-settings-labels.d.ts +24 -1
  64. package/dist/components/account-settings-labels.js +6 -2
  65. package/dist/components/account-settings-labels.js.map +1 -1
  66. package/dist/components/account-settings.d.ts +69 -8
  67. package/dist/components/account-settings.js +133 -25
  68. package/dist/components/account-settings.js.map +1 -1
  69. package/dist/components/amount-input.d.ts +21 -12
  70. package/dist/components/button-group.d.ts +21 -12
  71. package/dist/components/calculator.d.ts +21 -12
  72. package/dist/components/confirm-dialog.d.ts +21 -12
  73. package/dist/components/copy-button.d.ts +18 -9
  74. package/dist/components/danger-confirm.d.ts +21 -12
  75. package/dist/components/data-table.js +8 -1
  76. package/dist/components/data-table.js.map +1 -1
  77. package/dist/components/date-picker.js +9 -4
  78. package/dist/components/date-picker.js.map +1 -1
  79. package/dist/components/facing-pair.d.ts +21 -12
  80. package/dist/components/file-button.d.ts +21 -12
  81. package/dist/components/file-dropzone.d.ts +21 -12
  82. package/dist/components/form-actions.d.ts +21 -12
  83. package/dist/components/iban-input.d.ts +21 -12
  84. package/dist/components/language-select.d.ts +21 -12
  85. package/dist/components/legal-page.d.ts +21 -12
  86. package/dist/components/legal.d.ts +21 -12
  87. package/dist/components/money-field.d.ts +21 -12
  88. package/dist/components/number-field.d.ts +21 -12
  89. package/dist/components/number-input.d.ts +21 -12
  90. package/dist/components/numpad-sheet.d.ts +21 -12
  91. package/dist/components/passkeys-setting.js +2 -1
  92. package/dist/components/passkeys-setting.js.map +1 -1
  93. package/dist/components/phone-input.d.ts +21 -12
  94. package/dist/components/series-chart.d.ts +21 -12
  95. package/dist/components/settings-fields.d.ts +55 -13
  96. package/dist/components/settings-fields.js +20 -1
  97. package/dist/components/settings-fields.js.map +1 -1
  98. package/dist/components/share-card.d.ts +20 -11
  99. package/dist/components/text-link.d.ts +21 -12
  100. package/dist/components/time-input.d.ts +21 -12
  101. package/dist/components/ui.d.ts +17 -8
  102. package/dist/{register-form-V1OeNlMr.d.ts → delete-account-setting-DCFS5Vof.d.ts} +6612 -5612
  103. package/dist/demo/demo-banner.d.ts +51 -0
  104. package/dist/demo/demo-banner.js +97 -0
  105. package/dist/demo/demo-banner.js.map +1 -0
  106. package/dist/demo/demo-countdown.d.ts +58 -0
  107. package/dist/demo/demo-countdown.js +79 -0
  108. package/dist/demo/demo-countdown.js.map +1 -0
  109. package/dist/demo/demo-ended.d.ts +45 -0
  110. package/dist/demo/demo-ended.js +42 -0
  111. package/dist/demo/demo-ended.js.map +1 -0
  112. package/dist/demo/demo-labels.d.ts +53 -0
  113. package/dist/demo/demo-labels.js +29 -0
  114. package/dist/demo/demo-labels.js.map +1 -0
  115. package/dist/demo/demo-session.d.ts +30 -0
  116. package/dist/demo/demo-session.js +8 -0
  117. package/dist/demo/demo-session.js.map +1 -0
  118. package/dist/demo/demo-start.d.ts +54 -0
  119. package/dist/demo/demo-start.js +68 -0
  120. package/dist/demo/demo-start.js.map +1 -0
  121. package/dist/feedback/feedback-attachment.d.ts +21 -12
  122. package/dist/feedback/feedback-context.d.ts +21 -12
  123. package/dist/feedback/feedback-crash.d.ts +21 -12
  124. package/dist/feedback/feedback-dialog.d.ts +21 -12
  125. package/dist/feedback/feedback-inbox.d.ts +21 -12
  126. package/dist/feedback/feedback-inbox.js +12 -2
  127. package/dist/feedback/feedback-inbox.js.map +1 -1
  128. package/dist/feedback/feedback-labels.d.ts +21 -12
  129. package/dist/feedback/feedback-labels.js +1 -0
  130. package/dist/feedback/feedback-labels.js.map +1 -1
  131. package/dist/feedback/feedback-menu.d.ts +21 -12
  132. package/dist/feedback/feedback-record.d.ts +21 -12
  133. package/dist/feedback/feedback-record.js +2 -0
  134. package/dist/feedback/feedback-record.js.map +1 -1
  135. package/dist/feedback/feedback-row-detail.d.ts +21 -12
  136. package/dist/feedback/feedback-row-detail.js +7 -0
  137. package/dist/feedback/feedback-row-detail.js.map +1 -1
  138. package/dist/feedback/feedback-status-undo.d.ts +21 -12
  139. package/dist/feedback/feedback-submit.d.ts +21 -12
  140. package/dist/feedback/feedback-swipe.d.ts +22 -13
  141. package/dist/feedback/feedback-swipe.js.map +1 -1
  142. package/dist/feedback/feedback-table.d.ts +18 -9
  143. package/dist/feedback/feedback-thread.d.ts +21 -12
  144. package/dist/feedback.d.ts +21 -12
  145. package/dist/hooks/use-anchored-panel.d.ts +6 -2
  146. package/dist/hooks/use-anchored-panel.js +2 -1
  147. package/dist/hooks/use-anchored-panel.js.map +1 -1
  148. package/dist/hooks/use-file-drop.d.ts +21 -12
  149. package/dist/i18n/defaults.d.ts +21 -12
  150. package/dist/i18n/defaults.js +28 -0
  151. package/dist/i18n/defaults.js.map +1 -1
  152. package/dist/i18n/german.d.ts +21 -12
  153. package/dist/i18n/german.js +298 -12
  154. package/dist/i18n/german.js.map +1 -1
  155. package/dist/i18n/kit-labels.d.ts +14 -5
  156. package/dist/i18n/kit-labels.js.map +1 -1
  157. package/dist/i18n/languages.d.ts +15 -6
  158. package/dist/i18n/locales/de-CH.d.ts +21 -12
  159. package/dist/i18n/locales/en.d.ts +21 -12
  160. package/dist/i18n/locales/es.d.ts +21 -12
  161. package/dist/i18n/locales/es.js +287 -10
  162. package/dist/i18n/locales/es.js.map +1 -1
  163. package/dist/i18n/locales/fr.d.ts +21 -12
  164. package/dist/i18n/locales/fr.js +290 -7
  165. package/dist/i18n/locales/fr.js.map +1 -1
  166. package/dist/i18n/locales/hu.d.ts +21 -12
  167. package/dist/i18n/locales/hu.js +288 -12
  168. package/dist/i18n/locales/hu.js.map +1 -1
  169. package/dist/i18n/locales/it.d.ts +21 -12
  170. package/dist/i18n/locales/it.js +289 -7
  171. package/dist/i18n/locales/it.js.map +1 -1
  172. package/dist/i18n/locales/zh.d.ts +21 -12
  173. package/dist/i18n/locales/zh.js +279 -7
  174. package/dist/i18n/locales/zh.js.map +1 -1
  175. package/dist/i18n/review.d.ts +21 -12
  176. package/dist/i18n/review.js +50 -1
  177. package/dist/i18n/review.js.map +1 -1
  178. package/dist/index.d.ts +31 -4
  179. package/dist/index.js +148 -0
  180. package/dist/index.js.map +1 -1
  181. package/dist/landing/access.d.ts +48 -0
  182. package/dist/landing/access.js +38 -0
  183. package/dist/landing/access.js.map +1 -0
  184. package/dist/landing/landing-actions.d.ts +138 -0
  185. package/dist/landing/landing-actions.js +39 -0
  186. package/dist/landing/landing-actions.js.map +1 -0
  187. package/dist/landing/landing-labels.d.ts +43 -0
  188. package/dist/landing/landing-labels.js +23 -0
  189. package/dist/landing/landing-labels.js.map +1 -0
  190. package/dist/landing/landing-sections.d.ts +218 -0
  191. package/dist/landing/landing-sections.js +167 -0
  192. package/dist/landing/landing-sections.js.map +1 -0
  193. package/dist/landing/page-seo.d.ts +99 -0
  194. package/dist/landing/page-seo.js +121 -0
  195. package/dist/landing/page-seo.js.map +1 -0
  196. package/dist/landing/public-header.d.ts +148 -0
  197. package/dist/landing/public-header.js +49 -0
  198. package/dist/landing/public-header.js.map +1 -0
  199. package/dist/landing/routing.d.ts +218 -0
  200. package/dist/landing/routing.js +81 -0
  201. package/dist/landing/routing.js.map +1 -0
  202. package/dist/rhf/fields.d.ts +21 -12
  203. package/dist/rhf/form.d.ts +21 -12
  204. package/dist/rhf.d.ts +21 -12
  205. package/dist/settings/settings-catalogue.d.ts +84 -0
  206. package/dist/settings/settings-catalogue.js +47 -0
  207. package/dist/settings/settings-catalogue.js.map +1 -0
  208. package/dist/settings/settings-context.d.ts +31 -0
  209. package/dist/settings/settings-context.js +11 -0
  210. package/dist/settings/settings-context.js.map +1 -0
  211. package/dist/settings/settings-heading.d.ts +50 -0
  212. package/dist/settings/settings-heading.js +27 -0
  213. package/dist/settings/settings-heading.js.map +1 -0
  214. package/dist/settings/settings-labels.d.ts +41 -0
  215. package/dist/settings/settings-labels.js +38 -0
  216. package/dist/settings/settings-labels.js.map +1 -0
  217. package/dist/settings/settings-layout.d.ts +77 -0
  218. package/dist/settings/settings-layout.js +242 -0
  219. package/dist/settings/settings-layout.js.map +1 -0
  220. package/dist/settings/settings-section.d.ts +133 -0
  221. package/dist/settings/settings-section.js +63 -0
  222. package/dist/settings/settings-section.js.map +1 -0
  223. package/dist/settings/use-account-language.d.ts +83 -0
  224. package/dist/settings/use-account-language.js +62 -0
  225. package/dist/settings/use-account-language.js.map +1 -0
  226. package/dist/settings/use-settings-focus.d.ts +34 -0
  227. package/dist/settings/use-settings-focus.js +77 -0
  228. package/dist/settings/use-settings-focus.js.map +1 -0
  229. package/dist/settings/use-settings-route.d.ts +118 -0
  230. package/dist/settings/use-settings-route.js +120 -0
  231. package/dist/settings/use-settings-route.js.map +1 -0
  232. package/dist/shell/app-shell.d.ts +27 -12
  233. package/dist/shell/app-shell.js +3 -2
  234. package/dist/shell/app-shell.js.map +1 -1
  235. package/dist/shell/top-bar-brand.d.ts +21 -12
  236. package/dist/shell.d.ts +25 -6
  237. package/dist/shell.js +56 -0
  238. package/dist/shell.js.map +1 -1
  239. package/dist/wizard/stepper-nav.d.ts +21 -12
  240. package/dist/wizard.d.ts +21 -12
  241. package/package.json +2 -1
  242. package/src/account/account-parts.ts +67 -0
  243. package/src/account/data-export-setting.tsx +219 -0
  244. package/src/account/delete-account-setting.tsx +363 -0
  245. package/src/account/email-change-setting.tsx +454 -0
  246. package/src/account/sessions-setting.tsx +284 -0
  247. package/src/admin/admin-action-confirm.tsx +495 -0
  248. package/src/admin/admin-action-log.tsx +297 -0
  249. package/src/admin/admin-parts.ts +82 -0
  250. package/src/admin/invitations-panel.tsx +523 -0
  251. package/src/admin/reviewer-scope-editor.tsx +234 -0
  252. package/src/admin/role-select.tsx +286 -0
  253. package/src/admin/transfer-ownership-dialog.tsx +172 -0
  254. package/src/admin/user-roster.tsx +573 -0
  255. package/src/auth/auth-errors.ts +196 -8
  256. package/src/auth/forgot-password-form.tsx +21 -5
  257. package/src/auth/register-form.tsx +24 -6
  258. package/src/auth/reset-password-form.tsx +19 -5
  259. package/src/auth/sign-in-form.tsx +241 -38
  260. package/src/auth/verify-email.tsx +59 -8
  261. package/src/components/account-chips.tsx +64 -7
  262. package/src/components/account-settings-labels.ts +28 -1
  263. package/src/components/account-settings.tsx +231 -34
  264. package/src/components/data-table.tsx +8 -0
  265. package/src/components/date-picker.tsx +9 -4
  266. package/src/components/passkeys-setting.tsx +2 -1
  267. package/src/components/settings-fields.tsx +50 -1
  268. package/src/demo/demo-banner.tsx +159 -0
  269. package/src/demo/demo-countdown.ts +141 -0
  270. package/src/demo/demo-ended.tsx +89 -0
  271. package/src/demo/demo-labels.ts +78 -0
  272. package/src/demo/demo-session.ts +32 -0
  273. package/src/demo/demo-start.tsx +150 -0
  274. package/src/feedback/feedback-inbox.tsx +21 -8
  275. package/src/feedback/feedback-labels.ts +2 -1
  276. package/src/feedback/feedback-record.ts +10 -7
  277. package/src/feedback/feedback-row-detail.tsx +28 -4
  278. package/src/feedback/feedback-swipe.tsx +1 -1
  279. package/src/hooks/use-anchored-panel.ts +14 -3
  280. package/src/i18n/defaults.ts +28 -0
  281. package/src/i18n/german.ts +327 -5
  282. package/src/i18n/kit-labels.tsx +42 -0
  283. package/src/i18n/locales/es.ts +313 -3
  284. package/src/i18n/locales/fr.ts +316 -0
  285. package/src/i18n/locales/hu.ts +308 -5
  286. package/src/i18n/locales/it.ts +313 -0
  287. package/src/i18n/locales/zh.ts +286 -0
  288. package/src/i18n/review.ts +49 -0
  289. package/src/index.ts +184 -0
  290. package/src/landing/access.ts +97 -0
  291. package/src/landing/landing-actions.tsx +92 -0
  292. package/src/landing/landing-labels.ts +59 -0
  293. package/src/landing/landing-sections.tsx +337 -0
  294. package/src/landing/page-seo.ts +251 -0
  295. package/src/landing/public-header.tsx +120 -0
  296. package/src/landing/routing.tsx +209 -0
  297. package/src/settings/settings-catalogue.ts +132 -0
  298. package/src/settings/settings-context.ts +33 -0
  299. package/src/settings/settings-heading.tsx +65 -0
  300. package/src/settings/settings-labels.ts +75 -0
  301. package/src/settings/settings-layout.tsx +379 -0
  302. package/src/settings/settings-section.tsx +108 -0
  303. package/src/settings/use-account-language.ts +146 -0
  304. package/src/settings/use-settings-focus.ts +120 -0
  305. package/src/settings/use-settings-route.ts +258 -0
  306. package/src/shell/app-shell.tsx +10 -2
  307. package/src/shell.ts +45 -0
@@ -0,0 +1,297 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+ import type { DateInput } from "../lib/format";
4
+ import { useKitLabels } from "../i18n/kit-labels";
5
+ import { DateMark } from "../components/account-chips";
6
+ import { Chip } from "../components/chip";
7
+ import { Pagination } from "../components/data-table-pagination";
8
+ import { Caption } from "../components/text";
9
+ import { Button, Spinner } from "../components/ui";
10
+ import { usePersonLabel } from "./admin-parts";
11
+ import type { AdminPerson } from "./admin-parts";
12
+
13
+ /**
14
+ * The admin actions, as the audit table records them (docs/user-admin-harmonization.md
15
+ * §2.3, §4.3): `GET /admin/actions?limit=&offset=&target=`, newest first, on the admin
16
+ * page and filtered to one account from its row.
17
+ *
18
+ * Every app writes the same small `admin_actions` row in the transaction of the action
19
+ * (keksdose and Kurvenschmiede had log lines; kastlan has an HTTP log with no named
20
+ * action), so the vocabulary is shared — {@link AdminActionKind} — and an app's own
21
+ * actions join it through `labels.actions`.
22
+ *
23
+ * What a row says, and what it never does: who, what, to whom, when, and a summary of the
24
+ * `detail` the APP writes (ids, roles, counts — never content, §4.3). Three kinds of
25
+ * actor: an admin; **the user themselves** (`deletion_request`: the actor is the target);
26
+ * and **nobody** (the scheduled erasure, the hand-over at erasure, §2.7), said as
27
+ * "automatically". After an erasure the target's name and address are scrubbed and the
28
+ * row stays: it reads "an erased account".
29
+ *
30
+ * It never fetches: the page of rows and the paging are the app's.
31
+ */
32
+
33
+ /** The shared vocabulary of `admin_actions.action` (§4.3). */
34
+ export type AdminActionKind =
35
+ | "deactivate"
36
+ | "reactivate"
37
+ | "role"
38
+ | "membership_remove"
39
+ | "password_change_require"
40
+ | "password_change_withdraw"
41
+ | "mail_verification"
42
+ | "mail_reset"
43
+ | "reviewer"
44
+ | "invite"
45
+ | "invite_resend"
46
+ | "invite_revoke"
47
+ | "transfer"
48
+ | "deletion_request"
49
+ | "deletion_cancel"
50
+ | "erase";
51
+
52
+ /** Every {@link AdminActionKind}, in the contract's order. */
53
+ export const ADMIN_ACTION_KINDS: readonly AdminActionKind[] = [
54
+ "deactivate",
55
+ "reactivate",
56
+ "role",
57
+ "membership_remove",
58
+ "password_change_require",
59
+ "password_change_withdraw",
60
+ "mail_verification",
61
+ "mail_reset",
62
+ "reviewer",
63
+ "invite",
64
+ "invite_resend",
65
+ "invite_revoke",
66
+ "transfer",
67
+ "deletion_request",
68
+ "deletion_cancel",
69
+ "erase",
70
+ ];
71
+
72
+ /** One logged action — the app's `admin_actions` row with the people joined in. */
73
+ export interface AdminActionLogEntry {
74
+ id: string | number;
75
+ /** When, as the row's `at`. */
76
+ at: DateInput;
77
+ /**
78
+ * Who did it: a person; `"self"` — the user themselves (a deletion request); or `null`
79
+ * — nobody, the scheduled job. A person whose `id` (or address) is the target's is
80
+ * read as `"self"` too.
81
+ */
82
+ actor: AdminPerson | "self" | null;
83
+ /** A {@link AdminActionKind}, or one of the app's own (named in `labels.actions`). */
84
+ action: AdminActionKind | (string & {});
85
+ /** Whom it was done to. `null`, or a person with nothing left in it: an erased
86
+ * account. An invitation's target is its address (`target_email`). */
87
+ target?: AdminPerson | null;
88
+ /** The `detail` in the app's words — "Member → Admin", "12 items to Ben Example". */
89
+ detail?: ReactNode;
90
+ }
91
+
92
+ /** The `adminActionLog` namespace. */
93
+ export interface AdminActionLogLabels {
94
+ /** The list's accessible name. */
95
+ title: string;
96
+ empty: string;
97
+ loading: string;
98
+ /** "by Ada Example". */
99
+ by: (actor: string) => string;
100
+ /** The user acted on their own account. */
101
+ bySelf: string;
102
+ /** No actor: a scheduled job. */
103
+ automatic: string;
104
+ /** A target scrubbed by an erasure. */
105
+ erased: string;
106
+ /** The filter chip: only one account's actions are shown. */
107
+ filteredTo: (target: string) => string;
108
+ /** The chip's ×. */
109
+ showAll: string;
110
+ /** A target name as a filter button — "Show only the actions on Ada Example". */
111
+ filterBy: (target: string) => string;
112
+ /** What each action is called, past tense, keyed by the vocabulary. An app adds its
113
+ * own here (`labels={{ actions: { plan: "Plan changed" } }}`). */
114
+ actions: { [K in AdminActionKind]?: string } & { [action: string]: string | undefined };
115
+ }
116
+
117
+ export const DEFAULT_ADMIN_ACTION_LOG_LABELS: AdminActionLogLabels = {
118
+ title: "Admin actions",
119
+ empty: "No admin actions yet.",
120
+ loading: "Loading…",
121
+ by: (actor) => `by ${actor}`,
122
+ bySelf: "by the user themselves",
123
+ automatic: "automatically",
124
+ erased: "an erased account",
125
+ filteredTo: (target) => `Only ${target}`,
126
+ showAll: "Show all actions",
127
+ filterBy: (target) => `Show only the actions on ${target}`,
128
+ actions: {
129
+ deactivate: "Deactivated",
130
+ reactivate: "Reactivated",
131
+ role: "Role changed",
132
+ membership_remove: "Removed from the company",
133
+ password_change_require: "New password required",
134
+ password_change_withdraw: "Password requirement withdrawn",
135
+ mail_verification: "Verification mail sent",
136
+ mail_reset: "Password reset mail sent",
137
+ reviewer: "Reviewer scope changed",
138
+ invite: "Invited",
139
+ invite_resend: "Invitation resent",
140
+ invite_revoke: "Invitation revoked",
141
+ transfer: "Work transferred",
142
+ deletion_request: "Deletion requested",
143
+ deletion_cancel: "Deletion cancelled",
144
+ erase: "Account erased",
145
+ },
146
+ };
147
+
148
+ /** The paging of `GET /admin/actions` — DataTable's `ServerPagination`, which the
149
+ * footer is. */
150
+ export interface AdminActionLogPaging {
151
+ /** 0-based. */
152
+ page: number;
153
+ pageSize: number;
154
+ total: number;
155
+ onPageChange: (page: number) => void;
156
+ onPageSizeChange?: (pageSize: number) => void;
157
+ }
158
+
159
+ export interface AdminActionLogProps {
160
+ /** One page, newest first, as the server sends it. */
161
+ entries: readonly AdminActionLogEntry[];
162
+ /** Left out: no footer (a short list on a row). */
163
+ paging?: AdminActionLogPaging;
164
+ /** The page is being fetched. */
165
+ loading?: boolean;
166
+ /** The filter in force: only this account's actions are shown (`?target=`). With
167
+ * `onTargetFilter`, a chip says so and its × clears it. */
168
+ target?: AdminPerson | null;
169
+ /** Filter by an account — or clear it with `null`. Given, every target's name is a
170
+ * button that narrows the list to it. Left out, the list is not filterable here. */
171
+ onTargetFilter?: (target: AdminPerson | null) => void;
172
+ className?: string;
173
+ labels?: Partial<AdminActionLogLabels>;
174
+ }
175
+
176
+ /** Whether a person has nothing left that names them — an erased target. */
177
+ function scrubbed(person: AdminPerson | null | undefined): boolean {
178
+ return !person || ![person.first, person.last, person.name, person.email].some((part) => (part ?? "").trim());
179
+ }
180
+
181
+ function samePerson(a: AdminPerson, b: AdminPerson | null | undefined): boolean {
182
+ if (!b) return false;
183
+ if (a.id !== undefined && a.id !== null && b.id !== undefined && b.id !== null) return a.id === b.id;
184
+ const ea = (a.email ?? "").trim().toLowerCase();
185
+ return ea !== "" && ea === (b.email ?? "").trim().toLowerCase();
186
+ }
187
+
188
+ export function AdminActionLog({
189
+ entries,
190
+ paging,
191
+ loading = false,
192
+ target,
193
+ onTargetFilter,
194
+ className,
195
+ labels: labelsProp,
196
+ }: AdminActionLogProps) {
197
+ const labels = useKitLabels("adminActionLog", DEFAULT_ADMIN_ACTION_LOG_LABELS, labelsProp);
198
+ const nameOf = usePersonLabel();
199
+ const filtered = target !== undefined && target !== null && !scrubbed(target);
200
+
201
+ const actorText = (entry: AdminActionLogEntry): string => {
202
+ const { actor } = entry;
203
+ if (actor === null) return labels.automatic;
204
+ if (actor === "self" || samePerson(actor, entry.target)) return labels.bySelf;
205
+ return labels.by(nameOf(actor) || labels.erased);
206
+ };
207
+
208
+ const targetNode = (entry: AdminActionLogEntry): ReactNode => {
209
+ if (scrubbed(entry.target)) return <span className="italic text-[var(--text-muted)]">{labels.erased}</span>;
210
+ const person = entry.target!;
211
+ const name = nameOf(person);
212
+ const address = (person.email ?? "").trim();
213
+ const text = (
214
+ <>
215
+ <span data-private className="font-medium text-[var(--text-primary)]">
216
+ {name}
217
+ </span>
218
+ {address && address !== name && (
219
+ <span data-private className="ms-1 text-xs text-[var(--text-muted)]">
220
+ {address}
221
+ </span>
222
+ )}
223
+ </>
224
+ );
225
+ if (!onTargetFilter || (filtered && samePerson(person, target))) return text;
226
+ return (
227
+ <Button
228
+ type="button"
229
+ variant="link"
230
+ size="sm"
231
+ className="h-auto min-h-0 p-0 text-start"
232
+ aria-label={labels.filterBy(name)}
233
+ onClick={() => onTargetFilter(person)}
234
+ >
235
+ {text}
236
+ </Button>
237
+ );
238
+ };
239
+
240
+ return (
241
+ <div className={cn("space-y-3", className)}>
242
+ {filtered && onTargetFilter && (
243
+ <Chip size="sm" tone="brand" onRemove={() => onTargetFilter(null)} removeLabel={labels.showAll}>
244
+ <span data-private>{labels.filteredTo(nameOf(target))}</span>
245
+ </Chip>
246
+ )}
247
+ {loading && entries.length === 0 ? (
248
+ <div className="flex justify-center py-4">
249
+ <Spinner label={labels.loading} />
250
+ </div>
251
+ ) : entries.length === 0 ? (
252
+ <Caption>{labels.empty}</Caption>
253
+ ) : (
254
+ <ol
255
+ aria-label={labels.title}
256
+ aria-busy={loading || undefined}
257
+ className={cn("divide-y divide-[var(--border)]", loading && "opacity-60")}
258
+ >
259
+ {entries.map((entry) => (
260
+ <li key={entry.id} data-action={entry.action} className="space-y-0.5 py-2 first:pt-0 last:pb-0">
261
+ <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5">
262
+ <div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm">
263
+ <span className="font-medium text-[var(--text-primary)]">
264
+ {labels.actions[entry.action] ?? entry.action}
265
+ </span>
266
+ <span className="min-w-0 break-words">{targetNode(entry)}</span>
267
+ </div>
268
+ <DateMark value={entry.at} display="relative" className="text-xs text-[var(--text-muted)]" />
269
+ </div>
270
+ <div className="text-xs text-[var(--text-muted)]">
271
+ <span data-private={entry.actor !== null && entry.actor !== "self" ? true : undefined}>
272
+ {actorText(entry)}
273
+ </span>
274
+ {entry.detail !== undefined && entry.detail !== null && entry.detail !== "" && (
275
+ <>
276
+ {" · "}
277
+ <span className="text-[var(--text-secondary)]">{entry.detail}</span>
278
+ </>
279
+ )}
280
+ </div>
281
+ </li>
282
+ ))}
283
+ </ol>
284
+ )}
285
+ {paging && paging.total > 0 && (
286
+ <Pagination
287
+ page={paging.page}
288
+ pageSize={paging.pageSize}
289
+ total={paging.total}
290
+ totalPages={Math.max(1, Math.ceil(paging.total / Math.max(1, paging.pageSize)))}
291
+ onPage={paging.onPageChange}
292
+ onPageSize={paging.onPageSizeChange}
293
+ />
294
+ )}
295
+ </div>
296
+ );
297
+ }
@@ -0,0 +1,82 @@
1
+ import { useCallback } from "react";
2
+ import type { ReactNode } from "react";
3
+ import { useKitDateFormatter, useKitLocale } from "../i18n/kit-labels";
4
+ import { formatDate, toDate } from "../lib/format";
5
+ import type { DateInput } from "../lib/format";
6
+ import { toLocalIso } from "../lib/dates";
7
+ import { formatPersonName } from "../lib/person-name";
8
+
9
+ /**
10
+ * What the admin parts share and do not export (0.30.0). Internal to `src/admin`.
11
+ *
12
+ * @internal
13
+ */
14
+
15
+ /** A callback that may answer a promise: resolve = done, reject = stay and say why. */
16
+ export type MaybePromise<T = unknown> = void | Promise<T>;
17
+
18
+ /** A person as the admin parts show one: the two name parts of the auth contract
19
+ * (§3.2), an older whole `name` as the fallback, and the address. */
20
+ export interface AdminPerson {
21
+ id?: string | number | null;
22
+ first?: string | null;
23
+ last?: string | null;
24
+ /** A whole name, for an API that still sends `display_name` only. */
25
+ name?: string | null;
26
+ email?: string | null;
27
+ }
28
+
29
+ /** A label that is really there — `null`, `false` and `""` are what `cond && text`
30
+ * evaluates to on the path where there is none. */
31
+ export function hasMessage(node: ReactNode): boolean {
32
+ return node !== undefined && node !== null && node !== false && node !== "";
33
+ }
34
+
35
+ /**
36
+ * The name to say for a person, in the READER's order (auth §3.2,
37
+ * {@link formatPersonName}), else the whole `name`, else the address, else `""`.
38
+ */
39
+ export function personLabel(person: AdminPerson | null | undefined, locale: string | undefined): string {
40
+ if (!person) return "";
41
+ return (
42
+ formatPersonName({ first: person.first, last: person.last }, locale) ||
43
+ (person.name ?? "").trim() ||
44
+ (person.email ?? "").trim()
45
+ );
46
+ }
47
+
48
+ /** {@link personLabel} in the provider's locale. */
49
+ export function usePersonLabel(localeProp?: string): (person: AdminPerson | null | undefined) => string {
50
+ const locale = useKitLocale(localeProp);
51
+ return useCallback((person) => personLabel(person, locale), [locale]);
52
+ }
53
+
54
+ /**
55
+ * A day as words in a sentence ("6 Nov 2026"): the provider's `formatDate` when the app
56
+ * set one (keksdose K12), asked for no weekday — a date in a sentence carries none — else
57
+ * `Intl`'s medium date. `""` for no date.
58
+ */
59
+ export function useDayText(localeProp?: string): (value: DateInput) => string {
60
+ const locale = useKitLocale(localeProp);
61
+ const fromProvider = useKitDateFormatter();
62
+ return useCallback(
63
+ (value) => {
64
+ const date = toDate(value);
65
+ if (!date) return "";
66
+ const own = fromProvider
67
+ ? fromProvider(toLocalIso(date), { unit: "day", source: "dateMark", locale, weekday: false })
68
+ : "";
69
+ return own || formatDate(date, "medium", { locale });
70
+ },
71
+ [fromProvider, locale],
72
+ );
73
+ }
74
+
75
+ /** Call a callback that may throw or answer a promise, as a promise. */
76
+ export function settle<T>(call: () => MaybePromise<T>): Promise<T | void> {
77
+ try {
78
+ return Promise.resolve(call());
79
+ } catch (error) {
80
+ return Promise.reject(error);
81
+ }
82
+ }