@eifi1/ui-kit 0.33.0 → 0.33.1

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 (161) hide show
  1. package/README.md +3 -3
  2. package/dist/account/delete-account-setting.d.ts +2 -1
  3. package/dist/admin/admin-action-confirm.d.ts +2 -1
  4. package/dist/admin/invitations-panel.d.ts +2 -1
  5. package/dist/admin/plan-change.d.ts +2 -1
  6. package/dist/admin/plan-change.js +3 -1
  7. package/dist/admin/plan-change.js.map +1 -1
  8. package/dist/admin/reviewer-scope-editor.d.ts +2 -1
  9. package/dist/admin/role-select.d.ts +2 -1
  10. package/dist/admin/transfer-ownership-dialog.d.ts +2 -1
  11. package/dist/admin/user-roster.d.ts +2 -1
  12. package/dist/auth/register-form.d.ts +2 -1
  13. package/dist/billing/billing-banner.d.ts +2 -1
  14. package/dist/billing/billing-labels.d.ts +2 -1
  15. package/dist/billing/billing-lock.d.ts +2 -1
  16. package/dist/billing/billing-overview.d.ts +2 -1
  17. package/dist/billing/billing-standing.d.ts +2 -1
  18. package/dist/billing/checkout-processing.d.ts +2 -1
  19. package/dist/billing/pay-page.d.ts +11 -6
  20. package/dist/billing/pay-page.js.map +1 -1
  21. package/dist/billing/plan-card.d.ts +2 -1
  22. package/dist/billing/plan-limit-notice.d.ts +2 -1
  23. package/dist/billing/plan-limit-toast.d.ts +2 -1
  24. package/dist/billing/subscription-actions.d.ts +2 -1
  25. package/dist/billing/subscription-status-chip.d.ts +2 -1
  26. package/dist/chart.d.ts +2 -1
  27. package/dist/components/amount-input.d.ts +2 -1
  28. package/dist/components/autocomplete.js +2 -2
  29. package/dist/components/autocomplete.js.map +1 -1
  30. package/dist/components/button-group.d.ts +2 -1
  31. package/dist/components/calculator.d.ts +2 -1
  32. package/dist/components/combobox-core.js +2 -1
  33. package/dist/components/combobox-core.js.map +1 -1
  34. package/dist/components/combobox.js +2 -2
  35. package/dist/components/combobox.js.map +1 -1
  36. package/dist/components/confirm-dialog.d.ts +2 -1
  37. package/dist/components/copy-button.d.ts +2 -1
  38. package/dist/components/danger-confirm.d.ts +2 -1
  39. package/dist/components/dropdown.js +9 -1
  40. package/dist/components/dropdown.js.map +1 -1
  41. package/dist/components/facing-pair.d.ts +2 -1
  42. package/dist/components/file-button.d.ts +2 -1
  43. package/dist/components/file-dropzone.d.ts +2 -1
  44. package/dist/components/form-actions.d.ts +2 -1
  45. package/dist/components/full-bleed-dialog.js.map +1 -1
  46. package/dist/components/iban-input.d.ts +2 -1
  47. package/dist/components/language-select.d.ts +2 -1
  48. package/dist/components/legal-page.d.ts +2 -1
  49. package/dist/components/legal.d.ts +2 -1
  50. package/dist/components/modal.js.map +1 -1
  51. package/dist/components/money-field.d.ts +2 -1
  52. package/dist/components/number-field.d.ts +9 -1
  53. package/dist/components/number-field.js.map +1 -1
  54. package/dist/components/number-input.d.ts +29 -2
  55. package/dist/components/number-input.js +27 -12
  56. package/dist/components/number-input.js.map +1 -1
  57. package/dist/components/numpad-sheet.d.ts +2 -1
  58. package/dist/components/phone-input.d.ts +2 -1
  59. package/dist/components/popover.js +6 -2
  60. package/dist/components/popover.js.map +1 -1
  61. package/dist/components/row-actions.d.ts +2 -1
  62. package/dist/components/series-chart.d.ts +2 -1
  63. package/dist/components/settings-fields.d.ts +2 -1
  64. package/dist/components/share-card.d.ts +2 -1
  65. package/dist/components/skeleton.d.ts +10 -2
  66. package/dist/components/skeleton.js.map +1 -1
  67. package/dist/components/text-link.d.ts +2 -1
  68. package/dist/components/time-input.d.ts +2 -1
  69. package/dist/components/ui.d.ts +2 -1
  70. package/dist/{delete-account-setting-DiPX4Vue.d.ts → delete-account-setting-DAgGBrbn.d.ts} +44 -7
  71. package/dist/feedback/feedback-attachment.d.ts +2 -1
  72. package/dist/feedback/feedback-context.d.ts +2 -1
  73. package/dist/feedback/feedback-crash.d.ts +2 -1
  74. package/dist/feedback/feedback-dialog.d.ts +2 -1
  75. package/dist/feedback/feedback-inbox.d.ts +2 -1
  76. package/dist/feedback/feedback-labels.d.ts +2 -1
  77. package/dist/feedback/feedback-menu.d.ts +2 -1
  78. package/dist/feedback/feedback-record.d.ts +2 -1
  79. package/dist/feedback/feedback-row-detail.d.ts +2 -1
  80. package/dist/feedback/feedback-status-undo.d.ts +2 -1
  81. package/dist/feedback/feedback-submit.d.ts +2 -1
  82. package/dist/feedback/feedback-swipe.d.ts +2 -1
  83. package/dist/feedback/feedback-table.d.ts +2 -1
  84. package/dist/feedback/feedback-thread.d.ts +3 -2
  85. package/dist/feedback/feedback-thread.js +5 -1
  86. package/dist/feedback/feedback-thread.js.map +1 -1
  87. package/dist/feedback.d.ts +2 -1
  88. package/dist/hooks/use-anchored-panel.d.ts +37 -7
  89. package/dist/hooks/use-anchored-panel.js +6 -7
  90. package/dist/hooks/use-anchored-panel.js.map +1 -1
  91. package/dist/hooks/use-file-drop.d.ts +2 -1
  92. package/dist/hooks/use-focus-trap.d.ts +8 -0
  93. package/dist/hooks/use-focus-trap.js +17 -11
  94. package/dist/hooks/use-focus-trap.js.map +1 -1
  95. package/dist/i18n/defaults.d.ts +2 -1
  96. package/dist/i18n/german.d.ts +2 -1
  97. package/dist/i18n/german.js +4 -1
  98. package/dist/i18n/german.js.map +1 -1
  99. package/dist/i18n/kit-labels.d.ts +2 -1
  100. package/dist/i18n/languages.d.ts +2 -1
  101. package/dist/i18n/locales/de-CH.d.ts +2 -1
  102. package/dist/i18n/locales/en.d.ts +2 -1
  103. package/dist/i18n/locales/es.d.ts +2 -1
  104. package/dist/i18n/locales/es.js +2 -1
  105. package/dist/i18n/locales/es.js.map +1 -1
  106. package/dist/i18n/locales/fr.d.ts +2 -1
  107. package/dist/i18n/locales/fr.js +3 -1
  108. package/dist/i18n/locales/fr.js.map +1 -1
  109. package/dist/i18n/locales/hu.d.ts +2 -1
  110. package/dist/i18n/locales/hu.js +4 -2
  111. package/dist/i18n/locales/hu.js.map +1 -1
  112. package/dist/i18n/locales/it.d.ts +2 -1
  113. package/dist/i18n/locales/it.js +2 -1
  114. package/dist/i18n/locales/it.js.map +1 -1
  115. package/dist/i18n/locales/zh.d.ts +2 -1
  116. package/dist/i18n/locales/zh.js +1 -0
  117. package/dist/i18n/locales/zh.js.map +1 -1
  118. package/dist/i18n/plurals.d.ts +20 -1
  119. package/dist/i18n/plurals.js +11 -0
  120. package/dist/i18n/plurals.js.map +1 -1
  121. package/dist/i18n/review.d.ts +2 -1
  122. package/dist/index.d.ts +2 -2
  123. package/dist/index.js.map +1 -1
  124. package/dist/landing/landing-actions.d.ts +2 -1
  125. package/dist/landing/landing-sections.d.ts +2 -1
  126. package/dist/landing/public-header.d.ts +2 -1
  127. package/dist/landing/routing.d.ts +2 -1
  128. package/dist/rhf/fields.d.ts +2 -1
  129. package/dist/rhf/form.d.ts +2 -1
  130. package/dist/rhf.d.ts +2 -1
  131. package/dist/settings/settings-section.d.ts +2 -1
  132. package/dist/shell/app-shell.d.ts +2 -1
  133. package/dist/shell/top-bar-brand.d.ts +2 -1
  134. package/dist/shell.d.ts +2 -1
  135. package/dist/wizard/stepper-nav.d.ts +2 -1
  136. package/dist/wizard.d.ts +2 -1
  137. package/package.json +1 -1
  138. package/src/admin/plan-change.tsx +35 -7
  139. package/src/billing/pay-page.ts +9 -5
  140. package/src/components/autocomplete.tsx +5 -2
  141. package/src/components/combobox-core.tsx +5 -2
  142. package/src/components/combobox.tsx +4 -2
  143. package/src/components/dropdown.tsx +12 -2
  144. package/src/components/full-bleed-dialog.tsx +6 -0
  145. package/src/components/modal.tsx +5 -0
  146. package/src/components/number-field.tsx +7 -0
  147. package/src/components/number-input.tsx +71 -11
  148. package/src/components/popover.tsx +6 -2
  149. package/src/components/skeleton.tsx +10 -2
  150. package/src/feedback/feedback-thread.tsx +20 -1
  151. package/src/hooks/use-anchored-panel.ts +60 -14
  152. package/src/hooks/use-focus-trap.ts +55 -12
  153. package/src/i18n/german.ts +4 -1
  154. package/src/i18n/locales/es.ts +2 -1
  155. package/src/i18n/locales/fr.ts +3 -1
  156. package/src/i18n/locales/hu.ts +4 -2
  157. package/src/i18n/locales/it.ts +2 -1
  158. package/src/i18n/locales/zh.ts +1 -0
  159. package/src/i18n/plurals.ts +29 -0
  160. package/src/index.ts +3 -1
  161. package/tokens.css +18 -0
package/README.md CHANGED
@@ -382,14 +382,14 @@ columns, strings and permissions (see the note at the top of `src/feedback/feedb
382
382
 
383
383
  <!-- BEGIN GENERATED: exports — node scripts/gen-export-inventory.mjs -->
384
384
 
385
- **1876 names from 278 modules** — 879 values and 997 types. _Italic_ is a type-only export.
385
+ **1877 names from 278 modules** — 879 values and 998 types. _Italic_ is a type-only export.
386
386
 
387
387
  Generated from `dist/index.d.ts` by `node scripts/gen-export-inventory.mjs`; the count
388
388
  is pinned by `src/__tests__/public-surface.test.ts`. Do not edit between the markers.
389
389
 
390
390
  | Entry point | Names |
391
391
  |---|---|
392
- | `@eifi1/ui-kit` | 1876 |
392
+ | `@eifi1/ui-kit` | 1877 |
393
393
  | `@eifi1/ui-kit/chart` | 105 |
394
394
  | `@eifi1/ui-kit/shell` | 76 |
395
395
  | `@eifi1/ui-kit/data-table` | 51 |
@@ -499,7 +499,7 @@ re-slicing of it, never a second API.
499
499
  | `i18n/languages` | `formatLocaleOf`, `KIT_LANGUAGES`, `languageOptions`, `loadUiKitLabels`, `peekUiKitLabels`, `resolveLanguage`, `useUiKitLabels`, _`KitLanguage`_, _`KitLanguageCode`_ |
500
500
  | `i18n/defaults` | `DEFAULT_UI_KIT_LABELS` |
501
501
  | `i18n/review` | `kitLabelStrings`, _`KitLabelStringsOptions`_ |
502
- | `i18n/plurals` | `withAllPlurals` |
502
+ | `i18n/plurals` | `withAllPlurals`, _`PluralForms`_ |
503
503
 
504
504
  ### billing
505
505
 
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { cz as DEFAULT_DELETE_ACCOUNT_LABELS, d0 as DeleteAccountConsequence, d1 as DeleteAccountLabels, d2 as DeleteAccountSetting, d3 as DeleteAccountSettingProps, d4 as DeleteAccountValues, d5 as DeletionMode } from '../delete-account-setting-DiPX4Vue.js';
2
+ export { cz as DEFAULT_DELETE_ACCOUNT_LABELS, d0 as DeleteAccountConsequence, d1 as DeleteAccountLabels, d2 as DeleteAccountSetting, d3 as DeleteAccountSettingProps, d4 as DeleteAccountValues, d5 as DeletionMode } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
5
  import '../lib/clipping.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { bt as AdminActionConfirm, bu as AdminActionConfirmProps, bv as AdminActionConfirmValues, bw as AdminActionLabels, bx as AdminActionTarget, by as AdminConfirmLevel, cm as DEFAULT_ADMIN_ACTION_LABELS } from '../delete-account-setting-DiPX4Vue.js';
2
+ export { bt as AdminActionConfirm, bu as AdminActionConfirmProps, bv as AdminActionConfirmValues, bw as AdminActionLabels, bx as AdminActionTarget, by as AdminConfirmLevel, cm as DEFAULT_ADMIN_ACTION_LABELS } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import './admin-parts.js';
4
4
  import '../components/write-lock.js';
5
5
  import '../components/tooltip.js';
@@ -81,6 +81,7 @@ import '../components/account-chips.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,6 +1,6 @@
1
1
  import 'react';
2
2
  import '../lib/format.js';
3
- export { cE as DEFAULT_INVITATIONS_LABELS, dQ as InvitationDraft, dR as InvitationRow, dS as InvitationScope, dT as InvitationSentAnswer, dU as InvitationStatus, dV as InvitationsLabels, dW as InvitationsPanel, dX as InvitationsPanelProps } from '../delete-account-setting-DiPX4Vue.js';
3
+ export { cE as DEFAULT_INVITATIONS_LABELS, dQ as InvitationDraft, dR as InvitationRow, dS as InvitationScope, dT as InvitationSentAnswer, dU as InvitationStatus, dV as InvitationsLabels, dW as InvitationsPanel, dX as InvitationsPanelProps } from '../delete-account-setting-DAgGBrbn.js';
4
4
  import '../components/account-chips.js';
5
5
  import './admin-parts.js';
6
6
  import '../components/tooltip.js';
@@ -81,6 +81,7 @@ import '../account/data-export-setting.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,6 +1,7 @@
1
1
  import 'react';
2
+ import '../i18n/plurals.js';
2
3
  import '../data-table-labels-BJ5JE_X2.js';
3
- export { cL as DEFAULT_PLAN_CHANGE_LABELS, ev as PlanChange, ew as PlanChangeConfirm, ex as PlanChangeConfirmProps, ey as PlanChangeLabels, ez as PlanChangeLine, eA as PlanChangeOutcome, eB as PlanChangeResultOptions, eC as PlanChangeTone, eD as PlanColumnOptions, eE as PlanColumnUsage, g6 as planColumn, go as usePlanChangeResult, gp as usePlanColumn } from '../delete-account-setting-DiPX4Vue.js';
4
+ export { cL as DEFAULT_PLAN_CHANGE_LABELS, ev as PlanChange, ew as PlanChangeConfirm, ex as PlanChangeConfirmProps, ey as PlanChangeLabels, ez as PlanChangeLine, eA as PlanChangeOutcome, eB as PlanChangeResultOptions, eC as PlanChangeTone, eD as PlanColumnOptions, eE as PlanColumnUsage, g6 as planColumn, go as usePlanChangeResult, gp as usePlanColumn } from '../delete-account-setting-DAgGBrbn.js';
4
5
  import './admin-parts.js';
5
6
  import '../components/data-table-sort.js';
6
7
  import '../components/swipeable-row.js';
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useMemo, useState } from "react";
4
4
  import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
5
+ import { pluralForm } from "../i18n/plurals.js";
5
6
  import { formatNumber, EMPTY_FORMATTED_VALUE } from "../lib/format.js";
6
7
  import { toLocalIso } from "../lib/dates.js";
7
8
  import { DatePicker } from "../components/date-picker.js";
@@ -42,6 +43,7 @@ function PlanCell({
42
43
  }) {
43
44
  const locale = useKitLocale();
44
45
  const figure = (value) => formatNumber(value, { locale });
46
+ const word = (label, limit) => typeof label === "string" ? label : pluralForm(limit, label, locale);
45
47
  return /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", "data-plan": code ?? void 0, children: [
46
48
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
47
49
  /* @__PURE__ */ jsx("span", { className: code ? "font-medium" : "text-[var(--text-muted)]", children: name }),
@@ -49,7 +51,7 @@ function PlanCell({
49
51
  ] }),
50
52
  usage && usage.limit !== null && // "1/5" under the plan, "1 of 5 budgets" in the tooltip: the sentence spelled out
51
53
  // 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: [
54
+ /* @__PURE__ */ jsx(Tooltip, { label: usageOf(figure(usage.used), figure(usage.limit), word(usage.label, usage.limit)), children: /* @__PURE__ */ jsxs("span", { className: "block text-xs tabular-nums text-[var(--text-muted)]", children: [
53
55
  figure(usage.used),
54
56
  "/",
55
57
  figure(usage.limit)
@@ -1 +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":";AAqJM,SACE,KADF;AArJN,SAAS,aAAa,SAAS,gBAAgB;AAG/C,SAAS,cAAc,oBAAoB;AAC3C,SAAS,cAAc,6BAA6B;AACpD,SAAS,kBAAkB;AAE3B,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AAEjC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAEnC,SAAS,aAAa,kBAAkB;AAoDjC,MAAM,6BAA+C;AAAA,EAC1D,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS,CAAC,SAAS,iBAAiB,IAAI;AAAA,EACxC,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,WAAW;AAAA,EACX,aAAa;AAAA,EACb,SAAS,CAAC,MAAM,OAAO,qBAAqB,IAAI,OAAO,EAAE;AAAA,EACzD,KAAK,CAAC,OAAO,eAAe,EAAE;AAAA,EAC9B,UAAU;AAAA,EACV,WAAW,CAAC,SAAS,cAAc,IAAI;AAAA,EACvC,WAAW;AAAA,EACX,SAAS,CAAC,MAAM,OAAO,cAAc,GAAG,IAAI,OAAO,KAAK,IAAI,SAAS;AACvE;AAIA,SAAS,OAAO,KAA+C;AAC7D,QAAM,QAAQ,OAAO,IAAI,KAAK,EAAE,YAAY;AAC5C,SAAO,SAAS,KAAK,OAAO;AAC9B;AAyCA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,SAAS,aAAa;AAC5B,QAAM,SAAS,CAAC,UAAkB,aAAa,OAAO,EAAE,OAAO,CAAC;AAChE,SACE,qBAAC,SAAI,WAAU,qBAAoB,aAAW,QAAQ,QACpD;AAAA,yBAAC,SAAI,WAAU,6BACb;AAAA,0BAAC,UAAK,WAAW,OAAO,gBAAgB,4BAA6B,gBAAK;AAAA,MAEzE,WAAW,UAAa,oBAAC,0BAAuB,QAAgB,MAAK,MAAK;AAAA,OAC7E;AAAA,IACC,SAAS,MAAM,UAAU;AAAA;AAAA,IAGxB,oBAAC,WAAQ,OAAO,QAAQ,OAAO,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,GAAG,MAAM,KAAK,GAC1E,+BAAC,UAAK,WAAU,uDACb;AAAA,aAAO,MAAM,IAAI;AAAA,MAAE;AAAA,MAAE,OAAO,MAAM,KAAK;AAAA,OAC1C,GACF;AAAA,KAEJ;AAEJ;AAkBO,SAAS,WAAc,SAAmD;AAC/E,QAAM,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,OAAO,aAAa,QAAQ,YAAY,QAAQ,WAAW,IAAI;AAC7G,QAAM,SAA2B,EAAE,GAAG,4BAA4B,GAAG,WAAW;AAChF,QAAM,SAAS,cAAc,OAAO;AACpC,QAAM,SAAS,CAAC,SAAyB,OAAO,SAAS,IAAI,IAAI;AACjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,MAAM,CAAC,QAAQ;AACb,YAAM,OAAO,OAAO,KAAK,GAAG,CAAC;AAC7B,aACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,MAAM,OAAO,IAAI;AAAA,UACjB,QAAQ,SAAS,GAAG;AAAA,UACpB,OAAO,QAAQ,GAAG;AAAA,UAClB,SAAS,OAAO;AAAA;AAAA,MAClB;AAAA,IAEJ;AAAA,IACA,QAAQ,WAAW,QAAQ,SAAa,WAAW,CAAC,QAAQ,OAAO,KAAK,GAAG,CAAC,KAAK;AAAA,IACjF,QAAQ,cACJ;AAAA,MACE,MAAM;AAAA,MACN,UAAU,CAAC,QAAQ,OAAO,KAAK,GAAG,CAAC,KAAK;AAAA,MACxC,SAAS,CAAC,GAAG,IAAI,IAAI,YAAY,IAAI,CAAC,SAAS,OAAO,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,SAAS,IAAI,CAAC,CAAC,EAAE;AAAA,QAC7F,CAAC,UAAU,EAAE,OAAO,MAAM,OAAO,SAAS,IAAI,EAAE;AAAA,MAClD;AAAA,IACF,IACA;AAAA,EACN;AACF;AAOO,SAAS,cAAiB,SAAmD;AAClF,QAAM,EAAE,KAAK,MAAM,UAAU,QAAQ,OAAO,aAAa,QAAQ,YAAY,QAAQ,WAAW,IAAI;AACpG,QAAM,SAAS,aAAa,cAAc,4BAA4B,UAAU;AAChF,SAAO;AAAA,IACL,MAAM,WAAc,EAAE,KAAK,MAAM,UAAU,QAAQ,OAAO,aAAa,QAAQ,YAAY,OAAO,CAAC;AAAA,IACnG,CAAC,KAAK,MAAM,UAAU,QAAQ,OAAO,aAAa,QAAQ,YAAY,MAAM;AAAA,EAC9E;AACF;AA6CA,SAAS,SAAS,MAAsB;AACtC,UAAO,oBAAI,KAAK,GAAG,IAAI,WAAW,GAAE,YAAY;AAClD;AAWO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAA2B;AACzB,QAAM,SAAS,aAAa,cAAc,4BAA4B,UAAU;AAChF,QAAM,SAAS,aAAa;AAG5B,QAAM,UAAU,OAAO,WAAW;AAClC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,WAAW,EAAE;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AAErC,QAAM,CAAC,KAAK,IAAI,SAAS,MAAM,WAAW,oBAAI,KAAK,CAAC,CAAC;AACrD,QAAM,UAAU,MAAM,IAAI,CAAC,OAAO,EAAE,MAAM,OAAO,EAAE,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AACnF,QAAM,SAAS,CAAC,SAAiB,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,QAAQ;AAE/E,MAAI,WAAW,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,EAAG,SAAQ,QAAQ,EAAE,MAAM,SAAS,MAAM,QAAQ,CAAC;AACzG,QAAM,YAAY,SAAS,MAAO,SAAS,WAAW,CAAC;AAEvD,QAAM,MAAM,EAAE,GAAG,QAAQ,OAAO,OAAO,SAAS,YAAY,QAAQ,MAAM,EAAE;AAE5E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,MAAK;AAAA,MACL,QAAQ;AAAA,MACR,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,uBAAuB,YAAY,OAAO,cAAc;AAAA,MACxD;AAAA,MACA,QAAQ;AAAA,MACR,WAAW,CAAC,WAAW,UAAU,EAAE,MAAM,GAAI,QAAQ,EAAE,cAAc,SAAS,KAAK,EAAE,IAAI,CAAC,EAAG,GAAG,MAAM;AAAA,MACtG;AAAA,MAEE;AAAA,qBAAY,QAAQ,UAAU,WAC9B,qBAAC,SAAI,WAAU,gDAA+C,qBAAmB,WAAW,QACzF;AAAA,sBAAY,QAAQ,oBAAC,OAAG,iBAAO,QAAQ,OAAO,OAAO,CAAC,GAAE;AAAA,UACxD,UAAU,UAAa,oBAAC,OAAG,iBAAM;AAAA,WACpC;AAAA,QAEF,qBAAC,UAAO,OAAO,OAAO,MAAM,MAAM,OAAO,YAAY,OAAO,MAAM,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK,GACtG;AAAA,mBAAS,MACR,oBAAC,YAAO,OAAM,IAAG,UAAQ,MACtB,iCACH;AAAA,UAED,QAAQ,IAAI,CAAC,WACZ,oBAAC,YAAyB,OAAO,OAAO,MACrC,iBAAO,QADG,OAAO,IAEpB,CACD;AAAA,WACH;AAAA,QACC,YACC;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,OAAO;AAAA,YACd,MAAM,OAAO;AAAA,YACb,OAAO;AAAA,YACP,UAAU;AAAA,YACV,KAAK;AAAA,YACL,WAAS;AAAA;AAAA,QACX;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAuDO,SAAS,sBAIM;AACpB,QAAM,SAAS,aAAa,cAAc,0BAA0B;AACpE,QAAM,UAAU,iBAAiB;AACjC,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,WAAW;AAC3B,SAAO;AAAA,IACL,CAAC,KAAK,UAAU,UAAU,CAAC,MAAM;AAC/B,YAAM,EAAE,iBAAiB,YAAY,IAAI;AACzC,YAAM,OAAO,CAAC,QAAgB,SAAS,OAAO,GAAG,KAAK,GAAG;AACzD,YAAM,KAAK,KAAK,IAAI,IAAI;AACxB,YAAM,OAAO,OAAO,IAAI,aAAa;AACrC,YAAM,QAA0B;AAAA,QAC9B;AAAA,UACE,MAAM;AAAA,UACN,MAAM,SAAS,QAAQ,SAAS,OAAO,IAAI,IAAI,IAAI,OAAO,QAAQ,KAAK,IAAI,GAAG,EAAE,IAAI,OAAO,IAAI,EAAE;AAAA,QACnG;AAAA,MACF;AACA,UAAI,IAAI,UAAW,OAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,OAAO,SAAS,CAAC;AAAA,eAC5D,IAAI,aAAc,OAAM,KAAK,EAAE,MAAM,WAAW,MAAM,OAAO,UAAU,QAAQ,IAAI,YAAY,CAAC,EAAE,CAAC;AAC5G,UAAI,IAAI,YAAY;AAClB,cAAM,KAAK,EAAE,MAAM,WAAW,MAAM,OAAO,UAAU,CAAC;AACtD,cAAM,SAAS,CAAC,WAAmB,UACjC,cAAc,YAAY,WAAW,KAAK,IAAI,aAAa,OAAO,EAAE,OAAO,CAAC;AAE9E,mBAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,IAAI,UAAU,CAAC,CAAC,GAAG;AACjE,gBAAM,OAAO,IAAI,QAAQ,SAAS;AAClC,cAAI,UAAU,QAAQ,SAAS,UAAa,QAAQ,MAAO;AAC3D,gBAAM,KAAK;AAAA,YACT,MAAM;AAAA,YACN,MAAM,QAAQ;AAAA,cACZ,kBAAkB,SAAS,KAAK;AAAA,cAChC,OAAO,WAAW,IAAI;AAAA,cACtB,OAAO,WAAW,KAAK;AAAA,YACzB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,QAAQ,SAAS,QAAQ,OAAO;AAAA,EACnC;AACF;","names":[]}
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 { pluralForm } from \"../i18n/plurals\";\nimport type { PluralForms } from \"../i18n/plurals\";\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 /**\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.label` gives it — since 0.33.1\n * in the form the LIMIT takes when the label gives its plural forms (\"1 of 1 budget\",\n * \"1 of 5 budgets\"): in every shipped language the word follows the limit, or heads\n * the line in German, whose `von` would want a dative plural no app form carries\n * (\"Budgets: 1 von 5\"). An override of yours gets the same word.\n */\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 /**\n * The app's word for what is counted, as it reads after a number: \"budgets\" in \"1 of 5\n * budgets\" — the word in a sentence, not a heading's \"Budgets\".\n *\n * Its plural forms (0.33.1, keksdose): `{ one: \"budget\", other: \"budgets\" }` — your\n * catalogue's `_one` / `_other` pair, any CLDR category allowed, `other` for the rest\n * ({@link PluralForms}). The column picks the form for the limit in the provider's\n * locale, so a plan of one reads \"1 of 1 budget\". A plain string is used as it is,\n * whatever the count.\n */\n label: string | PluralForms;\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 (`planChange.usageOf`, with the label's form for the limit). Undefined\n * 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 * Client-side only, as every column's `sortBy`: a `DataTable` with `serverPagination`\n * holds one page, so it sorts nothing itself — with `onSortsChange` it reports the\n * column's `key` (default `\"plan\"`), and your server orders the rows by it.\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 const word = (label: PlanColumnUsage[\"label\"], limit: number) =>\n typeof label === \"string\" ? label : pluralForm(limit, label, 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), word(usage.label, usage.limit))}>\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) => ({\n * used: u.budgets_owned,\n * limit: u.limits.budgets,\n * label: { one: t(\"planUsage.budget_one\"), other: t(\"planUsage.budget_other\") },\n * }),\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,5 +1,5 @@
1
1
  import 'react';
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';
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-DAgGBrbn.js';
3
3
  import './admin-parts.js';
4
4
  import '../components/write-lock.js';
5
5
  import '../components/tooltip.js';
@@ -81,6 +81,7 @@ import '../components/account-chips.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,6 +1,6 @@
1
1
  import 'react';
2
2
  import '../components/account-chips.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';
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-DAgGBrbn.js';
4
4
  import '../components/write-lock.js';
5
5
  import 'lucide-react';
6
6
  import '../lib/format.js';
@@ -81,6 +81,7 @@ import './admin-parts.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import 'react';
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';
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-DAgGBrbn.js';
3
3
  import './admin-parts.js';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
@@ -81,6 +81,7 @@ import '../components/account-chips.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -5,7 +5,7 @@ import '../components/account-chips.js';
5
5
  import '../components/chip.js';
6
6
  import '../data-table-labels-BJ5JE_X2.js';
7
7
  import '../components/data-table-sort.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';
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-DAgGBrbn.js';
9
9
  import 'lucide-react';
10
10
  import '../components/field-parts.js';
11
11
  import '../components/tooltip.js';
@@ -81,6 +81,7 @@ import './admin-parts.js';
81
81
  import './admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { cM as DEFAULT_REGISTER_LABELS, eF as RegisterForm, eG as RegisterFormProps, eH as RegisterLabels, eI as RegisterValues } from '../delete-account-setting-DiPX4Vue.js';
2
+ export { cM as DEFAULT_REGISTER_LABELS, eF as RegisterForm, eG as RegisterFormProps, eH as RegisterLabels, eI as RegisterValues } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
5
  import '../lib/clipping.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentPropsWithoutRef } from 'react';
3
- import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DiPX4Vue.js';
3
+ import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DAgGBrbn.js';
4
4
  import { AlertTone, AlertSize } from '../components/alert-banner.js';
5
5
  import '../components/tooltip.js';
6
6
  import '../hooks/use-anchored-rect.js';
@@ -83,6 +83,7 @@ import '../admin/admin-parts.js';
83
83
  import '../admin/admin-action-log.js';
84
84
  import '../settings/settings-labels.js';
85
85
  import '../demo/demo-labels.js';
86
+ import '../i18n/plurals.js';
86
87
  import '../components/appearance-labels.js';
87
88
  import '../theme/contrast.js';
88
89
  import '../theme/text-size.js';
@@ -1,4 +1,4 @@
1
- export { bH as BillingLabels, cp as DEFAULT_BILLING_LABELS, f6 as SUBSCRIPTION_STATUSES, fn as SubscriptionStatus, gc as useBillingLabels } from '../delete-account-setting-DiPX4Vue.js';
1
+ export { bH as BillingLabels, cp as DEFAULT_BILLING_LABELS, f6 as SUBSCRIPTION_STATUSES, fn as SubscriptionStatus, gc as useBillingLabels } from '../delete-account-setting-DAgGBrbn.js';
2
2
  import 'react';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DiPX4Vue.js';
2
+ import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import { WriteLockHold, WriteLockKind } from '../components/write-lock.js';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import { DateInput } from '../lib/format.js';
2
- import { fn as SubscriptionStatus } from '../delete-account-setting-DiPX4Vue.js';
2
+ import { fn as SubscriptionStatus } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import { BillingCurrency } from './plan-price.js';
4
4
  import 'react';
5
5
  import '../components/tooltip.js';
@@ -82,6 +82,7 @@ import '../admin/admin-parts.js';
82
82
  import '../admin/admin-action-log.js';
83
83
  import '../settings/settings-labels.js';
84
84
  import '../demo/demo-labels.js';
85
+ import '../i18n/plurals.js';
85
86
  import '../components/appearance-labels.js';
86
87
  import '../theme/contrast.js';
87
88
  import '../theme/text-size.js';
@@ -1,5 +1,5 @@
1
1
  import { DateInput } from '../lib/format.js';
2
- import { fn as SubscriptionStatus } from '../delete-account-setting-DiPX4Vue.js';
2
+ import { fn as SubscriptionStatus } from '../delete-account-setting-DAgGBrbn.js';
3
3
  import 'react';
4
4
  import '../components/tooltip.js';
5
5
  import '../hooks/use-anchored-rect.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -1,6 +1,6 @@
1
1
  import { DateInput } from '../lib/format.js';
2
2
  import { BillingStanding } from './billing-standing.js';
3
- import '../delete-account-setting-DiPX4Vue.js';
3
+ import '../delete-account-setting-DAgGBrbn.js';
4
4
  import 'react';
5
5
  import '../components/tooltip.js';
6
6
  import '../hooks/use-anchored-rect.js';
@@ -82,6 +82,7 @@ import '../admin/admin-parts.js';
82
82
  import '../admin/admin-action-log.js';
83
83
  import '../settings/settings-labels.js';
84
84
  import '../demo/demo-labels.js';
85
+ import '../i18n/plurals.js';
85
86
  import '../components/appearance-labels.js';
86
87
  import '../theme/contrast.js';
87
88
  import '../theme/text-size.js';
@@ -1,4 +1,4 @@
1
- import { fJ as UiKitLabels } from '../delete-account-setting-DiPX4Vue.js';
1
+ import { fJ as UiKitLabels } from '../delete-account-setting-DAgGBrbn.js';
2
2
  import 'react';
3
3
  import '../components/tooltip.js';
4
4
  import '../hooks/use-anchored-rect.js';
@@ -81,6 +81,7 @@ import '../admin/admin-parts.js';
81
81
  import '../admin/admin-action-log.js';
82
82
  import '../settings/settings-labels.js';
83
83
  import '../demo/demo-labels.js';
84
+ import '../i18n/plurals.js';
84
85
  import '../components/appearance-labels.js';
85
86
  import '../theme/contrast.js';
86
87
  import '../theme/text-size.js';
@@ -130,9 +131,10 @@ declare const PAY_PAGE_TRANSACTION_PARAM = "_ptxn";
130
131
  * The kit's locale → Paddle's checkout locale: `de-CH` → `de`, `zh` → `zh-Hans`, `hu` →
131
132
  * `en` (Paddle has no Hungarian), the others as they are. Any tag is read as the kit
132
133
  * reads it (`de-AT` is the one German, `zh-TW` the one Chinese); one the kit doesn't
133
- * ship is English.
134
+ * ship is English, and so is `undefined` (0.33.1) — `useKitLocale()` with no provider
135
+ * locale.
134
136
  */
135
- declare function paddleLocale(kitLocale: string): string;
137
+ declare function paddleLocale(kitLocale: string | undefined): string;
136
138
  /**
137
139
  * The checkout's `{url}` with the buyer's language: `lang=<kit locale>` on a pay page
138
140
  * link (one that carries `_ptxn`); any other URL — a hosted checkout's, which takes its
@@ -140,11 +142,14 @@ declare function paddleLocale(kitLocale: string): string;
140
142
  *
141
143
  * const { url } = await api.checkout({ plan, interval, currency });
142
144
  * noteCheckoutStarted(user.id, overview);
143
- * window.location.assign(payPageUrl(url, i18n.language));
145
+ * window.location.assign(payPageUrl(url, locale)); // locale = useKitLocale()
144
146
  *
145
- * A wrong `lang` costs nothing but the language: the page falls back to the browser's.
147
+ * `locale` may be `undefined` (0.33.1, keksdose): `useKitLocale()` answers that with no
148
+ * provider locale, the runtime's language. The link then goes without `lang`, and the
149
+ * page speaks the browser's language, else English — what a missing or wrong `lang`
150
+ * already did: it costs nothing but the language.
146
151
  */
147
- declare function payPageUrl(checkoutUrl: string, locale: string): string;
152
+ declare function payPageUrl(checkoutUrl: string, locale: string | undefined): string;
148
153
  /**
149
154
  * The language the page speaks: its `lang` parameter, else the browser's languages in
150
155
  * their order, each matched to the kit's; else English. @internal
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/billing/pay-page.ts"],"sourcesContent":["import type { UiKitLabels } from \"../i18n/kit-labels\";\nimport { checkoutReturnUrl } from \"./checkout-return\";\n\n/**\n * The kit's pay page (docs/billing-harmonization.md §14.4, decision 19 as amended): a\n * standalone static page that ui-kit ships as `dist/pay/`, and that each app serves on\n * its own pay host, `pay.<app domain>` — a separate origin, approved in Paddle, where\n * Paddle.js shares the origin with nothing: no sign-in, no app code, no storage.\n *\n * Paddle's create-transaction answers `checkout.url`: the pay page's URL plus\n * `?_ptxn=<txn>`, and Paddle.js on that page opens the transaction by itself. The app\n * sends the buyer there with {@link payPageUrl}, which adds the buyer's language; the\n * page maps it to Paddle's checkout locale ({@link paddleLocale}). Everything else the\n * page needs — the client-side token, sandbox or live, the way back — is the app's\n * `pay-config.json`, written at its web build by the `eifi1-pay-page` script.\n *\n * Pure, and React-free: the page's own script (`pay-page/pay.ts`) and that build script\n * bundle this module. The exports below the barrel's four are theirs.\n */\n\n/** Paddle's two environments: the sandbox's client-side tokens start `test_`, live\n * ones `live_`. */\nexport type PaddleEnvironment = \"sandbox\" | \"live\";\n\n/** The pay page's language parameter: `?lang=<kit locale>`. */\nexport const PAY_PAGE_LANG_PARAM = \"lang\";\n\n/** The kit's seven languages (`KitLanguageCode`), as the page reads them. */\nconst PAY_PAGE_LANGUAGES = [\"de-CH\", \"en\", \"es\", \"fr\", \"it\", \"hu\", \"zh\"] as const;\n\n/** One of the kit's languages, as the page speaks it. */\nexport type PayPageLanguage = (typeof PAY_PAGE_LANGUAGES)[number];\n\n/**\n * The kit's language → Paddle's checkout locale (`Paddle.Checkout.open`,\n * `settings.locale`, read 2026-10-09: ar, zh-Hans, zh-TW, da, nl, en, fr, de, it, ja, ko,\n * no, pl, pt, pt-BR, tr, ru, es, sv). Paddle has no Hungarian: a Hungarian buyer gets\n * English always, not Paddle's guess from the browser. The page's own lines stay in the\n * kit's language, Hungarian included.\n */\nconst PADDLE_LOCALES: Readonly<Record<PayPageLanguage, string>> = {\n en: \"en\",\n \"de-CH\": \"de\",\n fr: \"fr\",\n it: \"it\",\n es: \"es\",\n zh: \"zh-Hans\",\n hu: \"en\",\n};\n\n/** Paddle's transaction parameter on a pay page link. @internal */\nexport const PAY_PAGE_TRANSACTION_PARAM = \"_ptxn\";\n\n/**\n * The kit language one tag names, or undefined — `resolveLanguage`'s rule\n * (src/i18n/languages.ts), restated here because that module loads the seven catalogues\n * and the page must stay a few kilobytes: exact first, ignoring case and reading `_` as\n * `-`; then by language, so every German is `de-CH`, every Chinese `zh` (Simplified),\n * and any regional form its base (`fr-CA` → `fr`).\n */\nfunction kitLanguageOf(tag: unknown): PayPageLanguage | undefined {\n if (typeof tag !== \"string\") return undefined;\n const clean = tag.trim().replace(/_/g, \"-\");\n if (!clean) return undefined;\n const lower = clean.toLowerCase();\n const exact = PAY_PAGE_LANGUAGES.find((code) => code.toLowerCase() === lower);\n if (exact) return exact;\n const language = lower.split(\"-\")[0];\n return PAY_PAGE_LANGUAGES.find((code) => code.split(\"-\")[0].toLowerCase() === language);\n}\n\n/**\n * The kit's locale → Paddle's checkout locale: `de-CH` → `de`, `zh` → `zh-Hans`, `hu` →\n * `en` (Paddle has no Hungarian), the others as they are. Any tag is read as the kit\n * reads it (`de-AT` is the one German, `zh-TW` the one Chinese); one the kit doesn't\n * ship is English.\n */\nexport function paddleLocale(kitLocale: string): string {\n return PADDLE_LOCALES[kitLanguageOf(kitLocale) ?? \"en\"];\n}\n\n/**\n * The checkout's `{url}` with the buyer's language: `lang=<kit locale>` on a pay page\n * link (one that carries `_ptxn`); any other URL — a hosted checkout's, which takes its\n * own `locale` — unchanged, as is a locale the kit doesn't ship.\n *\n * const { url } = await api.checkout({ plan, interval, currency });\n * noteCheckoutStarted(user.id, overview);\n * window.location.assign(payPageUrl(url, i18n.language));\n *\n * A wrong `lang` costs nothing but the language: the page falls back to the browser's.\n */\nexport function payPageUrl(checkoutUrl: string, locale: string): string {\n const language = kitLanguageOf(locale);\n if (!language) return checkoutUrl;\n let url: URL;\n try {\n url = new URL(checkoutUrl);\n } catch {\n return checkoutUrl;\n }\n if (!url.searchParams.has(PAY_PAGE_TRANSACTION_PARAM)) return checkoutUrl;\n url.searchParams.set(PAY_PAGE_LANG_PARAM, language);\n return url.toString();\n}\n\n/**\n * The language the page speaks: its `lang` parameter, else the browser's languages in\n * their order, each matched to the kit's; else English. @internal\n */\nexport function payPageLanguage(\n lang: string | null | undefined,\n browserLanguages: readonly string[] = [],\n): PayPageLanguage {\n for (const candidate of [lang, ...browserLanguages]) {\n const found = kitLanguageOf(candidate);\n if (found) return found;\n }\n return \"en\";\n}\n\n/* ── The page's words ──────────────────────────────────────────────────────── */\n\n/**\n * The page's words in one language — built into `pay.js` at the kit's build from the\n * seven catalogues, since the page has no React and no provider. @internal\n */\nexport interface PayPageStrings {\n /** `billing.payOpening`, while the checkout opens. */\n opening: string;\n /** `billing.payNothing`: no `_ptxn`, or no token (billing off). */\n nothing: string;\n /** `billing.payFailed`: Paddle.js didn't load, or the configuration is wrong. */\n failed: string;\n /** `billing.payBack`: the buyer closed the checkout. */\n closed: string;\n /** `common.back`: the link to the app's subscription page. */\n back: string;\n /** `legal.links.terms` and `legal.links.privacy`: the footer's links. */\n terms: string;\n privacy: string;\n /** `legal.navLabel`: the footer's `<nav>`. */\n legal: string;\n}\n\n/** The page's words, read from one of the kit's catalogues. @internal */\nexport function payPageStrings(labels: UiKitLabels): PayPageStrings {\n return {\n opening: labels.billing.payOpening,\n nothing: labels.billing.payNothing,\n failed: labels.billing.payFailed,\n closed: labels.billing.payBack,\n back: labels.common.back,\n terms: labels.legal.links.terms,\n privacy: labels.legal.links.privacy,\n legal: labels.legal.navLabel,\n };\n}\n\n/* ── pay-config.json ───────────────────────────────────────────────────────── */\n\n/** The configuration file beside the page, written at each app's web build. @internal */\nexport const PAY_CONFIG_FILE = \"pay-config.json\";\n\n/**\n * `pay-config.json`: what the page needs that differs per app and deployment. Read by\n * the page from its own origin — never from the query, where Paddle's own links (it\n * puts only `_ptxn` there) would lack it and a return URL would be an open redirect on\n * an approved payment domain. @internal\n */\nexport interface PayPageConfig {\n /** Paddle's client-side token (`test_…` for the sandbox, `live_…` for live); null\n * while billing is off, and the page says `payNothing`. */\n token: string | null;\n environment: PaddleEnvironment | null;\n /** The app's subscription page. Paddle.js's `successUrl` is\n * `checkoutReturnUrl(returnUrl)`; \"Back\" goes here. */\n returnUrl: string;\n appName: string;\n termsUrl: string;\n privacyUrl: string;\n}\n\n/** Paddle's client-side token prefixes, per environment. */\nconst TOKEN_PREFIX: Readonly<Record<PaddleEnvironment, string>> = { sandbox: \"test_\", live: \"live_\" };\n\n/** `https://`, or `http://` on this machine only (`localhost`, `127.0.0.1`, `[::1]`). */\nfunction isPageUrl(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol === \"https:\") return true;\n return url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n}\n\n/**\n * What is wrong with a `pay-config.json`, one line each — `[]` when nothing is. The\n * build script refuses to write a file with any (so a wrong pairing fails the build,\n * not a buyer), and the page says `payFailed` for one it reads:\n *\n * - a token whose prefix disagrees with the environment (`test_` ↔ sandbox, `live_` ↔\n * live), or a token without an environment;\n * - a return, terms or privacy URL that isn't `https://` (`http://` for localhost only);\n * - no app name. @internal\n */\nexport function payPageConfigProblems(config: unknown): string[] {\n if (typeof config !== \"object\" || config === null) return [\"the configuration is not an object\"];\n const c = config as Record<string, unknown>;\n const problems: string[] = [];\n const { token, environment } = c;\n if (environment !== null && environment !== undefined && environment !== \"sandbox\" && environment !== \"live\") {\n problems.push(`environment must be \"sandbox\" or \"live\", not ${JSON.stringify(environment)}`);\n }\n if (token !== null && token !== undefined) {\n if (typeof token !== \"string\" || token === \"\") {\n problems.push(\"token must be Paddle's client-side token, or null while billing is off\");\n } else if (environment !== \"sandbox\" && environment !== \"live\") {\n problems.push(\"a token needs its environment: sandbox or live\");\n } else if (!token.startsWith(TOKEN_PREFIX[environment]) || token.length === TOKEN_PREFIX[environment].length) {\n const other = environment === \"sandbox\" ? \"live\" : \"sandbox\";\n problems.push(\n token.startsWith(TOKEN_PREFIX[other])\n ? `the token is a ${other} token (${TOKEN_PREFIX[other]}…), but the environment is ${environment}`\n : `a ${environment} token starts with ${TOKEN_PREFIX[environment]}`,\n );\n }\n }\n for (const key of [\"returnUrl\", \"termsUrl\", \"privacyUrl\"] as const) {\n if (!isPageUrl(c[key])) problems.push(`${key} must be an https:// URL (http:// for localhost only)`);\n }\n if (typeof c.appName !== \"string\" || c.appName.trim() === \"\") problems.push(\"appName is missing\");\n return problems;\n}\n\n/** Paddle.js's `successUrl` for a configuration: the way back, `?checkout=done`. @internal */\nexport function paySuccessUrl(config: Pick<PayPageConfig, \"returnUrl\">): string {\n return checkoutReturnUrl(config.returnUrl);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/billing/pay-page.ts"],"sourcesContent":["import type { UiKitLabels } from \"../i18n/kit-labels\";\nimport { checkoutReturnUrl } from \"./checkout-return\";\n\n/**\n * The kit's pay page (docs/billing-harmonization.md §14.4, decision 19 as amended): a\n * standalone static page that ui-kit ships as `dist/pay/`, and that each app serves on\n * its own pay host, `pay.<app domain>` — a separate origin, approved in Paddle, where\n * Paddle.js shares the origin with nothing: no sign-in, no app code, no storage.\n *\n * Paddle's create-transaction answers `checkout.url`: the pay page's URL plus\n * `?_ptxn=<txn>`, and Paddle.js on that page opens the transaction by itself. The app\n * sends the buyer there with {@link payPageUrl}, which adds the buyer's language; the\n * page maps it to Paddle's checkout locale ({@link paddleLocale}). Everything else the\n * page needs — the client-side token, sandbox or live, the way back — is the app's\n * `pay-config.json`, written at its web build by the `eifi1-pay-page` script.\n *\n * Pure, and React-free: the page's own script (`pay-page/pay.ts`) and that build script\n * bundle this module. The exports below the barrel's four are theirs.\n */\n\n/** Paddle's two environments: the sandbox's client-side tokens start `test_`, live\n * ones `live_`. */\nexport type PaddleEnvironment = \"sandbox\" | \"live\";\n\n/** The pay page's language parameter: `?lang=<kit locale>`. */\nexport const PAY_PAGE_LANG_PARAM = \"lang\";\n\n/** The kit's seven languages (`KitLanguageCode`), as the page reads them. */\nconst PAY_PAGE_LANGUAGES = [\"de-CH\", \"en\", \"es\", \"fr\", \"it\", \"hu\", \"zh\"] as const;\n\n/** One of the kit's languages, as the page speaks it. */\nexport type PayPageLanguage = (typeof PAY_PAGE_LANGUAGES)[number];\n\n/**\n * The kit's language → Paddle's checkout locale (`Paddle.Checkout.open`,\n * `settings.locale`, read 2026-10-09: ar, zh-Hans, zh-TW, da, nl, en, fr, de, it, ja, ko,\n * no, pl, pt, pt-BR, tr, ru, es, sv). Paddle has no Hungarian: a Hungarian buyer gets\n * English always, not Paddle's guess from the browser. The page's own lines stay in the\n * kit's language, Hungarian included.\n */\nconst PADDLE_LOCALES: Readonly<Record<PayPageLanguage, string>> = {\n en: \"en\",\n \"de-CH\": \"de\",\n fr: \"fr\",\n it: \"it\",\n es: \"es\",\n zh: \"zh-Hans\",\n hu: \"en\",\n};\n\n/** Paddle's transaction parameter on a pay page link. @internal */\nexport const PAY_PAGE_TRANSACTION_PARAM = \"_ptxn\";\n\n/**\n * The kit language one tag names, or undefined — `resolveLanguage`'s rule\n * (src/i18n/languages.ts), restated here because that module loads the seven catalogues\n * and the page must stay a few kilobytes: exact first, ignoring case and reading `_` as\n * `-`; then by language, so every German is `de-CH`, every Chinese `zh` (Simplified),\n * and any regional form its base (`fr-CA` → `fr`).\n */\nfunction kitLanguageOf(tag: unknown): PayPageLanguage | undefined {\n if (typeof tag !== \"string\") return undefined;\n const clean = tag.trim().replace(/_/g, \"-\");\n if (!clean) return undefined;\n const lower = clean.toLowerCase();\n const exact = PAY_PAGE_LANGUAGES.find((code) => code.toLowerCase() === lower);\n if (exact) return exact;\n const language = lower.split(\"-\")[0];\n return PAY_PAGE_LANGUAGES.find((code) => code.split(\"-\")[0].toLowerCase() === language);\n}\n\n/**\n * The kit's locale → Paddle's checkout locale: `de-CH` → `de`, `zh` → `zh-Hans`, `hu` →\n * `en` (Paddle has no Hungarian), the others as they are. Any tag is read as the kit\n * reads it (`de-AT` is the one German, `zh-TW` the one Chinese); one the kit doesn't\n * ship is English, and so is `undefined` (0.33.1) — `useKitLocale()` with no provider\n * locale.\n */\nexport function paddleLocale(kitLocale: string | undefined): string {\n return PADDLE_LOCALES[kitLanguageOf(kitLocale) ?? \"en\"];\n}\n\n/**\n * The checkout's `{url}` with the buyer's language: `lang=<kit locale>` on a pay page\n * link (one that carries `_ptxn`); any other URL — a hosted checkout's, which takes its\n * own `locale` — unchanged, as is a locale the kit doesn't ship.\n *\n * const { url } = await api.checkout({ plan, interval, currency });\n * noteCheckoutStarted(user.id, overview);\n * window.location.assign(payPageUrl(url, locale)); // locale = useKitLocale()\n *\n * `locale` may be `undefined` (0.33.1, keksdose): `useKitLocale()` answers that with no\n * provider locale, the runtime's language. The link then goes without `lang`, and the\n * page speaks the browser's language, else English — what a missing or wrong `lang`\n * already did: it costs nothing but the language.\n */\nexport function payPageUrl(checkoutUrl: string, locale: string | undefined): string {\n const language = kitLanguageOf(locale);\n if (!language) return checkoutUrl;\n let url: URL;\n try {\n url = new URL(checkoutUrl);\n } catch {\n return checkoutUrl;\n }\n if (!url.searchParams.has(PAY_PAGE_TRANSACTION_PARAM)) return checkoutUrl;\n url.searchParams.set(PAY_PAGE_LANG_PARAM, language);\n return url.toString();\n}\n\n/**\n * The language the page speaks: its `lang` parameter, else the browser's languages in\n * their order, each matched to the kit's; else English. @internal\n */\nexport function payPageLanguage(\n lang: string | null | undefined,\n browserLanguages: readonly string[] = [],\n): PayPageLanguage {\n for (const candidate of [lang, ...browserLanguages]) {\n const found = kitLanguageOf(candidate);\n if (found) return found;\n }\n return \"en\";\n}\n\n/* ── The page's words ──────────────────────────────────────────────────────── */\n\n/**\n * The page's words in one language — built into `pay.js` at the kit's build from the\n * seven catalogues, since the page has no React and no provider. @internal\n */\nexport interface PayPageStrings {\n /** `billing.payOpening`, while the checkout opens. */\n opening: string;\n /** `billing.payNothing`: no `_ptxn`, or no token (billing off). */\n nothing: string;\n /** `billing.payFailed`: Paddle.js didn't load, or the configuration is wrong. */\n failed: string;\n /** `billing.payBack`: the buyer closed the checkout. */\n closed: string;\n /** `common.back`: the link to the app's subscription page. */\n back: string;\n /** `legal.links.terms` and `legal.links.privacy`: the footer's links. */\n terms: string;\n privacy: string;\n /** `legal.navLabel`: the footer's `<nav>`. */\n legal: string;\n}\n\n/** The page's words, read from one of the kit's catalogues. @internal */\nexport function payPageStrings(labels: UiKitLabels): PayPageStrings {\n return {\n opening: labels.billing.payOpening,\n nothing: labels.billing.payNothing,\n failed: labels.billing.payFailed,\n closed: labels.billing.payBack,\n back: labels.common.back,\n terms: labels.legal.links.terms,\n privacy: labels.legal.links.privacy,\n legal: labels.legal.navLabel,\n };\n}\n\n/* ── pay-config.json ───────────────────────────────────────────────────────── */\n\n/** The configuration file beside the page, written at each app's web build. @internal */\nexport const PAY_CONFIG_FILE = \"pay-config.json\";\n\n/**\n * `pay-config.json`: what the page needs that differs per app and deployment. Read by\n * the page from its own origin — never from the query, where Paddle's own links (it\n * puts only `_ptxn` there) would lack it and a return URL would be an open redirect on\n * an approved payment domain. @internal\n */\nexport interface PayPageConfig {\n /** Paddle's client-side token (`test_…` for the sandbox, `live_…` for live); null\n * while billing is off, and the page says `payNothing`. */\n token: string | null;\n environment: PaddleEnvironment | null;\n /** The app's subscription page. Paddle.js's `successUrl` is\n * `checkoutReturnUrl(returnUrl)`; \"Back\" goes here. */\n returnUrl: string;\n appName: string;\n termsUrl: string;\n privacyUrl: string;\n}\n\n/** Paddle's client-side token prefixes, per environment. */\nconst TOKEN_PREFIX: Readonly<Record<PaddleEnvironment, string>> = { sandbox: \"test_\", live: \"live_\" };\n\n/** `https://`, or `http://` on this machine only (`localhost`, `127.0.0.1`, `[::1]`). */\nfunction isPageUrl(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n if (url.protocol === \"https:\") return true;\n return url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n}\n\n/**\n * What is wrong with a `pay-config.json`, one line each — `[]` when nothing is. The\n * build script refuses to write a file with any (so a wrong pairing fails the build,\n * not a buyer), and the page says `payFailed` for one it reads:\n *\n * - a token whose prefix disagrees with the environment (`test_` ↔ sandbox, `live_` ↔\n * live), or a token without an environment;\n * - a return, terms or privacy URL that isn't `https://` (`http://` for localhost only);\n * - no app name. @internal\n */\nexport function payPageConfigProblems(config: unknown): string[] {\n if (typeof config !== \"object\" || config === null) return [\"the configuration is not an object\"];\n const c = config as Record<string, unknown>;\n const problems: string[] = [];\n const { token, environment } = c;\n if (environment !== null && environment !== undefined && environment !== \"sandbox\" && environment !== \"live\") {\n problems.push(`environment must be \"sandbox\" or \"live\", not ${JSON.stringify(environment)}`);\n }\n if (token !== null && token !== undefined) {\n if (typeof token !== \"string\" || token === \"\") {\n problems.push(\"token must be Paddle's client-side token, or null while billing is off\");\n } else if (environment !== \"sandbox\" && environment !== \"live\") {\n problems.push(\"a token needs its environment: sandbox or live\");\n } else if (!token.startsWith(TOKEN_PREFIX[environment]) || token.length === TOKEN_PREFIX[environment].length) {\n const other = environment === \"sandbox\" ? \"live\" : \"sandbox\";\n problems.push(\n token.startsWith(TOKEN_PREFIX[other])\n ? `the token is a ${other} token (${TOKEN_PREFIX[other]}…), but the environment is ${environment}`\n : `a ${environment} token starts with ${TOKEN_PREFIX[environment]}`,\n );\n }\n }\n for (const key of [\"returnUrl\", \"termsUrl\", \"privacyUrl\"] as const) {\n if (!isPageUrl(c[key])) problems.push(`${key} must be an https:// URL (http:// for localhost only)`);\n }\n if (typeof c.appName !== \"string\" || c.appName.trim() === \"\") problems.push(\"appName is missing\");\n return problems;\n}\n\n/** Paddle.js's `successUrl` for a configuration: the way back, `?checkout=done`. @internal */\nexport function paySuccessUrl(config: Pick<PayPageConfig, \"returnUrl\">): string {\n return checkoutReturnUrl(config.returnUrl);\n}\n"],"mappings":";AACA,SAAS,yBAAyB;AAwB3B,MAAM,sBAAsB;AAGnC,MAAM,qBAAqB,CAAC,SAAS,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAYvE,MAAM,iBAA4D;AAAA,EAChE,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGO,MAAM,6BAA6B;AAS1C,SAAS,cAAc,KAA2C;AAChE,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,QAAQ,IAAI,KAAK,EAAE,QAAQ,MAAM,GAAG;AAC1C,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,MAAM,YAAY;AAChC,QAAM,QAAQ,mBAAmB,KAAK,CAAC,SAAS,KAAK,YAAY,MAAM,KAAK;AAC5E,MAAI,MAAO,QAAO;AAClB,QAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC;AACnC,SAAO,mBAAmB,KAAK,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,YAAY,MAAM,QAAQ;AACxF;AASO,SAAS,aAAa,WAAuC;AAClE,SAAO,eAAe,cAAc,SAAS,KAAK,IAAI;AACxD;AAgBO,SAAS,WAAW,aAAqB,QAAoC;AAClF,QAAM,WAAW,cAAc,MAAM;AACrC,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,WAAW;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAI,aAAa,IAAI,0BAA0B,EAAG,QAAO;AAC9D,MAAI,aAAa,IAAI,qBAAqB,QAAQ;AAClD,SAAO,IAAI,SAAS;AACtB;AAMO,SAAS,gBACd,MACA,mBAAsC,CAAC,GACtB;AACjB,aAAW,aAAa,CAAC,MAAM,GAAG,gBAAgB,GAAG;AACnD,UAAM,QAAQ,cAAc,SAAS;AACrC,QAAI,MAAO,QAAO;AAAA,EACpB;AACA,SAAO;AACT;AA2BO,SAAS,eAAe,QAAqC;AAClE,SAAO;AAAA,IACL,SAAS,OAAO,QAAQ;AAAA,IACxB,SAAS,OAAO,QAAQ;AAAA,IACxB,QAAQ,OAAO,QAAQ;AAAA,IACvB,QAAQ,OAAO,QAAQ;AAAA,IACvB,MAAM,OAAO,OAAO;AAAA,IACpB,OAAO,OAAO,MAAM,MAAM;AAAA,IAC1B,SAAS,OAAO,MAAM,MAAM;AAAA,IAC5B,OAAO,OAAO,MAAM;AAAA,EACtB;AACF;AAKO,MAAM,kBAAkB;AAsB/B,MAAM,eAA4D,EAAE,SAAS,SAAS,MAAM,QAAQ;AAGpG,SAAS,UAAU,OAAyB;AAC1C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,KAAK;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,IAAI,aAAa,SAAU,QAAO;AACtC,SAAO,IAAI,aAAa,WAAW,CAAC,aAAa,aAAa,OAAO,EAAE,SAAS,IAAI,QAAQ;AAC9F;AAYO,SAAS,sBAAsB,QAA2B;AAC/D,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO,CAAC,oCAAoC;AAC/F,QAAM,IAAI;AACV,QAAM,WAAqB,CAAC;AAC5B,QAAM,EAAE,OAAO,YAAY,IAAI;AAC/B,MAAI,gBAAgB,QAAQ,gBAAgB,UAAa,gBAAgB,aAAa,gBAAgB,QAAQ;AAC5G,aAAS,KAAK,gDAAgD,KAAK,UAAU,WAAW,CAAC,EAAE;AAAA,EAC7F;AACA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,QAAI,OAAO,UAAU,YAAY,UAAU,IAAI;AAC7C,eAAS,KAAK,wEAAwE;AAAA,IACxF,WAAW,gBAAgB,aAAa,gBAAgB,QAAQ;AAC9D,eAAS,KAAK,gDAAgD;AAAA,IAChE,WAAW,CAAC,MAAM,WAAW,aAAa,WAAW,CAAC,KAAK,MAAM,WAAW,aAAa,WAAW,EAAE,QAAQ;AAC5G,YAAM,QAAQ,gBAAgB,YAAY,SAAS;AACnD,eAAS;AAAA,QACP,MAAM,WAAW,aAAa,KAAK,CAAC,IAChC,kBAAkB,KAAK,WAAW,aAAa,KAAK,CAAC,mCAA8B,WAAW,KAC9F,KAAK,WAAW,sBAAsB,aAAa,WAAW,CAAC;AAAA,MACrE;AAAA,IACF;AAAA,EACF;AACA,aAAW,OAAO,CAAC,aAAa,YAAY,YAAY,GAAY;AAClE,QAAI,CAAC,UAAU,EAAE,GAAG,CAAC,EAAG,UAAS,KAAK,GAAG,GAAG,uDAAuD;AAAA,EACrG;AACA,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,QAAQ,KAAK,MAAM,GAAI,UAAS,KAAK,oBAAoB;AAChG,SAAO;AACT;AAGO,SAAS,cAAc,QAAkD;AAC9E,SAAO,kBAAkB,OAAO,SAAS;AAC3C;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DiPX4Vue.js';
3
+ import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DAgGBrbn.js';
4
4
  import { ChoiceCardProps } from '../components/choice-card.js';
5
5
  import { PlanPrices, BillingCurrency, BillingInterval } from './plan-price.js';
6
6
  import '../components/tooltip.js';
@@ -84,6 +84,7 @@ import '../admin/admin-parts.js';
84
84
  import '../admin/admin-action-log.js';
85
85
  import '../settings/settings-labels.js';
86
86
  import '../demo/demo-labels.js';
87
+ import '../i18n/plurals.js';
87
88
  import '../components/appearance-labels.js';
88
89
  import '../theme/contrast.js';
89
90
  import '../theme/text-size.js';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentPropsWithoutRef } from 'react';
3
- import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DiPX4Vue.js';
3
+ import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DAgGBrbn.js';
4
4
  import { AlertTone } from '../components/alert-banner.js';
5
5
  import '../components/tooltip.js';
6
6
  import '../hooks/use-anchored-rect.js';
@@ -83,6 +83,7 @@ import '../admin/admin-parts.js';
83
83
  import '../admin/admin-action-log.js';
84
84
  import '../settings/settings-labels.js';
85
85
  import '../demo/demo-labels.js';
86
+ import '../i18n/plurals.js';
86
87
  import '../components/appearance-labels.js';
87
88
  import '../theme/contrast.js';
88
89
  import '../theme/text-size.js';
@@ -1,4 +1,4 @@
1
- import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DiPX4Vue.js';
1
+ import { e5 as LabelOverride, bH as BillingLabels } from '../delete-account-setting-DAgGBrbn.js';
2
2
  import { ToastId } from '../components/toast.js';
3
3
  import { PlanLimitRefusal } from './plan-limit.js';
4
4
  import 'react';
@@ -82,6 +82,7 @@ import '../admin/admin-parts.js';
82
82
  import '../admin/admin-action-log.js';
83
83
  import '../settings/settings-labels.js';
84
84
  import '../demo/demo-labels.js';
85
+ import '../i18n/plurals.js';
85
86
  import '../components/appearance-labels.js';
86
87
  import '../theme/contrast.js';
87
88
  import '../theme/text-size.js';