@eifi1/ui-kit 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -76
- package/dist/admin/plan-change.js +2 -2
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/user-roster.d.ts +6 -0
- package/dist/admin/user-roster.js +4 -0
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +26 -0
- package/dist/billing/billing-labels.js +9 -0
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/subscription-standing.d.ts +61 -0
- package/dist/billing/subscription-standing.js +51 -0
- package/dist/billing/subscription-standing.js.map +1 -0
- package/dist/billing/usage-meter.d.ts +52 -0
- package/dist/billing/usage-meter.js +52 -0
- package/dist/billing/usage-meter.js.map +1 -0
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +4 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +2 -3
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.js +3 -15
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +2 -2
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.js +2 -1
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button.d.ts +35 -1
- package/dist/components/button.js +5 -9
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -2
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/camera-button.d.ts +89 -0
- package/dist/components/camera-button.js +122 -0
- package/dist/components/camera-button.js.map +1 -0
- package/dist/components/camera-capture.d.ts +66 -0
- package/dist/components/camera-capture.js +190 -0
- package/dist/components/camera-capture.js.map +1 -0
- package/dist/components/camera-labels.d.ts +20 -0
- package/dist/components/camera-labels.js +15 -0
- package/dist/components/camera-labels.js.map +1 -0
- package/dist/components/chart-edge.d.ts +9 -0
- package/dist/components/chart-edge.js +14 -3
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-zoom-core.js +34 -29
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/checkbox-group.js +1 -1
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/combobox-core.js +3 -2
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.js +2 -2
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/consent-again-notice-labels.d.ts +25 -0
- package/dist/components/consent-again-notice-labels.js +15 -0
- package/dist/components/consent-again-notice-labels.js.map +1 -0
- package/dist/components/consent-again-notice.d.ts +85 -0
- package/dist/components/consent-again-notice.js +94 -0
- package/dist/components/consent-again-notice.js.map +1 -0
- package/dist/components/country-select.js +2 -7
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-select.js +2 -2
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.js +2 -2
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-virtual.js +13 -3
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/date-field.js +2 -2
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +2 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +2 -6
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.js +7 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.js +2 -2
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-parts.d.ts +108 -6
- package/dist/components/field-parts.js +59 -6
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-suggestion-labels.d.ts +2 -0
- package/dist/components/field-suggestion-labels.js +10 -0
- package/dist/components/field-suggestion-labels.js.map +1 -0
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/floating-panel.d.ts +7 -6
- package/dist/components/floating-panel.js +3 -17
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +3 -2
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.js +3 -2
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.d.ts +23 -5
- package/dist/components/icon-button.js +6 -8
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.js +2 -1
- package/dist/components/inline-confirm.js.map +1 -1
- package/dist/components/inline-entity-combobox.js +2 -2
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +3 -3
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal.d.ts +13 -1
- package/dist/components/legal.js +12 -0
- package/dist/components/legal.js.map +1 -1
- package/dist/components/legended-series-chart.d.ts +80 -0
- package/dist/components/legended-series-chart.js +70 -0
- package/dist/components/legended-series-chart.js.map +1 -0
- package/dist/components/lightbox.js +2 -1
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/mini-calendar.js +13 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +23 -2
- package/dist/components/modal.js +24 -6
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +1 -1
- package/dist/components/month-picker.js +6 -6
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.js +2 -2
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.js +2 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.js +2 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/page-header-layout.d.ts +18 -0
- package/dist/components/page-header-layout.js +23 -0
- package/dist/components/page-header-layout.js.map +1 -0
- package/dist/components/page-header-skeleton.d.ts +41 -0
- package/dist/components/page-header-skeleton.js +52 -0
- package/dist/components/page-header-skeleton.js.map +1 -0
- package/dist/components/page-header.js +4 -17
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/popover.js +7 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/release-notes-labels.d.ts +16 -0
- package/dist/components/release-notes-labels.js +18 -0
- package/dist/components/release-notes-labels.js.map +1 -0
- package/dist/components/release-notes.d.ts +65 -0
- package/dist/components/release-notes.js +65 -0
- package/dist/components/release-notes.js.map +1 -0
- package/dist/components/select.js +2 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +9 -0
- package/dist/components/series-chart-labels.js +3 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +5 -0
- package/dist/components/series-chart.js +89 -36
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/toggle-group.js +3 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/treemap-layout.js.map +1 -1
- package/dist/components/treemap.d.ts +17 -15
- package/dist/components/treemap.js +167 -85
- package/dist/components/treemap.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +2 -2
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-inbox-page.d.ts +260 -0
- package/dist/feedback/feedback-inbox-page.js +207 -0
- package/dist/feedback/feedback-inbox-page.js.map +1 -0
- package/dist/feedback/feedback-vocabulary.js +2 -2
- package/dist/feedback/feedback-vocabulary.js.map +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.js +6 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-debounce.d.ts +32 -5
- package/dist/hooks/use-debounce.js +6 -5
- package/dist/hooks/use-debounce.js.map +1 -1
- package/dist/hooks/use-file-picker.d.ts +3 -1
- package/dist/hooks/use-file-picker.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +19 -3
- package/dist/hooks/use-focus-trap.js +15 -5
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-has-camera.d.ts +36 -0
- package/dist/hooks/use-has-camera.js +53 -0
- package/dist/hooks/use-has-camera.js.map +1 -0
- package/dist/hooks/use-hotkey.js +1 -10
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-overlay-history.d.ts +6 -1
- package/dist/hooks/use-overlay-history.js +10 -2
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +11 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +75 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +20 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +92 -4
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +71 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +72 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +74 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +74 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +71 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +22 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.js +35 -1
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-labels.d.ts +8 -0
- package/dist/landing/landing-labels.js +2 -1
- package/dist/landing/landing-labels.js.map +1 -1
- package/dist/lib/changelog.d.ts +130 -0
- package/dist/lib/changelog.js +137 -0
- package/dist/lib/changelog.js.map +1 -0
- package/dist/lib/clipboard-minus.js +2 -2
- package/dist/lib/clipboard-minus.js.map +1 -1
- package/dist/lib/dates.d.ts +29 -3
- package/dist/lib/dates.js +41 -12
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/editable-content.d.ts +33 -0
- package/dist/lib/editable-content.js +15 -0
- package/dist/lib/editable-content.js.map +1 -0
- package/dist/lib/focus-left.d.ts +47 -1
- package/dist/lib/focus-left.js +11 -2
- package/dist/lib/focus-left.js.map +1 -1
- package/dist/lib/server-wake.d.ts +49 -9
- package/dist/lib/server-wake.js +25 -14
- package/dist/lib/server-wake.js.map +1 -1
- package/dist/lib/session-refresh.d.ts +53 -0
- package/dist/lib/session-refresh.js +27 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/rhf/fields.js +3 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/shell/app-shell.d.ts +25 -1
- package/dist/shell/app-shell.js +3 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +3 -0
- package/dist/shell/dev-menu.js +4 -2
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/held-changes-banner-labels.d.ts +23 -0
- package/dist/shell/held-changes-banner-labels.js +14 -0
- package/dist/shell/held-changes-banner-labels.js.map +1 -0
- package/dist/shell/held-changes-banner.d.ts +75 -0
- package/dist/shell/held-changes-banner.js +106 -0
- package/dist/shell/held-changes-banner.js.map +1 -0
- package/dist/shell/sync-status-indicator.d.ts +27 -12
- package/dist/shell/sync-status-indicator.js +13 -4
- package/dist/shell/sync-status-indicator.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +5 -0
- package/dist/shell/top-bar-parts-labels.js +3 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +3 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +11 -2
- package/dist/sync/engine.js +13 -5
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/hooks.d.ts +3 -2
- package/dist/sync/hooks.js +4 -2
- package/dist/sync/hooks.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +2 -2
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +18 -0
- package/dist/theme/chart-palette.js +17 -1
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +3 -3
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -4
- package/dist/theme/contrast.js.map +1 -1
- package/dist/tour/tour-overlay.js +2 -1
- package/dist/tour/tour-overlay.js.map +1 -1
- package/package.json +5 -9
- package/tokens.css +19 -54
- package/src/account/account-parts.ts +0 -47
- package/src/account/account-settings-labels.ts +0 -240
- package/src/account/account-settings.tsx +0 -698
- package/src/account/data-export-setting-labels.ts +0 -20
- package/src/account/data-export-setting.tsx +0 -200
- package/src/account/delete-account-setting-labels.ts +0 -33
- package/src/account/delete-account-setting.tsx +0 -342
- package/src/account/email-change-setting-labels.ts +0 -31
- package/src/account/email-change-setting.tsx +0 -443
- package/src/account/passkeys-setting.tsx +0 -445
- package/src/account/sessions-setting-labels.ts +0 -24
- package/src/account/sessions-setting.tsx +0 -283
- package/src/admin/admin-action-confirm-labels.ts +0 -19
- package/src/admin/admin-action-confirm.tsx +0 -509
- package/src/admin/admin-action-log-labels.ts +0 -83
- package/src/admin/admin-action-log.tsx +0 -464
- package/src/admin/admin-area-labels.ts +0 -114
- package/src/admin/admin-forbidden.tsx +0 -75
- package/src/admin/admin-overview.tsx +0 -258
- package/src/admin/admin-parts.ts +0 -68
- package/src/admin/admin-sections.tsx +0 -208
- package/src/admin/admin-tool-links.tsx +0 -90
- package/src/admin/invitations-panel-labels.ts +0 -31
- package/src/admin/invitations-panel.tsx +0 -516
- package/src/admin/mail-test-card-labels.ts +0 -19
- package/src/admin/mail-test-card.tsx +0 -189
- package/src/admin/plan-change-labels.ts +0 -5
- package/src/admin/plan-change.tsx +0 -474
- package/src/admin/reviewer-scope-editor-labels.ts +0 -16
- package/src/admin/reviewer-scope-editor.tsx +0 -224
- package/src/admin/role-select-labels.ts +0 -16
- package/src/admin/role-select.tsx +0 -282
- package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
- package/src/admin/transfer-ownership-dialog.tsx +0 -162
- package/src/admin/user-roster-labels.ts +0 -17
- package/src/admin/user-roster.tsx +0 -559
- package/src/auth/accept-invitation-labels.ts +0 -21
- package/src/auth/accept-invitation.tsx +0 -210
- package/src/auth/auth-errors-labels.ts +0 -23
- package/src/auth/auth-errors.ts +0 -439
- package/src/auth/complete-name-dialog-labels.ts +0 -16
- package/src/auth/complete-name-dialog.tsx +0 -253
- package/src/auth/email-tag.ts +0 -75
- package/src/auth/forgot-password-form-labels.ts +0 -20
- package/src/auth/forgot-password-form.tsx +0 -221
- package/src/auth/form-rules.ts +0 -40
- package/src/auth/new-password-fields.tsx +0 -85
- package/src/auth/not-found-page-labels.ts +0 -12
- package/src/auth/not-found-page.tsx +0 -95
- package/src/auth/register-form-labels.ts +0 -31
- package/src/auth/register-form.tsx +0 -456
- package/src/auth/reset-password-form-labels.ts +0 -27
- package/src/auth/reset-password-form.tsx +0 -341
- package/src/auth/sign-in-form-labels.ts +0 -44
- package/src/auth/sign-in-form.tsx +0 -858
- package/src/auth/status-parts.tsx +0 -132
- package/src/auth/verify-email-labels.ts +0 -26
- package/src/auth/verify-email.tsx +0 -445
- package/src/billing/billing-banner.tsx +0 -258
- package/src/billing/billing-labels.ts +0 -381
- package/src/billing/billing-lock.ts +0 -108
- package/src/billing/billing-overview.ts +0 -157
- package/src/billing/billing-standing.ts +0 -80
- package/src/billing/cancel-request.ts +0 -97
- package/src/billing/cancel-subscription.tsx +0 -98
- package/src/billing/checkout-processing.ts +0 -394
- package/src/billing/checkout-return.ts +0 -90
- package/src/billing/pay-page.ts +0 -330
- package/src/billing/plan-card.tsx +0 -562
- package/src/billing/plan-change-labels.ts +0 -66
- package/src/billing/plan-limit-notice.tsx +0 -148
- package/src/billing/plan-limit-toast.tsx +0 -90
- package/src/billing/plan-limit.ts +0 -52
- package/src/billing/plan-price.ts +0 -98
- package/src/billing/plan-switch.tsx +0 -216
- package/src/billing/provider-page.ts +0 -84
- package/src/billing/subscription-actions.tsx +0 -247
- package/src/billing/subscription-status-chip.tsx +0 -95
- package/src/chart.ts +0 -103
- package/src/components/account-chips-labels.ts +0 -16
- package/src/components/account-chips.tsx +0 -474
- package/src/components/alert-banner.tsx +0 -670
- package/src/components/amount-input.tsx +0 -816
- package/src/components/appearance-labels.ts +0 -63
- package/src/components/authed-image-labels.ts +0 -11
- package/src/components/authed-image.tsx +0 -209
- package/src/components/autocomplete.tsx +0 -645
- package/src/components/breadcrumbs-labels.ts +0 -10
- package/src/components/breadcrumbs.tsx +0 -205
- package/src/components/bulk-action-bar-labels.ts +0 -11
- package/src/components/bulk-action-bar.tsx +0 -443
- package/src/components/button-group.tsx +0 -223
- package/src/components/button-look.ts +0 -256
- package/src/components/button.tsx +0 -339
- package/src/components/calculator.tsx +0 -341
- package/src/components/calendar-heatmap-labels.ts +0 -15
- package/src/components/calendar-heatmap.tsx +0 -600
- package/src/components/card.tsx +0 -377
- package/src/components/chart-edge.ts +0 -106
- package/src/components/chart-figure.tsx +0 -35
- package/src/components/chart-height.ts +0 -30
- package/src/components/chart-zoom-core.tsx +0 -627
- package/src/components/chart-zoom.tsx +0 -22
- package/src/components/chart.tsx +0 -554
- package/src/components/checkbox-group.tsx +0 -247
- package/src/components/checkbox.tsx +0 -346
- package/src/components/chip-input.tsx +0 -370
- package/src/components/chip-labels.ts +0 -13
- package/src/components/chip.tsx +0 -843
- package/src/components/choice-card.tsx +0 -544
- package/src/components/column-mapper-labels.ts +0 -38
- package/src/components/column-mapper.tsx +0 -655
- package/src/components/combobox-core.tsx +0 -1321
- package/src/components/combobox-suggestions.tsx +0 -131
- package/src/components/combobox.tsx +0 -716
- package/src/components/confirm-context.ts +0 -7
- package/src/components/confirm-dialog-labels.ts +0 -13
- package/src/components/confirm-dialog.tsx +0 -341
- package/src/components/copy-button-labels.ts +0 -13
- package/src/components/copy-button.tsx +0 -192
- package/src/components/country-select-labels.ts +0 -11
- package/src/components/country-select.tsx +0 -539
- package/src/components/currency-option-list.tsx +0 -188
- package/src/components/currency-select.tsx +0 -343
- package/src/components/danger-confirm-labels.ts +0 -20
- package/src/components/danger-confirm.tsx +0 -987
- package/src/components/data-table-cells.tsx +0 -118
- package/src/components/data-table-column-settings.tsx +0 -198
- package/src/components/data-table-filter-popover.tsx +0 -289
- package/src/components/data-table-filters.ts +0 -315
- package/src/components/data-table-fit.ts +0 -301
- package/src/components/data-table-groups.tsx +0 -288
- package/src/components/data-table-head.tsx +0 -280
- package/src/components/data-table-labels.ts +0 -198
- package/src/components/data-table-leading-row.tsx +0 -58
- package/src/components/data-table-mobile.tsx +0 -1117
- package/src/components/data-table-pagination.tsx +0 -202
- package/src/components/data-table-resize.ts +0 -188
- package/src/components/data-table-row-actions.tsx +0 -302
- package/src/components/data-table-row.tsx +0 -496
- package/src/components/data-table-selection.ts +0 -71
- package/src/components/data-table-shared.tsx +0 -287
- package/src/components/data-table-sort.ts +0 -163
- package/src/components/data-table-virtual.ts +0 -576
- package/src/components/data-table.tsx +0 -2598
- package/src/components/date-field.tsx +0 -684
- package/src/components/date-format-setting.tsx +0 -64
- package/src/components/date-picker.tsx +0 -404
- package/src/components/date-range-picker.tsx +0 -518
- package/src/components/description-list-labels.ts +0 -9
- package/src/components/description-list.tsx +0 -386
- package/src/components/dialog-frame-labels.ts +0 -7
- package/src/components/dialog-frame.tsx +0 -284
- package/src/components/disclosure.tsx +0 -476
- package/src/components/dropdown.tsx +0 -461
- package/src/components/empty-state.tsx +0 -136
- package/src/components/entity-combobox.tsx +0 -422
- package/src/components/error-boundary-labels.ts +0 -20
- package/src/components/error-boundary.tsx +0 -675
- package/src/components/facing-pair.ts +0 -93
- package/src/components/field-base-labels.ts +0 -20
- package/src/components/field-base.tsx +0 -656
- package/src/components/field-parts.tsx +0 -825
- package/src/components/field-strip.tsx +0 -129
- package/src/components/field.tsx +0 -206
- package/src/components/file-button.tsx +0 -295
- package/src/components/file-dropzone.tsx +0 -559
- package/src/components/floating-panel-labels.ts +0 -10
- package/src/components/floating-panel.tsx +0 -828
- package/src/components/focus-ring.ts +0 -16
- package/src/components/form-actions-labels.ts +0 -11
- package/src/components/form-actions.tsx +0 -578
- package/src/components/full-bleed-dialog.tsx +0 -294
- package/src/components/grouped-picker.tsx +0 -184
- package/src/components/help-popover.tsx +0 -117
- package/src/components/hover-menu.tsx +0 -343
- package/src/components/iban-input-labels.ts +0 -14
- package/src/components/iban-input.tsx +0 -236
- package/src/components/icon-button.tsx +0 -959
- package/src/components/icon-picker-labels.ts +0 -13
- package/src/components/icon-picker.tsx +0 -232
- package/src/components/image-grid-labels.ts +0 -11
- package/src/components/image-grid.tsx +0 -202
- package/src/components/inline-confirm.tsx +0 -116
- package/src/components/inline-edit-field-labels.ts +0 -11
- package/src/components/inline-edit-field.tsx +0 -552
- package/src/components/inline-entity-combobox.tsx +0 -849
- package/src/components/input.tsx +0 -394
- package/src/components/jump-calendar.tsx +0 -265
- package/src/components/language-select.tsx +0 -93
- package/src/components/legal-labels.ts +0 -105
- package/src/components/legal-page.tsx +0 -711
- package/src/components/legal.tsx +0 -668
- package/src/components/lightbox-labels.ts +0 -18
- package/src/components/lightbox.tsx +0 -399
- package/src/components/line-items-labels.ts +0 -14
- package/src/components/line-items.tsx +0 -602
- package/src/components/list-labels.ts +0 -12
- package/src/components/list.tsx +0 -870
- package/src/components/loading-state.tsx +0 -78
- package/src/components/measured-grid-labels.ts +0 -25
- package/src/components/measured-grid.tsx +0 -837
- package/src/components/menu-item.tsx +0 -241
- package/src/components/mini-calendar-labels.ts +0 -17
- package/src/components/mini-calendar.tsx +0 -792
- package/src/components/modal.tsx +0 -534
- package/src/components/money-draft.ts +0 -83
- package/src/components/money-field.tsx +0 -76
- package/src/components/month-grid.tsx +0 -229
- package/src/components/month-picker-labels.ts +0 -22
- package/src/components/month-picker.tsx +0 -1087
- package/src/components/multi-entity-combobox.tsx +0 -369
- package/src/components/multi-select.tsx +0 -376
- package/src/components/nav-pills.tsx +0 -198
- package/src/components/number-field.tsx +0 -327
- package/src/components/number-input.tsx +0 -559
- package/src/components/numeric-field.ts +0 -169
- package/src/components/numpad-sheet.tsx +0 -285
- package/src/components/one-time-code-input.tsx +0 -270
- package/src/components/page-contents-labels.ts +0 -9
- package/src/components/page-contents.tsx +0 -282
- package/src/components/page-header-context.ts +0 -8
- package/src/components/page-header.tsx +0 -298
- package/src/components/password-strength-labels.ts +0 -22
- package/src/components/password-strength.tsx +0 -241
- package/src/components/phone-input-labels.ts +0 -10
- package/src/components/phone-input.tsx +0 -282
- package/src/components/picker-sheet.tsx +0 -330
- package/src/components/pie-chart-labels.ts +0 -40
- package/src/components/pie-chart.tsx +0 -480
- package/src/components/popover-labels.ts +0 -14
- package/src/components/popover.tsx +0 -268
- package/src/components/progress-bar-labels.ts +0 -10
- package/src/components/progress-bar.tsx +0 -479
- package/src/components/qr-code.tsx +0 -76
- package/src/components/reauth-dialog-labels.ts +0 -13
- package/src/components/reauth-dialog.tsx +0 -265
- package/src/components/row-actions-labels.ts +0 -12
- package/src/components/row-actions.tsx +0 -795
- package/src/components/scroll-area.tsx +0 -123
- package/src/components/search-field.tsx +0 -192
- package/src/components/select.tsx +0 -294
- package/src/components/separator.tsx +0 -33
- package/src/components/series-chart-axes.ts +0 -342
- package/src/components/series-chart-budget.ts +0 -150
- package/src/components/series-chart-labels.ts +0 -33
- package/src/components/series-chart-thin.ts +0 -180
- package/src/components/series-chart-ticks.ts +0 -194
- package/src/components/series-chart.tsx +0 -1906
- package/src/components/server-wake-labels.ts +0 -11
- package/src/components/server-wake.tsx +0 -129
- package/src/components/settings-fields.tsx +0 -237
- package/src/components/share-card-labels.ts +0 -30
- package/src/components/share-card.tsx +0 -675
- package/src/components/signature-pad-labels.ts +0 -24
- package/src/components/signature-pad.tsx +0 -747
- package/src/components/signed-amount-labels.ts +0 -10
- package/src/components/signed-amount.tsx +0 -336
- package/src/components/skeleton.tsx +0 -80
- package/src/components/slider.tsx +0 -338
- package/src/components/sparkline-labels.ts +0 -14
- package/src/components/sparkline.tsx +0 -322
- package/src/components/spinner.tsx +0 -81
- package/src/components/stat-tile-labels.ts +0 -15
- package/src/components/stat-tile.tsx +0 -754
- package/src/components/status-dot.tsx +0 -170
- package/src/components/swatch-picker-labels.ts +0 -10
- package/src/components/swatch-picker.tsx +0 -283
- package/src/components/swipeable-row.tsx +0 -386
- package/src/components/switch.tsx +0 -273
- package/src/components/table-skeleton.tsx +0 -136
- package/src/components/table.tsx +0 -647
- package/src/components/tabs-labels.ts +0 -13
- package/src/components/tabs.tsx +0 -693
- package/src/components/text-link.tsx +0 -359
- package/src/components/text.tsx +0 -150
- package/src/components/textarea.tsx +0 -330
- package/src/components/tile-radio.tsx +0 -332
- package/src/components/time-input.tsx +0 -121
- package/src/components/toast-labels.ts +0 -12
- package/src/components/toast.tsx +0 -519
- package/src/components/toggle-group.tsx +0 -752
- package/src/components/toggle-legend.tsx +0 -394
- package/src/components/tooltip.tsx +0 -1149
- package/src/components/translation-review-editor.tsx +0 -290
- package/src/components/translation-review-labels.ts +0 -181
- package/src/components/translation-review-parts.tsx +0 -272
- package/src/components/translation-review.tsx +0 -1240
- package/src/components/tree-view.tsx +0 -589
- package/src/components/treemap-labels.ts +0 -47
- package/src/components/treemap-layout.ts +0 -210
- package/src/components/treemap.tsx +0 -1184
- package/src/components/trigger-aria.ts +0 -44
- package/src/components/use-anchor-dir.ts +0 -20
- package/src/components/use-mobile-reveal.ts +0 -62
- package/src/components/use-table-state.ts +0 -753
- package/src/components/user-avatar.tsx +0 -116
- package/src/components/wizard-stepper.tsx +0 -42
- package/src/components/write-lock-labels.ts +0 -10
- package/src/components/write-lock.tsx +0 -253
- package/src/currencies.ts +0 -90
- package/src/data-table.ts +0 -42
- package/src/demo/demo-banner.tsx +0 -153
- package/src/demo/demo-countdown.ts +0 -136
- package/src/demo/demo-ended.tsx +0 -90
- package/src/demo/demo-labels.ts +0 -78
- package/src/demo/demo-session.ts +0 -32
- package/src/demo/demo-start.tsx +0 -156
- package/src/eslint/index.ts +0 -161
- package/src/eslint/role-utilities.generated.ts +0 -146
- package/src/eslint/role-utilities.ts +0 -233
- package/src/feedback/feedback-attachment-download.tsx +0 -77
- package/src/feedback/feedback-attachment-labels.ts +0 -37
- package/src/feedback/feedback-attachment.tsx +0 -1239
- package/src/feedback/feedback-capture.ts +0 -287
- package/src/feedback/feedback-comment-thread.tsx +0 -353
- package/src/feedback/feedback-context-labels.ts +0 -11
- package/src/feedback/feedback-context.tsx +0 -245
- package/src/feedback/feedback-crash.ts +0 -547
- package/src/feedback/feedback-diagnostics.tsx +0 -41
- package/src/feedback/feedback-dialog-labels.ts +0 -29
- package/src/feedback/feedback-dialog.tsx +0 -452
- package/src/feedback/feedback-entry-file.tsx +0 -150
- package/src/feedback/feedback-inbox.tsx +0 -552
- package/src/feedback/feedback-labels.ts +0 -144
- package/src/feedback/feedback-menu-labels.ts +0 -12
- package/src/feedback/feedback-menu.tsx +0 -177
- package/src/feedback/feedback-mutation.ts +0 -20
- package/src/feedback/feedback-page-link.ts +0 -90
- package/src/feedback/feedback-public-labels.ts +0 -81
- package/src/feedback/feedback-public-list.tsx +0 -524
- package/src/feedback/feedback-public-switch.tsx +0 -102
- package/src/feedback/feedback-public-types.ts +0 -101
- package/src/feedback/feedback-record.ts +0 -428
- package/src/feedback/feedback-replies-context.ts +0 -9
- package/src/feedback/feedback-replies.tsx +0 -343
- package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
- package/src/feedback/feedback-row-detail-labels.ts +0 -31
- package/src/feedback/feedback-row-detail.tsx +0 -871
- package/src/feedback/feedback-status-undo.ts +0 -109
- package/src/feedback/feedback-submit-dialog.tsx +0 -131
- package/src/feedback/feedback-submit.tsx +0 -270
- package/src/feedback/feedback-swipe.tsx +0 -192
- package/src/feedback/feedback-table-labels.ts +0 -27
- package/src/feedback/feedback-table.tsx +0 -912
- package/src/feedback/feedback-thread-labels.ts +0 -22
- package/src/feedback/feedback-thread-parts-labels.ts +0 -41
- package/src/feedback/feedback-thread.tsx +0 -905
- package/src/feedback/feedback-vocabulary.ts +0 -263
- package/src/feedback/feedback-vote-button.tsx +0 -124
- package/src/feedback.ts +0 -116
- package/src/hooks/use-anchored-panel.ts +0 -396
- package/src/hooks/use-anchored-rect.ts +0 -77
- package/src/hooks/use-announce.ts +0 -85
- package/src/hooks/use-authed-src.ts +0 -167
- package/src/hooks/use-body-scroll-lock.ts +0 -119
- package/src/hooks/use-breakpoint.ts +0 -133
- package/src/hooks/use-close-transition.ts +0 -107
- package/src/hooks/use-copy-to-clipboard.ts +0 -122
- package/src/hooks/use-debounce.ts +0 -114
- package/src/hooks/use-dismiss.ts +0 -371
- package/src/hooks/use-document-title.ts +0 -33
- package/src/hooks/use-file-drop.ts +0 -121
- package/src/hooks/use-file-picker-labels.ts +0 -28
- package/src/hooks/use-file-picker.tsx +0 -359
- package/src/hooks/use-focus-trap.ts +0 -199
- package/src/hooks/use-hotkey.ts +0 -212
- package/src/hooks/use-large-text.ts +0 -41
- package/src/hooks/use-long-press.ts +0 -140
- package/src/hooks/use-media-query.ts +0 -39
- package/src/hooks/use-mounted.ts +0 -39
- package/src/hooks/use-noindex.ts +0 -37
- package/src/hooks/use-overlay-history.ts +0 -714
- package/src/hooks/use-row-swipe.ts +0 -207
- package/src/hooks/use-search-param-state.ts +0 -434
- package/src/hooks/use-windowed-rows.ts +0 -225
- package/src/i18n/defaults.ts +0 -267
- package/src/i18n/german.ts +0 -2059
- package/src/i18n/kit-labels.tsx +0 -1003
- package/src/i18n/languages.ts +0 -349
- package/src/i18n/locales/de-CH.ts +0 -24
- package/src/i18n/locales/en.ts +0 -267
- package/src/i18n/locales/es.ts +0 -1999
- package/src/i18n/locales/fr.ts +0 -2037
- package/src/i18n/locales/hu.ts +0 -2004
- package/src/i18n/locales/it.ts +0 -2014
- package/src/i18n/locales/zh.ts +0 -1912
- package/src/i18n/plurals.ts +0 -86
- package/src/i18n/review.ts +0 -475
- package/src/i18n/shared-labels.ts +0 -138
- package/src/i18n/swiss.ts +0 -29
- package/src/index.ts +0 -1191
- package/src/landing/access.ts +0 -93
- package/src/landing/landing-actions.tsx +0 -97
- package/src/landing/landing-labels.ts +0 -59
- package/src/landing/landing-sections.tsx +0 -341
- package/src/landing/page-seo.ts +0 -261
- package/src/landing/public-header.tsx +0 -121
- package/src/landing/public-page.tsx +0 -106
- package/src/landing/routing.tsx +0 -225
- package/src/lib/broadcast.ts +0 -52
- package/src/lib/calc.ts +0 -323
- package/src/lib/chip-edge-snap.ts +0 -212
- package/src/lib/clipboard-minus.ts +0 -107
- package/src/lib/clipping.ts +0 -34
- package/src/lib/cn.ts +0 -28
- package/src/lib/column-mapping.ts +0 -234
- package/src/lib/countries.ts +0 -91
- package/src/lib/data-attributes.ts +0 -14
- package/src/lib/date-format.ts +0 -227
- package/src/lib/dates.ts +0 -288
- package/src/lib/day-format.ts +0 -96
- package/src/lib/decimal-marks.ts +0 -108
- package/src/lib/describe-failure.ts +0 -18
- package/src/lib/device-settings.ts +0 -147
- package/src/lib/direction.ts +0 -74
- package/src/lib/document-navigation.ts +0 -17
- package/src/lib/document-title.ts +0 -41
- package/src/lib/download.ts +0 -172
- package/src/lib/float-clearance.ts +0 -70
- package/src/lib/floating-stack.ts +0 -70
- package/src/lib/focus-left.ts +0 -156
- package/src/lib/format.ts +0 -585
- package/src/lib/http-status.ts +0 -38
- package/src/lib/iban.ts +0 -251
- package/src/lib/ime.ts +0 -24
- package/src/lib/intl-cache.ts +0 -27
- package/src/lib/iso-day.ts +0 -13
- package/src/lib/lazy-with-fallback.ts +0 -82
- package/src/lib/logger.ts +0 -69
- package/src/lib/minus.ts +0 -50
- package/src/lib/path-pattern.ts +0 -55
- package/src/lib/person-name.ts +0 -155
- package/src/lib/phone.ts +0 -208
- package/src/lib/plain-text.ts +0 -22
- package/src/lib/presence.ts +0 -18
- package/src/lib/qr-encode.ts +0 -444
- package/src/lib/quiet-focus.ts +0 -32
- package/src/lib/reduced-motion.ts +0 -22
- package/src/lib/refs.ts +0 -22
- package/src/lib/round.ts +0 -43
- package/src/lib/safe-storage.ts +0 -119
- package/src/lib/server-wake.ts +0 -315
- package/src/lib/session-refresh.ts +0 -472
- package/src/lib/share-format.ts +0 -18
- package/src/lib/status-tone.ts +0 -43
- package/src/lib/strip-fade.ts +0 -287
- package/src/lib/table-text.ts +0 -554
- package/src/lib/text-case.ts +0 -19
- package/src/lib/text-match.ts +0 -169
- package/src/lib/thenable.ts +0 -19
- package/src/lib/translation-review.ts +0 -769
- package/src/notifications/device-zone-report.ts +0 -67
- package/src/notifications/feedback-notification-types-labels.ts +0 -33
- package/src/notifications/feedback-notification-types.ts +0 -71
- package/src/notifications/notification-bell.tsx +0 -119
- package/src/notifications/notification-inbox.tsx +0 -309
- package/src/notifications/notification-parts.tsx +0 -81
- package/src/notifications/notification-types-setting.tsx +0 -406
- package/src/notifications/notification-types.ts +0 -89
- package/src/notifications/notifications-labels.ts +0 -287
- package/src/notifications/push-device-setting.tsx +0 -230
- package/src/notifications/push-devices-setting.tsx +0 -374
- package/src/notifications/push-worker.ts +0 -168
- package/src/notifications/quiet-hours-setting.tsx +0 -238
- package/src/notifications/unsubscribe-panel.tsx +0 -200
- package/src/notifications/use-push-device.ts +0 -448
- package/src/offline/local-first.ts +0 -100
- package/src/offline/pwa-update-prompt-labels.ts +0 -9
- package/src/offline/pwa-update-prompt.tsx +0 -78
- package/src/offline/query-persistence.ts +0 -391
- package/src/offline/service-worker-updates.ts +0 -81
- package/src/offline/shared-computer-labels.ts +0 -8
- package/src/offline/shared-computer.tsx +0 -87
- package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
- package/src/offline/sign-out-pending-dialog.tsx +0 -135
- package/src/offline.ts +0 -37
- package/src/push-worker.ts +0 -25
- package/src/rhf/fields.tsx +0 -2187
- package/src/rhf/form-core.tsx +0 -324
- package/src/rhf/form.tsx +0 -12
- package/src/rhf/line-items.tsx +0 -135
- package/src/rhf/use-rhf-wizard-step.ts +0 -104
- package/src/rhf.ts +0 -21
- package/src/search/command-palette-labels.ts +0 -16
- package/src/search/command-palette.tsx +0 -624
- package/src/search/global-search-labels.ts +0 -13
- package/src/search/global-search.tsx +0 -660
- package/src/search/search-index.ts +0 -283
- package/src/search/use-command-key.ts +0 -23
- package/src/search.ts +0 -14
- package/src/settings/device-reset-setting-labels.ts +0 -20
- package/src/settings/device-reset-setting.tsx +0 -116
- package/src/settings/settings-card-classes.ts +0 -11
- package/src/settings/settings-catalogue.ts +0 -150
- package/src/settings/settings-context.ts +0 -33
- package/src/settings/settings-heading.tsx +0 -67
- package/src/settings/settings-labels.ts +0 -87
- package/src/settings/settings-layout.tsx +0 -471
- package/src/settings/settings-section.tsx +0 -108
- package/src/settings/use-account-appearance.ts +0 -148
- package/src/settings/use-account-language.ts +0 -146
- package/src/settings/use-settings-focus.ts +0 -128
- package/src/settings/use-settings-route.ts +0 -258
- package/src/shell/account-menu-language.tsx +0 -130
- package/src/shell/account-menu-text-size.tsx +0 -80
- package/src/shell/admin-menu.tsx +0 -183
- package/src/shell/app-shell-labels.ts +0 -11
- package/src/shell/app-shell.tsx +0 -1560
- package/src/shell/auth-layout.tsx +0 -186
- package/src/shell/company-switcher-labels.ts +0 -13
- package/src/shell/company-switcher.tsx +0 -136
- package/src/shell/dev-menu.tsx +0 -257
- package/src/shell/fill-height.tsx +0 -45
- package/src/shell/language-code.ts +0 -5
- package/src/shell/option-switcher-menu.tsx +0 -84
- package/src/shell/sync-status-indicator.tsx +0 -303
- package/src/shell/top-bar-actions.tsx +0 -87
- package/src/shell/top-bar-brand.tsx +0 -78
- package/src/shell/top-bar-parts-labels.ts +0 -148
- package/src/shell/top-bar.tsx +0 -40
- package/src/shell/topbar-action-menu.tsx +0 -567
- package/src/shell/topbar-controls.tsx +0 -269
- package/src/shell/tour-menu.tsx +0 -187
- package/src/shell.ts +0 -108
- package/src/sync/db.ts +0 -129
- package/src/sync/engine.ts +0 -1011
- package/src/sync/hooks.ts +0 -98
- package/src/sync/testing.ts +0 -73
- package/src/sync/wire.ts +0 -61
- package/src/sync.ts +0 -37
- package/src/table-text.ts +0 -8
- package/src/testing/css-variables.generated.ts +0 -112
- package/src/testing/css-variables.ts +0 -189
- package/src/testing/index.ts +0 -29
- package/src/testing/react.ts +0 -58
- package/src/testing/tour.ts +0 -41
- package/src/testing/viewport.ts +0 -357
- package/src/theme/chart-palette.ts +0 -232
- package/src/theme/color.ts +0 -272
- package/src/theme/contrast-tokens.ts +0 -268
- package/src/theme/contrast.ts +0 -250
- package/src/theme/palette-derive.ts +0 -825
- package/src/theme/palette-presets.ts +0 -544
- package/src/theme/palette-store.ts +0 -108
- package/src/theme/text-size.ts +0 -289
- package/src/theme/theme-store.ts +0 -157
- package/src/tour/tour-catalogue-labels.ts +0 -75
- package/src/tour/tour-catalogue.tsx +0 -335
- package/src/tour/tour-dock.ts +0 -134
- package/src/tour/tour-engine.ts +0 -37
- package/src/tour/tour-gates.ts +0 -218
- package/src/tour/tour-guide-button.tsx +0 -64
- package/src/tour/tour-guide-labels.ts +0 -15
- package/src/tour/tour-info.ts +0 -144
- package/src/tour/tour-labels.ts +0 -23
- package/src/tour/tour-overlay.tsx +0 -1170
- package/src/tour/tour.tsx +0 -750
- package/src/tour/use-tour-progress.ts +0 -671
- package/src/tour/use-tour-url-sync.ts +0 -266
- package/src/tour.ts +0 -21
- package/src/vite-env.d.ts +0 -23
- package/src/wizard/stepper-nav.tsx +0 -360
- package/src/wizard/types.ts +0 -275
- package/src/wizard/use-wizard.ts +0 -560
- package/src/wizard/validation.ts +0 -39
- package/src/wizard/wizard-context.tsx +0 -74
- package/src/wizard/wizard-labels.ts +0 -29
- package/src/wizard/wizard-step.tsx +0 -76
- package/src/wizard/wizard-summary.tsx +0 -84
- package/src/wizard.ts +0 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/admin/user-roster.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { DateInput } from \"../lib/format\";\nimport type { PersonName } from \"../lib/person-name\";\nimport { useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { AccountStateChip, RoleChip, dateColumn } from \"../components/account-chips\";\nimport type { AccountState, RoleVocabulary } from \"../components/account-chips\";\nimport type { ChipTone } from \"../components/chip\";\nimport type { DataTableColumn } from \"../components/data-table\";\nimport { SelectWidthFloor } from \"../components/field-base\";\nimport { encodeSorts } from \"../components/data-table-sort\";\nimport type { SortState } from \"../components/data-table-sort\";\nimport { RowActions } from \"../components/row-actions\";\nimport type { RowAction, RowActionList } from \"../components/row-actions\";\nimport { UserAvatar } from \"../components/user-avatar\";\nimport { personLabel } from \"./admin-parts\";\nimport { RoleSelect } from \"./role-select\";\nimport type { RoleLock } from \"./role-select\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"./user-roster-labels\";\n\n/**\n * The admin's user list (docs/user-admin-harmonization.md §3): a column preset for the\n * kit's `DataTable`, not a table of its own.\n *\n * keksdose built its roster by hand (1150 lines, its own Badge), Kurvenschmiede on the\n * kit `DataTable` with the 0.18 chips, kastlan as a settings page. What the three share\n * is the row — name over address, a role, the account's states, created, last login, the\n * app's own columns, and a handful of actions — so that is what the kit draws, and the\n * table, its paging, its sort and its filters stay the app's `DataTable` wiring:\n *\n * ```tsx\n * const columns = useUserRosterColumns<AdminUserRow, Role>({ roles: ROLES, actions, extra });\n * <DataTable rows={page.users} columns={columns} rowKey={(r) => r.id}\n * serverPagination={{ page, pageSize, total: page.total, onPageChange }}\n * sorts={sorts} onSortsChange={setSorts} />\n * // GET /admin/users?sort=${userRosterSort(sorts)}&…\n * ```\n *\n * The column keys ARE the server's sort keys (§3.1): `name`, `email`, `role`, `created`,\n * `last_login` — so {@link userRosterSort} turns the table's sort state into the query\n * as it is. Phone cards come from the same columns: the identity is the card's headline\n * with the account's states as chips beside it, the role, last login and the actions are\n * its three fields, created is left off.\n */\n\n/* ── Rows and states ────────────────────────────────────────────────────────── */\n\n/**\n * The fields of the contract's `AdminUserRow` the preset reads (§3.2, the auth\n * contract's `UserResponse` core). An app's row has more — its `extra` — and the\n * preset passes the whole row to every callback.\n */\nexport interface UserRosterRow extends AdminUserStateFields {\n id: string | number;\n email: string;\n first_name?: string | null;\n last_name?: string | null;\n /** The derived whole name — only the fallback when both parts are blank. */\n display_name?: string | null;\n /** keksdose, Kurvenschmiede: one role. */\n role?: string | null;\n /** kastlan: the account's roles in the current company. Wins over `role`. */\n roles?: readonly string[] | null;\n created_at?: DateInput;\n /** Account-wide; kastlan's comes from its session rows (§3.2). */\n last_login_at?: DateInput;\n}\n\n/** The state fields of the contract's row that {@link adminUserStates} maps (§3.2). */\nexport interface AdminUserStateFields {\n is_active?: boolean;\n email_verified?: boolean;\n password_change_required_at?: string | null;\n /** `after_days` mode: the day the account is erased (§6.4). */\n deletion_scheduled_at?: string | null;\n /** When the user asked. The only deletion field in kastlan's `operator` mode, where\n * nothing is scheduled. */\n deletion_requested_at?: string | null;\n}\n\n/** One state chip of a row: an {@link AccountState}, or one with its `date` (the\n * `deletion` day), a `tone` or the app's own words. */\nexport type RosterState = AccountState | { state: AccountState; date?: DateInput; tone?: ChipTone; label?: ReactNode };\n\n/**\n * The contract's row as state chips (§3.2: \"map 1:1 to the kit's AccountStateChip\"):\n *\n * - marked for deletion → `deletion` with the scheduled day (none in `operator` mode),\n * IN PLACE of `inactive` — such an account is always deactivated (§2.1);\n * - else `active` / `inactive` from `is_active`;\n * - `unverified` when `email_verified` is `false` (not when it is missing);\n * - `passwordChange` while `password_change_required_at` is set.\n */\nexport function adminUserStates(row: AdminUserStateFields): RosterState[] {\n const out: RosterState[] = [];\n if (row.deletion_scheduled_at || row.deletion_requested_at) {\n out.push({ state: \"deletion\", date: row.deletion_scheduled_at ?? null });\n } else if (row.is_active !== undefined) {\n out.push(row.is_active ? \"active\" : \"inactive\");\n }\n if (row.email_verified === false) out.push(\"unverified\");\n if (row.password_change_required_at) out.push(\"passwordChange\");\n return out;\n}\n\n/** The server's sort keys, which are the preset's column keys (§3.1). */\nexport const USER_ROSTER_SORT_KEYS = [\"name\", \"email\", \"role\", \"created\", \"last_login\"] as const;\nexport type UserRosterSortKey = (typeof USER_ROSTER_SORT_KEYS)[number];\n\n/**\n * The table's sort state as the `sort` query parameter of `GET /admin/users` (§3.1), in\n * the DataTable's own encoding ({@link encodeSorts}: `\"last_login.desc\"`), which\n * server-kit 0.4 reads beside the `-key` form. Only the first criterion on one of the\n * server's keys: an app's own column (`owned`) is the client's to sort, and the query\n * takes one key — a shift-clicked second criterion is not sent. `undefined` when nothing\n * the server knows is sorted, so its default order applies.\n */\nexport function userRosterSort(sorts: readonly SortState[]): string | undefined {\n const first = sorts.find((s) => (USER_ROSTER_SORT_KEYS as readonly string[]).includes(s.key));\n return first ? (encodeSorts([first]) ?? undefined) : undefined;\n}\n\n/* ── Labels ─────────────────────────────────────────────────────────────────── */\n\n/** The `userRoster` namespace: the preset's headers and the action menu's words. */\nexport interface UserRosterLabels {\n /** The identity column — the name with the address under it. */\n name: string;\n /** The separate address column (`emailColumn`). */\n email: string;\n role: string;\n state: string;\n created: string;\n lastLogin: string;\n /** A last login that never happened. */\n never: string;\n /** The actions column's (hidden) header. */\n actions: string;\n /** The action menu's button and panel — \"Actions for Ada Example\". */\n actionsFor: (name: string) => string;\n}\n\n// Defined in ./user-roster-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_USER_ROSTER_LABELS };\n\n/* ── UserIdentityCell ───────────────────────────────────────────────────────── */\n\nexport interface UserIdentityCellProps {\n /** First and last name (auth §3.2), shown in the READER's order. */\n person?: PersonName | null;\n /** A whole name, for an API that still sends `display_name` only. */\n name?: string | null;\n email?: string | null;\n /** The initials avatar before the name. Default true. */\n avatar?: boolean;\n /** After the name, on its line — a \"You\" chip, a demo mark. */\n badge?: ReactNode;\n /** Over the provider's locale. */\n locale?: string;\n className?: string;\n}\n\n/**\n * Who an account is, in one cell (§3.3): the initials avatar, the name in the reader's\n * order ({@link formatPersonName} — \"Example Ada\" in Hungarian) and the address under it\n * — the cell both rosters drew by hand. An account with no name yet (a migrated row\n * before `CompleteNameDialog`, an invitee) shows its address once, as the name.\n *\n * Both lines carry `data-private`, so keksdose's demo blur reaches them, and both end\n * in \"…\" at Normal only: from Large up they wrap (`truncate-until-large`, 0.33,\n * docs/text-size-harmonization.md §10.17), since a cut name or address is a finding there.\n */\nexport function UserIdentityCell({\n person,\n name,\n email,\n avatar = true,\n badge,\n locale: localeProp,\n className,\n}: UserIdentityCellProps) {\n const locale = useKitLocale(localeProp);\n const named = personLabel({ first: person?.first, last: person?.last, name }, locale);\n const address = (email ?? \"\").trim();\n return (\n <div className={cn(\"flex min-w-0 items-center gap-2.5\", className)}>\n {avatar && <UserAvatar person={person} name={name} email={email} size=\"sm\" />}\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span data-private className=\"truncate-until-large font-medium text-primary\">\n {named || address}\n </span>\n {badge}\n </div>\n {named && address && (\n <div data-private className=\"truncate-until-large text-xs text-muted\">\n {address}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/* ── The row's actions ──────────────────────────────────────────────────────── */\n\n/** One entry of a row's action menu (§4) — the kit's {@link RowAction} since 0.32,\n * which generalised it for every dense row (docs/text-size-harmonization.md §10.8). */\nexport type UserRowAction = RowAction;\n\n/** What `actions(row)` may return: entries, and `false` / `null` for the ones a\n * condition left out (`row.is_active && {…}`). */\nexport type UserRowActionList = RowActionList;\n\nexport interface UserRowActionsProps {\n actions: UserRowActionList;\n /** The account's name, for the menu's name — \"Actions for Ada Example\". */\n name: string;\n labels?: LabelOverride<UserRosterLabels>;\n}\n\n/**\n * A row's actions as one \"…\" button and a menu of them: keksdose had a strip of icon\n * buttons in the expanded row, Kurvenschmiede five text buttons in a cell — neither fits\n * a phone card, and both grow with every action §2.4 adds. The menu is portalled, so a\n * table's scroller cannot clip it; Escape and an outside press close it, and focus goes\n * back to the button, which is where a confirmation dialog then returns it.\n *\n * Since 0.32 this is {@link RowActions} with `collapse=\"menu\"` — the menu at every size —\n * named by the roster's own `actionsFor`.\n */\nexport function UserRowActions({ actions, name, labels: labelsProp }: UserRowActionsProps) {\n const labels = useKitLabels(\"userRoster\", DEFAULT_USER_ROSTER_LABELS, labelsProp);\n return <RowActions actions={actions} name={name} collapse=\"menu\" labels={{ actionsFor: labels.actionsFor }} />;\n}\n\n/* ── The preset ─────────────────────────────────────────────────────────────── */\n\n/** How a row's role may be changed inline: `true`, or with reasons. */\nexport interface RoleEditing<R extends string> {\n /** Why the whole select is locked for this row — `\"self\"`, `\"last_admin\"`, or words. */\n disabledReason?: RoleLock;\n /** Why one role cannot be chosen for this row (a string: it goes inside an option). */\n optionDisabledReason?: (role: R) => string | undefined;\n /** The roles that may be chosen. Default: the whole vocabulary. */\n offered?: readonly R[];\n}\n\nexport interface UserRosterColumnsOptions<T extends UserRosterRow, R extends string = string> {\n /** The app's roles, as `RoleChip` reads them. */\n roles: RoleVocabulary<R>;\n /** The row's state chips. Default {@link adminUserStates}, which reads the contract's\n * fields (`is_active`, `email_verified`, `password_change_required_at`,\n * `deletion_scheduled_at`, `deletion_requested_at`). */\n stateOf?: (row: T) => readonly RosterState[];\n /** The app's own columns — keksdose's plan and key custody, Kurvenschmiede's owned\n * counts — after the dates, before the actions. */\n extra?: readonly DataTableColumn<T>[];\n /** The row's action menu (§4). Left out: no actions column. */\n actions?: (row: T) => UserRowActionList;\n /** The actions column's heading (0.46.1, keksdose dev#598 rework — a VISIBLE \"Actions\"\n * on every table with an actions column). Default: `labels.actions`, shown. A node\n * replaces it; `false` keeps the pre-0.46.1 heading, a screen reader's name only. */\n actionsHeader?: ReactNode | false;\n /**\n * Whether this row's role is a select rather than a chip, and with which reasons.\n * Left out, or `false`: a chip. kastlan's several roles are never a select here — they\n * are a \"Manage roles\" action that opens `RolesEditor`.\n */\n editableRole?: (row: T) => boolean | RoleEditing<R>;\n /** A role was picked in a row's select. The app confirms (`acknowledge`) and sends it. */\n onRoleChange?: (row: T, role: R) => void;\n /** The row's role(s). Default `row.roles ?? row.role`. */\n roleOf?: (row: T) => R | readonly R[] | null | undefined;\n /** A separate, sortable \"Email\" column (the server's `email` sort key) beside the\n * identity. Default false: the address is under the name already. */\n emailColumn?: boolean;\n /** The words, already resolved. Default English; {@link useUserRosterColumns} reads\n * the provider's `userRoster` namespace. */\n labels?: LabelOverride<UserRosterLabels>;\n}\n\nfunction rolesOf<T extends UserRosterRow, R extends string>(\n row: T,\n roleOf: UserRosterColumnsOptions<T, R>[\"roleOf\"],\n): R[] {\n const raw = roleOf ? roleOf(row) : ((row.roles ?? row.role) as R | readonly R[] | null | undefined);\n if (raw === null || raw === undefined || raw === \"\") return [];\n return typeof raw === \"string\" ? [raw as R] : [...(raw as readonly R[])];\n}\n\n/** What a row's role select can show as its value: every role's word, and a held key\n * the vocabulary does not know (RoleSelect shows it as the key). */\nfunction roleWordsOf<R extends string>(roles: RoleVocabulary<R>, held: R | undefined): string[] {\n const words = (Object.keys(roles) as R[]).map((role) => roles[role].label);\n return held !== undefined && !(held in roles) ? [...words, held] : words;\n}\n\n/** The row's name in the reader's order — a component, because the order is the\n * READER's and the locale comes from the provider. */\nfunction RowName<T extends UserRosterRow>({ row, children }: { row: T; children: (name: string) => ReactNode }) {\n const locale = useKitLocale();\n const name = personLabel(\n { first: row.first_name, last: row.last_name, name: row.display_name, email: row.email },\n locale,\n );\n return <>{children(name)}</>;\n}\n\nfunction StateChips({ states }: { states: readonly RosterState[] }) {\n if (states.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {states.map((entry) => {\n const spec = typeof entry === \"string\" ? { state: entry } : entry;\n return (\n <AccountStateChip key={spec.state} state={spec.state} date={spec.date} tone={spec.tone} size=\"xs\">\n {spec.label}\n </AccountStateChip>\n );\n })}\n </div>\n );\n}\n\n/**\n * The roster's columns, in the contract's order (§3.3):\n *\n * | key | cell | sort | phone | fit (0.50) |\n * |---|---|---|---|---|\n * | `name` | {@link UserIdentityCell} | last, first | the card's headline | `grow` |\n * | `email` | the address (`emailColumn` only) | address | hidden | — |\n * | `role` | `RoleChip`(s), or {@link RoleSelect} where `editableRole` | role | field | — |\n * | `state` | `AccountStateChip`s from `stateOf` | — | chips beside the headline | — |\n * | `created` | `dateColumn` | instant | hidden | `fitPriority: 1` |\n * | `last_login` | `dateColumn`, relative, \"Never\" | instant | field | `fitPriority: 2` |\n * | …`extra` | the app's | the app's | off the card unless marked `mobile: \"secondary\"` | the app's |\n * | `actions` | {@link UserRowActions} | — | the card's actions row (`role: \"actions\"`) | — |\n *\n * The fit (0.50, kastlan K2): Name takes the rest of the row, and a table narrower than\n * its columns leaves out Created, then Last login (DataTable's `grow` / `fitPriority`).\n * The role and state chips still wrap in their cells (0.48), and the other cells keep one\n * line at Normal. `{ ...col, grow: undefined, fitPriority: undefined }` restores the 0.49\n * layout; an `extra` column takes a `fitPriority` of its own to leave too.\n *\n * The card holds three fields — `role`, `last_login` and one more: an `extra` column marked\n * `mobile: \"secondary\"` takes that place. The actions are `role: \"actions\"` (0.49), drawn\n * after the fields and never one of the three. Drop them from the card with\n * `mobile: \"hidden\"`, or draw a `mobileCard`.\n *\n * Pure: hand it resolved words, or use {@link useUserRosterColumns}. The keys are stable,\n * so an app may drop or move a column (`columns.filter((c) => c.key !== \"created\")`).\n * Under `serverPagination` the `sortBy`s only make the headers clickable; the app sends\n * the sort ({@link userRosterSort}).\n */\nexport function userRosterColumns<T extends UserRosterRow, R extends string = string>({\n roles,\n stateOf = adminUserStates,\n extra = [],\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn = false,\n labels: labelsProp,\n}: UserRosterColumnsOptions<T, R>): DataTableColumn<T>[] {\n const labels: UserRosterLabels = { ...DEFAULT_USER_ROSTER_LABELS, ...labelsProp };\n const roleWords = (row: T) =>\n rolesOf(row, roleOf)\n .map((role) => roles[role]?.label ?? role)\n .join(\", \");\n const columns: DataTableColumn<T>[] = [\n {\n key: \"name\",\n header: labels.name,\n cell: (row) => (\n <UserIdentityCell\n person={{ first: row.first_name, last: row.last_name }}\n name={row.display_name}\n email={row.email}\n />\n ),\n // By last name, then first (auth §3.2), as the server sorts `name`.\n sortBy: (row) => `${row.last_name ?? \"\"} ${row.first_name ?? \"\"}`.trim().toLocaleLowerCase() || row.email,\n // Either part, in either order, or the address (auth §10.6).\n filterBy: (row) =>\n [row.first_name, row.last_name, row.last_name, row.first_name, row.display_name, row.email]\n .filter(Boolean)\n .join(\" \"),\n mobilePrimary: true,\n // Takes the rest of the row and ellipsises (0.50, kastlan K2): the roster fits its\n // pane — a settings column, an admin page — instead of scrolling sideways.\n grow: true,\n },\n ];\n if (emailColumn) {\n columns.push({\n key: \"email\",\n header: labels.email,\n className: \"whitespace-nowrap\",\n cell: (row) => (\n <span data-private className=\"text-secondary\">\n {row.email}\n </span>\n ),\n sortBy: (row) => row.email.toLowerCase(),\n filterBy: (row) => row.email,\n mobileHidden: true,\n });\n }\n columns.push(\n {\n key: \"role\",\n header: labels.role,\n cell: (row) => {\n const held = rolesOf(row, roleOf);\n const editing = held.length <= 1 ? editableRole?.(row) : false;\n if (editing && onRoleChange) {\n const rules: RoleEditing<R> = editing === true ? {} : editing;\n return (\n <RowName row={row}>\n {(name) => (\n // The column keeps room for the select's longest ROLE word in the app's\n // language (0.51, Kurvenschmiede N5): beside the `grow` Name every other\n // column is squeezed to its min-content, which a `w-full` select does not\n // have, so the role select was the header's ~92px and read \"Meml…\",\n // \"Admi…\". Every word of the vocabulary, not only the row's offered\n // ones: the column is as wide as its widest row, so the rows line up, and\n // a role held but not offered (KS's Reviewer) is still a selected value.\n // Not the options' reasons (\"Customer (owns work)\"): they are never the\n // selected value, and the open list is as wide as it needs anyway.\n <div>\n <RoleSelect<R>\n size=\"sm\"\n name={name}\n value={held[0] ?? null}\n roles={roles}\n offered={rules.offered}\n disabledReason={rules.disabledReason}\n optionDisabledReason={rules.optionDisabledReason}\n onChange={(role) => onRoleChange(row, role)}\n />\n <SelectWidthFloor size=\"sm\" values={roleWordsOf(roles, held[0])} />\n </div>\n )}\n </RowName>\n );\n }\n if (held.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {held.map((role) => (\n <RoleChip<R> key={role} value={role} roles={roles} size=\"xs\" />\n ))}\n </div>\n );\n },\n sortBy: roleWords,\n filter: {\n type: \"select\",\n getValue: (row) => rolesOf(row, roleOf)[0] ?? \"\",\n options: (Object.keys(roles) as R[]).map((value) => ({ value, label: roles[value].label })),\n },\n mobile: \"secondary\",\n },\n {\n key: \"state\",\n header: labels.state,\n // No states, no cell: the phone card then shows no chip beside the name.\n cell: (row) => {\n const states = stateOf(row);\n return states.length === 0 ? null : <StateChips states={states} />;\n },\n // Beside the name on the phone card, so the role, last login and the actions keep\n // the card's three fields.\n role: \"status\",\n },\n dateColumn<T>({\n key: \"created\",\n header: labels.created,\n value: (row) => row.created_at ?? null,\n // The first to leave a narrow table (0.50, kastlan K2), then last login.\n column: { mobileHidden: true, fitPriority: 1 },\n }),\n // How long ago rather than a date — the question is \"recently?\" — with the exact\n // time beside it (keksdose's last-seen column, Kurvenschmiede's; 0.32 §4: never only\n // in a tooltip).\n dateColumn<T>({\n key: \"last_login\",\n header: labels.lastLogin,\n value: (row) => row.last_login_at ?? null,\n display: \"relative\",\n empty: labels.never,\n column: { mobile: \"secondary\", fitPriority: 2 },\n }),\n ...extra,\n );\n if (actions) {\n columns.push({\n key: \"actions\",\n // On screen, as every actions column's heading is (0.46.1, keksdose dev#598 rework).\n header:\n actionsHeader === false ? <span className=\"sr-only\">{labels.actions}</span> : (actionsHeader ?? labels.actions),\n headerText: labels.actions,\n // The caller's own words only: the menu reads the provider's itself, and the\n // English defaults merged in above must not shadow them.\n cell: (row) => (\n <RowName row={row}>\n {(name) => <UserRowActions actions={actions(row)} name={name} labels={labelsProp} />}\n </RowName>\n ),\n className: \"w-px whitespace-nowrap text-end\",\n headClassName: \"w-px text-end\",\n // The menu button keeps its own click: a row link must not wrap it.\n noRowLink: true,\n // Always on the phone card, after the fields and never one of the three (0.49).\n role: \"actions\",\n });\n }\n return columns;\n}\n\n/**\n * {@link userRosterColumns} with the `userRoster` words from the provider, memoised on\n * the options — pass stable callbacks (`useCallback`) and a module-level vocabulary, or\n * the columns are rebuilt on every render.\n */\nexport function useUserRosterColumns<T extends UserRosterRow, R extends string = string>({\n roles,\n stateOf,\n extra,\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn,\n labels: labelsProp,\n}: UserRosterColumnsOptions<T, R>): DataTableColumn<T>[] {\n const labels = useKitLabels(\"userRoster\", DEFAULT_USER_ROSTER_LABELS, labelsProp);\n return useMemo(\n () =>\n userRosterColumns<T, R>({\n roles,\n stateOf,\n extra,\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn,\n labels,\n }),\n [roles, stateOf, extra, actions, actionsHeader, editableRole, onRoleChange, roleOf, emailColumn, labels],\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/admin/user-roster.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { DateInput } from \"../lib/format\";\nimport type { PersonName } from \"../lib/person-name\";\nimport { useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { AccountStateChip, RoleChip, dateColumn } from \"../components/account-chips\";\nimport type { AccountState, RoleVocabulary } from \"../components/account-chips\";\nimport type { ChipTone } from \"../components/chip\";\nimport type { DataTableColumn } from \"../components/data-table\";\nimport { SelectWidthFloor } from \"../components/field-base\";\nimport { encodeSorts } from \"../components/data-table-sort\";\nimport type { SortState } from \"../components/data-table-sort\";\nimport { RowActions } from \"../components/row-actions\";\nimport type { RowAction, RowActionList } from \"../components/row-actions\";\nimport { UserAvatar } from \"../components/user-avatar\";\nimport { personLabel } from \"./admin-parts\";\nimport { RoleSelect } from \"./role-select\";\nimport type { RoleLock } from \"./role-select\";\nimport { DEFAULT_USER_ROSTER_LABELS } from \"./user-roster-labels\";\n\n/**\n * The admin's user list (docs/user-admin-harmonization.md §3): a column preset for the\n * kit's `DataTable`, not a table of its own.\n *\n * keksdose built its roster by hand (1150 lines, its own Badge), Kurvenschmiede on the\n * kit `DataTable` with the 0.18 chips, kastlan as a settings page. What the three share\n * is the row — name over address, a role, the account's states, created, last login, the\n * app's own columns, and a handful of actions — so that is what the kit draws, and the\n * table, its paging, its sort and its filters stay the app's `DataTable` wiring:\n *\n * ```tsx\n * const columns = useUserRosterColumns<AdminUserRow, Role>({ roles: ROLES, actions, extra });\n * <DataTable rows={page.users} columns={columns} rowKey={(r) => r.id}\n * serverPagination={{ page, pageSize, total: page.total, onPageChange }}\n * sorts={sorts} onSortsChange={setSorts} />\n * // GET /admin/users?sort=${userRosterSort(sorts)}&…\n * ```\n *\n * The column keys ARE the server's sort keys (§3.1): `name`, `email`, `role`, `created`,\n * `last_login` — so {@link userRosterSort} turns the table's sort state into the query\n * as it is. Phone cards come from the same columns: the identity is the card's headline\n * with the account's states as chips beside it, the role, last login and the actions are\n * its three fields, created is left off.\n */\n\n/* ── Rows and states ────────────────────────────────────────────────────────── */\n\n/**\n * The fields of the contract's `AdminUserRow` the preset reads (§3.2, the auth\n * contract's `UserResponse` core). An app's row has more — its `extra` — and the\n * preset passes the whole row to every callback.\n */\nexport interface UserRosterRow extends AdminUserStateFields {\n id: string | number;\n email: string;\n first_name?: string | null;\n last_name?: string | null;\n /** The derived whole name — only the fallback when both parts are blank. */\n display_name?: string | null;\n /** keksdose, Kurvenschmiede: one role. */\n role?: string | null;\n /** kastlan: the account's roles in the current company. Wins over `role`. */\n roles?: readonly string[] | null;\n created_at?: DateInput;\n /** Account-wide; kastlan's comes from its session rows (§3.2). */\n last_login_at?: DateInput;\n}\n\n/** The state fields of the contract's row that {@link adminUserStates} maps (§3.2). */\nexport interface AdminUserStateFields {\n is_active?: boolean;\n email_verified?: boolean;\n password_change_required_at?: string | null;\n /** `after_days` mode: the day the account is erased (§6.4). */\n deletion_scheduled_at?: string | null;\n /** When the user asked. The only deletion field in kastlan's `operator` mode, where\n * nothing is scheduled. */\n deletion_requested_at?: string | null;\n}\n\n/** One state chip of a row: an {@link AccountState}, or one with its `date` (the\n * `deletion` day), a `tone` or the app's own words. */\nexport type RosterState = AccountState | { state: AccountState; date?: DateInput; tone?: ChipTone; label?: ReactNode };\n\n/**\n * The contract's row as state chips (§3.2: \"map 1:1 to the kit's AccountStateChip\"):\n *\n * - marked for deletion → `deletion` with the scheduled day (none in `operator` mode),\n * IN PLACE of `inactive` — such an account is always deactivated (§2.1);\n * - else `active` / `inactive` from `is_active`;\n * - `unverified` when `email_verified` is `false` (not when it is missing);\n * - `passwordChange` while `password_change_required_at` is set.\n */\nexport function adminUserStates(row: AdminUserStateFields): RosterState[] {\n const out: RosterState[] = [];\n if (row.deletion_scheduled_at || row.deletion_requested_at) {\n out.push({ state: \"deletion\", date: row.deletion_scheduled_at ?? null });\n } else if (row.is_active !== undefined) {\n out.push(row.is_active ? \"active\" : \"inactive\");\n }\n if (row.email_verified === false) out.push(\"unverified\");\n if (row.password_change_required_at) out.push(\"passwordChange\");\n return out;\n}\n\n/** The server's sort keys, which are the preset's column keys (§3.1). */\nexport const USER_ROSTER_SORT_KEYS = [\"name\", \"email\", \"role\", \"created\", \"last_login\"] as const;\nexport type UserRosterSortKey = (typeof USER_ROSTER_SORT_KEYS)[number];\n\n/** server-kit's `user_admin.DEFAULT_PAGE_SIZE`: the rows `GET /admin/users` answers when the\n * query names no `page_size` (keksdose audit F-63, which copied it into its users panel). */\nexport const USER_ROSTER_PAGE_SIZE = 25;\n/** server-kit's `user_admin.MAX_PAGE_SIZE`: the most rows one page may ask for, which a\n * pager's \"All\" resolves to; a larger `page_size` is refused. */\nexport const USER_ROSTER_MAX_PAGE_SIZE = 200;\n\n/**\n * The table's sort state as the `sort` query parameter of `GET /admin/users` (§3.1), in\n * the DataTable's own encoding ({@link encodeSorts}: `\"last_login.desc\"`), which\n * server-kit 0.4 reads beside the `-key` form. Only the first criterion on one of the\n * server's keys: an app's own column (`owned`) is the client's to sort, and the query\n * takes one key — a shift-clicked second criterion is not sent. `undefined` when nothing\n * the server knows is sorted, so its default order applies.\n */\nexport function userRosterSort(sorts: readonly SortState[]): string | undefined {\n const first = sorts.find((s) => (USER_ROSTER_SORT_KEYS as readonly string[]).includes(s.key));\n return first ? (encodeSorts([first]) ?? undefined) : undefined;\n}\n\n/* ── Labels ─────────────────────────────────────────────────────────────────── */\n\n/** The `userRoster` namespace: the preset's headers and the action menu's words. */\nexport interface UserRosterLabels {\n /** The identity column — the name with the address under it. */\n name: string;\n /** The separate address column (`emailColumn`). */\n email: string;\n role: string;\n state: string;\n created: string;\n lastLogin: string;\n /** A last login that never happened. */\n never: string;\n /** The actions column's (hidden) header. */\n actions: string;\n /** The action menu's button and panel — \"Actions for Ada Example\". */\n actionsFor: (name: string) => string;\n}\n\n// Defined in ./user-roster-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_USER_ROSTER_LABELS };\n\n/* ── UserIdentityCell ───────────────────────────────────────────────────────── */\n\nexport interface UserIdentityCellProps {\n /** First and last name (auth §3.2), shown in the READER's order. */\n person?: PersonName | null;\n /** A whole name, for an API that still sends `display_name` only. */\n name?: string | null;\n email?: string | null;\n /** The initials avatar before the name. Default true. */\n avatar?: boolean;\n /** After the name, on its line — a \"You\" chip, a demo mark. */\n badge?: ReactNode;\n /** Over the provider's locale. */\n locale?: string;\n className?: string;\n}\n\n/**\n * Who an account is, in one cell (§3.3): the initials avatar, the name in the reader's\n * order ({@link formatPersonName} — \"Example Ada\" in Hungarian) and the address under it\n * — the cell both rosters drew by hand. An account with no name yet (a migrated row\n * before `CompleteNameDialog`, an invitee) shows its address once, as the name.\n *\n * Both lines carry `data-private`, so keksdose's demo blur reaches them, and both end\n * in \"…\" at Normal only: from Large up they wrap (`truncate-until-large`, 0.33,\n * docs/text-size-harmonization.md §10.17), since a cut name or address is a finding there.\n */\nexport function UserIdentityCell({\n person,\n name,\n email,\n avatar = true,\n badge,\n locale: localeProp,\n className,\n}: UserIdentityCellProps) {\n const locale = useKitLocale(localeProp);\n const named = personLabel({ first: person?.first, last: person?.last, name }, locale);\n const address = (email ?? \"\").trim();\n return (\n <div className={cn(\"flex min-w-0 items-center gap-2.5\", className)}>\n {avatar && <UserAvatar person={person} name={name} email={email} size=\"sm\" />}\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <span data-private className=\"truncate-until-large font-medium text-primary\">\n {named || address}\n </span>\n {badge}\n </div>\n {named && address && (\n <div data-private className=\"truncate-until-large text-xs text-muted\">\n {address}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/* ── The row's actions ──────────────────────────────────────────────────────── */\n\n/** One entry of a row's action menu (§4) — the kit's {@link RowAction} since 0.32,\n * which generalised it for every dense row (docs/text-size-harmonization.md §10.8). */\nexport type UserRowAction = RowAction;\n\n/** What `actions(row)` may return: entries, and `false` / `null` for the ones a\n * condition left out (`row.is_active && {…}`). */\nexport type UserRowActionList = RowActionList;\n\nexport interface UserRowActionsProps {\n actions: UserRowActionList;\n /** The account's name, for the menu's name — \"Actions for Ada Example\". */\n name: string;\n labels?: LabelOverride<UserRosterLabels>;\n}\n\n/**\n * A row's actions as one \"…\" button and a menu of them: keksdose had a strip of icon\n * buttons in the expanded row, Kurvenschmiede five text buttons in a cell — neither fits\n * a phone card, and both grow with every action §2.4 adds. The menu is portalled, so a\n * table's scroller cannot clip it; Escape and an outside press close it, and focus goes\n * back to the button, which is where a confirmation dialog then returns it.\n *\n * Since 0.32 this is {@link RowActions} with `collapse=\"menu\"` — the menu at every size —\n * named by the roster's own `actionsFor`.\n */\nexport function UserRowActions({ actions, name, labels: labelsProp }: UserRowActionsProps) {\n const labels = useKitLabels(\"userRoster\", DEFAULT_USER_ROSTER_LABELS, labelsProp);\n return <RowActions actions={actions} name={name} collapse=\"menu\" labels={{ actionsFor: labels.actionsFor }} />;\n}\n\n/* ── The preset ─────────────────────────────────────────────────────────────── */\n\n/** How a row's role may be changed inline: `true`, or with reasons. */\nexport interface RoleEditing<R extends string> {\n /** Why the whole select is locked for this row — `\"self\"`, `\"last_admin\"`, or words. */\n disabledReason?: RoleLock;\n /** Why one role cannot be chosen for this row (a string: it goes inside an option). */\n optionDisabledReason?: (role: R) => string | undefined;\n /** The roles that may be chosen. Default: the whole vocabulary. */\n offered?: readonly R[];\n}\n\nexport interface UserRosterColumnsOptions<T extends UserRosterRow, R extends string = string> {\n /** The app's roles, as `RoleChip` reads them. */\n roles: RoleVocabulary<R>;\n /** The row's state chips. Default {@link adminUserStates}, which reads the contract's\n * fields (`is_active`, `email_verified`, `password_change_required_at`,\n * `deletion_scheduled_at`, `deletion_requested_at`). */\n stateOf?: (row: T) => readonly RosterState[];\n /** The app's own columns — keksdose's plan and key custody, Kurvenschmiede's owned\n * counts — after the dates, before the actions. */\n extra?: readonly DataTableColumn<T>[];\n /** The row's action menu (§4). Left out: no actions column. */\n actions?: (row: T) => UserRowActionList;\n /** The actions column's heading (0.46.1, keksdose dev#598 rework — a VISIBLE \"Actions\"\n * on every table with an actions column). Default: `labels.actions`, shown. A node\n * replaces it; `false` keeps the pre-0.46.1 heading, a screen reader's name only. */\n actionsHeader?: ReactNode | false;\n /**\n * Whether this row's role is a select rather than a chip, and with which reasons.\n * Left out, or `false`: a chip. kastlan's several roles are never a select here — they\n * are a \"Manage roles\" action that opens `RolesEditor`.\n */\n editableRole?: (row: T) => boolean | RoleEditing<R>;\n /** A role was picked in a row's select. The app confirms (`acknowledge`) and sends it. */\n onRoleChange?: (row: T, role: R) => void;\n /** The row's role(s). Default `row.roles ?? row.role`. */\n roleOf?: (row: T) => R | readonly R[] | null | undefined;\n /** A separate, sortable \"Email\" column (the server's `email` sort key) beside the\n * identity. Default false: the address is under the name already. */\n emailColumn?: boolean;\n /** The words, already resolved. Default English; {@link useUserRosterColumns} reads\n * the provider's `userRoster` namespace. */\n labels?: LabelOverride<UserRosterLabels>;\n}\n\nfunction rolesOf<T extends UserRosterRow, R extends string>(\n row: T,\n roleOf: UserRosterColumnsOptions<T, R>[\"roleOf\"],\n): R[] {\n const raw = roleOf ? roleOf(row) : ((row.roles ?? row.role) as R | readonly R[] | null | undefined);\n if (raw === null || raw === undefined || raw === \"\") return [];\n return typeof raw === \"string\" ? [raw as R] : [...(raw as readonly R[])];\n}\n\n/** What a row's role select can show as its value: every role's word, and a held key\n * the vocabulary does not know (RoleSelect shows it as the key). */\nfunction roleWordsOf<R extends string>(roles: RoleVocabulary<R>, held: R | undefined): string[] {\n const words = (Object.keys(roles) as R[]).map((role) => roles[role].label);\n return held !== undefined && !(held in roles) ? [...words, held] : words;\n}\n\n/** The row's name in the reader's order — a component, because the order is the\n * READER's and the locale comes from the provider. */\nfunction RowName<T extends UserRosterRow>({ row, children }: { row: T; children: (name: string) => ReactNode }) {\n const locale = useKitLocale();\n const name = personLabel(\n { first: row.first_name, last: row.last_name, name: row.display_name, email: row.email },\n locale,\n );\n return <>{children(name)}</>;\n}\n\nfunction StateChips({ states }: { states: readonly RosterState[] }) {\n if (states.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {states.map((entry) => {\n const spec = typeof entry === \"string\" ? { state: entry } : entry;\n return (\n <AccountStateChip key={spec.state} state={spec.state} date={spec.date} tone={spec.tone} size=\"xs\">\n {spec.label}\n </AccountStateChip>\n );\n })}\n </div>\n );\n}\n\n/**\n * The roster's columns, in the contract's order (§3.3):\n *\n * | key | cell | sort | phone | fit (0.50) |\n * |---|---|---|---|---|\n * | `name` | {@link UserIdentityCell} | last, first | the card's headline | `grow` |\n * | `email` | the address (`emailColumn` only) | address | hidden | — |\n * | `role` | `RoleChip`(s), or {@link RoleSelect} where `editableRole` | role | field | — |\n * | `state` | `AccountStateChip`s from `stateOf` | — | chips beside the headline | — |\n * | `created` | `dateColumn` | instant | hidden | `fitPriority: 1` |\n * | `last_login` | `dateColumn`, relative, \"Never\" | instant | field | `fitPriority: 2` |\n * | …`extra` | the app's | the app's | off the card unless marked `mobile: \"secondary\"` | the app's |\n * | `actions` | {@link UserRowActions} | — | the card's actions row (`role: \"actions\"`) | — |\n *\n * The fit (0.50, kastlan K2): Name takes the rest of the row, and a table narrower than\n * its columns leaves out Created, then Last login (DataTable's `grow` / `fitPriority`).\n * The role and state chips still wrap in their cells (0.48), and the other cells keep one\n * line at Normal. `{ ...col, grow: undefined, fitPriority: undefined }` restores the 0.49\n * layout; an `extra` column takes a `fitPriority` of its own to leave too.\n *\n * The card holds three fields — `role`, `last_login` and one more: an `extra` column marked\n * `mobile: \"secondary\"` takes that place. The actions are `role: \"actions\"` (0.49), drawn\n * after the fields and never one of the three. Drop them from the card with\n * `mobile: \"hidden\"`, or draw a `mobileCard`.\n *\n * Pure: hand it resolved words, or use {@link useUserRosterColumns}. The keys are stable,\n * so an app may drop or move a column (`columns.filter((c) => c.key !== \"created\")`).\n * Under `serverPagination` the `sortBy`s only make the headers clickable; the app sends\n * the sort ({@link userRosterSort}).\n */\nexport function userRosterColumns<T extends UserRosterRow, R extends string = string>({\n roles,\n stateOf = adminUserStates,\n extra = [],\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn = false,\n labels: labelsProp,\n}: UserRosterColumnsOptions<T, R>): DataTableColumn<T>[] {\n const labels: UserRosterLabels = { ...DEFAULT_USER_ROSTER_LABELS, ...labelsProp };\n const roleWords = (row: T) =>\n rolesOf(row, roleOf)\n .map((role) => roles[role]?.label ?? role)\n .join(\", \");\n const columns: DataTableColumn<T>[] = [\n {\n key: \"name\",\n header: labels.name,\n cell: (row) => (\n <UserIdentityCell\n person={{ first: row.first_name, last: row.last_name }}\n name={row.display_name}\n email={row.email}\n />\n ),\n // By last name, then first (auth §3.2), as the server sorts `name`.\n sortBy: (row) => `${row.last_name ?? \"\"} ${row.first_name ?? \"\"}`.trim().toLocaleLowerCase() || row.email,\n // Either part, in either order, or the address (auth §10.6).\n filterBy: (row) =>\n [row.first_name, row.last_name, row.last_name, row.first_name, row.display_name, row.email]\n .filter(Boolean)\n .join(\" \"),\n mobilePrimary: true,\n // Takes the rest of the row and ellipsises (0.50, kastlan K2): the roster fits its\n // pane — a settings column, an admin page — instead of scrolling sideways.\n grow: true,\n },\n ];\n if (emailColumn) {\n columns.push({\n key: \"email\",\n header: labels.email,\n className: \"whitespace-nowrap\",\n cell: (row) => (\n <span data-private className=\"text-secondary\">\n {row.email}\n </span>\n ),\n sortBy: (row) => row.email.toLowerCase(),\n filterBy: (row) => row.email,\n mobileHidden: true,\n });\n }\n columns.push(\n {\n key: \"role\",\n header: labels.role,\n cell: (row) => {\n const held = rolesOf(row, roleOf);\n const editing = held.length <= 1 ? editableRole?.(row) : false;\n if (editing && onRoleChange) {\n const rules: RoleEditing<R> = editing === true ? {} : editing;\n return (\n <RowName row={row}>\n {(name) => (\n // The column keeps room for the select's longest ROLE word in the app's\n // language (0.51, Kurvenschmiede N5): beside the `grow` Name every other\n // column is squeezed to its min-content, which a `w-full` select does not\n // have, so the role select was the header's ~92px and read \"Meml…\",\n // \"Admi…\". Every word of the vocabulary, not only the row's offered\n // ones: the column is as wide as its widest row, so the rows line up, and\n // a role held but not offered (KS's Reviewer) is still a selected value.\n // Not the options' reasons (\"Customer (owns work)\"): they are never the\n // selected value, and the open list is as wide as it needs anyway.\n <div>\n <RoleSelect<R>\n size=\"sm\"\n name={name}\n value={held[0] ?? null}\n roles={roles}\n offered={rules.offered}\n disabledReason={rules.disabledReason}\n optionDisabledReason={rules.optionDisabledReason}\n onChange={(role) => onRoleChange(row, role)}\n />\n <SelectWidthFloor size=\"sm\" values={roleWordsOf(roles, held[0])} />\n </div>\n )}\n </RowName>\n );\n }\n if (held.length === 0) return null;\n return (\n <div className=\"flex flex-wrap gap-1\">\n {held.map((role) => (\n <RoleChip<R> key={role} value={role} roles={roles} size=\"xs\" />\n ))}\n </div>\n );\n },\n sortBy: roleWords,\n filter: {\n type: \"select\",\n getValue: (row) => rolesOf(row, roleOf)[0] ?? \"\",\n options: (Object.keys(roles) as R[]).map((value) => ({ value, label: roles[value].label })),\n },\n mobile: \"secondary\",\n },\n {\n key: \"state\",\n header: labels.state,\n // No states, no cell: the phone card then shows no chip beside the name.\n cell: (row) => {\n const states = stateOf(row);\n return states.length === 0 ? null : <StateChips states={states} />;\n },\n // Beside the name on the phone card, so the role, last login and the actions keep\n // the card's three fields.\n role: \"status\",\n },\n dateColumn<T>({\n key: \"created\",\n header: labels.created,\n value: (row) => row.created_at ?? null,\n // The first to leave a narrow table (0.50, kastlan K2), then last login.\n column: { mobileHidden: true, fitPriority: 1 },\n }),\n // How long ago rather than a date — the question is \"recently?\" — with the exact\n // time beside it (keksdose's last-seen column, Kurvenschmiede's; 0.32 §4: never only\n // in a tooltip).\n dateColumn<T>({\n key: \"last_login\",\n header: labels.lastLogin,\n value: (row) => row.last_login_at ?? null,\n display: \"relative\",\n empty: labels.never,\n column: { mobile: \"secondary\", fitPriority: 2 },\n }),\n ...extra,\n );\n if (actions) {\n columns.push({\n key: \"actions\",\n // On screen, as every actions column's heading is (0.46.1, keksdose dev#598 rework).\n header:\n actionsHeader === false ? <span className=\"sr-only\">{labels.actions}</span> : (actionsHeader ?? labels.actions),\n headerText: labels.actions,\n // The caller's own words only: the menu reads the provider's itself, and the\n // English defaults merged in above must not shadow them.\n cell: (row) => (\n <RowName row={row}>\n {(name) => <UserRowActions actions={actions(row)} name={name} labels={labelsProp} />}\n </RowName>\n ),\n className: \"w-px whitespace-nowrap text-end\",\n headClassName: \"w-px text-end\",\n // The menu button keeps its own click: a row link must not wrap it.\n noRowLink: true,\n // Always on the phone card, after the fields and never one of the three (0.49).\n role: \"actions\",\n });\n }\n return columns;\n}\n\n/**\n * {@link userRosterColumns} with the `userRoster` words from the provider, memoised on\n * the options — pass stable callbacks (`useCallback`) and a module-level vocabulary, or\n * the columns are rebuilt on every render.\n */\nexport function useUserRosterColumns<T extends UserRosterRow, R extends string = string>({\n roles,\n stateOf,\n extra,\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn,\n labels: labelsProp,\n}: UserRosterColumnsOptions<T, R>): DataTableColumn<T>[] {\n const labels = useKitLabels(\"userRoster\", DEFAULT_USER_ROSTER_LABELS, labelsProp);\n return useMemo(\n () =>\n userRosterColumns<T, R>({\n roles,\n stateOf,\n extra,\n actions,\n actionsHeader,\n editableRole,\n onRoleChange,\n roleOf,\n emailColumn,\n labels,\n }),\n [roles, stateOf, extra, actions, actionsHeader, editableRole, onRoleChange, roleOf, emailColumn, labels],\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -69,6 +69,32 @@ export interface BillingLabels {
|
|
|
69
69
|
pickFirst: string;
|
|
70
70
|
/** One word per status. */
|
|
71
71
|
status: Record<SubscriptionStatus, string>;
|
|
72
|
+
/**
|
|
73
|
+
* The subscription's standing in one line, under the plan's name: `SubscriptionStanding`
|
|
74
|
+
* and `subscriptionStanding` say the FIRST that applies, in this order. `date` arrives
|
|
75
|
+
* formatted. The words are keksdose's reviewed ones (`billing.trial_until`, …), which
|
|
76
|
+
* kastlan's page said in words of its own (kastlan reuse audit 2026-11-02, proposal 3).
|
|
77
|
+
*/
|
|
78
|
+
standing: {
|
|
79
|
+
/** Out of good standing (§3.3): the first sentence of {@link BillingLabels.planEnded}
|
|
80
|
+
* and {@link BillingLabels.lockReason}, which the plan-ended banner goes on with —
|
|
81
|
+
* "Your plan has ended." */
|
|
82
|
+
ended: string;
|
|
83
|
+
/** `past_due` while the provider retries (§3.3): still in good standing, and NO date —
|
|
84
|
+
* the payment-failed banner carries the action, and "renews on" would promise what
|
|
85
|
+
* the retries may not keep. */
|
|
86
|
+
pastDue: string;
|
|
87
|
+
/** `trialing`, with its `trial_ends_at`: "Trial until 9 Nov 2026". */
|
|
88
|
+
trialUntil: (date: string) => string;
|
|
89
|
+
/** `comped`, with its `comped_until`: "Free until 9 Nov 2026". */
|
|
90
|
+
freeUntil: (date: string) => string;
|
|
91
|
+
/** `comped` without an end (§12.8): "Free, with no end date". */
|
|
92
|
+
free: string;
|
|
93
|
+
/** Set to cancel at the period's end (`cancel_at_period_end`): "Ends on 9 Nov 2026". */
|
|
94
|
+
endsOn: (date: string) => string;
|
|
95
|
+
/** `active`, at `current_period_end`: "Renews on 9 Nov 2026". */
|
|
96
|
+
renewsOn: (date: string) => string;
|
|
97
|
+
};
|
|
72
98
|
/** A trial ends in `days` calendar days — 0 is today, 1 tomorrow. */
|
|
73
99
|
trialEnding: (days: number) => string;
|
|
74
100
|
/** A free grant (`comped_until`: a beta user's 12 months, an operator's grant) ends in
|
|
@@ -42,6 +42,15 @@ const DEFAULT_BILLING_LABELS = {
|
|
|
42
42
|
expired: "Expired",
|
|
43
43
|
comped: "Complimentary"
|
|
44
44
|
},
|
|
45
|
+
standing: {
|
|
46
|
+
ended: "Your plan has ended.",
|
|
47
|
+
pastDue: "Payment failed \u2014 we\u2019re retrying. Your plan stays active meanwhile.",
|
|
48
|
+
trialUntil: (date) => `Trial until ${date}`,
|
|
49
|
+
freeUntil: (date) => `Free until ${date}`,
|
|
50
|
+
free: "Free, with no end date",
|
|
51
|
+
endsOn: (date) => `Ends on ${date}`,
|
|
52
|
+
renewsOn: (date) => `Renews on ${date}`
|
|
53
|
+
},
|
|
45
54
|
trialEnding: (days) => `Your trial ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,
|
|
46
55
|
grantEnding: (days) => `Your free access ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,
|
|
47
56
|
paymentFailed: "Your last payment didn\u2019t go through. Update your payment method to keep your plan.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/billing/billing-labels.ts"],"sourcesContent":["import { useKitLabels } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\n\n/**\n * The subscription's status vocabulary (docs/billing-harmonization.md §3.2) — server-kit's\n * `SubscriptionStatus`, value for value:\n *\n * - `trialing` — the 30-day trial without a card (§2.7), or a pure guest's row whose trial\n * has not started (§12.9, `trial_ends_at` empty);\n * - `active` — paid;\n * - `past_due` — a payment failed; in good standing during the provider's retries (§3.3);\n * - `canceled` — the payer cancelled; the American spelling is the wire's, the word shown\n * is \"Cancelled\";\n * - `expired` — a trial, a grant or a failed subscription ran out: read-only (§2.3);\n * - `comped` — free by the operator's grant or as a beta user (§3.2, `comped_until`).\n */\nexport type SubscriptionStatus = \"trialing\" | \"active\" | \"past_due\" | \"canceled\" | \"expired\" | \"comped\";\n\n/** Every status, in the order a lifecycle reads: trial, paid, trouble, ended, granted. */\nexport const SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[] = Object.freeze([\n \"trialing\",\n \"active\",\n \"past_due\",\n \"canceled\",\n \"expired\",\n \"comped\",\n]);\n\n/**\n * The words of the subscription parts — the `billing` namespace of `<UiKitProvider labels>`\n * (docs/billing-harmonization.md §7, §10; ui-kit 0.32.0).\n *\n * WORDING (§12.24): the English group is \"Subscription\", never \"Plan\" — keksdose's \"plan\"\n * is the month's budget plan (\"One plan per month\"), and two meanings of one word on one\n * settings page is the clash the review found. \"Plan\" stays the word for WHAT is bought\n * (Free, Pro), \"subscription\" for the standing arrangement. German says \"Tarif\" for the\n * plan and \"Abonnement\" for the subscription (§10).\n *\n * Prices are GROSS (§12.17): Swiss consumer law (PBV) wants a consumer price with the\n * MWST in it, so the cards say so (`vatIncluded`).\n */\nexport interface BillingLabels {\n /** The settings group or admin section the parts live on (§7, §12.24): \"Subscription\". */\n group: string;\n\n /* ── PlanPicker / PlanCard (§7) ── */\n /** The picker's name when it has no visible `legend`: \"Plans\". */\n plans: string;\n /** The name of the billing-period switch: \"Billing period\". */\n interval: string;\n /** The switch's two options — yearly first (§12.17). */\n yearly: string;\n monthly: string;\n /** The name of the currency switch: \"Currency\". */\n currency: string;\n /** After a price: \"per year\", \"per month\". */\n perYear: string;\n perMonth: string;\n /** Under a price (§12.17): \"VAT included\". German: \"inkl. MWST\". */\n vatIncluded: string;\n /** A plan with no price, or a price of 0: \"Free\". */\n free: string;\n /** A plan with prices, but none for this currency and period. */\n notOffered: string;\n /** A limit of `null` (§3.1): \"Unlimited\". */\n unlimited: string;\n /** The accessible names of a card's two lists. */\n limits: string;\n features: string;\n /** The chip on the payer's current plan. */\n current: string;\n /** The picker's button for `plan` (the plan's display name) with no current plan —\n * during a trial, or after a plan ended: \"Choose Pro\". */\n choose: (plan: string) => string;\n /** …for a plan after the current one in the catalogue's order: \"Upgrade to Pro\". */\n upgrade: (plan: string) => string;\n /** …for a plan before it: \"Switch to Free\". */\n downgrade: (plan: string) => string;\n /** Why the button is off while the current plan is the one picked. */\n isCurrent: string;\n /** Why it is off while nothing is picked. */\n pickFirst: string;\n\n /* ── SubscriptionStatusChip (§6) ── */\n /** One word per status. */\n status: Record<SubscriptionStatus, string>;\n\n /* ── BillingBanner (§3.3, §12.6, §12.21) ── */\n /** A trial ends in `days` calendar days — 0 is today, 1 tomorrow. */\n trialEnding: (days: number) => string;\n /** A free grant (`comped_until`: a beta user's 12 months, an operator's grant) ends in\n * `days` calendar days. */\n grantEnding: (days: number) => string;\n /** The first failed payment (`past_due`), while the provider retries. */\n paymentFailed: string;\n /** Out of good standing: read-only, never locked (§2.3). */\n planEnded: string;\n /**\n * A guest in someone else's item that is read-only for billing (§12.6). It never says\n * WHY beyond \"for now\": the owner's payment status is the owner's personal data.\n * `item` is the item's own name (\"Household 2026\"), shown in quotes — or `undefined`.\n */\n guestReadOnly: (item?: string) => string;\n /** Back from checkout before the provider's webhook landed (§12.21). */\n processing: string;\n /** The action of the trial, grant and plan-ended banners, and of `PlanLimitNotice`'s\n * `upgrade` mode: \"Choose a plan\". */\n choosePlan: string;\n /** The payment-failed banner's action, to the provider's portal: \"Update payment method\". */\n updatePayment: string;\n /**\n * 0.42 (§16.7 A, §16.8): the payment-failed banner for an admin who isn't the buyer\n * (`portal: \"buyer_only\"`), with no button — only the buyer can change the card.\n * `buyer` is their display name, or `undefined` when they are gone (\"the admin who\n * bought the subscription\").\n */\n paymentFailedAskBuyer: (buyer?: string) => string;\n /** The processing banner's action, when the app gives one: \"Check again\". */\n checkAgain: string;\n\n /* ── The read-only lock (§3.3, §12.6) ── */\n /** The `WriteLockProvider` reason for the payer (§3.3, verbatim). A guest's reason is\n * {@link guestReadOnly}. */\n lockReason: string;\n\n /* ── PlanLimitNotice (§3.4) ── */\n /** The notice's first line: \"Plan limit reached\". */\n limitReached: string;\n /** Mode `upgrade`: what the action does. */\n limitUpgrade: string;\n /** Mode `contact` (the operator grants plans by hand, keksdose's today). */\n limitContact: string;\n /** Mode `contact`'s action, a mail to support: \"Ask for more\". */\n askForMore: string;\n /** The meter's value: \"3 of 3\". Both figures are formatted already. */\n usage: (used: string, limit: string) => string;\n /** The subject of the `contact` mail; `dimension` is the app's name for it (\"Budgets\"). */\n contactSubject: (dimension: string) => string;\n /**\n * The figure line of the limit's toast (`usePlanLimitToast`, §14.11) and of an\n * operator's plan change over the new limit (§14.12): \"Budgets: 3 of 3\". A key of its\n * own rather than `${dimension}: ${usage}`, because French puts a space before the\n * colon. Both figures are formatted already.\n */\n limitUsageLine: (dimension: string, used: string, limit: string) => string;\n\n /* ── The provider's portal (§7, §12.26) ── */\n /** The portal: payment method and invoices (§2.8, the provider hosts both). */\n manage: string;\n /**\n * The visible cancel link on the app's subscription page (§12.26). German: §312k BGB\n * wants the button that leads to the cancellation labelled \"Verträge hier kündigen\" or\n * as unambiguously — a translator should use exactly that.\n */\n cancel: string;\n /**\n * In place of both while the payer hasn't reached the provider (the overview's\n * `at_provider` is false, §14.5): there is no customer to open a portal for yet. Also\n * the words for `409 billing_not_at_provider`, a stale page that asked anyway.\n */\n notAtProvider: string;\n\n /* ── The portal for the buyer only, the in-app cancel (0.42, §16) ── */\n /**\n * In place of \"Payment and invoices\" for an admin who isn't the buyer (`portal:\n * \"buyer_only\"`, §16.2 decision 3): the provider's portal opens only for the buyer.\n * `buyer` is their display name — or `undefined` when they are gone or have left the\n * company, and the words then say \"the admin who bought the subscription\".\n */\n buyerHolds: (buyer?: string) => string;\n /** `CancelSubscriptionConfirm`'s title: \"Cancel subscription\". */\n cancelConfirmTitle: string;\n /** Its body; `date` is the period's end, formatted: \"Your subscription ends on\n * 9 Nov 2026. Until then everything works as before.\" */\n cancelConfirmBody: (date: string) => string;\n /** Its confirm button (§16.4): \"Cancel subscription now\". German: \"Jetzt kündigen\",\n * the confirmation button's words in §312k BGB. */\n cancelConfirmAction: string;\n /** In place of \"Cancel subscription\" while a cancel is scheduled (§16.7 C, `POST\n * /billing/resume`): \"Keep subscription\". */\n keepSubscription: string;\n /** 0.43 (§17.3): the muted note under the actions while the portal opens in a new tab —\n * `provider` the provider's name (\"Paddle\"), `app` the app's; without the app, \"this\n * page\". \"Payment and invoices open at Paddle in a new tab; Kastlan stays open here.\" */\n portalOpensNewTab: (provider: string, app?: string) => string;\n\n /* ── The pay page (§14.4) ── */\n /* Built into the static page `dist/pay/` at the kit's build: it has no React and no\n provider, so an app's override never reaches it. */\n /** While the checkout opens. */\n payOpening: string;\n /** A link without a transaction, or billing switched off: nothing to pay. */\n payNothing: string;\n /** Paddle's script didn't load, or the page's configuration is wrong. */\n payFailed: string;\n /** The buyer closed the checkout without paying. */\n payBack: string;\n /** 0.39 (§15.2.5): paid, for a transaction that names no app the page knows (a\n * payment-method update from Paddle's e-mail) — no way back to offer. */\n payDone: string;\n\n /* ── PlanSwitchConfirm (0.39, §15.2.10) ── */\n /** The dialog's title: \"Change plan\". */\n switchTitle: string;\n /** The line naming both plans, each with its billing period: \"From Pro (Monthly) to\n * Business (Yearly)\". */\n switchFromTo: (from: string, to: string) => string;\n /** What an upgrade charges at once (Paddle's preview, prorated): \"Charged now\". */\n switchChargedNow: string;\n /** What a downgrade credits — the customer's balance, which Paddle takes off the next\n * charges: \"Credited towards your next charges\". */\n switchCredited: string;\n /** The next regular charge; `date` is formatted: \"Next charge on 9 Nov 2026\". */\n switchNextCharge: (date: string) => string;\n /** The confirm button: \"Change plan\". */\n switchConfirm: string;\n\n /* ── The permanent-grant warning (0.42, §16.2 decision 5, §16.8 B) ── */\n /** The warning's title, before a checkout (`PlanPicker`) or a switch\n * (`PlanSwitchConfirm`) under a grant without an end: \"Your free access continues\". */\n grantWarningTitle: string;\n /** What the purchase does under it: nothing to the plan while the grant runs. */\n grantWarningBody: string;\n /** `PlanPicker`'s button that goes on to the checkout: \"Buy anyway\". */\n grantWarningAction: string;\n\n /* ── Offline (§12.5) ── */\n /** Changes refused as `billing_read_only` inside a sync reply, kept queued and sent\n * after payment: \"3 changes waiting for a plan\". */\n waitingChanges: (count: number) => string;\n /** The sign-out guard's way out for someone who will not pay. */\n discardWaiting: string;\n\n /* ── Refusals with no part of their own (0.32.1, kastlan's 0.32 report) ── */\n /**\n * `billing_not_configured`: billing is on but the provider's settings are missing — the\n * operator's mistake, so the words ask the payer to wait, not to act. For the toast or\n * alert after \"Choose a plan\" or \"Payment and invoices\" (`isBillingError(err,\n * \"billing_not_configured\")`). `billing_read_only` and `plan_limit` need none: the\n * write lock, the banner and `PlanLimitNotice` say them.\n */\n notConfigured: string;\n /** `billing_disabled`: billing is switched off on this server (§2.9), for a page or a\n * link that reached it anyway. */\n disabled: string;\n /* 0.33.0 — server-kit 0.7's refusals (§14.2). `billing_not_at_provider` is\n {@link notAtProvider}'s: a stale page that asked for the portal anyway. */\n /** `billing_provider_unavailable` (502): the provider didn't answer. Try again. */\n providerUnavailable: string;\n /** `billing_already_subscribed` (409): a second checkout while a subscription runs\n * (§14.6). Since 0.39 plan changes go through the app's own switch (§15.2.10), not the\n * portal, and the words say so. */\n alreadySubscribed: string;\n /** `billing_plan_not_sold` (422): the catalogue doesn't sell this plan with that\n * period or currency — at a checkout, or (0.39) a switch to a plan no longer sold. */\n planNotSold: string;\n /** 0.39 (§15.2.10): `billing_not_subscribed` (409), a plan switch with no running\n * provider subscription — the page offers the checkout instead. */\n notSubscribed: string;\n /** 0.39: `billing_plan_unchanged` (409), a switch to the price the payer pays now. */\n planUnchanged: string;\n}\n\n/** \"today\", \"tomorrow\", \"in 5 days\". */\nfunction inDays(days: number): string {\n if (days <= 0) return \"today\";\n if (days === 1) return \"tomorrow\";\n return `in ${days} days`;\n}\n\nexport const DEFAULT_BILLING_LABELS: BillingLabels = {\n group: \"Subscription\",\n\n plans: \"Plans\",\n interval: \"Billing period\",\n yearly: \"Yearly\",\n monthly: \"Monthly\",\n currency: \"Currency\",\n perYear: \"per year\",\n perMonth: \"per month\",\n vatIncluded: \"VAT included\",\n free: \"Free\",\n notOffered: \"Not offered for this billing period\",\n unlimited: \"Unlimited\",\n limits: \"Limits\",\n features: \"Included\",\n current: \"Current plan\",\n choose: (plan) => `Choose ${plan}`,\n upgrade: (plan) => `Upgrade to ${plan}`,\n downgrade: (plan) => `Switch to ${plan}`,\n isCurrent: \"This is your current plan.\",\n pickFirst: \"Pick a plan first.\",\n\n status: {\n trialing: \"Trial\",\n active: \"Active\",\n past_due: \"Payment overdue\",\n canceled: \"Cancelled\",\n expired: \"Expired\",\n comped: \"Complimentary\",\n },\n\n trialEnding: (days) =>\n `Your trial ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,\n grantEnding: (days) =>\n `Your free access ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,\n paymentFailed: \"Your last payment didn’t go through. Update your payment method to keep your plan.\",\n planEnded: \"Your plan has ended. You can still view and export everything; choose a plan to make changes again.\",\n guestReadOnly: (item) =>\n item\n ? `“${item}” is read-only for now; its owner can lift that.`\n : \"This is read-only for now; its owner can lift that.\",\n processing:\n \"Your payment is being processed. Your plan starts as soon as it’s confirmed; this page updates by itself.\",\n choosePlan: \"Choose a plan\",\n updatePayment: \"Update payment method\",\n paymentFailedAskBuyer: (buyer) =>\n buyer\n ? `The payment failed. ${buyer} can update the payment method.`\n : \"The payment failed. The admin who bought the subscription can update the payment method.\",\n checkAgain: \"Check again\",\n\n lockReason: \"Your plan has ended. Choose a plan to make changes again.\",\n\n limitReached: \"Plan limit reached\",\n limitUpgrade: \"To add more, choose a plan with a higher limit.\",\n limitContact: \"To add more, ask us for a higher limit.\",\n askForMore: \"Ask for more\",\n usage: (used, limit) => `${used} of ${limit}`,\n contactSubject: (dimension) => `Plan limit: ${dimension}`,\n limitUsageLine: (dimension, used, limit) => `${dimension}: ${used} of ${limit}`,\n\n manage: \"Payment and invoices\",\n cancel: \"Cancel subscription\",\n notAtProvider: \"Nothing to manage yet. Payments and invoices appear here once you have a paid plan.\",\n buyerHolds: (buyer) =>\n buyer\n ? `The payment method and the invoices are with ${buyer}, who bought the subscription.`\n : \"The payment method and the invoices are with the admin who bought the subscription.\",\n cancelConfirmTitle: \"Cancel subscription\",\n cancelConfirmBody: (date) => `Your subscription ends on ${date}. Until then everything works as before.`,\n cancelConfirmAction: \"Cancel subscription now\",\n keepSubscription: \"Keep subscription\",\n portalOpensNewTab: (provider, app) =>\n `Payment and invoices open at ${provider} in a new tab; ${app ? `${app} stays open here` : \"this page stays open\"}.`,\n\n payOpening: \"Opening the secure checkout…\",\n payNothing: \"There’s no payment to make here. To choose a plan, go back to the app.\",\n payFailed: \"The checkout couldn’t be opened. Please go back and try again in a moment.\",\n payBack: \"The checkout was closed. You can go back, or reload this page to open it again.\",\n payDone: \"Payment complete. You can close this page.\",\n\n switchTitle: \"Change plan\",\n switchFromTo: (from, to) => `From ${from} to ${to}`,\n switchChargedNow: \"Charged now\",\n switchCredited: \"Credited towards your next charges\",\n switchNextCharge: (date) => `Next charge on ${date}`,\n switchConfirm: \"Change plan\",\n\n grantWarningTitle: \"Your free access continues\",\n grantWarningBody:\n \"You have free access with no end date. A purchase won’t change your plan while the free access runs: you would pay for a plan that doesn’t apply.\",\n grantWarningAction: \"Buy anyway\",\n\n waitingChanges: (count) => (count === 1 ? \"1 change waiting for a plan\" : `${count} changes waiting for a plan`),\n discardWaiting: \"Discard waiting changes\",\n\n notConfigured: \"Payments aren't set up yet. Please try again later.\",\n disabled: \"Subscriptions aren't available here.\",\n providerUnavailable: \"The payment provider can’t be reached right now. Please try again in a moment.\",\n alreadySubscribed: \"You already have a subscription. Change your plan above.\",\n planNotSold: \"This plan isn’t offered for that billing period or currency. Please choose another.\",\n notSubscribed: \"There’s no running subscription to change. Choose a plan to subscribe.\",\n planUnchanged: \"That’s already your plan. Pick another plan or billing period.\",\n};\n\n/** The `billing` namespace, resolved: English, then the provider, then `labels` — which\n * may be partial at every depth (`{ status: { comped: \"Beta\" } }`). */\nexport function useBillingLabels(labels?: LabelOverride<BillingLabels>): BillingLabels {\n return useKitLabels(\"billing\", DEFAULT_BILLING_LABELS, labels);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/billing/billing-labels.ts"],"sourcesContent":["import { useKitLabels } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\n\n/**\n * The subscription's status vocabulary (docs/billing-harmonization.md §3.2) — server-kit's\n * `SubscriptionStatus`, value for value:\n *\n * - `trialing` — the 30-day trial without a card (§2.7), or a pure guest's row whose trial\n * has not started (§12.9, `trial_ends_at` empty);\n * - `active` — paid;\n * - `past_due` — a payment failed; in good standing during the provider's retries (§3.3);\n * - `canceled` — the payer cancelled; the American spelling is the wire's, the word shown\n * is \"Cancelled\";\n * - `expired` — a trial, a grant or a failed subscription ran out: read-only (§2.3);\n * - `comped` — free by the operator's grant or as a beta user (§3.2, `comped_until`).\n */\nexport type SubscriptionStatus = \"trialing\" | \"active\" | \"past_due\" | \"canceled\" | \"expired\" | \"comped\";\n\n/** Every status, in the order a lifecycle reads: trial, paid, trouble, ended, granted. */\nexport const SUBSCRIPTION_STATUSES: readonly SubscriptionStatus[] = Object.freeze([\n \"trialing\",\n \"active\",\n \"past_due\",\n \"canceled\",\n \"expired\",\n \"comped\",\n]);\n\n/**\n * The words of the subscription parts — the `billing` namespace of `<UiKitProvider labels>`\n * (docs/billing-harmonization.md §7, §10; ui-kit 0.32.0).\n *\n * WORDING (§12.24): the English group is \"Subscription\", never \"Plan\" — keksdose's \"plan\"\n * is the month's budget plan (\"One plan per month\"), and two meanings of one word on one\n * settings page is the clash the review found. \"Plan\" stays the word for WHAT is bought\n * (Free, Pro), \"subscription\" for the standing arrangement. German says \"Tarif\" for the\n * plan and \"Abonnement\" for the subscription (§10).\n *\n * Prices are GROSS (§12.17): Swiss consumer law (PBV) wants a consumer price with the\n * MWST in it, so the cards say so (`vatIncluded`).\n */\nexport interface BillingLabels {\n /** The settings group or admin section the parts live on (§7, §12.24): \"Subscription\". */\n group: string;\n\n /* ── PlanPicker / PlanCard (§7) ── */\n /** The picker's name when it has no visible `legend`: \"Plans\". */\n plans: string;\n /** The name of the billing-period switch: \"Billing period\". */\n interval: string;\n /** The switch's two options — yearly first (§12.17). */\n yearly: string;\n monthly: string;\n /** The name of the currency switch: \"Currency\". */\n currency: string;\n /** After a price: \"per year\", \"per month\". */\n perYear: string;\n perMonth: string;\n /** Under a price (§12.17): \"VAT included\". German: \"inkl. MWST\". */\n vatIncluded: string;\n /** A plan with no price, or a price of 0: \"Free\". */\n free: string;\n /** A plan with prices, but none for this currency and period. */\n notOffered: string;\n /** A limit of `null` (§3.1): \"Unlimited\". */\n unlimited: string;\n /** The accessible names of a card's two lists. */\n limits: string;\n features: string;\n /** The chip on the payer's current plan. */\n current: string;\n /** The picker's button for `plan` (the plan's display name) with no current plan —\n * during a trial, or after a plan ended: \"Choose Pro\". */\n choose: (plan: string) => string;\n /** …for a plan after the current one in the catalogue's order: \"Upgrade to Pro\". */\n upgrade: (plan: string) => string;\n /** …for a plan before it: \"Switch to Free\". */\n downgrade: (plan: string) => string;\n /** Why the button is off while the current plan is the one picked. */\n isCurrent: string;\n /** Why it is off while nothing is picked. */\n pickFirst: string;\n\n /* ── SubscriptionStatusChip (§6) ── */\n /** One word per status. */\n status: Record<SubscriptionStatus, string>;\n\n /* ── SubscriptionStanding (0.55, §7; Marcel 2026-11-04) ── */\n /**\n * The subscription's standing in one line, under the plan's name: `SubscriptionStanding`\n * and `subscriptionStanding` say the FIRST that applies, in this order. `date` arrives\n * formatted. The words are keksdose's reviewed ones (`billing.trial_until`, …), which\n * kastlan's page said in words of its own (kastlan reuse audit 2026-11-02, proposal 3).\n */\n standing: {\n /** Out of good standing (§3.3): the first sentence of {@link BillingLabels.planEnded}\n * and {@link BillingLabels.lockReason}, which the plan-ended banner goes on with —\n * \"Your plan has ended.\" */\n ended: string;\n /** `past_due` while the provider retries (§3.3): still in good standing, and NO date —\n * the payment-failed banner carries the action, and \"renews on\" would promise what\n * the retries may not keep. */\n pastDue: string;\n /** `trialing`, with its `trial_ends_at`: \"Trial until 9 Nov 2026\". */\n trialUntil: (date: string) => string;\n /** `comped`, with its `comped_until`: \"Free until 9 Nov 2026\". */\n freeUntil: (date: string) => string;\n /** `comped` without an end (§12.8): \"Free, with no end date\". */\n free: string;\n /** Set to cancel at the period's end (`cancel_at_period_end`): \"Ends on 9 Nov 2026\". */\n endsOn: (date: string) => string;\n /** `active`, at `current_period_end`: \"Renews on 9 Nov 2026\". */\n renewsOn: (date: string) => string;\n };\n\n /* ── BillingBanner (§3.3, §12.6, §12.21) ── */\n /** A trial ends in `days` calendar days — 0 is today, 1 tomorrow. */\n trialEnding: (days: number) => string;\n /** A free grant (`comped_until`: a beta user's 12 months, an operator's grant) ends in\n * `days` calendar days. */\n grantEnding: (days: number) => string;\n /** The first failed payment (`past_due`), while the provider retries. */\n paymentFailed: string;\n /** Out of good standing: read-only, never locked (§2.3). */\n planEnded: string;\n /**\n * A guest in someone else's item that is read-only for billing (§12.6). It never says\n * WHY beyond \"for now\": the owner's payment status is the owner's personal data.\n * `item` is the item's own name (\"Household 2026\"), shown in quotes — or `undefined`.\n */\n guestReadOnly: (item?: string) => string;\n /** Back from checkout before the provider's webhook landed (§12.21). */\n processing: string;\n /** The action of the trial, grant and plan-ended banners, and of `PlanLimitNotice`'s\n * `upgrade` mode: \"Choose a plan\". */\n choosePlan: string;\n /** The payment-failed banner's action, to the provider's portal: \"Update payment method\". */\n updatePayment: string;\n /**\n * 0.42 (§16.7 A, §16.8): the payment-failed banner for an admin who isn't the buyer\n * (`portal: \"buyer_only\"`), with no button — only the buyer can change the card.\n * `buyer` is their display name, or `undefined` when they are gone (\"the admin who\n * bought the subscription\").\n */\n paymentFailedAskBuyer: (buyer?: string) => string;\n /** The processing banner's action, when the app gives one: \"Check again\". */\n checkAgain: string;\n\n /* ── The read-only lock (§3.3, §12.6) ── */\n /** The `WriteLockProvider` reason for the payer (§3.3, verbatim). A guest's reason is\n * {@link guestReadOnly}. */\n lockReason: string;\n\n /* ── PlanLimitNotice (§3.4) ── */\n /** The notice's first line: \"Plan limit reached\". */\n limitReached: string;\n /** Mode `upgrade`: what the action does. */\n limitUpgrade: string;\n /** Mode `contact` (the operator grants plans by hand, keksdose's today). */\n limitContact: string;\n /** Mode `contact`'s action, a mail to support: \"Ask for more\". */\n askForMore: string;\n /** The meter's value: \"3 of 3\". Both figures are formatted already. */\n usage: (used: string, limit: string) => string;\n /** The subject of the `contact` mail; `dimension` is the app's name for it (\"Budgets\"). */\n contactSubject: (dimension: string) => string;\n /**\n * The figure line of the limit's toast (`usePlanLimitToast`, §14.11) and of an\n * operator's plan change over the new limit (§14.12): \"Budgets: 3 of 3\". A key of its\n * own rather than `${dimension}: ${usage}`, because French puts a space before the\n * colon. Both figures are formatted already.\n */\n limitUsageLine: (dimension: string, used: string, limit: string) => string;\n\n /* ── The provider's portal (§7, §12.26) ── */\n /** The portal: payment method and invoices (§2.8, the provider hosts both). */\n manage: string;\n /**\n * The visible cancel link on the app's subscription page (§12.26). German: §312k BGB\n * wants the button that leads to the cancellation labelled \"Verträge hier kündigen\" or\n * as unambiguously — a translator should use exactly that.\n */\n cancel: string;\n /**\n * In place of both while the payer hasn't reached the provider (the overview's\n * `at_provider` is false, §14.5): there is no customer to open a portal for yet. Also\n * the words for `409 billing_not_at_provider`, a stale page that asked anyway.\n */\n notAtProvider: string;\n\n /* ── The portal for the buyer only, the in-app cancel (0.42, §16) ── */\n /**\n * In place of \"Payment and invoices\" for an admin who isn't the buyer (`portal:\n * \"buyer_only\"`, §16.2 decision 3): the provider's portal opens only for the buyer.\n * `buyer` is their display name — or `undefined` when they are gone or have left the\n * company, and the words then say \"the admin who bought the subscription\".\n */\n buyerHolds: (buyer?: string) => string;\n /** `CancelSubscriptionConfirm`'s title: \"Cancel subscription\". */\n cancelConfirmTitle: string;\n /** Its body; `date` is the period's end, formatted: \"Your subscription ends on\n * 9 Nov 2026. Until then everything works as before.\" */\n cancelConfirmBody: (date: string) => string;\n /** Its confirm button (§16.4): \"Cancel subscription now\". German: \"Jetzt kündigen\",\n * the confirmation button's words in §312k BGB. */\n cancelConfirmAction: string;\n /** In place of \"Cancel subscription\" while a cancel is scheduled (§16.7 C, `POST\n * /billing/resume`): \"Keep subscription\". */\n keepSubscription: string;\n /** 0.43 (§17.3): the muted note under the actions while the portal opens in a new tab —\n * `provider` the provider's name (\"Paddle\"), `app` the app's; without the app, \"this\n * page\". \"Payment and invoices open at Paddle in a new tab; Kastlan stays open here.\" */\n portalOpensNewTab: (provider: string, app?: string) => string;\n\n /* ── The pay page (§14.4) ── */\n /* Built into the static page `dist/pay/` at the kit's build: it has no React and no\n provider, so an app's override never reaches it. */\n /** While the checkout opens. */\n payOpening: string;\n /** A link without a transaction, or billing switched off: nothing to pay. */\n payNothing: string;\n /** Paddle's script didn't load, or the page's configuration is wrong. */\n payFailed: string;\n /** The buyer closed the checkout without paying. */\n payBack: string;\n /** 0.39 (§15.2.5): paid, for a transaction that names no app the page knows (a\n * payment-method update from Paddle's e-mail) — no way back to offer. */\n payDone: string;\n\n /* ── PlanSwitchConfirm (0.39, §15.2.10) ── */\n /** The dialog's title: \"Change plan\". */\n switchTitle: string;\n /** The line naming both plans, each with its billing period: \"From Pro (Monthly) to\n * Business (Yearly)\". */\n switchFromTo: (from: string, to: string) => string;\n /** What an upgrade charges at once (Paddle's preview, prorated): \"Charged now\". */\n switchChargedNow: string;\n /** What a downgrade credits — the customer's balance, which Paddle takes off the next\n * charges: \"Credited towards your next charges\". */\n switchCredited: string;\n /** The next regular charge; `date` is formatted: \"Next charge on 9 Nov 2026\". */\n switchNextCharge: (date: string) => string;\n /** The confirm button: \"Change plan\". */\n switchConfirm: string;\n\n /* ── The permanent-grant warning (0.42, §16.2 decision 5, §16.8 B) ── */\n /** The warning's title, before a checkout (`PlanPicker`) or a switch\n * (`PlanSwitchConfirm`) under a grant without an end: \"Your free access continues\". */\n grantWarningTitle: string;\n /** What the purchase does under it: nothing to the plan while the grant runs. */\n grantWarningBody: string;\n /** `PlanPicker`'s button that goes on to the checkout: \"Buy anyway\". */\n grantWarningAction: string;\n\n /* ── Offline (§12.5) ── */\n /** Changes refused as `billing_read_only` inside a sync reply, kept queued and sent\n * after payment: \"3 changes waiting for a plan\". */\n waitingChanges: (count: number) => string;\n /** The sign-out guard's way out for someone who will not pay. */\n discardWaiting: string;\n\n /* ── Refusals with no part of their own (0.32.1, kastlan's 0.32 report) ── */\n /**\n * `billing_not_configured`: billing is on but the provider's settings are missing — the\n * operator's mistake, so the words ask the payer to wait, not to act. For the toast or\n * alert after \"Choose a plan\" or \"Payment and invoices\" (`isBillingError(err,\n * \"billing_not_configured\")`). `billing_read_only` and `plan_limit` need none: the\n * write lock, the banner and `PlanLimitNotice` say them.\n */\n notConfigured: string;\n /** `billing_disabled`: billing is switched off on this server (§2.9), for a page or a\n * link that reached it anyway. */\n disabled: string;\n /* 0.33.0 — server-kit 0.7's refusals (§14.2). `billing_not_at_provider` is\n {@link notAtProvider}'s: a stale page that asked for the portal anyway. */\n /** `billing_provider_unavailable` (502): the provider didn't answer. Try again. */\n providerUnavailable: string;\n /** `billing_already_subscribed` (409): a second checkout while a subscription runs\n * (§14.6). Since 0.39 plan changes go through the app's own switch (§15.2.10), not the\n * portal, and the words say so. */\n alreadySubscribed: string;\n /** `billing_plan_not_sold` (422): the catalogue doesn't sell this plan with that\n * period or currency — at a checkout, or (0.39) a switch to a plan no longer sold. */\n planNotSold: string;\n /** 0.39 (§15.2.10): `billing_not_subscribed` (409), a plan switch with no running\n * provider subscription — the page offers the checkout instead. */\n notSubscribed: string;\n /** 0.39: `billing_plan_unchanged` (409), a switch to the price the payer pays now. */\n planUnchanged: string;\n}\n\n/** \"today\", \"tomorrow\", \"in 5 days\". */\nfunction inDays(days: number): string {\n if (days <= 0) return \"today\";\n if (days === 1) return \"tomorrow\";\n return `in ${days} days`;\n}\n\nexport const DEFAULT_BILLING_LABELS: BillingLabels = {\n group: \"Subscription\",\n\n plans: \"Plans\",\n interval: \"Billing period\",\n yearly: \"Yearly\",\n monthly: \"Monthly\",\n currency: \"Currency\",\n perYear: \"per year\",\n perMonth: \"per month\",\n vatIncluded: \"VAT included\",\n free: \"Free\",\n notOffered: \"Not offered for this billing period\",\n unlimited: \"Unlimited\",\n limits: \"Limits\",\n features: \"Included\",\n current: \"Current plan\",\n choose: (plan) => `Choose ${plan}`,\n upgrade: (plan) => `Upgrade to ${plan}`,\n downgrade: (plan) => `Switch to ${plan}`,\n isCurrent: \"This is your current plan.\",\n pickFirst: \"Pick a plan first.\",\n\n status: {\n trialing: \"Trial\",\n active: \"Active\",\n past_due: \"Payment overdue\",\n canceled: \"Cancelled\",\n expired: \"Expired\",\n comped: \"Complimentary\",\n },\n\n standing: {\n ended: \"Your plan has ended.\",\n pastDue: \"Payment failed — we’re retrying. Your plan stays active meanwhile.\",\n trialUntil: (date) => `Trial until ${date}`,\n freeUntil: (date) => `Free until ${date}`,\n free: \"Free, with no end date\",\n endsOn: (date) => `Ends on ${date}`,\n renewsOn: (date) => `Renews on ${date}`,\n },\n\n trialEnding: (days) =>\n `Your trial ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,\n grantEnding: (days) =>\n `Your free access ends ${inDays(days)}. After that you can still view everything, but changes need a plan.`,\n paymentFailed: \"Your last payment didn’t go through. Update your payment method to keep your plan.\",\n planEnded: \"Your plan has ended. You can still view and export everything; choose a plan to make changes again.\",\n guestReadOnly: (item) =>\n item\n ? `“${item}” is read-only for now; its owner can lift that.`\n : \"This is read-only for now; its owner can lift that.\",\n processing:\n \"Your payment is being processed. Your plan starts as soon as it’s confirmed; this page updates by itself.\",\n choosePlan: \"Choose a plan\",\n updatePayment: \"Update payment method\",\n paymentFailedAskBuyer: (buyer) =>\n buyer\n ? `The payment failed. ${buyer} can update the payment method.`\n : \"The payment failed. The admin who bought the subscription can update the payment method.\",\n checkAgain: \"Check again\",\n\n lockReason: \"Your plan has ended. Choose a plan to make changes again.\",\n\n limitReached: \"Plan limit reached\",\n limitUpgrade: \"To add more, choose a plan with a higher limit.\",\n limitContact: \"To add more, ask us for a higher limit.\",\n askForMore: \"Ask for more\",\n usage: (used, limit) => `${used} of ${limit}`,\n contactSubject: (dimension) => `Plan limit: ${dimension}`,\n limitUsageLine: (dimension, used, limit) => `${dimension}: ${used} of ${limit}`,\n\n manage: \"Payment and invoices\",\n cancel: \"Cancel subscription\",\n notAtProvider: \"Nothing to manage yet. Payments and invoices appear here once you have a paid plan.\",\n buyerHolds: (buyer) =>\n buyer\n ? `The payment method and the invoices are with ${buyer}, who bought the subscription.`\n : \"The payment method and the invoices are with the admin who bought the subscription.\",\n cancelConfirmTitle: \"Cancel subscription\",\n cancelConfirmBody: (date) => `Your subscription ends on ${date}. Until then everything works as before.`,\n cancelConfirmAction: \"Cancel subscription now\",\n keepSubscription: \"Keep subscription\",\n portalOpensNewTab: (provider, app) =>\n `Payment and invoices open at ${provider} in a new tab; ${app ? `${app} stays open here` : \"this page stays open\"}.`,\n\n payOpening: \"Opening the secure checkout…\",\n payNothing: \"There’s no payment to make here. To choose a plan, go back to the app.\",\n payFailed: \"The checkout couldn’t be opened. Please go back and try again in a moment.\",\n payBack: \"The checkout was closed. You can go back, or reload this page to open it again.\",\n payDone: \"Payment complete. You can close this page.\",\n\n switchTitle: \"Change plan\",\n switchFromTo: (from, to) => `From ${from} to ${to}`,\n switchChargedNow: \"Charged now\",\n switchCredited: \"Credited towards your next charges\",\n switchNextCharge: (date) => `Next charge on ${date}`,\n switchConfirm: \"Change plan\",\n\n grantWarningTitle: \"Your free access continues\",\n grantWarningBody:\n \"You have free access with no end date. A purchase won’t change your plan while the free access runs: you would pay for a plan that doesn’t apply.\",\n grantWarningAction: \"Buy anyway\",\n\n waitingChanges: (count) => (count === 1 ? \"1 change waiting for a plan\" : `${count} changes waiting for a plan`),\n discardWaiting: \"Discard waiting changes\",\n\n notConfigured: \"Payments aren't set up yet. Please try again later.\",\n disabled: \"Subscriptions aren't available here.\",\n providerUnavailable: \"The payment provider can’t be reached right now. Please try again in a moment.\",\n alreadySubscribed: \"You already have a subscription. Change your plan above.\",\n planNotSold: \"This plan isn’t offered for that billing period or currency. Please choose another.\",\n notSubscribed: \"There’s no running subscription to change. Choose a plan to subscribe.\",\n planUnchanged: \"That’s already your plan. Pick another plan or billing period.\",\n};\n\n/** The `billing` namespace, resolved: English, then the provider, then `labels` — which\n * may be partial at every depth (`{ status: { comped: \"Beta\" } }`). */\nexport function useBillingLabels(labels?: LabelOverride<BillingLabels>): BillingLabels {\n return useKitLabels(\"billing\", DEFAULT_BILLING_LABELS, labels);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from "react";
|
|
2
|
+
import type { LabelOverride } from "../i18n/kit-labels.js";
|
|
3
|
+
import type { DateInput } from "../lib/format.js";
|
|
4
|
+
import type { BillingLabels } from "./billing-labels.js";
|
|
5
|
+
import type { BillingOverview } from "./billing-overview.js";
|
|
6
|
+
/**
|
|
7
|
+
* What the standing line reads of `GET /billing/overview` (docs/billing-harmonization.md
|
|
8
|
+
* §4), under the wire names. Pass the overview as it came — or with the app's own
|
|
9
|
+
* knowledge laid over it: kastlan says "Ends on …" the moment its in-app cancel is asked
|
|
10
|
+
* (`{ ...overview, cancel_at_period_end: view.cancelScheduled }`, `useCancelRequest`).
|
|
11
|
+
*/
|
|
12
|
+
export type SubscriptionStandingOverview = Pick<BillingOverview, "status" | "in_good_standing" | "trial_ends_at" | "comped_until" | "current_period_end" | "cancel_at_period_end">;
|
|
13
|
+
/**
|
|
14
|
+
* The subscription's standing in one line — or `null` when nothing applies (kastlan reuse
|
|
15
|
+
* audit 2026-11-02, proposal 3; billing §7, Marcel 2026-11-04). The FIRST that applies:
|
|
16
|
+
*
|
|
17
|
+
* 1. not in good standing → "Your plan has ended." (`billing.standing.ended`, the first
|
|
18
|
+
* sentence of the plan-ended banner's);
|
|
19
|
+
* 2. `past_due` → "Payment failed — we’re retrying. Your plan stays active meanwhile.",
|
|
20
|
+
* with NO renewal date: the payment-failed banner keeps the action;
|
|
21
|
+
* 3. `trialing` → "Trial until {trial_ends_at}" (a pure guest's trial that has not
|
|
22
|
+
* started, §12.9, has no date and says nothing);
|
|
23
|
+
* 4. `comped` → "Free until {comped_until}", or "Free, with no end date";
|
|
24
|
+
* 5. set to cancel at the period's end → "Ends on {current_period_end}";
|
|
25
|
+
* 6. `active` → "Renews on {current_period_end}";
|
|
26
|
+
* 7. otherwise nothing.
|
|
27
|
+
*
|
|
28
|
+
* kastlan and keksdose each wrote this line, and they drifted: kastlan said nothing for
|
|
29
|
+
* an ended plan or a failed payment, and keksdose's `past_due` said "Renews on …".
|
|
30
|
+
*
|
|
31
|
+
* `format` prints a date in a sentence; default the kit's `formatDate` (`"medium"`) in
|
|
32
|
+
* the runtime's locale. Inside a component, `<SubscriptionStanding>` prints it the
|
|
33
|
+
* provider's way — its `formatDate` (the app's date preference), else its locale and
|
|
34
|
+
* time zone.
|
|
35
|
+
*/
|
|
36
|
+
export declare function subscriptionStanding(overview: SubscriptionStandingOverview, labels: Pick<BillingLabels, "standing">, format?: (value: DateInput) => string): string | null;
|
|
37
|
+
export interface SubscriptionStandingProps extends Omit<ComponentPropsWithRef<"p">, "children"> {
|
|
38
|
+
/** The overview, or the fields of it the line reads. */
|
|
39
|
+
overview: SubscriptionStandingOverview;
|
|
40
|
+
/** The date's locale. Default: the provider's. */
|
|
41
|
+
locale?: string;
|
|
42
|
+
/** Prop > `<UiKitProvider labels={{ billing: { standing } }}>` > English. */
|
|
43
|
+
labels?: LabelOverride<BillingLabels>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* {@link subscriptionStanding} as the muted line under the plan's name on the
|
|
47
|
+
* subscription page (billing §7) — nothing at all when no line applies. The date is a
|
|
48
|
+
* day in a sentence, without its weekday: the `<UiKitProvider formatDate>` (the app's
|
|
49
|
+
* date preference) when it set one, else the kit's `formatDate`, `"medium"`, in the
|
|
50
|
+
* provider's locale and `timeZone` — as `CancelSubscriptionConfirm` names the same day.
|
|
51
|
+
*
|
|
52
|
+
* Carries `data-standing` with the line's key (`ended`, `pastDue`, `trialUntil`,
|
|
53
|
+
* `freeUntil`, `free`, `endsOn`, `renewsOn`), for a test or a style hook.
|
|
54
|
+
*
|
|
55
|
+
* <SubscriptionStanding overview={overview} />
|
|
56
|
+
*
|
|
57
|
+
* Back from a checkout whose webhook has not landed (§12.21), the ended line is the stale
|
|
58
|
+
* one: render it only when `useCheckoutProcessing`'s `processing` is false, and the
|
|
59
|
+
* processing banner speaks meanwhile.
|
|
60
|
+
*/
|
|
61
|
+
export declare function SubscriptionStanding({ overview, locale: localeProp, labels: labelsProp, className, ...rest }: SubscriptionStandingProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { useDayFormatter } from "../lib/day-format.js";
|
|
5
|
+
import { formatDate } from "../lib/format.js";
|
|
6
|
+
import { useBillingLabels } from "./billing-labels.js";
|
|
7
|
+
const given = (value) => value !== null && value !== void 0 && value !== "";
|
|
8
|
+
function standingOf(o) {
|
|
9
|
+
if (!o.in_good_standing) return { kind: "ended" };
|
|
10
|
+
if (o.status === "past_due") return { kind: "pastDue" };
|
|
11
|
+
if (o.status === "trialing") return given(o.trial_ends_at) ? { kind: "trialUntil", date: o.trial_ends_at } : null;
|
|
12
|
+
if (o.status === "comped") {
|
|
13
|
+
return given(o.comped_until) ? { kind: "freeUntil", date: o.comped_until } : { kind: "free" };
|
|
14
|
+
}
|
|
15
|
+
if (!given(o.current_period_end)) return null;
|
|
16
|
+
if (o.cancel_at_period_end) return { kind: "endsOn", date: o.current_period_end };
|
|
17
|
+
if (o.status === "active") return { kind: "renewsOn", date: o.current_period_end };
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function standingText(s, words, day) {
|
|
21
|
+
switch (s.kind) {
|
|
22
|
+
case "ended":
|
|
23
|
+
case "pastDue":
|
|
24
|
+
case "free":
|
|
25
|
+
return words[s.kind];
|
|
26
|
+
default:
|
|
27
|
+
return words[s.kind](day(s.date));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function subscriptionStanding(overview, labels, format = (value) => formatDate(value)) {
|
|
31
|
+
const s = standingOf(overview);
|
|
32
|
+
return s && standingText(s, labels.standing, format);
|
|
33
|
+
}
|
|
34
|
+
function SubscriptionStanding({
|
|
35
|
+
overview,
|
|
36
|
+
locale: localeProp,
|
|
37
|
+
labels: labelsProp,
|
|
38
|
+
className,
|
|
39
|
+
...rest
|
|
40
|
+
}) {
|
|
41
|
+
const labels = useBillingLabels(labelsProp);
|
|
42
|
+
const day = useDayFormatter(void 0, localeProp);
|
|
43
|
+
const s = standingOf(overview);
|
|
44
|
+
if (!s) return null;
|
|
45
|
+
return /* @__PURE__ */ jsx("p", { ...rest, "data-standing": s.kind, className: cn("text-sm text-muted", className), children: standingText(s, labels.standing, (value) => day(value)) });
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
SubscriptionStanding,
|
|
49
|
+
subscriptionStanding
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=subscription-standing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/billing/subscription-standing.tsx"],"sourcesContent":["import type { ComponentPropsWithRef } from \"react\";\n\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { useDayFormatter } from \"../lib/day-format\";\nimport { formatDate } from \"../lib/format\";\nimport type { DateInput } from \"../lib/format\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\nimport type { BillingOverview } from \"./billing-overview\";\n\n/**\n * What the standing line reads of `GET /billing/overview` (docs/billing-harmonization.md\n * §4), under the wire names. Pass the overview as it came — or with the app's own\n * knowledge laid over it: kastlan says \"Ends on …\" the moment its in-app cancel is asked\n * (`{ ...overview, cancel_at_period_end: view.cancelScheduled }`, `useCancelRequest`).\n */\nexport type SubscriptionStandingOverview = Pick<\n BillingOverview,\n \"status\" | \"in_good_standing\" | \"trial_ends_at\" | \"comped_until\" | \"current_period_end\" | \"cancel_at_period_end\"\n>;\n\n/** Which of `billing.standing`'s lines applies, and the date it names. */\ntype Standing =\n | { kind: \"ended\" | \"pastDue\" | \"free\" }\n | { kind: \"trialUntil\" | \"freeUntil\" | \"endsOn\" | \"renewsOn\"; date: DateInput };\n\nconst given = (value: DateInput): boolean => value !== null && value !== undefined && value !== \"\";\n\n/** Marcel's order (billing §7, 2026-11-04): the first that applies, or none. */\nfunction standingOf(o: SubscriptionStandingOverview): Standing | null {\n // The server's word beats the status: `canceled` and `expired` are never in good\n // standing, and a lapsed `past_due` is no longer \"retrying\" (§3.3).\n if (!o.in_good_standing) return { kind: \"ended\" };\n // Still in good standing, and no date: a renewal the retries may not reach.\n if (o.status === \"past_due\") return { kind: \"pastDue\" };\n if (o.status === \"trialing\") return given(o.trial_ends_at) ? { kind: \"trialUntil\", date: o.trial_ends_at } : null;\n if (o.status === \"comped\") {\n return given(o.comped_until) ? { kind: \"freeUntil\", date: o.comped_until } : { kind: \"free\" };\n }\n if (!given(o.current_period_end)) return null;\n if (o.cancel_at_period_end) return { kind: \"endsOn\", date: o.current_period_end };\n if (o.status === \"active\") return { kind: \"renewsOn\", date: o.current_period_end };\n return null;\n}\n\nfunction standingText(s: Standing, words: BillingLabels[\"standing\"], day: (value: DateInput) => string): string {\n switch (s.kind) {\n case \"ended\":\n case \"pastDue\":\n case \"free\":\n return words[s.kind];\n default:\n return words[s.kind](day(s.date));\n }\n}\n\n/**\n * The subscription's standing in one line — or `null` when nothing applies (kastlan reuse\n * audit 2026-11-02, proposal 3; billing §7, Marcel 2026-11-04). The FIRST that applies:\n *\n * 1. not in good standing → \"Your plan has ended.\" (`billing.standing.ended`, the first\n * sentence of the plan-ended banner's);\n * 2. `past_due` → \"Payment failed — we’re retrying. Your plan stays active meanwhile.\",\n * with NO renewal date: the payment-failed banner keeps the action;\n * 3. `trialing` → \"Trial until {trial_ends_at}\" (a pure guest's trial that has not\n * started, §12.9, has no date and says nothing);\n * 4. `comped` → \"Free until {comped_until}\", or \"Free, with no end date\";\n * 5. set to cancel at the period's end → \"Ends on {current_period_end}\";\n * 6. `active` → \"Renews on {current_period_end}\";\n * 7. otherwise nothing.\n *\n * kastlan and keksdose each wrote this line, and they drifted: kastlan said nothing for\n * an ended plan or a failed payment, and keksdose's `past_due` said \"Renews on …\".\n *\n * `format` prints a date in a sentence; default the kit's `formatDate` (`\"medium\"`) in\n * the runtime's locale. Inside a component, `<SubscriptionStanding>` prints it the\n * provider's way — its `formatDate` (the app's date preference), else its locale and\n * time zone.\n */\nexport function subscriptionStanding(\n overview: SubscriptionStandingOverview,\n labels: Pick<BillingLabels, \"standing\">,\n format: (value: DateInput) => string = (value) => formatDate(value),\n): string | null {\n const s = standingOf(overview);\n return s && standingText(s, labels.standing, format);\n}\n\nexport interface SubscriptionStandingProps extends Omit<ComponentPropsWithRef<\"p\">, \"children\"> {\n /** The overview, or the fields of it the line reads. */\n overview: SubscriptionStandingOverview;\n /** The date's locale. Default: the provider's. */\n locale?: string;\n /** Prop > `<UiKitProvider labels={{ billing: { standing } }}>` > English. */\n labels?: LabelOverride<BillingLabels>;\n}\n\n/**\n * {@link subscriptionStanding} as the muted line under the plan's name on the\n * subscription page (billing §7) — nothing at all when no line applies. The date is a\n * day in a sentence, without its weekday: the `<UiKitProvider formatDate>` (the app's\n * date preference) when it set one, else the kit's `formatDate`, `\"medium\"`, in the\n * provider's locale and `timeZone` — as `CancelSubscriptionConfirm` names the same day.\n *\n * Carries `data-standing` with the line's key (`ended`, `pastDue`, `trialUntil`,\n * `freeUntil`, `free`, `endsOn`, `renewsOn`), for a test or a style hook.\n *\n * <SubscriptionStanding overview={overview} />\n *\n * Back from a checkout whose webhook has not landed (§12.21), the ended line is the stale\n * one: render it only when `useCheckoutProcessing`'s `processing` is false, and the\n * processing banner speaks meanwhile.\n */\nexport function SubscriptionStanding({\n overview,\n locale: localeProp,\n labels: labelsProp,\n className,\n ...rest\n}: SubscriptionStandingProps) {\n const labels = useBillingLabels(labelsProp);\n const day = useDayFormatter(undefined, localeProp);\n const s = standingOf(overview);\n if (!s) return null;\n return (\n <p {...rest} data-standing={s.kind} className={cn(\"text-sm text-muted\", className)}>\n {standingText(s, labels.standing, (value) => day(value))}\n </p>\n );\n}\n"],"mappings":";AA8HI;AA3HJ,SAAS,UAAU;AACnB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAE3B,SAAS,wBAAwB;AAoBjC,MAAM,QAAQ,CAAC,UAA8B,UAAU,QAAQ,UAAU,UAAa,UAAU;AAGhG,SAAS,WAAW,GAAkD;AAGpE,MAAI,CAAC,EAAE,iBAAkB,QAAO,EAAE,MAAM,QAAQ;AAEhD,MAAI,EAAE,WAAW,WAAY,QAAO,EAAE,MAAM,UAAU;AACtD,MAAI,EAAE,WAAW,WAAY,QAAO,MAAM,EAAE,aAAa,IAAI,EAAE,MAAM,cAAc,MAAM,EAAE,cAAc,IAAI;AAC7G,MAAI,EAAE,WAAW,UAAU;AACzB,WAAO,MAAM,EAAE,YAAY,IAAI,EAAE,MAAM,aAAa,MAAM,EAAE,aAAa,IAAI,EAAE,MAAM,OAAO;AAAA,EAC9F;AACA,MAAI,CAAC,MAAM,EAAE,kBAAkB,EAAG,QAAO;AACzC,MAAI,EAAE,qBAAsB,QAAO,EAAE,MAAM,UAAU,MAAM,EAAE,mBAAmB;AAChF,MAAI,EAAE,WAAW,SAAU,QAAO,EAAE,MAAM,YAAY,MAAM,EAAE,mBAAmB;AACjF,SAAO;AACT;AAEA,SAAS,aAAa,GAAa,OAAkC,KAA2C;AAC9G,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,MAAM,EAAE,IAAI;AAAA,IACrB;AACE,aAAO,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;AAAA,EACpC;AACF;AAyBO,SAAS,qBACd,UACA,QACA,SAAuC,CAAC,UAAU,WAAW,KAAK,GACnD;AACf,QAAM,IAAI,WAAW,QAAQ;AAC7B,SAAO,KAAK,aAAa,GAAG,OAAO,UAAU,MAAM;AACrD;AA2BO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAA8B;AAC5B,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,MAAM,gBAAgB,QAAW,UAAU;AACjD,QAAM,IAAI,WAAW,QAAQ;AAC7B,MAAI,CAAC,EAAG,QAAO;AACf,SACE,oBAAC,OAAG,GAAG,MAAM,iBAAe,EAAE,MAAM,WAAW,GAAG,sBAAsB,SAAS,GAC9E,uBAAa,GAAG,OAAO,UAAU,CAAC,UAAU,IAAI,KAAK,CAAC,GACzD;AAEJ;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LabelOverride } from "../i18n/kit-labels.js";
|
|
3
|
+
import type { ProgressBarSize } from "../components/progress-bar.js";
|
|
4
|
+
import type { BillingLabels } from "./billing-labels.js";
|
|
5
|
+
export interface UsageMeterProps {
|
|
6
|
+
/** The dimension's name in the reader's language: "Budgets", "Units", "Files". The
|
|
7
|
+
* meter's visible label and its accessible name. */
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
/** What the payer has — the overview's `usage[dimension]` (§12.15). */
|
|
10
|
+
used: number;
|
|
11
|
+
/**
|
|
12
|
+
* The plan's limit — the overview's `limits[dimension]`. `null` is unlimited (§3.1), and
|
|
13
|
+
* so is a dimension the overview's `limits` leaves out: no bar and no tone, the count
|
|
14
|
+
* and "Unlimited" as text.
|
|
15
|
+
*/
|
|
16
|
+
limit?: number | null;
|
|
17
|
+
/** How a figure is written — kastlan's storage limit is bytes (`formatFileSize`).
|
|
18
|
+
* Default: a number in the kit's locale, with its digit grouping ("12’345"). */
|
|
19
|
+
formatValue?: (value: number) => string;
|
|
20
|
+
/** The track's height. Default `slim` (6px), a bar under a line of text. */
|
|
21
|
+
size?: ProgressBarSize;
|
|
22
|
+
/** Over the provider's locale, for the default figures. */
|
|
23
|
+
locale?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
/** Prop > `<UiKitProvider labels={{ billing }}>` > English: `usage` ("3 of 10") and
|
|
26
|
+
* `unlimited`. The over-the-limit line is `progressBar.overLimit`'s. */
|
|
27
|
+
labels?: LabelOverride<BillingLabels>;
|
|
28
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What the payer uses of one plan limit, on `ProgressBar`'s meter variant (billing §7;
|
|
32
|
+
* kastlan reuse audit 2026-11-02, proposal 3): the dimension's name, "3 of 10", and a
|
|
33
|
+
* bar whose tone says what creating one more would do (Marcel 2026-11-04):
|
|
34
|
+
*
|
|
35
|
+
* - below the limit: `neutral` — nothing to warn about;
|
|
36
|
+
* - AT it: `warning` — nothing more can be created (§3.4);
|
|
37
|
+
* - PAST it: `danger` — after a downgrade, which never deletes what is over the new limit
|
|
38
|
+
* (§3.4); the bar stays full and a line under it says by how much
|
|
39
|
+
* (`progressBar.overLimit`, "2 over the limit");
|
|
40
|
+
* - unlimited (`null`): no bar and no tone, the count and "Unlimited" as text.
|
|
41
|
+
*
|
|
42
|
+
* Carries `data-usage` (`below`, `at`, `over`, `unlimited`), for a test or a style hook.
|
|
43
|
+
*
|
|
44
|
+
* kastlan's `UsageProgressBar` turned amber past 80 % and wrote its counts with
|
|
45
|
+
* `n.toString()`; keksdose's own `UsageMeter` warned at the limit but drew nothing past
|
|
46
|
+
* it. Every figure here goes through {@link UsageMeterProps.formatValue}, the overage too.
|
|
47
|
+
*
|
|
48
|
+
* {DIMENSIONS.map((d) => (
|
|
49
|
+
* <UsageMeter key={d} label={names[d]} used={overview.usage[d] ?? 0} limit={overview.limits[d]} />
|
|
50
|
+
* ))}
|
|
51
|
+
*/
|
|
52
|
+
export declare function UsageMeter({ label, used, limit, formatValue, size, locale: localeProp, className, labels: labelsProp, ...rest }: UsageMeterProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
4
|
+
import { formatNumber } from "../lib/format.js";
|
|
5
|
+
import { ProgressBar } from "../components/progress-bar.js";
|
|
6
|
+
import { DEFAULT_PROGRESS_BAR_LABELS } from "../components/progress-bar-labels.js";
|
|
7
|
+
import { useBillingLabels } from "./billing-labels.js";
|
|
8
|
+
const TONE = {
|
|
9
|
+
below: "neutral",
|
|
10
|
+
at: "warning",
|
|
11
|
+
over: "danger"
|
|
12
|
+
};
|
|
13
|
+
function UsageMeter({
|
|
14
|
+
label,
|
|
15
|
+
used,
|
|
16
|
+
limit = null,
|
|
17
|
+
formatValue,
|
|
18
|
+
size = "slim",
|
|
19
|
+
locale: localeProp,
|
|
20
|
+
className,
|
|
21
|
+
labels: labelsProp,
|
|
22
|
+
...rest
|
|
23
|
+
}) {
|
|
24
|
+
const labels = useBillingLabels(labelsProp);
|
|
25
|
+
const bar = useKitLabels("progressBar", DEFAULT_PROGRESS_BAR_LABELS);
|
|
26
|
+
const locale = useKitLocale(localeProp);
|
|
27
|
+
const figure = (value) => formatValue ? formatValue(value) : formatNumber(value, { locale });
|
|
28
|
+
const state = limit === null ? "unlimited" : used < limit ? "below" : used === limit ? "at" : "over";
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
ProgressBar,
|
|
31
|
+
{
|
|
32
|
+
...rest,
|
|
33
|
+
variant: "meter",
|
|
34
|
+
size,
|
|
35
|
+
label,
|
|
36
|
+
showValue: true,
|
|
37
|
+
value: used,
|
|
38
|
+
max: limit,
|
|
39
|
+
tone: state === "unlimited" ? void 0 : TONE[state],
|
|
40
|
+
formatValue: () => limit === null ? figure(used) : labels.usage(figure(used), figure(limit)),
|
|
41
|
+
overage: (over) => bar.overLimit(figure(over)),
|
|
42
|
+
locale,
|
|
43
|
+
labels: { unlimited: labels.unlimited },
|
|
44
|
+
"data-usage": state,
|
|
45
|
+
className
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
UsageMeter
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=usage-meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/billing/usage-meter.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { formatNumber } from \"../lib/format\";\nimport { ProgressBar } from \"../components/progress-bar\";\nimport type { ProgressBarSize, ProgressBarTone } from \"../components/progress-bar\";\nimport { DEFAULT_PROGRESS_BAR_LABELS } from \"../components/progress-bar-labels\";\nimport { useBillingLabels } from \"./billing-labels\";\nimport type { BillingLabels } from \"./billing-labels\";\n\nexport interface UsageMeterProps {\n /** The dimension's name in the reader's language: \"Budgets\", \"Units\", \"Files\". The\n * meter's visible label and its accessible name. */\n label: ReactNode;\n /** What the payer has — the overview's `usage[dimension]` (§12.15). */\n used: number;\n /**\n * The plan's limit — the overview's `limits[dimension]`. `null` is unlimited (§3.1), and\n * so is a dimension the overview's `limits` leaves out: no bar and no tone, the count\n * and \"Unlimited\" as text.\n */\n limit?: number | null;\n /** How a figure is written — kastlan's storage limit is bytes (`formatFileSize`).\n * Default: a number in the kit's locale, with its digit grouping (\"12’345\"). */\n formatValue?: (value: number) => string;\n /** The track's height. Default `slim` (6px), a bar under a line of text. */\n size?: ProgressBarSize;\n /** Over the provider's locale, for the default figures. */\n locale?: string;\n className?: string;\n /** Prop > `<UiKitProvider labels={{ billing }}>` > English: `usage` (\"3 of 10\") and\n * `unlimited`. The over-the-limit line is `progressBar.overLimit`'s. */\n labels?: LabelOverride<BillingLabels>;\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** Where the use stands against the limit — `data-usage`, and the tone it takes. */\ntype UsageState = \"below\" | \"at\" | \"over\" | \"unlimited\";\n\nconst TONE: Record<Exclude<UsageState, \"unlimited\">, ProgressBarTone> = {\n below: \"neutral\",\n at: \"warning\",\n over: \"danger\",\n};\n\n/**\n * What the payer uses of one plan limit, on `ProgressBar`'s meter variant (billing §7;\n * kastlan reuse audit 2026-11-02, proposal 3): the dimension's name, \"3 of 10\", and a\n * bar whose tone says what creating one more would do (Marcel 2026-11-04):\n *\n * - below the limit: `neutral` — nothing to warn about;\n * - AT it: `warning` — nothing more can be created (§3.4);\n * - PAST it: `danger` — after a downgrade, which never deletes what is over the new limit\n * (§3.4); the bar stays full and a line under it says by how much\n * (`progressBar.overLimit`, \"2 over the limit\");\n * - unlimited (`null`): no bar and no tone, the count and \"Unlimited\" as text.\n *\n * Carries `data-usage` (`below`, `at`, `over`, `unlimited`), for a test or a style hook.\n *\n * kastlan's `UsageProgressBar` turned amber past 80 % and wrote its counts with\n * `n.toString()`; keksdose's own `UsageMeter` warned at the limit but drew nothing past\n * it. Every figure here goes through {@link UsageMeterProps.formatValue}, the overage too.\n *\n * {DIMENSIONS.map((d) => (\n * <UsageMeter key={d} label={names[d]} used={overview.usage[d] ?? 0} limit={overview.limits[d]} />\n * ))}\n */\nexport function UsageMeter({\n label,\n used,\n limit = null,\n formatValue,\n size = \"slim\",\n locale: localeProp,\n className,\n labels: labelsProp,\n ...rest\n}: UsageMeterProps) {\n const labels = useBillingLabels(labelsProp);\n const bar = useKitLabels(\"progressBar\", DEFAULT_PROGRESS_BAR_LABELS);\n const locale = useKitLocale(localeProp);\n const figure = (value: number) => (formatValue ? formatValue(value) : formatNumber(value, { locale }));\n const state: UsageState = limit === null ? \"unlimited\" : used < limit ? \"below\" : used === limit ? \"at\" : \"over\";\n\n return (\n <ProgressBar\n {...rest}\n variant=\"meter\"\n size={size}\n label={label}\n showValue\n value={used}\n max={limit}\n tone={state === \"unlimited\" ? undefined : TONE[state]}\n // The bar hands its formatter the value clamped at `max`; the figures are the real\n // ones, so \"5 of 3\" after a downgrade says what the bar cannot draw. Unlimited, it\n // asks with `max` = Infinity and shows the count beside \"Unlimited\".\n formatValue={() => (limit === null ? figure(used) : labels.usage(figure(used), figure(limit)))}\n overage={(over) => bar.overLimit(figure(over))}\n locale={locale}\n labels={{ unlimited: labels.unlimited }}\n data-usage={state}\n className={className}\n />\n );\n}\n"],"mappings":";AAsFI;AApFJ,SAAS,cAAc,oBAAoB;AAE3C,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAE5B,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAgCjC,MAAM,OAAkE;AAAA,EACtE,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,MAAM;AACR;AAwBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,GAAoB;AAClB,QAAM,SAAS,iBAAiB,UAAU;AAC1C,QAAM,MAAM,aAAa,eAAe,2BAA2B;AACnE,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,SAAS,CAAC,UAAmB,cAAc,YAAY,KAAK,IAAI,aAAa,OAAO,EAAE,OAAO,CAAC;AACpG,QAAM,QAAoB,UAAU,OAAO,cAAc,OAAO,QAAQ,UAAU,SAAS,QAAQ,OAAO;AAE1G,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,SAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA,WAAS;AAAA,MACT,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM,UAAU,cAAc,SAAY,KAAK,KAAK;AAAA,MAIpD,aAAa,MAAO,UAAU,OAAO,OAAO,IAAI,IAAI,OAAO,MAAM,OAAO,IAAI,GAAG,OAAO,KAAK,CAAC;AAAA,MAC5F,SAAS,CAAC,SAAS,IAAI,UAAU,OAAO,IAAI,CAAC;AAAA,MAC7C;AAAA,MACA,QAAQ,EAAE,WAAW,OAAO,UAAU;AAAA,MACtC,cAAY;AAAA,MACZ;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
package/dist/chart.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { PALETTE_SIZE, CVD_ORDER, paletteFor, CHART_COLORS, CHART_ROLES, CHART_M
|
|
|
3
3
|
export * from "./components/treemap.js";
|
|
4
4
|
export * from "./components/treemap-labels.js";
|
|
5
5
|
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, soleSeriesColor, visibleSeries, seriesLegendEntries, StaticSeriesChart, SeriesChart, type TimeTickUnit, type SeriesChartAxisBudget, type SeriesSource, type SeriesChartSeries, type SeriesChartType, type SeriesChartPoint, type SeriesChartRow, type SeriesChartXValue, type SeriesChartTickValues, type SeriesChartAxis, type SeriesChartXTick, type SeriesChartX, type SeriesChartTooltipPlacement, type SeriesChartTooltip, type SeriesChartSpan, type SeriesChartTone, type SeriesChartReference, type SeriesChartMarker, type SeriesChartHit, type SeriesChartProps, type SeriesLegendAxes, } from "./components/series-chart.js";
|
|
6
|
+
export { LegendedSeriesChart, type LegendedSeriesChartLegend, type LegendedSeriesChartProps, } from "./components/legended-series-chart.js";
|
|
6
7
|
export type { ChartHeight } from "./components/chart-height.js";
|
|
7
8
|
export * from "./components/chart-zoom.js";
|
|
8
9
|
export { SharedXZoom } from "./components/chart-zoom-core.js";
|
package/dist/chart.js
CHANGED
|
@@ -31,6 +31,9 @@ import {
|
|
|
31
31
|
StaticSeriesChart,
|
|
32
32
|
SeriesChart
|
|
33
33
|
} from "./components/series-chart.js";
|
|
34
|
+
import {
|
|
35
|
+
LegendedSeriesChart
|
|
36
|
+
} from "./components/legended-series-chart.js";
|
|
34
37
|
export * from "./components/chart-zoom.js";
|
|
35
38
|
import { SharedXZoom } from "./components/chart-zoom-core.js";
|
|
36
39
|
import {
|
|
@@ -56,6 +59,7 @@ export {
|
|
|
56
59
|
HEATMAP_HEX,
|
|
57
60
|
LegendColumn,
|
|
58
61
|
LegendGroup,
|
|
62
|
+
LegendedSeriesChart,
|
|
59
63
|
PALETTE_HEX,
|
|
60
64
|
PALETTE_SIZE,
|
|
61
65
|
STEP_DASH,
|