@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,363 @@
1
+ import { isValidElement, useRef, useState } from "react";
2
+ import type { ComponentPropsWithoutRef, ReactNode } from "react";
3
+ import { UserX } from "lucide-react";
4
+
5
+ import { useKitLabels, useKitLocale } from "../i18n/kit-labels";
6
+ import { cn } from "../lib/cn";
7
+ import { AlertBanner } from "../components/alert-banner";
8
+ import { hasMessage } from "../components/choice-parts";
9
+ import {
10
+ CurrentPasswordInput,
11
+ DangerConfirm,
12
+ DEFAULT_DANGER_CONFIRM_LABELS,
13
+ TypedConfirmField,
14
+ typedMatches,
15
+ } from "../components/danger-confirm";
16
+ import type { DangerConsequence } from "../components/danger-confirm";
17
+ import { Card } from "../components/ui";
18
+ import { authErrorCode, englishRateLimited, isRateLimited, retryAfterSeconds } from "../auth/auth-errors";
19
+ import { useFocusWhen } from "../auth/status-parts";
20
+ import { CARD_DESCRIPTION_CLASS, refusalCompanies, settle, useMounted } from "./account-parts";
21
+ import { SettingsCardTitle } from "../settings/settings-heading";
22
+
23
+ /* ── Labels ──────────────────────────────────────────────────────────────── */
24
+
25
+ /**
26
+ * Every string {@link DeleteAccountSetting} renders — the `deleteAccount` namespace of
27
+ * `<UiKitProvider labels>`, overridable per instance through `labels`. The confirm's
28
+ * Cancel, "Password" and "Type … to confirm" are `dangerConfirm`'s.
29
+ */
30
+ export interface DeleteAccountLabels {
31
+ /** The card's title. */
32
+ title: string;
33
+ /** Mode `after_days`, given the days: what happens and when. */
34
+ afterDays: (days: number) => string;
35
+ /** Mode `operator`: what happens, and who erases it. */
36
+ operator: string;
37
+ /** The button that opens the confirm. */
38
+ arm: string;
39
+ /** The confirm's warning, over the consequences. */
40
+ prompt: string;
41
+ /** The line in the consequences when `handOverCount` is given (Kurvenschmiede, §2.7),
42
+ * given the count. */
43
+ handOver: (count: number) => string;
44
+ /** The button that sends it. */
45
+ confirm: string;
46
+ /** Said once the request is accepted — the app signs out next. */
47
+ done: string;
48
+ /** `password_incorrect` (server-kit's `400` for a wrong current password), under the
49
+ * password field. */
50
+ wrongPassword: string;
51
+ /** `confirmation_mismatch` — the server did not take the typed address as the
52
+ * account's — under the address field. */
53
+ confirmationMismatch: string;
54
+ /** `last_admin` — the account is the last active admin. */
55
+ lastAdmin: string;
56
+ /** `last_admin` naming the companies (kastlan checks per company), given them as one
57
+ * list in the reader's language ("Example AG and Sample GmbH"). */
58
+ lastAdminOf: (companies: string) => string;
59
+ /** `household_has_members` — keksdose's guard (§6.4). */
60
+ householdHasMembers: string;
61
+ /** A request was throttled — HTTP 429 — given its `Retry-After` wait in seconds, or
62
+ * `undefined` without one. */
63
+ rateLimited: (seconds?: number) => string;
64
+ /** Any other failure. */
65
+ failed: string;
66
+ }
67
+
68
+ export const DEFAULT_DELETE_ACCOUNT_LABELS: DeleteAccountLabels = {
69
+ title: "Delete account",
70
+ afterDays: (days) =>
71
+ days === 1
72
+ ? "Your account is deactivated at once and erased for good 1 day later."
73
+ : `Your account is deactivated at once and erased for good ${days} days later.`,
74
+ operator: "Your account is deactivated at once, and an operator will erase it for good.",
75
+ arm: "Delete account…",
76
+ prompt: "You will be signed out on every device, and your account can no longer be used.",
77
+ handOver: (count) =>
78
+ count === 1
79
+ ? "1 item others can see will pass to an administrator."
80
+ : `${count} items others can see will pass to an administrator.`,
81
+ confirm: "Delete my account",
82
+ done: "Your account is deactivated. You are being signed out.",
83
+ wrongPassword: "The password is incorrect.",
84
+ confirmationMismatch: "This is not your account’s address.",
85
+ lastAdmin: "You are the last administrator. Make someone else an administrator first.",
86
+ lastAdminOf: (companies) =>
87
+ `You are the last administrator of ${companies}. Make someone else an administrator there first.`,
88
+ householdHasMembers:
89
+ "Your household has other members, so the account can’t be deleted here. Please write to the operator.",
90
+ rateLimited: englishRateLimited,
91
+ failed: "Your account could not be deleted. Please try again.",
92
+ };
93
+
94
+ /* ── Props ───────────────────────────────────────────────────────────────── */
95
+
96
+ /** What happens after the deactivation (§2.1, §6.4) — the app's setting. */
97
+ export type DeletionMode = "after_days" | "operator";
98
+
99
+ /** What {@link DeleteAccountSettingProps.onRequest} gets: the `POST /auth/me/deletion`
100
+ * body. */
101
+ export interface DeleteAccountValues {
102
+ /** As typed. */
103
+ password: string;
104
+ /** The address as TYPED, trimmed — the server re-checks it against the account
105
+ * (server-kit's `confirm_email_matches`), as keksdose's admin actions do. */
106
+ confirmEmail: string;
107
+ }
108
+
109
+ /** One line of the consequences: a node of the app's, or a {@link DangerConsequence}
110
+ * for a key of its own and the `severe` mark. */
111
+ export type DeleteAccountConsequence = ReactNode | DangerConsequence;
112
+
113
+ export interface DeleteAccountSettingProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
114
+ /** The account's address — the text to type. */
115
+ email: string;
116
+ /** `"after_days"` (keksdose, Kurvenschmiede: erased by a scheduled job) or
117
+ * `"operator"` (kastlan: company data is involved, an operator erases it). */
118
+ mode: DeletionMode;
119
+ /** With `after_days`: how many. Default 30 (§2.1). */
120
+ days?: number;
121
+ /**
122
+ * What goes, in the app's words (§6.4: "the app's own list") — derived from the
123
+ * account's own facts where it can be: keksdose's "Budget X will be deleted; its 2
124
+ * guests are told", rather than the worst case for everybody.
125
+ */
126
+ consequences?: readonly DeleteAccountConsequence[];
127
+ /** Kurvenschmiede (§2.7): how many owned items others can see — they pass to the
128
+ * first active admin at erasure. A line of its own in the consequences; 0 or left
129
+ * out, none. */
130
+ handOverCount?: number;
131
+ /**
132
+ * The confirm was answered: send `POST /auth/me/deletion {password, confirm_email}`.
133
+ * Resolve once it is accepted — the card says "You are being signed out" — and then
134
+ * sign the user out: the server ended every session with the deactivation. Reject
135
+ * with the client's error: `password_incorrect` under the password,
136
+ * `confirmation_mismatch` under the address, `last_admin` (with kastlan's
137
+ * `companies`) and `household_has_members` above the buttons, a 429 as
138
+ * `rateLimited`, anything else (a demo account's 403, an app's own code) through
139
+ * `describeError`, else `failed`. The confirm stays open with what was typed, for a
140
+ * retry.
141
+ */
142
+ onRequest: (values: DeleteAccountValues) => Promise<unknown> | void;
143
+ /** The app's own words for a failure, or `undefined` for the kit's. Asked first, for
144
+ * every failure — kastlan's `last_admin` names the companies. */
145
+ describeError?: (error: unknown) => ReactNode | undefined;
146
+ labels?: Partial<DeleteAccountLabels>;
147
+ }
148
+
149
+ /** Whether an item is a `{ text }` line rather than a node — a React element is an
150
+ * object too, and so would a `{ text }` lookalike be, so the element test comes first. */
151
+ function isConsequence(item: DeleteAccountConsequence): item is DangerConsequence {
152
+ return typeof item === "object" && item !== null && !isValidElement(item) && "text" in item;
153
+ }
154
+
155
+ /**
156
+ * Deleting one's own account (docs/user-admin-harmonization.md §6.4, §2.1): what it does,
157
+ * the password, the typed address — then the app signs out.
158
+ *
159
+ * **Two stages, said before the button.** The request DEACTIVATES the account at once
160
+ * (no sign-in, every session ended, marked for deletion); the erasure follows per the
161
+ * app's `mode` — "erased for good 30 days later" (`after_days`, a scheduled job) or "an
162
+ * operator will erase it" (`operator`, kastlan, where company records are involved).
163
+ * The sentence is under the title, so it is read before anything is pressed.
164
+ *
165
+ * **The confirm is a {@link DangerConfirm}** with the two guards of every "type the
166
+ * address" rule in the apps: the address typed (compared caseless, as in all of them)
167
+ * and the password. They are the tile's own {@link TypedConfirmField} and
168
+ * {@link CurrentPasswordInput}, drawn in its fields slot and held by
169
+ * `confirmDisabledReason` with the tile's own sentences, rather than its built-in
170
+ * `phrase` / `requirePassword` — those have no error of their own, and a wrong password
171
+ * belongs under the password. Its consequences are the app's list, plus
172
+ * Kurvenschmiede's hand-over line (`handOverCount`, §2.7: shared work goes to an
173
+ * administrator, never a refusal). The typed address goes to `onRequest` as typed, so
174
+ * the server re-checks it (`confirm_email_matches`).
175
+ *
176
+ * **Refusals** (§6.4) are read by code ({@link authErrorCode}): `password_incorrect` (a
177
+ * `400`, so an app's 401 handling never signs the person out over a typo) under the
178
+ * password, `confirmation_mismatch` under the address, `last_admin` — naming kastlan's
179
+ * companies when the answer lists them — and keksdose's `household_has_members` guard
180
+ * above the buttons; a 429; and the rest in the app's words. The confirm stays open with
181
+ * what was typed, for a retry; Cancel wipes it.
182
+ *
183
+ * **After success** the card says the account is deactivated and the person is being
184
+ * signed out — the app's to do, since the kit sends nothing (auth §8).
185
+ */
186
+ export function DeleteAccountSetting({
187
+ email,
188
+ mode,
189
+ days = 30,
190
+ consequences,
191
+ handOverCount,
192
+ onRequest,
193
+ describeError,
194
+ labels: labelsProp,
195
+ className,
196
+ ...rest
197
+ }: DeleteAccountSettingProps) {
198
+ const labels = useKitLabels("deleteAccount", DEFAULT_DELETE_ACCOUNT_LABELS, labelsProp);
199
+ // The guards speak the tile's words, so "Type “…” to confirm" and "Enter your password
200
+ // to confirm" are translated once, in `dangerConfirm`.
201
+ const guardLabels = useKitLabels("dangerConfirm", DEFAULT_DANGER_CONFIRM_LABELS);
202
+ const locale = useKitLocale();
203
+ const [typed, setTyped] = useState("");
204
+ const [password, setPassword] = useState("");
205
+ const [busy, setBusy] = useState(false);
206
+ const [fieldError, setFieldError] = useState<"email" | "password" | null>(null);
207
+ const [failure, setFailure] = useState<ReactNode>(null);
208
+ const [done, setDone] = useState(false);
209
+ const mounted = useMounted();
210
+ const typedRef = useRef<HTMLInputElement>(null);
211
+ const passwordRef = useRef<HTMLInputElement>(null);
212
+ const doneRef = useRef<HTMLDivElement>(null);
213
+ // The tile went with the press; the answer takes the focus, so it is read out.
214
+ useFocusWhen(done, doneRef);
215
+
216
+ const modeSentence = mode === "operator" ? labels.operator : labels.afterDays(days);
217
+
218
+ const lines: (string | DangerConsequence)[] = (consequences ?? [])
219
+ // `{cond && "…"}` lines that came out empty are no lines.
220
+ .filter((item) => item !== null && item !== undefined && item !== false && item !== "")
221
+ .map((item, index) => (isConsequence(item) ? item : { key: `app-${index}`, text: item }));
222
+ if (handOverCount !== undefined && handOverCount > 0) {
223
+ lines.push({ key: "hand-over", text: labels.handOver(handOverCount) });
224
+ }
225
+
226
+ // The first guard still open, in the order the fields are drawn — DangerConfirm's rule.
227
+ const held = !typedMatches(typed, email, "caseless")
228
+ ? typeof guardLabels.needsPhrase === "function"
229
+ ? guardLabels.needsPhrase(email)
230
+ : guardLabels.needsPhrase
231
+ : password === ""
232
+ ? guardLabels.needsPassword
233
+ : undefined;
234
+
235
+ const present = (error: unknown) => {
236
+ const own = describeError?.(error);
237
+ if (hasMessage(own)) {
238
+ setFailure(own);
239
+ return;
240
+ }
241
+ if (isRateLimited(error)) {
242
+ setFailure(labels.rateLimited(retryAfterSeconds(error)));
243
+ return;
244
+ }
245
+ const code = authErrorCode(error);
246
+ // `invalid_credentials` too, for a server that answered a wrong password so before
247
+ // server-kit 0.4.0 named `password_incorrect`.
248
+ if (code === "password_incorrect" || code === "invalid_credentials") {
249
+ setFieldError("password");
250
+ passwordRef.current?.focus();
251
+ return;
252
+ }
253
+ if (code === "confirmation_mismatch" || code === "confirmation_required") {
254
+ setFieldError("email");
255
+ typedRef.current?.focus();
256
+ return;
257
+ }
258
+ if (code === "last_admin") {
259
+ const companies = refusalCompanies(error);
260
+ setFailure(
261
+ companies
262
+ ? labels.lastAdminOf(new Intl.ListFormat(locale, { type: "conjunction" }).format(companies))
263
+ : labels.lastAdmin,
264
+ );
265
+ return;
266
+ }
267
+ setFailure(code === "household_has_members" ? labels.householdHasMembers : labels.failed);
268
+ };
269
+
270
+ const confirm = () => {
271
+ setFailure(null);
272
+ setFieldError(null);
273
+ setBusy(true);
274
+ const values: DeleteAccountValues = { password, confirmEmail: typed.trim() };
275
+ return settle(() => onRequest(values)).then(
276
+ () => {
277
+ if (!mounted.current) return;
278
+ setBusy(false);
279
+ setDone(true);
280
+ },
281
+ (error: unknown) => {
282
+ if (mounted.current) {
283
+ setBusy(false);
284
+ present(error);
285
+ }
286
+ // Rejected again, so DangerConfirm stays armed with what was typed.
287
+ throw error;
288
+ },
289
+ );
290
+ };
291
+
292
+ // A disarmed tile holds no password — DangerConfirm's own rule, for the fields it
293
+ // does not own here.
294
+ const disarmed = (armed: boolean) => {
295
+ if (armed) return;
296
+ setTyped("");
297
+ setPassword("");
298
+ setFieldError(null);
299
+ setFailure(null);
300
+ };
301
+
302
+ return (
303
+ <Card
304
+ {...rest}
305
+ data-state={done ? "done" : "form"}
306
+ tone="danger"
307
+ className={cn("p-4 space-y-3", className)}
308
+ >
309
+ <div>
310
+ <SettingsCardTitle>{labels.title}</SettingsCardTitle>
311
+ <div className={CARD_DESCRIPTION_CLASS}>{modeSentence}</div>
312
+ </div>
313
+ {done ? (
314
+ <div ref={doneRef} tabIndex={-1} role="status" className="flex items-start gap-2 text-sm outline-none">
315
+ <UserX aria-hidden className="mt-0.5 size-4 shrink-0 text-[var(--text-muted)]" />
316
+ <span>{labels.done}</span>
317
+ </div>
318
+ ) : (
319
+ <>
320
+ <DangerConfirm
321
+ commit
322
+ armLabel={labels.arm}
323
+ prompt={labels.prompt}
324
+ consequences={lines}
325
+ confirmLabel={labels.confirm}
326
+ confirmDisabledReason={held}
327
+ busy={busy}
328
+ onArmedChange={disarmed}
329
+ onConfirm={confirm}
330
+ >
331
+ <TypedConfirmField
332
+ ref={typedRef}
333
+ target={email}
334
+ match="caseless"
335
+ value={typed}
336
+ onValueChange={(next) => {
337
+ setTyped(next);
338
+ if (fieldError === "email") setFieldError(null);
339
+ }}
340
+ error={fieldError === "email" ? labels.confirmationMismatch : undefined}
341
+ busy={busy}
342
+ />
343
+ <CurrentPasswordInput
344
+ ref={passwordRef}
345
+ value={password}
346
+ onValueChange={(next) => {
347
+ setPassword(next);
348
+ if (fieldError === "password") setFieldError(null);
349
+ }}
350
+ error={fieldError === "password" ? labels.wrongPassword : undefined}
351
+ busy={busy}
352
+ />
353
+ </DangerConfirm>
354
+ {hasMessage(failure) && (
355
+ <AlertBanner tone="danger" size="sm" role="alert">
356
+ {failure}
357
+ </AlertBanner>
358
+ )}
359
+ </>
360
+ )}
361
+ </Card>
362
+ );
363
+ }