@eifi1/ui-kit 0.32.1 → 0.33.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 (373) hide show
  1. package/README.md +16 -8
  2. package/dist/account/delete-account-setting.d.ts +10 -10
  3. package/dist/account/delete-account-setting.js +2 -1
  4. package/dist/account/delete-account-setting.js.map +1 -1
  5. package/dist/account/email-change-setting.d.ts +5 -0
  6. package/dist/account/email-change-setting.js +4 -3
  7. package/dist/account/email-change-setting.js.map +1 -1
  8. package/dist/account/sessions-setting.d.ts +4 -0
  9. package/dist/account/sessions-setting.js +3 -2
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.d.ts +10 -10
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/invitations-panel.d.ts +10 -10
  14. package/dist/admin/invitations-panel.js +4 -3
  15. package/dist/admin/invitations-panel.js.map +1 -1
  16. package/dist/admin/plan-change.d.ts +100 -0
  17. package/dist/admin/plan-change.js +208 -0
  18. package/dist/admin/plan-change.js.map +1 -0
  19. package/dist/admin/reviewer-scope-editor.d.ts +10 -10
  20. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  21. package/dist/admin/role-select.d.ts +7 -7
  22. package/dist/admin/role-select.js.map +1 -1
  23. package/dist/admin/transfer-ownership-dialog.d.ts +10 -10
  24. package/dist/admin/user-roster.d.ts +7 -7
  25. package/dist/admin/user-roster.js +2 -2
  26. package/dist/admin/user-roster.js.map +1 -1
  27. package/dist/auth/auth-errors.d.ts +13 -2
  28. package/dist/auth/auth-errors.js +5 -1
  29. package/dist/auth/auth-errors.js.map +1 -1
  30. package/dist/auth/complete-name-dialog.d.ts +4 -1
  31. package/dist/auth/complete-name-dialog.js +2 -1
  32. package/dist/auth/complete-name-dialog.js.map +1 -1
  33. package/dist/auth/register-form.d.ts +10 -10
  34. package/dist/billing/billing-banner.d.ts +13 -11
  35. package/dist/billing/billing-banner.js.map +1 -1
  36. package/dist/billing/billing-labels.d.ts +10 -10
  37. package/dist/billing/billing-labels.js +10 -1
  38. package/dist/billing/billing-labels.js.map +1 -1
  39. package/dist/billing/billing-lock.d.ts +39 -12
  40. package/dist/billing/billing-lock.js +9 -3
  41. package/dist/billing/billing-lock.js.map +1 -1
  42. package/dist/billing/billing-overview.d.ts +154 -0
  43. package/dist/billing/billing-overview.js +2 -0
  44. package/dist/billing/billing-overview.js.map +1 -0
  45. package/dist/billing/billing-standing.d.ts +10 -10
  46. package/dist/billing/checkout-processing.d.ts +217 -0
  47. package/dist/billing/checkout-processing.js +198 -0
  48. package/dist/billing/checkout-processing.js.map +1 -0
  49. package/dist/billing/checkout-return.d.ts +51 -0
  50. package/dist/billing/checkout-return.js +43 -0
  51. package/dist/billing/checkout-return.js.map +1 -0
  52. package/dist/billing/pay-page.d.ts +210 -0
  53. package/dist/billing/pay-page.js +113 -0
  54. package/dist/billing/pay-page.js.map +1 -0
  55. package/dist/billing/plan-card.d.ts +10 -10
  56. package/dist/billing/plan-limit-notice.d.ts +10 -10
  57. package/dist/billing/plan-limit-toast.d.ts +143 -0
  58. package/dist/billing/plan-limit-toast.js +40 -0
  59. package/dist/billing/plan-limit-toast.js.map +1 -0
  60. package/dist/billing/subscription-actions.d.ts +38 -15
  61. package/dist/billing/subscription-actions.js +9 -2
  62. package/dist/billing/subscription-actions.js.map +1 -1
  63. package/dist/billing/subscription-status-chip.d.ts +9 -9
  64. package/dist/bin/eifi1-pay-page.mjs +166 -0
  65. package/dist/chart.d.ts +10 -10
  66. package/dist/components/account-chips.d.ts +6 -1
  67. package/dist/components/account-settings.d.ts +4 -1
  68. package/dist/components/account-settings.js +2 -1
  69. package/dist/components/account-settings.js.map +1 -1
  70. package/dist/components/amount-input.d.ts +10 -10
  71. package/dist/components/autocomplete.d.ts +2 -1
  72. package/dist/components/autocomplete.js +3 -1
  73. package/dist/components/autocomplete.js.map +1 -1
  74. package/dist/components/button-group.d.ts +10 -10
  75. package/dist/components/button-group.js +1 -1
  76. package/dist/components/button-group.js.map +1 -1
  77. package/dist/components/calculator.d.ts +10 -10
  78. package/dist/components/calculator.js +3 -3
  79. package/dist/components/calculator.js.map +1 -1
  80. package/dist/components/checkbox-group.d.ts +2 -1
  81. package/dist/components/checkbox-group.js.map +1 -1
  82. package/dist/components/checkbox.d.ts +2 -1
  83. package/dist/components/checkbox.js.map +1 -1
  84. package/dist/components/chip.d.ts +2 -1
  85. package/dist/components/chip.js +19 -17
  86. package/dist/components/chip.js.map +1 -1
  87. package/dist/components/choice-card.d.ts +3 -1
  88. package/dist/components/choice-card.js +1 -1
  89. package/dist/components/choice-card.js.map +1 -1
  90. package/dist/components/combobox.d.ts +4 -3
  91. package/dist/components/combobox.js.map +1 -1
  92. package/dist/components/confirm-dialog.d.ts +10 -10
  93. package/dist/components/confirm-dialog.js +6 -3
  94. package/dist/components/confirm-dialog.js.map +1 -1
  95. package/dist/components/copy-button.d.ts +11 -11
  96. package/dist/components/country-select.d.ts +2 -1
  97. package/dist/components/country-select.js.map +1 -1
  98. package/dist/components/danger-confirm.d.ts +10 -10
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.d.ts +12 -1
  101. package/dist/components/data-table-filter-popover.d.ts +12 -1
  102. package/dist/components/data-table-filters.d.ts +12 -1
  103. package/dist/components/data-table-labels.d.ts +12 -1
  104. package/dist/components/data-table-pagination.d.ts +12 -1
  105. package/dist/components/data-table.d.ts +12 -1
  106. package/dist/components/data-table.js +3 -3
  107. package/dist/components/data-table.js.map +1 -1
  108. package/dist/components/date-picker.js +6 -4
  109. package/dist/components/date-picker.js.map +1 -1
  110. package/dist/components/entity-combobox.d.ts +3 -2
  111. package/dist/components/entity-combobox.js.map +1 -1
  112. package/dist/components/facing-pair.d.ts +10 -10
  113. package/dist/components/field-parts.d.ts +32 -4
  114. package/dist/components/field-parts.js +54 -19
  115. package/dist/components/field-parts.js.map +1 -1
  116. package/dist/components/file-button.d.ts +10 -10
  117. package/dist/components/file-button.js +3 -2
  118. package/dist/components/file-button.js.map +1 -1
  119. package/dist/components/file-dropzone.d.ts +11 -11
  120. package/dist/components/file-dropzone.js.map +1 -1
  121. package/dist/components/floating-panel.js +5 -5
  122. package/dist/components/floating-panel.js.map +1 -1
  123. package/dist/components/form-actions.d.ts +9 -9
  124. package/dist/components/form-actions.js +3 -3
  125. package/dist/components/form-actions.js.map +1 -1
  126. package/dist/components/iban-input.d.ts +10 -10
  127. package/dist/components/inline-edit-field.d.ts +2 -1
  128. package/dist/components/inline-edit-field.js +6 -6
  129. package/dist/components/inline-edit-field.js.map +1 -1
  130. package/dist/components/language-select.d.ts +10 -10
  131. package/dist/components/legal-page.d.ts +10 -10
  132. package/dist/components/legal.d.ts +10 -10
  133. package/dist/components/list.d.ts +2 -1
  134. package/dist/components/list.js +4 -5
  135. package/dist/components/list.js.map +1 -1
  136. package/dist/components/money-field.d.ts +10 -10
  137. package/dist/components/month-picker.js +54 -44
  138. package/dist/components/month-picker.js.map +1 -1
  139. package/dist/components/multi-entity-combobox.d.ts +3 -2
  140. package/dist/components/multi-entity-combobox.js.map +1 -1
  141. package/dist/components/number-field.d.ts +10 -10
  142. package/dist/components/number-input.d.ts +10 -10
  143. package/dist/components/numpad-sheet.d.ts +10 -10
  144. package/dist/components/numpad-sheet.js +2 -2
  145. package/dist/components/numpad-sheet.js.map +1 -1
  146. package/dist/components/page-header.js +6 -1
  147. package/dist/components/page-header.js.map +1 -1
  148. package/dist/components/phone-input.d.ts +10 -10
  149. package/dist/components/popover.d.ts +1 -1
  150. package/dist/components/popover.js +2 -0
  151. package/dist/components/popover.js.map +1 -1
  152. package/dist/components/row-actions.d.ts +5 -5
  153. package/dist/components/row-actions.js +130 -38
  154. package/dist/components/row-actions.js.map +1 -1
  155. package/dist/components/series-chart.d.ts +10 -10
  156. package/dist/components/settings-fields.d.ts +10 -10
  157. package/dist/components/share-card.d.ts +7 -7
  158. package/dist/components/share-card.js +26 -10
  159. package/dist/components/share-card.js.map +1 -1
  160. package/dist/components/status-dot.d.ts +1 -0
  161. package/dist/components/status-dot.js +2 -2
  162. package/dist/components/status-dot.js.map +1 -1
  163. package/dist/components/swatch-picker.d.ts +2 -1
  164. package/dist/components/swatch-picker.js.map +1 -1
  165. package/dist/components/swipeable-row.d.ts +88 -17
  166. package/dist/components/swipeable-row.js +46 -9
  167. package/dist/components/swipeable-row.js.map +1 -1
  168. package/dist/components/switch.d.ts +2 -1
  169. package/dist/components/switch.js.map +1 -1
  170. package/dist/components/table.d.ts +2 -1
  171. package/dist/components/table.js +6 -0
  172. package/dist/components/table.js.map +1 -1
  173. package/dist/components/text-link.d.ts +9 -9
  174. package/dist/components/time-input.d.ts +10 -10
  175. package/dist/components/toggle-group.d.ts +2 -1
  176. package/dist/components/toggle-group.js +1 -1
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/translation-review-editor.d.ts +4 -0
  179. package/dist/components/translation-review-editor.js +4 -3
  180. package/dist/components/translation-review-editor.js.map +1 -1
  181. package/dist/components/translation-review-parts.d.ts +1 -0
  182. package/dist/components/translation-review.d.ts +1 -0
  183. package/dist/components/translation-review.js +15 -18
  184. package/dist/components/translation-review.js.map +1 -1
  185. package/dist/components/ui.d.ts +9 -9
  186. package/dist/components/ui.js +48 -28
  187. package/dist/components/ui.js.map +1 -1
  188. package/dist/components/use-table-state.d.ts +12 -1
  189. package/dist/components/user-avatar.d.ts +1 -0
  190. package/dist/components/write-lock.d.ts +101 -13
  191. package/dist/components/write-lock.js +37 -13
  192. package/dist/components/write-lock.js.map +1 -1
  193. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  194. package/dist/data-table.d.ts +12 -1
  195. package/dist/{delete-account-setting-FLyJf-kZ.d.ts → delete-account-setting-DiPX4Vue.d.ts} +365 -47
  196. package/dist/feedback/feedback-attachment.d.ts +10 -10
  197. package/dist/feedback/feedback-attachment.js +7 -7
  198. package/dist/feedback/feedback-attachment.js.map +1 -1
  199. package/dist/feedback/feedback-context.d.ts +10 -10
  200. package/dist/feedback/feedback-crash.d.ts +10 -10
  201. package/dist/feedback/feedback-dialog.d.ts +10 -10
  202. package/dist/feedback/feedback-inbox.d.ts +9 -9
  203. package/dist/feedback/feedback-inbox.js +7 -3
  204. package/dist/feedback/feedback-inbox.js.map +1 -1
  205. package/dist/feedback/feedback-labels.d.ts +10 -10
  206. package/dist/feedback/feedback-menu.d.ts +3 -3
  207. package/dist/feedback/feedback-record.d.ts +10 -10
  208. package/dist/feedback/feedback-row-detail.d.ts +10 -10
  209. package/dist/feedback/feedback-row-detail.js.map +1 -1
  210. package/dist/feedback/feedback-status-undo.d.ts +10 -10
  211. package/dist/feedback/feedback-submit.d.ts +10 -10
  212. package/dist/feedback/feedback-swipe.d.ts +10 -10
  213. package/dist/feedback/feedback-swipe.js +3 -6
  214. package/dist/feedback/feedback-swipe.js.map +1 -1
  215. package/dist/feedback/feedback-table.d.ts +10 -10
  216. package/dist/feedback/feedback-table.js.map +1 -1
  217. package/dist/feedback/feedback-thread.d.ts +10 -10
  218. package/dist/feedback.d.ts +10 -10
  219. package/dist/hooks/use-file-drop.d.ts +10 -10
  220. package/dist/i18n/defaults.d.ts +10 -10
  221. package/dist/i18n/defaults.js +2 -0
  222. package/dist/i18n/defaults.js.map +1 -1
  223. package/dist/i18n/german.d.ts +10 -10
  224. package/dist/i18n/german.js +34 -1
  225. package/dist/i18n/german.js.map +1 -1
  226. package/dist/i18n/kit-labels.d.ts +4 -4
  227. package/dist/i18n/kit-labels.js.map +1 -1
  228. package/dist/i18n/languages.d.ts +10 -10
  229. package/dist/i18n/locales/de-CH.d.ts +10 -10
  230. package/dist/i18n/locales/en.d.ts +10 -10
  231. package/dist/i18n/locales/es.d.ts +10 -10
  232. package/dist/i18n/locales/es.js +34 -1
  233. package/dist/i18n/locales/es.js.map +1 -1
  234. package/dist/i18n/locales/fr.d.ts +10 -10
  235. package/dist/i18n/locales/fr.js +35 -1
  236. package/dist/i18n/locales/fr.js.map +1 -1
  237. package/dist/i18n/locales/hu.d.ts +10 -10
  238. package/dist/i18n/locales/hu.js +36 -1
  239. package/dist/i18n/locales/hu.js.map +1 -1
  240. package/dist/i18n/locales/it.d.ts +10 -10
  241. package/dist/i18n/locales/it.js +34 -1
  242. package/dist/i18n/locales/it.js.map +1 -1
  243. package/dist/i18n/locales/zh.d.ts +10 -10
  244. package/dist/i18n/locales/zh.js +34 -1
  245. package/dist/i18n/locales/zh.js.map +1 -1
  246. package/dist/i18n/review.d.ts +10 -10
  247. package/dist/i18n/review.js +8 -1
  248. package/dist/i18n/review.js.map +1 -1
  249. package/dist/index.d.ts +10 -5
  250. package/dist/index.js +40 -2
  251. package/dist/index.js.map +1 -1
  252. package/dist/landing/landing-actions.d.ts +10 -10
  253. package/dist/landing/landing-sections.d.ts +10 -10
  254. package/dist/landing/public-header.d.ts +10 -10
  255. package/dist/landing/routing.d.ts +10 -10
  256. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  257. package/dist/pay/index.html +30 -0
  258. package/dist/pay/pay.css +126 -0
  259. package/dist/pay/pay.js +198 -0
  260. package/dist/rhf/fields.d.ts +10 -10
  261. package/dist/rhf/form.d.ts +10 -10
  262. package/dist/rhf.d.ts +10 -10
  263. package/dist/settings/settings-section.d.ts +10 -10
  264. package/dist/shell/app-shell.d.ts +9 -9
  265. package/dist/shell/app-shell.js +2 -1
  266. package/dist/shell/app-shell.js.map +1 -1
  267. package/dist/shell/top-bar-brand.d.ts +10 -10
  268. package/dist/shell/topbar-action-menu.d.ts +1 -0
  269. package/dist/shell.d.ts +10 -10
  270. package/dist/testing/css-variables.d.ts +74 -0
  271. package/dist/testing/css-variables.generated.d.ts +7 -0
  272. package/dist/testing/css-variables.generated.js +8 -0
  273. package/dist/testing/css-variables.generated.js.map +1 -0
  274. package/dist/testing/css-variables.js +93 -0
  275. package/dist/testing/css-variables.js.map +1 -0
  276. package/dist/testing/index.d.ts +1 -0
  277. package/dist/testing/index.js +12 -0
  278. package/dist/testing/index.js.map +1 -0
  279. package/dist/theme/contrast-tokens.d.ts +44 -1
  280. package/dist/theme/contrast-tokens.js +21 -0
  281. package/dist/theme/contrast-tokens.js.map +1 -1
  282. package/dist/theme/contrast.d.ts +2 -2
  283. package/dist/theme/palette-derive.js +60 -11
  284. package/dist/theme/palette-derive.js.map +1 -1
  285. package/dist/theme/palette-presets.js +45 -43
  286. package/dist/theme/palette-presets.js.map +1 -1
  287. package/dist/wizard/stepper-nav.d.ts +11 -11
  288. package/dist/wizard/stepper-nav.js.map +1 -1
  289. package/dist/wizard.d.ts +10 -10
  290. package/package.json +10 -2
  291. package/src/account/delete-account-setting.tsx +6 -1
  292. package/src/account/email-change-setting.tsx +9 -3
  293. package/src/account/sessions-setting.tsx +7 -2
  294. package/src/admin/admin-action-confirm.tsx +2 -1
  295. package/src/admin/invitations-panel.tsx +9 -3
  296. package/src/admin/plan-change.tsx +459 -0
  297. package/src/admin/reviewer-scope-editor.tsx +2 -1
  298. package/src/admin/role-select.tsx +2 -1
  299. package/src/admin/user-roster.tsx +5 -3
  300. package/src/auth/auth-errors.ts +25 -2
  301. package/src/auth/complete-name-dialog.tsx +6 -2
  302. package/src/billing/billing-banner.tsx +3 -1
  303. package/src/billing/billing-labels.ts +45 -0
  304. package/src/billing/billing-lock.ts +36 -3
  305. package/src/billing/billing-overview.ts +58 -0
  306. package/src/billing/checkout-processing.ts +391 -0
  307. package/src/billing/checkout-return.ts +90 -0
  308. package/src/billing/pay-page.ts +242 -0
  309. package/src/billing/plan-limit-toast.tsx +91 -0
  310. package/src/billing/subscription-actions.tsx +41 -8
  311. package/src/components/account-settings.tsx +6 -2
  312. package/src/components/autocomplete.tsx +5 -2
  313. package/src/components/button-group.tsx +4 -1
  314. package/src/components/calculator.tsx +10 -6
  315. package/src/components/checkbox-group.tsx +2 -2
  316. package/src/components/checkbox.tsx +2 -1
  317. package/src/components/chip.tsx +37 -32
  318. package/src/components/choice-card.tsx +4 -2
  319. package/src/components/combobox.tsx +4 -4
  320. package/src/components/confirm-dialog.tsx +13 -6
  321. package/src/components/country-select.tsx +2 -2
  322. package/src/components/danger-confirm.tsx +2 -2
  323. package/src/components/data-table.tsx +20 -9
  324. package/src/components/date-picker.tsx +10 -7
  325. package/src/components/entity-combobox.tsx +3 -3
  326. package/src/components/field-parts.tsx +61 -14
  327. package/src/components/file-button.tsx +5 -4
  328. package/src/components/file-dropzone.tsx +2 -2
  329. package/src/components/floating-panel.tsx +7 -5
  330. package/src/components/form-actions.tsx +12 -5
  331. package/src/components/inline-edit-field.tsx +13 -13
  332. package/src/components/list.tsx +10 -9
  333. package/src/components/month-picker.tsx +8 -2
  334. package/src/components/multi-entity-combobox.tsx +3 -3
  335. package/src/components/numpad-sheet.tsx +8 -4
  336. package/src/components/page-header.tsx +15 -1
  337. package/src/components/popover.tsx +4 -0
  338. package/src/components/row-actions.tsx +201 -50
  339. package/src/components/share-card.tsx +67 -13
  340. package/src/components/status-dot.tsx +7 -7
  341. package/src/components/swatch-picker.tsx +2 -2
  342. package/src/components/swipeable-row.tsx +138 -27
  343. package/src/components/switch.tsx +2 -1
  344. package/src/components/table.tsx +8 -1
  345. package/src/components/toggle-group.tsx +5 -3
  346. package/src/components/translation-review-editor.tsx +8 -3
  347. package/src/components/translation-review.tsx +18 -22
  348. package/src/components/ui.tsx +98 -44
  349. package/src/components/write-lock.tsx +150 -24
  350. package/src/feedback/feedback-attachment.tsx +13 -9
  351. package/src/feedback/feedback-inbox.tsx +10 -6
  352. package/src/feedback/feedback-row-detail.tsx +3 -2
  353. package/src/feedback/feedback-swipe.tsx +6 -11
  354. package/src/feedback/feedback-table.tsx +2 -1
  355. package/src/i18n/defaults.ts +2 -0
  356. package/src/i18n/german.ts +38 -0
  357. package/src/i18n/kit-labels.tsx +4 -0
  358. package/src/i18n/locales/es.ts +34 -0
  359. package/src/i18n/locales/fr.ts +39 -0
  360. package/src/i18n/locales/hu.ts +37 -0
  361. package/src/i18n/locales/it.ts +35 -0
  362. package/src/i18n/locales/zh.ts +33 -0
  363. package/src/i18n/review.ts +7 -0
  364. package/src/index.ts +48 -4
  365. package/src/shell/app-shell.tsx +11 -6
  366. package/src/testing/css-variables.generated.ts +112 -0
  367. package/src/testing/css-variables.ts +191 -0
  368. package/src/testing/index.ts +13 -0
  369. package/src/theme/contrast-tokens.ts +53 -2
  370. package/src/theme/palette-derive.ts +87 -10
  371. package/src/theme/palette-presets.ts +45 -43
  372. package/src/wizard/stepper-nav.tsx +2 -1
  373. package/tokens.css +306 -46
@@ -0,0 +1,208 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
5
+ import { formatNumber, EMPTY_FORMATTED_VALUE } from "../lib/format.js";
6
+ import { toLocalIso } from "../lib/dates.js";
7
+ import { DatePicker } from "../components/date-picker.js";
8
+ import { Tooltip } from "../components/tooltip.js";
9
+ import { Select } from "../components/ui.js";
10
+ import { COMMIT_EXCEPT_BILLING } from "../components/write-lock.js";
11
+ import { useBillingLabels } from "../billing/billing-labels.js";
12
+ import { SubscriptionStatusChip } from "../billing/subscription-status-chip.js";
13
+ import { AdminActionConfirm } from "./admin-action-confirm.js";
14
+ import { personLabel, useDayText } from "./admin-parts.js";
15
+ const DEFAULT_PLAN_CHANGE_LABELS = {
16
+ column: "Plan",
17
+ title: "Change plan",
18
+ confirm: "Change plan",
19
+ plan: "Plan",
20
+ current: (plan) => `Current plan: ${plan}`,
21
+ keepsItems: "A smaller plan only blocks creating more; nothing is deleted.",
22
+ until: "Free until",
23
+ untilHint: "Leave empty for no end. A running beta keeps its own end.",
24
+ needsChange: "Pick another plan or an end date.",
25
+ changed: (from, to) => `Plan changed from ${from} to ${to}.`,
26
+ set: (to) => `Plan set to ${to}.`,
27
+ keptBeta: "The beta keeps its end; only the plan changed.",
28
+ untilDone: (date) => `Free until ${date}.`,
29
+ overLimit: "Above the new plan\u2019s limit: nothing is removed, new items are blocked.",
30
+ usageOf: (used, limit, dimension) => `${used} of ${limit} ${dimension}`
31
+ };
32
+ function codeOf(raw) {
33
+ const code = (raw ?? "").trim().toLowerCase();
34
+ return code === "" ? null : code;
35
+ }
36
+ function PlanCell({
37
+ code,
38
+ name,
39
+ status,
40
+ usage,
41
+ usageOf
42
+ }) {
43
+ const locale = useKitLocale();
44
+ const figure = (value) => formatNumber(value, { locale });
45
+ return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", "data-plan": code ?? void 0, children: [
46
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
47
+ /* @__PURE__ */ jsx("span", { className: code ? "font-medium" : "text-[var(--text-muted)]", children: name }),
48
+ status !== void 0 && /* @__PURE__ */ jsx(SubscriptionStatusChip, { status, size: "xs" })
49
+ ] }),
50
+ usage && usage.limit !== null && // "1/5" under the plan, "1 of 5 budgets" in the tooltip: the sentence spelled out
51
+ // inline was the widest thing in keksdose's table.
52
+ /* @__PURE__ */ jsx(Tooltip, { label: usageOf(figure(usage.used), figure(usage.limit), usage.label), children: /* @__PURE__ */ jsxs("span", { className: "block text-xs tabular-nums text-[var(--text-muted)]", children: [
53
+ figure(usage.used),
54
+ "/",
55
+ figure(usage.limit)
56
+ ] }) })
57
+ ] });
58
+ }
59
+ function planColumn(options) {
60
+ const { key = "plan", plan, planName, status, usage, filterPlans, sortBy, headerText, labels: labelsProp } = options;
61
+ const labels = { ...DEFAULT_PLAN_CHANGE_LABELS, ...labelsProp };
62
+ const header = headerText ?? labels.column;
63
+ const nameOf = (code) => code ? planName(code) : EMPTY_FORMATTED_VALUE;
64
+ return {
65
+ key,
66
+ header,
67
+ headerText: header,
68
+ cell: (row) => {
69
+ const code = codeOf(plan(row));
70
+ return /* @__PURE__ */ jsx(
71
+ PlanCell,
72
+ {
73
+ code,
74
+ name: nameOf(code),
75
+ status: status?.(row),
76
+ usage: usage?.(row),
77
+ usageOf: labels.usageOf
78
+ }
79
+ );
80
+ },
81
+ sortBy: sortBy === false ? void 0 : sortBy ?? ((row) => codeOf(plan(row)) ?? ""),
82
+ filter: filterPlans ? {
83
+ type: "select",
84
+ getValue: (row) => codeOf(plan(row)) ?? "",
85
+ options: [...new Set(filterPlans.map((code) => codeOf(code)).filter((code) => code !== null))].map(
86
+ (code) => ({ value: code, label: planName(code) })
87
+ )
88
+ } : void 0
89
+ };
90
+ }
91
+ function usePlanColumn(options) {
92
+ const { key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels: labelsProp } = options;
93
+ const labels = useKitLabels("planChange", DEFAULT_PLAN_CHANGE_LABELS, labelsProp);
94
+ return useMemo(
95
+ () => planColumn({ key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels }),
96
+ [key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels]
97
+ );
98
+ }
99
+ function endOfDay(date) {
100
+ return (/* @__PURE__ */ new Date(`${date}T23:59:59`)).toISOString();
101
+ }
102
+ function PlanChangeConfirm({
103
+ target,
104
+ plans,
105
+ current: currentProp,
106
+ usage,
107
+ grantEnd = false,
108
+ onConfirm,
109
+ onClose,
110
+ describeError,
111
+ labels: labelsProp
112
+ }) {
113
+ const labels = useKitLabels("planChange", DEFAULT_PLAN_CHANGE_LABELS, labelsProp);
114
+ const locale = useKitLocale();
115
+ const current = codeOf(currentProp);
116
+ const [plan, setPlan] = useState(current ?? "");
117
+ const [until, setUntil] = useState("");
118
+ const [today] = useState(() => toLocalIso(/* @__PURE__ */ new Date()));
119
+ const options = plans.map((p) => ({ code: codeOf(p.code) ?? p.code, name: p.name }));
120
+ const nameOf = (code) => options.find((p) => p.code === code)?.name ?? code;
121
+ if (current && !options.some((p) => p.code === current)) options.unshift({ code: current, name: current });
122
+ const unchanged = plan === "" || plan === current && !until;
123
+ const who = { ...target, email: target.email || personLabel(target, locale) };
124
+ return /* @__PURE__ */ jsxs(
125
+ AdminActionConfirm,
126
+ {
127
+ level: "acknowledge",
128
+ tone: "warning",
129
+ target: who,
130
+ title: labels.title,
131
+ confirmLabel: labels.confirm,
132
+ confirmDisabledReason: unchanged ? labels.needsChange : void 0,
133
+ describeError,
134
+ commit: COMMIT_EXCEPT_BILLING,
135
+ onConfirm: (values) => onConfirm({ plan, ...until ? { comped_until: endOfDay(until) } : {} }, values),
136
+ onClose,
137
+ children: [
138
+ (current !== null || usage !== void 0) && /* @__PURE__ */ jsxs("div", { className: "space-y-0.5 text-xs text-[var(--text-muted)]", "data-plan-current": current ?? void 0, children: [
139
+ current !== null && /* @__PURE__ */ jsx("p", { children: labels.current(nameOf(current)) }),
140
+ usage !== void 0 && /* @__PURE__ */ jsx("p", { children: usage })
141
+ ] }),
142
+ /* @__PURE__ */ jsxs(Select, { label: labels.plan, hint: labels.keepsItems, value: plan, onChange: (e) => setPlan(e.target.value), children: [
143
+ plan === "" && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: EMPTY_FORMATTED_VALUE }),
144
+ options.map((option) => /* @__PURE__ */ jsx("option", { value: option.code, children: option.name }, option.code))
145
+ ] }),
146
+ grantEnd && /* @__PURE__ */ jsx(
147
+ DatePicker,
148
+ {
149
+ label: labels.until,
150
+ hint: labels.untilHint,
151
+ value: until,
152
+ onChange: setUntil,
153
+ min: today,
154
+ clearable: true
155
+ }
156
+ )
157
+ ]
158
+ }
159
+ );
160
+ }
161
+ function usePlanChangeResult() {
162
+ const labels = useKitLabels("planChange", DEFAULT_PLAN_CHANGE_LABELS);
163
+ const billing = useBillingLabels();
164
+ const locale = useKitLocale();
165
+ const dayText = useDayText();
166
+ return useCallback(
167
+ (res, planName, options = {}) => {
168
+ const { dimensionLabels, formatValue } = options;
169
+ const name = (raw) => planName(codeOf(raw) ?? raw);
170
+ const to = name(res.plan);
171
+ const from = codeOf(res.previous_plan);
172
+ const lines = [
173
+ {
174
+ tone: "success",
175
+ text: from !== null && from !== codeOf(res.plan) ? labels.changed(name(from), to) : labels.set(to)
176
+ }
177
+ ];
178
+ if (res.kept_beta) lines.push({ tone: "info", text: labels.keptBeta });
179
+ else if (res.comped_until) lines.push({ tone: "success", text: labels.untilDone(dayText(res.comped_until)) });
180
+ if (res.over_limit) {
181
+ lines.push({ tone: "warning", text: labels.overLimit });
182
+ const figure = (dimension, value) => formatValue ? formatValue(dimension, value) : formatNumber(value, { locale });
183
+ for (const [dimension, limit] of Object.entries(res.limits ?? {})) {
184
+ const used = res.usage?.[dimension];
185
+ if (limit === null || used === void 0 || used <= limit) continue;
186
+ lines.push({
187
+ tone: "warning",
188
+ text: billing.limitUsageLine(
189
+ dimensionLabels?.[dimension] ?? dimension,
190
+ figure(dimension, used),
191
+ figure(dimension, limit)
192
+ )
193
+ });
194
+ }
195
+ }
196
+ return lines;
197
+ },
198
+ [labels, billing, locale, dayText]
199
+ );
200
+ }
201
+ export {
202
+ DEFAULT_PLAN_CHANGE_LABELS,
203
+ PlanChangeConfirm,
204
+ planColumn,
205
+ usePlanChangeResult,
206
+ usePlanColumn
207
+ };
208
+ //# sourceMappingURL=plan-change.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/plan-change.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport { formatNumber, EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport { toLocalIso } from \"../lib/dates\";\nimport type { DataTableColumn } from \"../components/data-table\";\nimport { DatePicker } from \"../components/date-picker\";\nimport { Tooltip } from \"../components/tooltip\";\nimport { Select } from \"../components/ui\";\nimport { COMMIT_EXCEPT_BILLING } from \"../components/write-lock\";\nimport { useBillingLabels } from \"../billing/billing-labels\";\nimport type { SubscriptionStatus } from \"../billing/billing-labels\";\nimport { SubscriptionStatusChip } from \"../billing/subscription-status-chip\";\nimport { AdminActionConfirm } from \"./admin-action-confirm\";\nimport type { AdminActionConfirmValues } from \"./admin-action-confirm\";\nimport { personLabel, useDayText } from \"./admin-parts\";\nimport type { AdminPerson, MaybePromise } from \"./admin-parts\";\n\n/**\n * The operator's plan parts (docs/billing-harmonization.md §14.12, decision 27):\n * {@link planColumn} for the roster or the companies list, {@link PlanChangeConfirm}\n * for the grant, and {@link usePlanChangeResult} for what the answer says.\n *\n * All three apps have the route (`POST /admin/users/{id}/plan`, kastlan's\n * `POST /platform/companies/{id}/plan`) and server-kit's `PlanChangeRequest` /\n * `PlanChangeResponse`; only keksdose had a UI (`users-panel.tsx`, `PlanConfirm` in\n * `user-admin-confirm.tsx`), and these are its parts, lifted. The plan names are the\n * app's (§3.1): every part takes `planName` or the plans with their names. The plan\n * codes come from `GET /admin/plans` (kastlan `GET /platform/plans`), which answers\n * whatever the billing switch says.\n */\n\n/** The `planChange` namespace: the column, the grant dialog and the result's lines. */\nexport interface PlanChangeLabels {\n /** The column's header: \"Plan\". */\n column: string;\n /** The dialog's title: \"Change plan\". */\n title: string;\n /** Its confirm button: \"Change plan\". */\n confirm: string;\n /** The plan select's label: \"Plan\". */\n plan: string;\n /** The line over the select: \"Current plan: Free\". */\n current: (plan: string) => string;\n /** Under the select — the fact that is not on the button. */\n keepsItems: string;\n /** The end of a free grant's field: \"Free until\". */\n until: string;\n /** Under it. */\n untilHint: string;\n /** Why the confirm is held while nothing would change. */\n needsChange: string;\n /** The result: the plan moved from one to another. */\n changed: (from: string, to: string) => string;\n /** The result: an account with no plan before, or the same plan again. */\n set: (to: string) => string;\n /** The result: a running beta kept its own end (§12.34); only the plan moved. */\n keptBeta: string;\n /** The result: the grant runs until `date` (formatted). */\n untilDone: (date: string) => string;\n /** The result: the account owns more than the new plan allows. */\n overLimit: string;\n /** The column's usage, spelled out (its tooltip): \"1 of 5 budgets\". Figures arrive\n * formatted; `dimension` is the app's word, as `usage` gives it. */\n usageOf: (used: string, limit: string, dimension: string) => string;\n}\n\nexport const DEFAULT_PLAN_CHANGE_LABELS: PlanChangeLabels = {\n column: \"Plan\",\n title: \"Change plan\",\n confirm: \"Change plan\",\n plan: \"Plan\",\n current: (plan) => `Current plan: ${plan}`,\n keepsItems: \"A smaller plan only blocks creating more; nothing is deleted.\",\n until: \"Free until\",\n untilHint: \"Leave empty for no end. A running beta keeps its own end.\",\n needsChange: \"Pick another plan or an end date.\",\n changed: (from, to) => `Plan changed from ${from} to ${to}.`,\n set: (to) => `Plan set to ${to}.`,\n keptBeta: \"The beta keeps its end; only the plan changed.\",\n untilDone: (date) => `Free until ${date}.`,\n overLimit: \"Above the new plan’s limit: nothing is removed, new items are blocked.\",\n usageOf: (used, limit, dimension) => `${used} of ${limit} ${dimension}`,\n};\n\n/** A plan code as the parts compare it: lowercase (§12.16 — an older row's \"FREE\" is\n * the same plan as \"free\"), and no code at all as null. */\nfunction codeOf(raw: string | null | undefined): string | null {\n const code = (raw ?? \"\").trim().toLowerCase();\n return code === \"\" ? null : code;\n}\n\n/* ── The column ────────────────────────────────────────────────────────────── */\n\n/** What a row's usage is, for the line under its plan: \"1/5\". */\nexport interface PlanColumnUsage {\n used: number;\n /** The plan's limit for it; `null` (unlimited, or none known): no line. */\n limit: number | null;\n /** The app's word for what is counted, as in \"1 of 5 budgets\". */\n label: string;\n}\n\nexport interface PlanColumnOptions<T> {\n /** The column's key. Default `\"plan\"`. */\n key?: string;\n /** The row's plan code, read without case (§12.16); null for none. */\n plan: (row: T) => string | null;\n /** The plan's display name, the app's (§3.1). Gets the lowercase code. */\n planName: (code: string) => string;\n /** The row's standing, as a `SubscriptionStatusChip` beside the name. Left out, or\n * undefined for a row: no chip — billing off, where a status says nothing. */\n status?: (row: T) => SubscriptionStatus | undefined;\n /** The row's usage of its plan's limit: \"1/5\" under the name, \"1 of 5 budgets\" in\n * the tooltip. Undefined for a row, or a `null` limit: no line. */\n usage?: (row: T) => PlanColumnUsage | undefined;\n /** A select filter over these codes, each named by `planName`. Left out: none. */\n filterPlans?: readonly string[];\n /**\n * The sort. Default: by the plan code (keksdose's, `users-panel.tsx:739`). A function:\n * by its value — the catalogue's `sort` for rank order. `false`: not sortable.\n */\n sortBy?: false | ((row: T) => string | number | null | undefined);\n /** The header's text. Default `planChange.column`. */\n headerText?: string;\n /** The words, already resolved. Default English; {@link usePlanColumn} reads the\n * provider's `planChange` namespace. */\n labels?: Partial<PlanChangeLabels>;\n}\n\n/** The cell: a component, so the figures follow the provider's locale. */\nfunction PlanCell({\n code,\n name,\n status,\n usage,\n usageOf,\n}: {\n code: string | null;\n name: string;\n status: SubscriptionStatus | undefined;\n usage: PlanColumnUsage | undefined;\n usageOf: PlanChangeLabels[\"usageOf\"];\n}) {\n const locale = useKitLocale();\n const figure = (value: number) => formatNumber(value, { locale });\n return (\n <div className=\"whitespace-nowrap\" data-plan={code ?? undefined}>\n <div className=\"flex items-center gap-1.5\">\n <span className={code ? \"font-medium\" : \"text-[var(--text-muted)]\"}>{name}</span>\n {/* The standing at a glance (§6): the kit's word and tone, never colour alone. */}\n {status !== undefined && <SubscriptionStatusChip status={status} size=\"xs\" />}\n </div>\n {usage && usage.limit !== null && (\n // \"1/5\" under the plan, \"1 of 5 budgets\" in the tooltip: the sentence spelled out\n // inline was the widest thing in keksdose's table.\n <Tooltip label={usageOf(figure(usage.used), figure(usage.limit), usage.label)}>\n <span className=\"block text-xs tabular-nums text-[var(--text-muted)]\">\n {figure(usage.used)}/{figure(usage.limit)}\n </span>\n </Tooltip>\n )}\n </div>\n );\n}\n\n/**\n * The roster's (or the companies list's) plan column: the plan's display name, the\n * standing chip beside it while billing is on, and the usage under it — keksdose's\n * column, `users-panel.tsx:706-745`. Pass it in `userRosterColumns`' `extra`, or in\n * any `DataTable`'s columns:\n *\n * extra: [planColumn<AdminUserRow>({\n * plan: (u) => u.plan,\n * planName,\n * status: billingOn ? (u) => u.subscription?.status : undefined,\n * usage: (u) => ({ used: u.budgets_owned, limit: u.limits.budgets, label: t(\"budgets\") }),\n * filterPlans: plans.map((p) => p.code),\n * })]\n *\n * Pure: hand it resolved words, or use {@link usePlanColumn}.\n */\nexport function planColumn<T>(options: PlanColumnOptions<T>): DataTableColumn<T> {\n const { key = \"plan\", plan, planName, status, usage, filterPlans, sortBy, headerText, labels: labelsProp } = options;\n const labels: PlanChangeLabels = { ...DEFAULT_PLAN_CHANGE_LABELS, ...labelsProp };\n const header = headerText ?? labels.column;\n const nameOf = (code: string | null) => (code ? planName(code) : EMPTY_FORMATTED_VALUE);\n return {\n key,\n header,\n headerText: header,\n cell: (row) => {\n const code = codeOf(plan(row));\n return (\n <PlanCell\n code={code}\n name={nameOf(code)}\n status={status?.(row)}\n usage={usage?.(row)}\n usageOf={labels.usageOf}\n />\n );\n },\n sortBy: sortBy === false ? undefined : (sortBy ?? ((row) => codeOf(plan(row)) ?? \"\")),\n filter: filterPlans\n ? {\n type: \"select\",\n getValue: (row) => codeOf(plan(row)) ?? \"\",\n options: [...new Set(filterPlans.map((code) => codeOf(code)).filter((code) => code !== null))].map(\n (code) => ({ value: code, label: planName(code) }),\n ),\n }\n : undefined,\n };\n}\n\n/**\n * {@link planColumn} with the `planChange` words from the provider, memoised on the\n * options — pass stable callbacks (`useCallback`), or the column is rebuilt on every\n * render.\n */\nexport function usePlanColumn<T>(options: PlanColumnOptions<T>): DataTableColumn<T> {\n const { key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels: labelsProp } = options;\n const labels = useKitLabels(\"planChange\", DEFAULT_PLAN_CHANGE_LABELS, labelsProp);\n return useMemo(\n () => planColumn<T>({ key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels }),\n [key, plan, planName, status, usage, filterPlans, sortBy, headerText, labels],\n );\n}\n\n/* ── The grant ─────────────────────────────────────────────────────────────── */\n\n/** What {@link PlanChangeConfirmProps.onConfirm} sends: server-kit's\n * `PlanChangeRequest` without its `acknowledged`, which comes in `values`. */\nexport interface PlanChange {\n /** The picked plan's code. */\n plan: string;\n /** The end of the free grant, as ISO UTC — only when one was picked. */\n comped_until?: string;\n}\n\nexport interface PlanChangeConfirmProps {\n /**\n * Whom it is about — a user (`AdminActionTarget`), or kastlan's company, which has no\n * address: its name is said instead (the level is `acknowledge`, so nothing is typed).\n */\n target: AdminPerson & { email?: string | null };\n /** The plans to offer, in the catalogue's order, by their display names (§3.1) — from\n * `GET /admin/plans`. */\n plans: readonly { code: string; name: string }[];\n /** The account's plan now; null for none. The select opens on it. */\n current: string | null;\n /** What the account uses, in the app's sentence: \"3 of 5 budgets\". */\n usage?: ReactNode;\n /** Offer \"Free until\" — only meaningful while billing is on, where a grant's end\n * means something to the payer. */\n grantEnd?: boolean;\n /**\n * Sends it: `{plan}`, and `comped_until` only when an end was picked — the END of the\n * picked day in the operator's time zone, as ISO UTC (a grant \"until 31 January\" lasts\n * through it). No end: a grant without one, or on a running beta only the plan moves\n * and the beta keeps its own end (§12.34). `values.acknowledged` is the body's\n * `acknowledged`. A promise is handled as `AdminActionConfirm`'s.\n */\n onConfirm: (change: PlanChange, values: AdminActionConfirmValues) => MaybePromise;\n onClose: (done: boolean) => void;\n /** The app's words for a failure the kit has no code for. */\n describeError?: (error: unknown) => ReactNode | undefined;\n labels?: Partial<PlanChangeLabels>;\n}\n\n/** The end of the day `date` (YYYY-MM-DD) names, in this device's time zone, as ISO\n * UTC — keksdose's `endOfDay` (`user-admin-confirm.tsx:44-46`). */\nfunction endOfDay(date: string): string {\n return new Date(`${date}T23:59:59`).toISOString();\n}\n\n/**\n * An operator's plan change (§14.12): `AdminActionConfirm` at `acknowledge`, the line\n * with the current plan and the app's usage sentence, a plan select, and — with\n * `grantEnd` — the end of a free grant. The confirm is held while nothing would change.\n *\n * Exempt from a BILLING lock, built in (`COMMIT_EXCEPT_BILLING`, §12.36): a plan change\n * is an admin route (§12.13), and the operator's own account or company may be lapsed —\n * kastlan's review. A demo lock still holds it.\n */\nexport function PlanChangeConfirm({\n target,\n plans,\n current: currentProp,\n usage,\n grantEnd = false,\n onConfirm,\n onClose,\n describeError,\n labels: labelsProp,\n}: PlanChangeConfirmProps) {\n const labels = useKitLabels(\"planChange\", DEFAULT_PLAN_CHANGE_LABELS, labelsProp);\n const locale = useKitLocale();\n // Seeded from the account, so the picker opens on the truth and confirming without\n // touching it is held rather than a silent free → free in the audit.\n const current = codeOf(currentProp);\n const [plan, setPlan] = useState(current ?? \"\");\n const [until, setUntil] = useState(\"\");\n // A grant ends today at the earliest; read once, not on every render.\n const [today] = useState(() => toLocalIso(new Date()));\n const options = plans.map((p) => ({ code: codeOf(p.code) ?? p.code, name: p.name }));\n const nameOf = (code: string) => options.find((p) => p.code === code)?.name ?? code;\n // An account on a plan the catalogue no longer lists still opens on it.\n if (current && !options.some((p) => p.code === current)) options.unshift({ code: current, name: current });\n const unchanged = plan === \"\" || (plan === current && !until);\n // A company has no address: the dialog says its name in the address's place.\n const who = { ...target, email: target.email || personLabel(target, locale) };\n\n return (\n <AdminActionConfirm\n level=\"acknowledge\"\n tone=\"warning\"\n target={who}\n title={labels.title}\n confirmLabel={labels.confirm}\n confirmDisabledReason={unchanged ? labels.needsChange : undefined}\n describeError={describeError}\n commit={COMMIT_EXCEPT_BILLING}\n onConfirm={(values) => onConfirm({ plan, ...(until ? { comped_until: endOfDay(until) } : {}) }, values)}\n onClose={onClose}\n >\n {(current !== null || usage !== undefined) && (\n <div className=\"space-y-0.5 text-xs text-[var(--text-muted)]\" data-plan-current={current ?? undefined}>\n {current !== null && <p>{labels.current(nameOf(current))}</p>}\n {usage !== undefined && <p>{usage}</p>}\n </div>\n )}\n <Select label={labels.plan} hint={labels.keepsItems} value={plan} onChange={(e) => setPlan(e.target.value)}>\n {plan === \"\" && (\n <option value=\"\" disabled>\n {EMPTY_FORMATTED_VALUE}\n </option>\n )}\n {options.map((option) => (\n <option key={option.code} value={option.code}>\n {option.name}\n </option>\n ))}\n </Select>\n {grantEnd && (\n <DatePicker\n label={labels.until}\n hint={labels.untilHint}\n value={until}\n onChange={setUntil}\n min={today}\n clearable\n />\n )}\n </AdminActionConfirm>\n );\n}\n\n/* ── The answer ────────────────────────────────────────────────────────────── */\n\n/**\n * A `PlanChangeResponse` as the page reads it — server-kit 0.7's (§14.12), which gained\n * `previous_plan` nullable, `usage`, `kept_beta` and `comped_until`.\n */\nexport interface PlanChangeOutcome {\n /** null: the account had no subscription. */\n previous_plan: string | null;\n plan: string;\n over_limit: boolean;\n /** The new plan's limits; null for unlimited. */\n limits: Readonly<Record<string, number | null>>;\n /** What the payer has, per dimension — the figures for the over-limit lines:\n * `{ budgets: 7 }`. */\n usage: Readonly<Record<string, number>>;\n /** A running beta kept its own end (§12.34). */\n kept_beta?: boolean;\n /** The grant's end as the change wrote it; null: no end, or no grant. */\n comped_until?: string | null;\n}\n\n/** A line's tone: the app's toast kind. */\nexport type PlanChangeTone = \"success\" | \"info\" | \"warning\";\n\nexport interface PlanChangeLine {\n tone: PlanChangeTone;\n text: string;\n}\n\nexport interface PlanChangeResultOptions {\n /** The app's word per dimension, for the over-limit figures: `{ budgets: \"Budgets\" }`.\n * A dimension without one is named by its key. */\n dimensionLabels?: Readonly<Record<string, string>>;\n /** How a figure is written. Default: a number in the kit's locale. */\n formatValue?: (dimension: string, value: number) => string;\n}\n\n/**\n * The lines for a plan change's answer, each with its tone, for the app's toasts —\n * echoing the SERVER's before and after, not what was picked: a toast that reported the\n * request would say \"done\" to a write that landed differently (keksdose's rule).\n *\n * const lines = usePlanChangeResult();\n * for (const line of lines(res, planName)) toast[line.tone](line.text);\n *\n * - `changed(from, to)`, or `set(to)` with no previous plan (or the same one): success;\n * - a running beta that kept its end: `keptBeta`, info; else a grant's end:\n * `untilDone(date)`, success;\n * - over the new limit: `overLimit`, then one figure line per dimension over it (\"Budgets:\n * 7 of 5\", `billing.limitUsageLine`), from `usage` and `limits`: warning. A `null`\n * limit gives no figure line.\n */\nexport function usePlanChangeResult(): (\n res: PlanChangeOutcome,\n planName: (code: string) => string,\n options?: PlanChangeResultOptions,\n) => PlanChangeLine[] {\n const labels = useKitLabels(\"planChange\", DEFAULT_PLAN_CHANGE_LABELS);\n const billing = useBillingLabels();\n const locale = useKitLocale();\n const dayText = useDayText();\n return useCallback(\n (res, planName, options = {}) => {\n const { dimensionLabels, formatValue } = options;\n const name = (raw: string) => planName(codeOf(raw) ?? raw);\n const to = name(res.plan);\n const from = codeOf(res.previous_plan);\n const lines: PlanChangeLine[] = [\n {\n tone: \"success\",\n text: from !== null && from !== codeOf(res.plan) ? labels.changed(name(from), to) : labels.set(to),\n },\n ];\n if (res.kept_beta) lines.push({ tone: \"info\", text: labels.keptBeta });\n else if (res.comped_until) lines.push({ tone: \"success\", text: labels.untilDone(dayText(res.comped_until)) });\n if (res.over_limit) {\n lines.push({ tone: \"warning\", text: labels.overLimit });\n const figure = (dimension: string, value: number) =>\n formatValue ? formatValue(dimension, value) : formatNumber(value, { locale });\n // Read defensively: an app's server from before 0.7 sent neither.\n for (const [dimension, limit] of Object.entries(res.limits ?? {})) {\n const used = res.usage?.[dimension];\n if (limit === null || used === undefined || used <= limit) continue;\n lines.push({\n tone: \"warning\",\n text: billing.limitUsageLine(\n dimensionLabels?.[dimension] ?? dimension,\n figure(dimension, used),\n figure(dimension, limit),\n ),\n });\n }\n }\n return lines;\n },\n [labels, billing, locale, dayText],\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +1,24 @@
1
1
  import 'react';
2
- export { cM as DEFAULT_REVIEWER_SCOPE_LABELS, ez as ReviewerScope, eA as ReviewerScopeArea, eB as ReviewerScopeEditor, eC as ReviewerScopeEditorProps, eD as ReviewerScopeLabels, eE as ReviewerScopeLanguage } from '../delete-account-setting-FLyJf-kZ.js';
2
+ export { cN as DEFAULT_REVIEWER_SCOPE_LABELS, eK as ReviewerScope, eL as ReviewerScopeArea, eM as ReviewerScopeEditor, eN as ReviewerScopeEditorProps, eO as ReviewerScopeLabels, eP as ReviewerScopeLanguage } from '../delete-account-setting-DiPX4Vue.js';
3
3
  import './admin-parts.js';
4
+ import '../components/write-lock.js';
4
5
  import '../components/tooltip.js';
5
6
  import '../hooks/use-anchored-rect.js';
6
7
  import '../lib/clipping.js';
7
- import '../data-table-labels-kpAAVi6g.js';
8
+ import '../data-table-labels-BJ5JE_X2.js';
8
9
  import '../components/data-table-sort.js';
9
10
  import '../components/swipeable-row.js';
11
+ import '../components/status-dot.js';
12
+ import '../components/progress-bar.js';
13
+ import '../components/signed-amount.js';
14
+ import '../components/stat-tile.js';
15
+ import '../components/chip.js';
16
+ import 'lucide-react';
17
+ import '../components/field-parts.js';
10
18
  import '../components/mini-calendar.js';
11
19
  import '../components/calendar-heatmap.js';
12
20
  import '../components/popover.js';
13
21
  import '../hooks/use-breakpoint.js';
14
- import '../components/chip.js';
15
- import 'lucide-react';
16
- import '../components/field-parts.js';
17
22
  import '../components/field-sync.js';
18
23
  import '../wizard/types.js';
19
24
  import '../tour/tour.js';
@@ -25,7 +30,6 @@ import '../components/page-contents.js';
25
30
  import '../components/series-chart-labels.js';
26
31
  import '../components/pie-chart-labels.js';
27
32
  import '../components/sparkline.js';
28
- import '../components/stat-tile.js';
29
33
  import '../components/signature-pad.js';
30
34
  import '../components/password-strength.js';
31
35
  import '../components/swatch-picker.js';
@@ -37,9 +41,6 @@ import '../hooks/use-search-param-state.js';
37
41
  import '../components/measured-grid.js';
38
42
  import '../components/user-avatar.js';
39
43
  import '../lib/person-name.js';
40
- import '../components/status-dot.js';
41
- import '../components/progress-bar.js';
42
- import '../components/signed-amount.js';
43
44
  import '../shell/topbar-action-menu.js';
44
45
  import '../components/menu-item.js';
45
46
  import '../components/floating-panel.js';
@@ -55,7 +56,6 @@ import '../components/error-boundary.js';
55
56
  import '../components/authed-image.js';
56
57
  import '../components/image-grid.js';
57
58
  import '../components/lightbox.js';
58
- import '../components/write-lock.js';
59
59
  import '../auth/sign-in-form.js';
60
60
  import '../landing/access.js';
61
61
  import '../landing/landing-labels.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/reviewer-scope-editor.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { KIT_LANGUAGES } from \"../i18n/languages\";\nimport type { KitLanguageCode } from \"../i18n/languages\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { CheckboxGroup } from \"../components/checkbox-group\";\nimport type { CheckboxGroupOption } from \"../components/checkbox-group\";\nimport { FormActions } from \"../components/form-actions\";\nimport { Caption } from \"../components/text\";\nimport { hasMessage } from \"./admin-parts\";\nimport type { MaybePromise } from \"./admin-parts\";\n\n/**\n * Who may review which translations: the REVIEWER role and its scope, in one editor\n * (docs/user-admin-harmonization.md §4.1 `PUT /admin/users/{id}/reviewer {locales, areas}`,\n * §7). It replaces three: keksdose's `user-reviewer-role.tsx` (a checkbox per language,\n * saved on each click), Kurvenschmiede's `reviewer-dialog.tsx` (a `CheckboxGroup` in a\n * dialog with Save) and kastlan's platform column.\n *\n * - **Languages** are the scope. Ticking none takes the role away — that is how both\n * apps remove a reviewer — and the editor says so under the boxes.\n * - **Areas** narrow it: \"Only the legal pages\" (Imprint, Privacy Policy, Terms) for a\n * lawyer, who reads law, not French. An area only means something beside at least one\n * language, so the areas wait for one. Saved as `areas: null` when none is ticked —\n * server-kit's \"every area\" — never `[]`, which would be \"no area at all\".\n *\n * A draft with Save, not a save per click: keksdose saved every tick, and two quick ticks\n * could land as a set nobody chose; one Save sends the whole scope, in the languages'\n * order (CheckboxGroup's), so the answer equals the request and the row does not\n * reshuffle on refetch. The server keeps its rules (an admin reviews everything already,\n * a customer is not given the role): `disabledReason` says them here.\n */\n\n/** A reviewer's scope — the request body of `PUT …/reviewer`. */\nexport interface ReviewerScope {\n /** Language codes, in the editor's order. Empty: not a reviewer. */\n locales: string[];\n /** The areas the scope is narrowed to; `null` = every area (server-kit's rule). */\n areas: string[] | null;\n}\n\n/** One area the scope can be narrowed to. */\nexport interface ReviewerScopeArea {\n value: string;\n label: ReactNode;\n hint?: ReactNode;\n}\n\n/** One language the editor offers, when the app's list is not the kit's codes. */\nexport interface ReviewerScopeLanguage {\n value: string;\n label: ReactNode;\n}\n\n/** The `reviewerScope` namespace. */\nexport interface ReviewerScopeLabels {\n /** The language group's legend. */\n languages: string;\n /** Under the languages. */\n hint: string;\n /** Under the languages while none is ticked. */\n none: string;\n /** The area group's legend. */\n areas: string;\n /** The default area, `legal`. */\n legalOnly: string;\n legalOnlyHint: string;\n /** A rejected save, unless `describeError` says better. */\n failed: string;\n}\n\nexport const DEFAULT_REVIEWER_SCOPE_LABELS: ReviewerScopeLabels = {\n languages: \"Languages\",\n hint: \"Tick the languages this person reviews.\",\n none: \"No language: saving takes the reviewer role away.\",\n areas: \"Limit to\",\n legalOnly: \"Only the legal pages\",\n legalOnlyHint: \"Imprint, Privacy Policy and Terms — for a lawyer rather than a native speaker.\",\n failed: \"The reviewer scope couldn’t be saved. Please try again.\",\n};\n\nexport interface ReviewerScopeEditorProps {\n /** The scope the server has — the draft starts from it. Key the editor by the\n * account (`key={user.id}`) so another account starts a fresh draft. */\n value: { locales?: readonly string[] | null; areas?: readonly string[] | null };\n /** Sends the scope. A promise keeps the editor busy, and a rejection keeps the draft\n * and shows why. */\n onSave: (scope: ReviewerScope) => MaybePromise;\n /** A Cancel beside Save — a dialog's way out. */\n onCancel?: () => void;\n /**\n * The languages offered: kit codes (named by the kit's registry, each in its own\n * language — \"Deutsch\", \"Français\") or the app's own `{ value, label }` rows. Default:\n * all seven of the kit's.\n */\n languages?: readonly KitLanguageCode[] | readonly ReviewerScopeLanguage[];\n /** The areas the scope can be narrowed to. Default: `legal` alone. `[]` for none. */\n areas?: readonly ReviewerScopeArea[];\n /** Why the scope cannot be changed for this account (\"Administrators review every\n * language already\"). Every box says it; there is no Save. */\n disabledReason?: ReactNode;\n /** The app's words for a failed save. */\n describeError?: (error: unknown) => ReactNode | undefined;\n /** Over the languages: what the role is, in the app's words. */\n intro?: ReactNode;\n /** Columns for the languages from `sm` up. Default 2 (Kurvenschmiede's dialog). */\n columns?: 1 | 2 | 3 | 4;\n /** Save COMMITS: under a locked `WriteLockProvider` it is held with the lock's reason. */\n commit?: boolean;\n labels?: Partial<ReviewerScopeLabels>;\n}\n\nfunction languageOptions(languages: ReviewerScopeEditorProps[\"languages\"]): CheckboxGroupOption[] {\n const rows = languages ?? KIT_LANGUAGES.map((language) => language.code);\n return rows.map((row) => {\n if (typeof row !== \"string\") return { value: row.value, label: row.label };\n const language = KIT_LANGUAGES.find((entry) => entry.code === row);\n return {\n value: row,\n // Each name in its own language, marked so a screen reader says it so.\n label: language ? <span lang={language.formatLocale}>{language.nativeName}</span> : row,\n };\n });\n}\n\n/** The same set, regardless of order. */\nfunction sameSet(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n const seen = new Set(a);\n return b.every((x) => seen.has(x));\n}\n\nexport function ReviewerScopeEditor({\n value,\n onSave,\n onCancel,\n languages,\n areas: areasProp,\n disabledReason,\n describeError,\n intro,\n columns = 2,\n commit,\n labels: labelsProp,\n}: ReviewerScopeEditorProps) {\n const labels = useKitLabels(\"reviewerScope\", DEFAULT_REVIEWER_SCOPE_LABELS, labelsProp);\n const areaOptions: readonly ReviewerScopeArea[] =\n areasProp ?? [{ value: \"legal\", label: labels.legalOnly, hint: labels.legalOnlyHint }];\n const savedLocales = [...(value.locales ?? [])];\n const savedAreas = [...(value.areas ?? [])];\n const [locales, setLocales] = useState<string[]>(savedLocales);\n const [areas, setAreas] = useState<string[]>(savedAreas);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const locked = hasMessage(disabledReason);\n\n const nextAreas = locales.length === 0 ? [] : areas;\n const dirty = !sameSet(locales, savedLocales) || !sameSet(nextAreas, locales.length === 0 ? [] : savedAreas);\n\n const save = () => {\n if (pending || locked || !dirty) return;\n const scope: ReviewerScope = {\n locales,\n areas: locales.length > 0 && areas.length > 0 ? areas : null,\n };\n setFailure(null);\n let result: MaybePromise;\n try {\n result = onSave(scope);\n } catch (error) {\n setFailure(describeError?.(error) ?? labels.failed);\n return;\n }\n if (!result || typeof result.then !== \"function\") return;\n setPending(true);\n result.then(\n () => setPending(false),\n (error: unknown) => {\n setPending(false);\n const own = describeError?.(error);\n setFailure(hasMessage(own) ? own : labels.failed);\n },\n );\n };\n\n return (\n <div className=\"space-y-3\">\n {intro && <div className=\"text-sm text-[var(--text-secondary)]\">{intro}</div>}\n <CheckboxGroup\n legend={labels.languages}\n options={languageOptions(languages)}\n value={locales}\n onChange={(next) => {\n setFailure(null);\n setLocales(next);\n }}\n columns={columns}\n hint={locked ? undefined : locales.length === 0 ? labels.none : labels.hint}\n disabledReason={disabledReason}\n />\n {areaOptions.length > 0 && (\n <CheckboxGroup\n legend={labels.areas}\n options={areaOptions.map((area) => ({ value: area.value, label: area.label, hint: area.hint }))}\n value={nextAreas}\n onChange={(next) => {\n setFailure(null);\n setAreas(next);\n }}\n // An area narrows a language scope; with no language there is none to narrow.\n disabled={locales.length === 0}\n disabledReason={disabledReason}\n />\n )}\n {failure && (\n <AlertBanner tone=\"danger\" size=\"sm\">\n {failure}\n </AlertBanner>\n )}\n {locked ? (\n <Caption>{disabledReason}</Caption>\n ) : (\n <FormActions\n onSubmit={save}\n onCancel={onCancel}\n pending={pending}\n submitDisabled={!dirty}\n commit={commit}\n />\n )}\n </div>\n );\n}\n"],"mappings":";AAyHwB,cAkEpB,YAlEoB;AAzHxB,SAAS,gBAAgB;AAEzB,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAE9B,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAE9B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,kBAAkB;AA8DpB,MAAM,gCAAqD;AAAA,EAChE,WAAW;AAAA,EACX,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,eAAe;AAAA,EACf,QAAQ;AACV;AAiCA,SAAS,gBAAgB,WAAyE;AAChG,QAAM,OAAO,aAAa,cAAc,IAAI,CAAC,aAAa,SAAS,IAAI;AACvE,SAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,QAAI,OAAO,QAAQ,SAAU,QAAO,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM;AACzE,UAAM,WAAW,cAAc,KAAK,CAAC,UAAU,MAAM,SAAS,GAAG;AACjE,WAAO;AAAA,MACL,OAAO;AAAA;AAAA,MAEP,OAAO,WAAW,oBAAC,UAAK,MAAM,SAAS,cAAe,mBAAS,YAAW,IAAU;AAAA,IACtF;AAAA,EACF,CAAC;AACH;AAGA,SAAS,QAAQ,GAAsB,GAA+B;AACpE,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,QAAM,OAAO,IAAI,IAAI,CAAC;AACtB,SAAO,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;AACnC;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,QAAQ;AACV,GAA6B;AAC3B,QAAM,SAAS,aAAa,iBAAiB,+BAA+B,UAAU;AACtF,QAAM,cACJ,aAAa,CAAC,EAAE,OAAO,SAAS,OAAO,OAAO,WAAW,MAAM,OAAO,cAAc,CAAC;AACvF,QAAM,eAAe,CAAC,GAAI,MAAM,WAAW,CAAC,CAAE;AAC9C,QAAM,aAAa,CAAC,GAAI,MAAM,SAAS,CAAC,CAAE;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAmB,YAAY;AAC7D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAmB,UAAU;AACvD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoB,IAAI;AACtD,QAAM,SAAS,WAAW,cAAc;AAExC,QAAM,YAAY,QAAQ,WAAW,IAAI,CAAC,IAAI;AAC9C,QAAM,QAAQ,CAAC,QAAQ,SAAS,YAAY,KAAK,CAAC,QAAQ,WAAW,QAAQ,WAAW,IAAI,CAAC,IAAI,UAAU;AAE3G,QAAM,OAAO,MAAM;AACjB,QAAI,WAAW,UAAU,CAAC,MAAO;AACjC,UAAM,QAAuB;AAAA,MAC3B;AAAA,MACA,OAAO,QAAQ,SAAS,KAAK,MAAM,SAAS,IAAI,QAAQ;AAAA,IAC1D;AACA,eAAW,IAAI;AACf,QAAI;AACJ,QAAI;AACF,eAAS,OAAO,KAAK;AAAA,IACvB,SAAS,OAAO;AACd,iBAAW,gBAAgB,KAAK,KAAK,OAAO,MAAM;AAClD;AAAA,IACF;AACA,QAAI,CAAC,UAAU,OAAO,OAAO,SAAS,WAAY;AAClD,eAAW,IAAI;AACf,WAAO;AAAA,MACL,MAAM,WAAW,KAAK;AAAA,MACtB,CAAC,UAAmB;AAClB,mBAAW,KAAK;AAChB,cAAM,MAAM,gBAAgB,KAAK;AACjC,mBAAW,WAAW,GAAG,IAAI,MAAM,OAAO,MAAM;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,aAAS,oBAAC,SAAI,WAAU,wCAAwC,iBAAM;AAAA,IACvE;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,OAAO;AAAA,QACf,SAAS,gBAAgB,SAAS;AAAA,QAClC,OAAO;AAAA,QACP,UAAU,CAAC,SAAS;AAClB,qBAAW,IAAI;AACf,qBAAW,IAAI;AAAA,QACjB;AAAA,QACA;AAAA,QACA,MAAM,SAAS,SAAY,QAAQ,WAAW,IAAI,OAAO,OAAO,OAAO;AAAA,QACvE;AAAA;AAAA,IACF;AAAA,IACC,YAAY,SAAS,KACpB;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,OAAO;AAAA,QACf,SAAS,YAAY,IAAI,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,OAAO,MAAM,KAAK,KAAK,EAAE;AAAA,QAC9F,OAAO;AAAA,QACP,UAAU,CAAC,SAAS;AAClB,qBAAW,IAAI;AACf,mBAAS,IAAI;AAAA,QACf;AAAA,QAEA,UAAU,QAAQ,WAAW;AAAA,QAC7B;AAAA;AAAA,IACF;AAAA,IAED,WACC,oBAAC,eAAY,MAAK,UAAS,MAAK,MAC7B,mBACH;AAAA,IAED,SACC,oBAAC,WAAS,0BAAe,IAEzB;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA,gBAAgB,CAAC;AAAA,QACjB;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/reviewer-scope-editor.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { KIT_LANGUAGES } from \"../i18n/languages\";\nimport type { KitLanguageCode } from \"../i18n/languages\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { CheckboxGroup } from \"../components/checkbox-group\";\nimport type { CheckboxGroupOption } from \"../components/checkbox-group\";\nimport { FormActions } from \"../components/form-actions\";\nimport { Caption } from \"../components/text\";\nimport { hasMessage } from \"./admin-parts\";\nimport type { MaybePromise } from \"./admin-parts\";\nimport type { CommitScope } from \"../components/write-lock\";\n\n/**\n * Who may review which translations: the REVIEWER role and its scope, in one editor\n * (docs/user-admin-harmonization.md §4.1 `PUT /admin/users/{id}/reviewer {locales, areas}`,\n * §7). It replaces three: keksdose's `user-reviewer-role.tsx` (a checkbox per language,\n * saved on each click), Kurvenschmiede's `reviewer-dialog.tsx` (a `CheckboxGroup` in a\n * dialog with Save) and kastlan's platform column.\n *\n * - **Languages** are the scope. Ticking none takes the role away — that is how both\n * apps remove a reviewer — and the editor says so under the boxes.\n * - **Areas** narrow it: \"Only the legal pages\" (Imprint, Privacy Policy, Terms) for a\n * lawyer, who reads law, not French. An area only means something beside at least one\n * language, so the areas wait for one. Saved as `areas: null` when none is ticked —\n * server-kit's \"every area\" — never `[]`, which would be \"no area at all\".\n *\n * A draft with Save, not a save per click: keksdose saved every tick, and two quick ticks\n * could land as a set nobody chose; one Save sends the whole scope, in the languages'\n * order (CheckboxGroup's), so the answer equals the request and the row does not\n * reshuffle on refetch. The server keeps its rules (an admin reviews everything already,\n * a customer is not given the role): `disabledReason` says them here.\n */\n\n/** A reviewer's scope — the request body of `PUT …/reviewer`. */\nexport interface ReviewerScope {\n /** Language codes, in the editor's order. Empty: not a reviewer. */\n locales: string[];\n /** The areas the scope is narrowed to; `null` = every area (server-kit's rule). */\n areas: string[] | null;\n}\n\n/** One area the scope can be narrowed to. */\nexport interface ReviewerScopeArea {\n value: string;\n label: ReactNode;\n hint?: ReactNode;\n}\n\n/** One language the editor offers, when the app's list is not the kit's codes. */\nexport interface ReviewerScopeLanguage {\n value: string;\n label: ReactNode;\n}\n\n/** The `reviewerScope` namespace. */\nexport interface ReviewerScopeLabels {\n /** The language group's legend. */\n languages: string;\n /** Under the languages. */\n hint: string;\n /** Under the languages while none is ticked. */\n none: string;\n /** The area group's legend. */\n areas: string;\n /** The default area, `legal`. */\n legalOnly: string;\n legalOnlyHint: string;\n /** A rejected save, unless `describeError` says better. */\n failed: string;\n}\n\nexport const DEFAULT_REVIEWER_SCOPE_LABELS: ReviewerScopeLabels = {\n languages: \"Languages\",\n hint: \"Tick the languages this person reviews.\",\n none: \"No language: saving takes the reviewer role away.\",\n areas: \"Limit to\",\n legalOnly: \"Only the legal pages\",\n legalOnlyHint: \"Imprint, Privacy Policy and Terms — for a lawyer rather than a native speaker.\",\n failed: \"The reviewer scope couldn’t be saved. Please try again.\",\n};\n\nexport interface ReviewerScopeEditorProps {\n /** The scope the server has — the draft starts from it. Key the editor by the\n * account (`key={user.id}`) so another account starts a fresh draft. */\n value: { locales?: readonly string[] | null; areas?: readonly string[] | null };\n /** Sends the scope. A promise keeps the editor busy, and a rejection keeps the draft\n * and shows why. */\n onSave: (scope: ReviewerScope) => MaybePromise;\n /** A Cancel beside Save — a dialog's way out. */\n onCancel?: () => void;\n /**\n * The languages offered: kit codes (named by the kit's registry, each in its own\n * language — \"Deutsch\", \"Français\") or the app's own `{ value, label }` rows. Default:\n * all seven of the kit's.\n */\n languages?: readonly KitLanguageCode[] | readonly ReviewerScopeLanguage[];\n /** The areas the scope can be narrowed to. Default: `legal` alone. `[]` for none. */\n areas?: readonly ReviewerScopeArea[];\n /** Why the scope cannot be changed for this account (\"Administrators review every\n * language already\"). Every box says it; there is no Save. */\n disabledReason?: ReactNode;\n /** The app's words for a failed save. */\n describeError?: (error: unknown) => ReactNode | undefined;\n /** Over the languages: what the role is, in the app's words. */\n intro?: ReactNode;\n /** Columns for the languages from `sm` up. Default 2 (Kurvenschmiede's dialog). */\n columns?: 1 | 2 | 3 | 4;\n /** Save COMMITS: under a locked `WriteLockProvider` it is held with the lock's reason. */\n commit?: CommitScope;\n labels?: Partial<ReviewerScopeLabels>;\n}\n\nfunction languageOptions(languages: ReviewerScopeEditorProps[\"languages\"]): CheckboxGroupOption[] {\n const rows = languages ?? KIT_LANGUAGES.map((language) => language.code);\n return rows.map((row) => {\n if (typeof row !== \"string\") return { value: row.value, label: row.label };\n const language = KIT_LANGUAGES.find((entry) => entry.code === row);\n return {\n value: row,\n // Each name in its own language, marked so a screen reader says it so.\n label: language ? <span lang={language.formatLocale}>{language.nativeName}</span> : row,\n };\n });\n}\n\n/** The same set, regardless of order. */\nfunction sameSet(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n const seen = new Set(a);\n return b.every((x) => seen.has(x));\n}\n\nexport function ReviewerScopeEditor({\n value,\n onSave,\n onCancel,\n languages,\n areas: areasProp,\n disabledReason,\n describeError,\n intro,\n columns = 2,\n commit,\n labels: labelsProp,\n}: ReviewerScopeEditorProps) {\n const labels = useKitLabels(\"reviewerScope\", DEFAULT_REVIEWER_SCOPE_LABELS, labelsProp);\n const areaOptions: readonly ReviewerScopeArea[] =\n areasProp ?? [{ value: \"legal\", label: labels.legalOnly, hint: labels.legalOnlyHint }];\n const savedLocales = [...(value.locales ?? [])];\n const savedAreas = [...(value.areas ?? [])];\n const [locales, setLocales] = useState<string[]>(savedLocales);\n const [areas, setAreas] = useState<string[]>(savedAreas);\n const [pending, setPending] = useState(false);\n const [failure, setFailure] = useState<ReactNode>(null);\n const locked = hasMessage(disabledReason);\n\n const nextAreas = locales.length === 0 ? [] : areas;\n const dirty = !sameSet(locales, savedLocales) || !sameSet(nextAreas, locales.length === 0 ? [] : savedAreas);\n\n const save = () => {\n if (pending || locked || !dirty) return;\n const scope: ReviewerScope = {\n locales,\n areas: locales.length > 0 && areas.length > 0 ? areas : null,\n };\n setFailure(null);\n let result: MaybePromise;\n try {\n result = onSave(scope);\n } catch (error) {\n setFailure(describeError?.(error) ?? labels.failed);\n return;\n }\n if (!result || typeof result.then !== \"function\") return;\n setPending(true);\n result.then(\n () => setPending(false),\n (error: unknown) => {\n setPending(false);\n const own = describeError?.(error);\n setFailure(hasMessage(own) ? own : labels.failed);\n },\n );\n };\n\n return (\n <div className=\"space-y-3\">\n {intro && <div className=\"text-sm text-[var(--text-secondary)]\">{intro}</div>}\n <CheckboxGroup\n legend={labels.languages}\n options={languageOptions(languages)}\n value={locales}\n onChange={(next) => {\n setFailure(null);\n setLocales(next);\n }}\n columns={columns}\n hint={locked ? undefined : locales.length === 0 ? labels.none : labels.hint}\n disabledReason={disabledReason}\n />\n {areaOptions.length > 0 && (\n <CheckboxGroup\n legend={labels.areas}\n options={areaOptions.map((area) => ({ value: area.value, label: area.label, hint: area.hint }))}\n value={nextAreas}\n onChange={(next) => {\n setFailure(null);\n setAreas(next);\n }}\n // An area narrows a language scope; with no language there is none to narrow.\n disabled={locales.length === 0}\n disabledReason={disabledReason}\n />\n )}\n {failure && (\n <AlertBanner tone=\"danger\" size=\"sm\">\n {failure}\n </AlertBanner>\n )}\n {locked ? (\n <Caption>{disabledReason}</Caption>\n ) : (\n <FormActions\n onSubmit={save}\n onCancel={onCancel}\n pending={pending}\n submitDisabled={!dirty}\n commit={commit}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,7 @@
1
1
  import 'react';
2
2
  import '../components/account-chips.js';
3
- export { cN as DEFAULT_ROLE_SELECT_LABELS, eG as RoleLock, eH as RoleLockCode, eI as RoleSelect, eJ as RoleSelectLabels, eK as RoleSelectProps, eL as RolesEditor, eM as RolesEditorProps } from '../delete-account-setting-FLyJf-kZ.js';
3
+ export { cO as DEFAULT_ROLE_SELECT_LABELS, eR as RoleLock, eS as RoleLockCode, eT as RoleSelect, eU as RoleSelectLabels, eV as RoleSelectProps, eW as RolesEditor, eX as RolesEditorProps } from '../delete-account-setting-DiPX4Vue.js';
4
+ import '../components/write-lock.js';
4
5
  import 'lucide-react';
5
6
  import '../lib/format.js';
6
7
  import '../components/chip.js';
@@ -8,9 +9,13 @@ import '../components/field-parts.js';
8
9
  import '../components/tooltip.js';
9
10
  import '../hooks/use-anchored-rect.js';
10
11
  import '../lib/clipping.js';
11
- import '../data-table-labels-kpAAVi6g.js';
12
+ import '../data-table-labels-BJ5JE_X2.js';
12
13
  import '../components/data-table-sort.js';
13
14
  import '../components/swipeable-row.js';
15
+ import '../components/status-dot.js';
16
+ import '../components/progress-bar.js';
17
+ import '../components/signed-amount.js';
18
+ import '../components/stat-tile.js';
14
19
  import '../components/mini-calendar.js';
15
20
  import '../components/calendar-heatmap.js';
16
21
  import '../components/popover.js';
@@ -26,7 +31,6 @@ import '../components/page-contents.js';
26
31
  import '../components/series-chart-labels.js';
27
32
  import '../components/pie-chart-labels.js';
28
33
  import '../components/sparkline.js';
29
- import '../components/stat-tile.js';
30
34
  import '../components/signature-pad.js';
31
35
  import '../components/password-strength.js';
32
36
  import '../components/swatch-picker.js';
@@ -38,9 +42,6 @@ import '../hooks/use-search-param-state.js';
38
42
  import '../components/measured-grid.js';
39
43
  import '../components/user-avatar.js';
40
44
  import '../lib/person-name.js';
41
- import '../components/status-dot.js';
42
- import '../components/progress-bar.js';
43
- import '../components/signed-amount.js';
44
45
  import '../shell/topbar-action-menu.js';
45
46
  import '../components/menu-item.js';
46
47
  import '../components/floating-panel.js';
@@ -56,7 +57,6 @@ import '../components/error-boundary.js';
56
57
  import '../components/authed-image.js';
57
58
  import '../components/image-grid.js';
58
59
  import '../components/lightbox.js';
59
- import '../components/write-lock.js';
60
60
  import '../auth/sign-in-form.js';
61
61
  import '../landing/access.js';
62
62
  import '../landing/landing-labels.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/role-select.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { RoleVocabulary } from \"../components/account-chips\";\nimport { CheckboxGroup } from \"../components/checkbox-group\";\nimport type { CheckboxGroupOption } from \"../components/checkbox-group\";\nimport { Select } from \"../components/ui\";\nimport type { SelectProps } from \"../components/ui\";\n\n/**\n * The role of an account, chosen by an admin (docs/user-admin-harmonization.md §3.3,\n * §4.1, §7): {@link RoleSelect} for the apps where an account has ONE role (keksdose,\n * Kurvenschmiede) and {@link RolesEditor} for kastlan, where an account has several per\n * company.\n *\n * Both take the app's {@link RoleVocabulary} — the one `RoleChip` reads — so a role is\n * called the same in the chip, the select and the editor. Neither sends a request: a role\n * change is an `acknowledge` action (§4.2), so the app opens `AdminActionConfirm` from\n * `onChange` and the value stays where the server has it until the server says otherwise.\n *\n * WHY THE REASONS. The server refuses the last active admin's demotion (`last_admin`),\n * your own admin role (`self`), Kurvenschmiede's CUSTOMER for an account that owns work,\n * kastlan's TENANT until a tenant portal exists (§4.1, §9.6). A choice the server will\n * refuse is shown as unavailable, with why, rather than offered and answered with an\n * error after a confirmation dialog — the 0.18 write-lock rule: a control that cannot\n * act says so where it is. `\"last_admin\"` and `\"self\"` are the kit's words in every\n * language; anything else is the app's own sentence.\n */\n\n/** A reason the kit words itself. `self_action` is the server's code for `self` (the\n * account error codes), so a refusal's code can be handed on as it came. */\nexport type RoleLockCode = \"last_admin\" | \"self\" | \"self_action\";\n\n/**\n * Why a role (or the whole control) cannot be changed: one of the kit's\n * {@link RoleLockCode}s, or the app's own words.\n */\nexport type RoleLock = RoleLockCode | (string & {}) | ReactNode;\n\n/** The `roleSelect` namespace — RoleSelect's and RolesEditor's words. */\nexport interface RoleSelectLabels {\n /** The select's name where no `label` is drawn, and RolesEditor's legend. \"Role\". */\n label: string;\n /** RolesEditor's legend. \"Roles\". */\n rolesLegend: string;\n /** The select's accessible name in a roster row — \"Role of Ada Example\". */\n roleOf: (name: string) => string;\n /** The whole control is locked: the account is the last active administrator. */\n lockedLastAdmin: string;\n /** The whole control is locked: it is the admin's own account. */\n lockedSelf: string;\n /** One option is unavailable for the last active administrator — said after the\n * role's name, inside the option. */\n notForLastAdmin: string;\n /** One option is unavailable on the admin's own account. */\n notForSelf: string;\n /** An unavailable option's text: the role's name and why. */\n unavailable: (role: string, reason: string) => string;\n}\n\nexport const DEFAULT_ROLE_SELECT_LABELS: RoleSelectLabels = {\n label: \"Role\",\n rolesLegend: \"Roles\",\n roleOf: (name) => `Role of ${name}`,\n lockedLastAdmin: \"The last active administrator keeps this role.\",\n lockedSelf: \"You can’t change your own role.\",\n notForLastAdmin: \"not for the last administrator\",\n notForSelf: \"not for your own account\",\n unavailable: (role, reason) => `${role} (${reason})`,\n};\n\n/** The whole-control reason, the kit's codes in the kit's words. */\nfunction lockText(reason: RoleLock, labels: RoleSelectLabels): ReactNode {\n if (reason === \"last_admin\") return labels.lockedLastAdmin;\n if (reason === \"self\" || reason === \"self_action\") return labels.lockedSelf;\n return reason;\n}\n\n/** An option's reason as the text a native `<option>` can hold. */\nfunction optionReasonText(reason: string, labels: RoleSelectLabels): string {\n if (reason === \"last_admin\") return labels.notForLastAdmin;\n if (reason === \"self\" || reason === \"self_action\") return labels.notForSelf;\n return reason;\n}\n\nfunction present(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\n/** The vocabulary's keys, in its own order. */\nfunction keysOf<R extends string>(roles: RoleVocabulary<R>): R[] {\n return Object.keys(roles) as R[];\n}\n\n/* ── RoleSelect ─────────────────────────────────────────────────────────────── */\n\nexport interface RoleSelectProps<R extends string = string>\n extends Omit<SelectProps, \"value\" | \"defaultValue\" | \"onChange\" | \"children\" | \"multiple\" | \"disabledReason\"> {\n /** The role the server has. Controlled: a pick does not move the select until the\n * app's request lands and the row comes back with the new role. */\n value: R | null | undefined;\n /** The app's roles — what `RoleChip` reads. Their order is the list's. */\n roles: RoleVocabulary<R>;\n /** A role other than `value` was picked. The app asks `AdminActionConfirm` (an\n * `acknowledge` action, §4.2) and sends the change. */\n onChange: (role: R) => void;\n /**\n * The roles that may be CHOSEN here, in the vocabulary's order whatever order this is\n * in. Default: all of them. A role the account holds that is not offered (Kurvenschmiede's\n * REVIEWER, given only through the reviewer scope, §9.6) is still shown, unavailable:\n * a select that dropped it would show the account as something it is not.\n */\n offered?: readonly R[];\n /**\n * Why the whole choice is locked for this account: `\"last_admin\"`, `\"self\"`, or the\n * app's own words. The select stays focusable and says so (Select's `disabledReason`).\n */\n disabledReason?: RoleLock;\n /**\n * Why ONE role cannot be chosen for this account: `\"last_admin\"`, `\"self\"`, or the\n * app's own words (Kurvenschmiede: \"owns work\", for CUSTOMER) — a STRING, because a\n * native option holds text only. The option is disabled and reads \"Member (not for the\n * last administrator)\". `undefined` for an available role.\n */\n optionDisabledReason?: (role: R) => string | undefined;\n /** The account's name, for the select's accessible name in a roster row (\"Role of\n * Ada Example\") where no `label` is drawn. */\n name?: string;\n labels?: Partial<RoleSelectLabels>;\n}\n\n/**\n * One role, picked from the app's vocabulary — Kurvenschmiede's roster `Select`\n * (users-panel.tsx), with the reasons it left to the server's toast drawn in place.\n *\n * Unlabelled by default, for a table cell: named \"Role of Ada Example\" (`name`) or\n * \"Role\". Pass `label` for a form field. Every {@link Select} prop passes through\n * (`size`, `commit`, `hint`, `error`, `data-*`).\n */\nexport function RoleSelect<R extends string = string>({\n value,\n roles,\n onChange,\n offered,\n disabledReason,\n optionDisabledReason,\n name,\n labels: labelsProp,\n label,\n ...rest\n}: RoleSelectProps<R>) {\n const labels = useKitLabels(\"roleSelect\", DEFAULT_ROLE_SELECT_LABELS, labelsProp);\n const all = keysOf(roles);\n const choosable = new Set<R>(offered ?? all);\n const held = value ?? null;\n // A cell has no room for a visible label: the select is named for the account it\n // belongs to, so fifty of them in a roster are not fifty \"Role\"s.\n const ariaLabel =\n label === undefined ? (rest[\"aria-label\"] ?? (name ? labels.roleOf(name) : labels.label)) : rest[\"aria-label\"];\n return (\n <Select\n {...rest}\n label={label}\n aria-label={ariaLabel}\n value={held ?? \"\"}\n disabledReason={present(disabledReason) ? lockText(disabledReason, labels) : undefined}\n onChange={(event) => {\n const next = event.target.value as R;\n if (next && next !== held) onChange(next);\n }}\n >\n {held === null && <option value=\"\" disabled />}\n {all\n .filter((role) => choosable.has(role) || role === held)\n .map((role) => {\n const word = roles[role].label;\n const reason = role === held ? undefined : optionDisabledReason?.(role);\n const unavailable = !choosable.has(role) || present(reason);\n return (\n <option key={role} value={role} disabled={unavailable && role !== held}>\n {reason ? labels.unavailable(word, optionReasonText(reason, labels)) : word}\n </option>\n );\n })}\n {/* A role the vocabulary does not know (an older client against a newer API) is\n still the account's role: shown as its key, as RoleChip shows it. */}\n {held !== null && !all.includes(held) && (\n <option value={held} disabled>\n {held}\n </option>\n )}\n </Select>\n );\n}\n\n/* ── RolesEditor ────────────────────────────────────────────────────────────── */\n\nexport interface RolesEditorProps<R extends string = string> {\n /** The roles ticked — the draft the app holds while its dialog is open. */\n value: readonly R[];\n roles: RoleVocabulary<R>;\n /** The new set, in the vocabulary's order (a role the editor does not offer, but the\n * account holds, is kept after the rest — CheckboxGroup's rule). */\n onChange: (roles: R[]) => void;\n /** The roles that may be ticked or unticked here. Default: all. kastlan leaves TENANT\n * out until a tenant portal exists (§4.1). */\n offered?: readonly R[];\n /** Why nothing can be changed: `\"last_admin\"`, `\"self\"` or the app's words. */\n disabledReason?: RoleLock;\n /** Why one role cannot be changed — shown under it, and the box keeps its state:\n * `\"last_admin\"` on the last administrator's ADMIN, say. */\n optionDisabledReason?: (role: R) => RoleLock | undefined;\n /** Default `roleSelect.rolesLegend` — \"Roles\". kastlan names the company: \"Roles in\n * Example Ltd\". */\n legend?: ReactNode;\n legendVisibility?: \"visible\" | \"sr-only\";\n /** At least one role (kastlan's dialog saves none). Draws the required star. */\n required?: boolean;\n hint?: ReactNode;\n error?: ReactNode;\n columns?: 1 | 2 | 3 | 4;\n /** The editor SAVES on change — the write lock's opt-in (CheckboxGroup's `commit`). */\n commit?: boolean;\n className?: string;\n labels?: Partial<RoleSelectLabels>;\n}\n\n/**\n * Several roles at once — kastlan's manage-roles dialog (`ChoiceCardGroup multiple`),\n * which holds an account's roles in the CURRENT company (§4.1: `PUT /admin/users/{id}/roles`).\n * A kit {@link CheckboxGroup} of the vocabulary, with the reasons of {@link RoleSelect}:\n * a role that cannot change keeps its box as it is and says why under it.\n *\n * A field, not a form: the app's dialog holds the draft, and its Save goes through\n * `AdminActionConfirm` (`acknowledge`, §4.2) — which can draw this editor as its\n * `children`, so the roles and the tick are one dialog.\n */\nexport function RolesEditor<R extends string = string>({\n value,\n roles,\n onChange,\n offered,\n disabledReason,\n optionDisabledReason,\n legend,\n legendVisibility,\n required,\n hint,\n error,\n columns,\n commit,\n className,\n labels: labelsProp,\n}: RolesEditorProps<R>) {\n const labels = useKitLabels(\"roleSelect\", DEFAULT_ROLE_SELECT_LABELS, labelsProp);\n const all = keysOf(roles);\n const choosable = new Set<R>(offered ?? all);\n const held = new Set<R>(value);\n const options: CheckboxGroupOption<R>[] = all\n .filter((role) => choosable.has(role) || held.has(role))\n .map((role) => {\n const reason = optionDisabledReason?.(role);\n const locked = present(reason);\n return {\n value: role,\n label: roles[role].label,\n hint: locked ? lockText(reason, labels) : undefined,\n disabled: locked || !choosable.has(role),\n };\n });\n return (\n <CheckboxGroup<R>\n legend={legend ?? labels.rolesLegend}\n legendVisibility={legendVisibility}\n options={options}\n value={[...value]}\n onChange={onChange}\n required={required}\n hint={hint}\n error={error}\n columns={columns}\n commit={commit}\n disabledReason={present(disabledReason) ? lockText(disabledReason, labels) : undefined}\n className={className}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/role-select.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { RoleVocabulary } from \"../components/account-chips\";\nimport { CheckboxGroup } from \"../components/checkbox-group\";\nimport type { CheckboxGroupOption } from \"../components/checkbox-group\";\nimport { Select } from \"../components/ui\";\nimport type { SelectProps } from \"../components/ui\";\nimport type { CommitScope } from \"../components/write-lock\";\n\n/**\n * The role of an account, chosen by an admin (docs/user-admin-harmonization.md §3.3,\n * §4.1, §7): {@link RoleSelect} for the apps where an account has ONE role (keksdose,\n * Kurvenschmiede) and {@link RolesEditor} for kastlan, where an account has several per\n * company.\n *\n * Both take the app's {@link RoleVocabulary} — the one `RoleChip` reads — so a role is\n * called the same in the chip, the select and the editor. Neither sends a request: a role\n * change is an `acknowledge` action (§4.2), so the app opens `AdminActionConfirm` from\n * `onChange` and the value stays where the server has it until the server says otherwise.\n *\n * WHY THE REASONS. The server refuses the last active admin's demotion (`last_admin`),\n * your own admin role (`self`), Kurvenschmiede's CUSTOMER for an account that owns work,\n * kastlan's TENANT until a tenant portal exists (§4.1, §9.6). A choice the server will\n * refuse is shown as unavailable, with why, rather than offered and answered with an\n * error after a confirmation dialog — the 0.18 write-lock rule: a control that cannot\n * act says so where it is. `\"last_admin\"` and `\"self\"` are the kit's words in every\n * language; anything else is the app's own sentence.\n */\n\n/** A reason the kit words itself. `self_action` is the server's code for `self` (the\n * account error codes), so a refusal's code can be handed on as it came. */\nexport type RoleLockCode = \"last_admin\" | \"self\" | \"self_action\";\n\n/**\n * Why a role (or the whole control) cannot be changed: one of the kit's\n * {@link RoleLockCode}s, or the app's own words.\n */\nexport type RoleLock = RoleLockCode | (string & {}) | ReactNode;\n\n/** The `roleSelect` namespace — RoleSelect's and RolesEditor's words. */\nexport interface RoleSelectLabels {\n /** The select's name where no `label` is drawn, and RolesEditor's legend. \"Role\". */\n label: string;\n /** RolesEditor's legend. \"Roles\". */\n rolesLegend: string;\n /** The select's accessible name in a roster row — \"Role of Ada Example\". */\n roleOf: (name: string) => string;\n /** The whole control is locked: the account is the last active administrator. */\n lockedLastAdmin: string;\n /** The whole control is locked: it is the admin's own account. */\n lockedSelf: string;\n /** One option is unavailable for the last active administrator — said after the\n * role's name, inside the option. */\n notForLastAdmin: string;\n /** One option is unavailable on the admin's own account. */\n notForSelf: string;\n /** An unavailable option's text: the role's name and why. */\n unavailable: (role: string, reason: string) => string;\n}\n\nexport const DEFAULT_ROLE_SELECT_LABELS: RoleSelectLabels = {\n label: \"Role\",\n rolesLegend: \"Roles\",\n roleOf: (name) => `Role of ${name}`,\n lockedLastAdmin: \"The last active administrator keeps this role.\",\n lockedSelf: \"You can’t change your own role.\",\n notForLastAdmin: \"not for the last administrator\",\n notForSelf: \"not for your own account\",\n unavailable: (role, reason) => `${role} (${reason})`,\n};\n\n/** The whole-control reason, the kit's codes in the kit's words. */\nfunction lockText(reason: RoleLock, labels: RoleSelectLabels): ReactNode {\n if (reason === \"last_admin\") return labels.lockedLastAdmin;\n if (reason === \"self\" || reason === \"self_action\") return labels.lockedSelf;\n return reason;\n}\n\n/** An option's reason as the text a native `<option>` can hold. */\nfunction optionReasonText(reason: string, labels: RoleSelectLabels): string {\n if (reason === \"last_admin\") return labels.notForLastAdmin;\n if (reason === \"self\" || reason === \"self_action\") return labels.notForSelf;\n return reason;\n}\n\nfunction present(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\n/** The vocabulary's keys, in its own order. */\nfunction keysOf<R extends string>(roles: RoleVocabulary<R>): R[] {\n return Object.keys(roles) as R[];\n}\n\n/* ── RoleSelect ─────────────────────────────────────────────────────────────── */\n\nexport interface RoleSelectProps<R extends string = string>\n extends Omit<SelectProps, \"value\" | \"defaultValue\" | \"onChange\" | \"children\" | \"multiple\" | \"disabledReason\"> {\n /** The role the server has. Controlled: a pick does not move the select until the\n * app's request lands and the row comes back with the new role. */\n value: R | null | undefined;\n /** The app's roles — what `RoleChip` reads. Their order is the list's. */\n roles: RoleVocabulary<R>;\n /** A role other than `value` was picked. The app asks `AdminActionConfirm` (an\n * `acknowledge` action, §4.2) and sends the change. */\n onChange: (role: R) => void;\n /**\n * The roles that may be CHOSEN here, in the vocabulary's order whatever order this is\n * in. Default: all of them. A role the account holds that is not offered (Kurvenschmiede's\n * REVIEWER, given only through the reviewer scope, §9.6) is still shown, unavailable:\n * a select that dropped it would show the account as something it is not.\n */\n offered?: readonly R[];\n /**\n * Why the whole choice is locked for this account: `\"last_admin\"`, `\"self\"`, or the\n * app's own words. The select stays focusable and says so (Select's `disabledReason`).\n */\n disabledReason?: RoleLock;\n /**\n * Why ONE role cannot be chosen for this account: `\"last_admin\"`, `\"self\"`, or the\n * app's own words (Kurvenschmiede: \"owns work\", for CUSTOMER) — a STRING, because a\n * native option holds text only. The option is disabled and reads \"Member (not for the\n * last administrator)\". `undefined` for an available role.\n */\n optionDisabledReason?: (role: R) => string | undefined;\n /** The account's name, for the select's accessible name in a roster row (\"Role of\n * Ada Example\") where no `label` is drawn. */\n name?: string;\n labels?: Partial<RoleSelectLabels>;\n}\n\n/**\n * One role, picked from the app's vocabulary — Kurvenschmiede's roster `Select`\n * (users-panel.tsx), with the reasons it left to the server's toast drawn in place.\n *\n * Unlabelled by default, for a table cell: named \"Role of Ada Example\" (`name`) or\n * \"Role\". Pass `label` for a form field. Every {@link Select} prop passes through\n * (`size`, `commit`, `hint`, `error`, `data-*`).\n */\nexport function RoleSelect<R extends string = string>({\n value,\n roles,\n onChange,\n offered,\n disabledReason,\n optionDisabledReason,\n name,\n labels: labelsProp,\n label,\n ...rest\n}: RoleSelectProps<R>) {\n const labels = useKitLabels(\"roleSelect\", DEFAULT_ROLE_SELECT_LABELS, labelsProp);\n const all = keysOf(roles);\n const choosable = new Set<R>(offered ?? all);\n const held = value ?? null;\n // A cell has no room for a visible label: the select is named for the account it\n // belongs to, so fifty of them in a roster are not fifty \"Role\"s.\n const ariaLabel =\n label === undefined ? (rest[\"aria-label\"] ?? (name ? labels.roleOf(name) : labels.label)) : rest[\"aria-label\"];\n return (\n <Select\n {...rest}\n label={label}\n aria-label={ariaLabel}\n value={held ?? \"\"}\n disabledReason={present(disabledReason) ? lockText(disabledReason, labels) : undefined}\n onChange={(event) => {\n const next = event.target.value as R;\n if (next && next !== held) onChange(next);\n }}\n >\n {held === null && <option value=\"\" disabled />}\n {all\n .filter((role) => choosable.has(role) || role === held)\n .map((role) => {\n const word = roles[role].label;\n const reason = role === held ? undefined : optionDisabledReason?.(role);\n const unavailable = !choosable.has(role) || present(reason);\n return (\n <option key={role} value={role} disabled={unavailable && role !== held}>\n {reason ? labels.unavailable(word, optionReasonText(reason, labels)) : word}\n </option>\n );\n })}\n {/* A role the vocabulary does not know (an older client against a newer API) is\n still the account's role: shown as its key, as RoleChip shows it. */}\n {held !== null && !all.includes(held) && (\n <option value={held} disabled>\n {held}\n </option>\n )}\n </Select>\n );\n}\n\n/* ── RolesEditor ────────────────────────────────────────────────────────────── */\n\nexport interface RolesEditorProps<R extends string = string> {\n /** The roles ticked — the draft the app holds while its dialog is open. */\n value: readonly R[];\n roles: RoleVocabulary<R>;\n /** The new set, in the vocabulary's order (a role the editor does not offer, but the\n * account holds, is kept after the rest — CheckboxGroup's rule). */\n onChange: (roles: R[]) => void;\n /** The roles that may be ticked or unticked here. Default: all. kastlan leaves TENANT\n * out until a tenant portal exists (§4.1). */\n offered?: readonly R[];\n /** Why nothing can be changed: `\"last_admin\"`, `\"self\"` or the app's words. */\n disabledReason?: RoleLock;\n /** Why one role cannot be changed — shown under it, and the box keeps its state:\n * `\"last_admin\"` on the last administrator's ADMIN, say. */\n optionDisabledReason?: (role: R) => RoleLock | undefined;\n /** Default `roleSelect.rolesLegend` — \"Roles\". kastlan names the company: \"Roles in\n * Example Ltd\". */\n legend?: ReactNode;\n legendVisibility?: \"visible\" | \"sr-only\";\n /** At least one role (kastlan's dialog saves none). Draws the required star. */\n required?: boolean;\n hint?: ReactNode;\n error?: ReactNode;\n columns?: 1 | 2 | 3 | 4;\n /** The editor SAVES on change — the write lock's opt-in (CheckboxGroup's `commit`). */\n commit?: CommitScope;\n className?: string;\n labels?: Partial<RoleSelectLabels>;\n}\n\n/**\n * Several roles at once — kastlan's manage-roles dialog (`ChoiceCardGroup multiple`),\n * which holds an account's roles in the CURRENT company (§4.1: `PUT /admin/users/{id}/roles`).\n * A kit {@link CheckboxGroup} of the vocabulary, with the reasons of {@link RoleSelect}:\n * a role that cannot change keeps its box as it is and says why under it.\n *\n * A field, not a form: the app's dialog holds the draft, and its Save goes through\n * `AdminActionConfirm` (`acknowledge`, §4.2) — which can draw this editor as its\n * `children`, so the roles and the tick are one dialog.\n */\nexport function RolesEditor<R extends string = string>({\n value,\n roles,\n onChange,\n offered,\n disabledReason,\n optionDisabledReason,\n legend,\n legendVisibility,\n required,\n hint,\n error,\n columns,\n commit,\n className,\n labels: labelsProp,\n}: RolesEditorProps<R>) {\n const labels = useKitLabels(\"roleSelect\", DEFAULT_ROLE_SELECT_LABELS, labelsProp);\n const all = keysOf(roles);\n const choosable = new Set<R>(offered ?? all);\n const held = new Set<R>(value);\n const options: CheckboxGroupOption<R>[] = all\n .filter((role) => choosable.has(role) || held.has(role))\n .map((role) => {\n const reason = optionDisabledReason?.(role);\n const locked = present(reason);\n return {\n value: role,\n label: roles[role].label,\n hint: locked ? lockText(reason, labels) : undefined,\n disabled: locked || !choosable.has(role),\n };\n });\n return (\n <CheckboxGroup<R>\n legend={legend ?? labels.rolesLegend}\n legendVisibility={legendVisibility}\n options={options}\n value={[...value]}\n onChange={onChange}\n required={required}\n hint={hint}\n error={error}\n columns={columns}\n commit={commit}\n disabledReason={present(disabledReason) ? lockText(disabledReason, labels) : undefined}\n className={className}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,19 +1,24 @@
1
1
  import 'react';
2
- export { cT as DEFAULT_TRANSFER_OWNERSHIP_LABELS, fl as TransferCandidate, fm as TransferOwnershipDialog, fn as TransferOwnershipDialogProps, fo as TransferOwnershipLabels, fp as TransferUnavailable, fq as TransferValues } from '../delete-account-setting-FLyJf-kZ.js';
2
+ export { cU as DEFAULT_TRANSFER_OWNERSHIP_LABELS, fx as TransferCandidate, fy as TransferOwnershipDialog, fz as TransferOwnershipDialogProps, fA as TransferOwnershipLabels, fB as TransferUnavailable, fC as TransferValues } from '../delete-account-setting-DiPX4Vue.js';
3
3
  import './admin-parts.js';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
6
6
  import '../lib/clipping.js';
7
- import '../data-table-labels-kpAAVi6g.js';
7
+ import '../data-table-labels-BJ5JE_X2.js';
8
8
  import '../components/data-table-sort.js';
9
9
  import '../components/swipeable-row.js';
10
+ import '../components/status-dot.js';
11
+ import '../components/progress-bar.js';
12
+ import '../components/signed-amount.js';
13
+ import '../components/stat-tile.js';
14
+ import '../components/chip.js';
15
+ import 'lucide-react';
16
+ import '../components/field-parts.js';
17
+ import '../components/write-lock.js';
10
18
  import '../components/mini-calendar.js';
11
19
  import '../components/calendar-heatmap.js';
12
20
  import '../components/popover.js';
13
21
  import '../hooks/use-breakpoint.js';
14
- import '../components/chip.js';
15
- import 'lucide-react';
16
- import '../components/field-parts.js';
17
22
  import '../components/field-sync.js';
18
23
  import '../wizard/types.js';
19
24
  import '../tour/tour.js';
@@ -25,7 +30,6 @@ import '../components/page-contents.js';
25
30
  import '../components/series-chart-labels.js';
26
31
  import '../components/pie-chart-labels.js';
27
32
  import '../components/sparkline.js';
28
- import '../components/stat-tile.js';
29
33
  import '../components/signature-pad.js';
30
34
  import '../components/password-strength.js';
31
35
  import '../components/swatch-picker.js';
@@ -37,9 +41,6 @@ import '../hooks/use-search-param-state.js';
37
41
  import '../components/measured-grid.js';
38
42
  import '../components/user-avatar.js';
39
43
  import '../lib/person-name.js';
40
- import '../components/status-dot.js';
41
- import '../components/progress-bar.js';
42
- import '../components/signed-amount.js';
43
44
  import '../shell/topbar-action-menu.js';
44
45
  import '../components/menu-item.js';
45
46
  import '../components/floating-panel.js';
@@ -55,7 +56,6 @@ import '../components/error-boundary.js';
55
56
  import '../components/authed-image.js';
56
57
  import '../components/image-grid.js';
57
58
  import '../components/lightbox.js';
58
- import '../components/write-lock.js';
59
59
  import '../auth/sign-in-form.js';
60
60
  import '../landing/access.js';
61
61
  import '../landing/landing-labels.js';
@@ -3,15 +3,20 @@ import '../lib/format.js';
3
3
  import '../lib/person-name.js';
4
4
  import '../components/account-chips.js';
5
5
  import '../components/chip.js';
6
- import '../data-table-labels-kpAAVi6g.js';
6
+ import '../data-table-labels-BJ5JE_X2.js';
7
7
  import '../components/data-table-sort.js';
8
- export { bz as AdminUserStateFields, cU as DEFAULT_USER_ROSTER_LABELS, eF as RoleEditing, eN as RosterState, fv as USER_ROSTER_SORT_KEYS, fC as UserIdentityCell, fD as UserIdentityCellProps, fE as UserRosterColumnsOptions, fF as UserRosterLabels, fG as UserRosterRow, fH as UserRosterSortKey, fI as UserRowAction, fJ as UserRowActionList, fK as UserRowActions, fL as UserRowActionsProps, fM as adminUserStates, gc as useUserRosterColumns, gd as userRosterColumns, ge as userRosterSort } from '../delete-account-setting-FLyJf-kZ.js';
8
+ export { bz as AdminUserStateFields, cV as DEFAULT_USER_ROSTER_LABELS, eQ as RoleEditing, eY as RosterState, fH as USER_ROSTER_SORT_KEYS, fO as UserIdentityCell, fP as UserIdentityCellProps, fQ as UserRosterColumnsOptions, fR as UserRosterLabels, fS as UserRosterRow, fT as UserRosterSortKey, fU as UserRowAction, fV as UserRowActionList, fW as UserRowActions, fX as UserRowActionsProps, fY as adminUserStates, gr as useUserRosterColumns, gs as userRosterColumns, gt as userRosterSort } from '../delete-account-setting-DiPX4Vue.js';
9
9
  import 'lucide-react';
10
10
  import '../components/field-parts.js';
11
11
  import '../components/tooltip.js';
12
12
  import '../hooks/use-anchored-rect.js';
13
13
  import '../lib/clipping.js';
14
+ import '../components/write-lock.js';
14
15
  import '../components/swipeable-row.js';
16
+ import '../components/status-dot.js';
17
+ import '../components/progress-bar.js';
18
+ import '../components/signed-amount.js';
19
+ import '../components/stat-tile.js';
15
20
  import '../components/mini-calendar.js';
16
21
  import '../components/calendar-heatmap.js';
17
22
  import '../components/popover.js';
@@ -27,7 +32,6 @@ import '../components/page-contents.js';
27
32
  import '../components/series-chart-labels.js';
28
33
  import '../components/pie-chart-labels.js';
29
34
  import '../components/sparkline.js';
30
- import '../components/stat-tile.js';
31
35
  import '../components/signature-pad.js';
32
36
  import '../components/password-strength.js';
33
37
  import '../components/swatch-picker.js';
@@ -38,9 +42,6 @@ import '../components/modal.js';
38
42
  import '../hooks/use-search-param-state.js';
39
43
  import '../components/measured-grid.js';
40
44
  import '../components/user-avatar.js';
41
- import '../components/status-dot.js';
42
- import '../components/progress-bar.js';
43
- import '../components/signed-amount.js';
44
45
  import '../shell/topbar-action-menu.js';
45
46
  import '../components/menu-item.js';
46
47
  import '../components/floating-panel.js';
@@ -56,7 +57,6 @@ import '../components/error-boundary.js';
56
57
  import '../components/authed-image.js';
57
58
  import '../components/image-grid.js';
58
59
  import '../components/lightbox.js';
59
- import '../components/write-lock.js';
60
60
  import '../auth/sign-in-form.js';
61
61
  import '../landing/access.js';
62
62
  import '../landing/landing-labels.js';