@openrails/billing-ui 0.248.1 → 0.250.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +40 -35
  3. package/dist/account/account-billing.d.ts +2 -2
  4. package/dist/account/cancel-dialog.d.ts +2 -2
  5. package/dist/account/change-card-dialog.d.ts +2 -2
  6. package/dist/account/payment-history.d.ts +2 -2
  7. package/dist/account/payment-methods-panel.d.ts +2 -2
  8. package/dist/account/section.d.ts +2 -2
  9. package/dist/account/subscriptions-panel.d.ts +2 -2
  10. package/dist/admin/context.d.ts +2 -2
  11. package/dist/admin/lib/queries.d.ts +1 -1
  12. package/dist/admin/messages.d.ts +3 -3
  13. package/dist/admin/pages/catalog/psp-links.d.ts +1 -1
  14. package/dist/admin/theme.d.ts +2 -2
  15. package/dist/admin/types.d.ts +2 -2
  16. package/dist/{admin-attempts-lAoW-Urd.js → admin-attempts-DxRHTODq.js} +8 -8
  17. package/dist/{admin-attempts-lAoW-Urd.js.map → admin-attempts-DxRHTODq.js.map} +1 -1
  18. package/dist/{admin-auth-DQGHtt0j.js → admin-auth-CiuZ-Hlm.js} +2 -2
  19. package/dist/{admin-auth-DQGHtt0j.js.map → admin-auth-CiuZ-Hlm.js.map} +1 -1
  20. package/dist/{admin-badge-DR5-1qd8.js → admin-badge-VCGbnfiN.js} +2 -2
  21. package/dist/{admin-badge-DR5-1qd8.js.map → admin-badge-VCGbnfiN.js.map} +1 -1
  22. package/dist/{admin-button-CTL3DgOt.js → admin-button-Oip73Plq.js} +2 -2
  23. package/dist/{admin-button-CTL3DgOt.js.map → admin-button-Oip73Plq.js.map} +1 -1
  24. package/dist/{admin-capabilities-BuigAgtm.js → admin-capabilities-BGv7oG8v.js} +2 -2
  25. package/dist/{admin-capabilities-BuigAgtm.js.map → admin-capabilities-BGv7oG8v.js.map} +1 -1
  26. package/dist/{admin-card-bzAks3-3.js → admin-card-DGbi6ZHZ.js} +2 -2
  27. package/dist/{admin-card-bzAks3-3.js.map → admin-card-DGbi6ZHZ.js.map} +1 -1
  28. package/dist/{admin-catalog-CK6CM9I2.js → admin-catalog-3pPEAuDY.js} +19 -19
  29. package/dist/{admin-catalog-CK6CM9I2.js.map → admin-catalog-3pPEAuDY.js.map} +1 -1
  30. package/dist/{admin-customers-D8u5gq3M.js → admin-customers-cnKMEV4q.js} +6 -6
  31. package/dist/{admin-customers-D8u5gq3M.js.map → admin-customers-cnKMEV4q.js.map} +1 -1
  32. package/dist/{admin-dashboard-DqqFjOzq.js → admin-dashboard-DfSsUyo-.js} +14 -14
  33. package/dist/{admin-dashboard-DqqFjOzq.js.map → admin-dashboard-DfSsUyo-.js.map} +1 -1
  34. package/dist/{admin-data-table-qfXLOFmN.js → admin-data-table-CgeWv__T.js} +6 -6
  35. package/dist/{admin-data-table-qfXLOFmN.js.map → admin-data-table-CgeWv__T.js.map} +1 -1
  36. package/dist/{admin-detail-X82kR2f7.js → admin-detail-BATuJjVs.js} +12 -12
  37. package/dist/{admin-detail-X82kR2f7.js.map → admin-detail-BATuJjVs.js.map} +1 -1
  38. package/dist/{admin-detail-DUAJQg8G.js → admin-detail-Cq59e14B.js} +15 -15
  39. package/dist/{admin-detail-DUAJQg8G.js.map → admin-detail-Cq59e14B.js.map} +1 -1
  40. package/dist/{admin-detail-BHe51ma9.js → admin-detail-DG-YaHhS.js} +11 -11
  41. package/dist/{admin-detail-BHe51ma9.js.map → admin-detail-DG-YaHhS.js.map} +1 -1
  42. package/dist/{admin-detail-CGOyqs7s.js → admin-detail-Dc7rU4mM.js} +15 -15
  43. package/dist/{admin-detail-CGOyqs7s.js.map → admin-detail-Dc7rU4mM.js.map} +1 -1
  44. package/dist/{admin-dialog-BjuKI5Hp.js → admin-dialog-Ffx95AIT.js} +3 -3
  45. package/dist/{admin-dialog-BjuKI5Hp.js.map → admin-dialog-Ffx95AIT.js.map} +1 -1
  46. package/dist/{admin-dom-uMmZzRvU.js → admin-dom-lnNSQ7ke.js} +4 -4
  47. package/dist/{admin-dom-uMmZzRvU.js.map → admin-dom-lnNSQ7ke.js.map} +1 -1
  48. package/dist/{admin-dropdown-menu-BW1D_h6s.js → admin-dropdown-menu-CCiW4-oz.js} +2 -2
  49. package/dist/{admin-dropdown-menu-BW1D_h6s.js.map → admin-dropdown-menu-CCiW4-oz.js.map} +1 -1
  50. package/dist/{admin-fact-card-DO_fHosg.js → admin-fact-card-D6mZgxpN.js} +2 -2
  51. package/dist/{admin-fact-card-DO_fHosg.js.map → admin-fact-card-D6mZgxpN.js.map} +1 -1
  52. package/dist/{admin-health-B4jN2LwR.js → admin-health-CvJX9prw.js} +13 -13
  53. package/dist/{admin-health-B4jN2LwR.js.map → admin-health-CvJX9prw.js.map} +1 -1
  54. package/dist/{admin-input-CcfQfJFN.js → admin-input-DO5lvCLL.js} +2 -2
  55. package/dist/{admin-input-CcfQfJFN.js.map → admin-input-DO5lvCLL.js.map} +1 -1
  56. package/dist/{admin-label-BiEN5cjb.js → admin-label-BzJpr3lg.js} +2 -2
  57. package/dist/{admin-label-BiEN5cjb.js.map → admin-label-BzJpr3lg.js.map} +1 -1
  58. package/dist/{admin-linked-table-row-BsaUruCN.js → admin-linked-table-row-5vFb6rXY.js} +4 -4
  59. package/dist/{admin-linked-table-row-BsaUruCN.js.map → admin-linked-table-row-5vFb6rXY.js.map} +1 -1
  60. package/dist/{admin-metering-COFcSPQy.js → admin-metering-BksTFRRP.js} +14 -14
  61. package/dist/{admin-metering-COFcSPQy.js.map → admin-metering-BksTFRRP.js.map} +1 -1
  62. package/dist/{admin-ops-B3mcr76u.js → admin-ops-Ch2-BmUp.js} +11 -11
  63. package/dist/{admin-ops-B3mcr76u.js.map → admin-ops-Ch2-BmUp.js.map} +1 -1
  64. package/dist/{admin-orders-dxC99Idl.js → admin-orders-8XzHf7b-.js} +8 -8
  65. package/dist/{admin-orders-dxC99Idl.js.map → admin-orders-8XzHf7b-.js.map} +1 -1
  66. package/dist/{admin-payments-Bs7HSfP0.js → admin-payments-pNLNIU0X.js} +9 -9
  67. package/dist/{admin-payments-Bs7HSfP0.js.map → admin-payments-pNLNIU0X.js.map} +1 -1
  68. package/dist/{admin-price-detail-DRquxpA9.js → admin-price-detail-BeGDPxKX.js} +11 -11
  69. package/dist/admin-price-detail-BeGDPxKX.js.map +1 -0
  70. package/dist/{admin-price-wizard-CI7K-2Nz.js → admin-price-wizard-ferZSTfO.js} +7 -7
  71. package/dist/{admin-price-wizard-CI7K-2Nz.js.map → admin-price-wizard-ferZSTfO.js.map} +1 -1
  72. package/dist/{admin-range-picker-Dr2M1EZr.js → admin-range-picker-DaIvC1oC.js} +5 -5
  73. package/dist/{admin-range-picker-Dr2M1EZr.js.map → admin-range-picker-DaIvC1oC.js.map} +1 -1
  74. package/dist/{admin-reports-CywKLiSA.js → admin-reports-CA-cnNur.js} +8 -8
  75. package/dist/{admin-reports-CywKLiSA.js.map → admin-reports-CA-cnNur.js.map} +1 -1
  76. package/dist/{admin-select-DBgprVW6.js → admin-select-CUXvh6j1.js} +2 -2
  77. package/dist/{admin-select-DBgprVW6.js.map → admin-select-CUXvh6j1.js.map} +1 -1
  78. package/dist/{admin-settings-Bmoy95uO.js → admin-settings-C6eWFkDn.js} +16 -16
  79. package/dist/{admin-settings-Bmoy95uO.js.map → admin-settings-C6eWFkDn.js.map} +1 -1
  80. package/dist/{admin-skeleton-DsM7crYi.js → admin-skeleton-DEY3Q8Dg.js} +2 -2
  81. package/dist/{admin-skeleton-DsM7crYi.js.map → admin-skeleton-DEY3Q8Dg.js.map} +1 -1
  82. package/dist/{admin-status-badge-BFPgo71d.js → admin-status-badge-CwqIU8tK.js} +2 -2
  83. package/dist/{admin-status-badge-BFPgo71d.js.map → admin-status-badge-CwqIU8tK.js.map} +1 -1
  84. package/dist/{admin-subscriptions-CLYc_HEV.js → admin-subscriptions-lJG_EzMe.js} +9 -9
  85. package/dist/{admin-subscriptions-CLYc_HEV.js.map → admin-subscriptions-lJG_EzMe.js.map} +1 -1
  86. package/dist/{admin-switch-BJUxLCsa.js → admin-switch-S4g_ppaU.js} +2 -2
  87. package/dist/{admin-switch-BJUxLCsa.js.map → admin-switch-S4g_ppaU.js.map} +1 -1
  88. package/dist/{admin-table-CH9TrXJf.js → admin-table-CWhiCb9d.js} +2 -2
  89. package/dist/{admin-table-CH9TrXJf.js.map → admin-table-CWhiCb9d.js.map} +1 -1
  90. package/dist/{admin-tabs-528FJMjt.js → admin-tabs-Cjm5a6UJ.js} +2 -2
  91. package/dist/{admin-tabs-528FJMjt.js.map → admin-tabs-Cjm5a6UJ.js.map} +1 -1
  92. package/dist/{admin-textarea-DEz38RaI.js → admin-textarea-DK1Fq31x.js} +2 -2
  93. package/dist/{admin-textarea-DEz38RaI.js.map → admin-textarea-DK1Fq31x.js.map} +1 -1
  94. package/dist/{admin-typed-confirm-dialog-DU7m9-O7.js → admin-typed-confirm-dialog-BloZvFPA.js} +6 -6
  95. package/dist/{admin-typed-confirm-dialog-DU7m9-O7.js.map → admin-typed-confirm-dialog-BloZvFPA.js.map} +1 -1
  96. package/dist/{admin-utils-BK4Vh1o_.js → admin-utils-DnVY5fYd.js} +3 -3
  97. package/dist/admin-utils-DnVY5fYd.js.map +1 -0
  98. package/dist/{admin-widget-viz-Da48c-Zf.js → admin-widget-viz-RCkxUspL.js} +8 -8
  99. package/dist/{admin-widget-viz-Da48c-Zf.js.map → admin-widget-viz-RCkxUspL.js.map} +1 -1
  100. package/dist/admin.js +31 -31
  101. package/dist/admin.js.map +1 -1
  102. package/dist/appearance.d.ts +7 -7
  103. package/dist/buy-button.d.ts +12 -18
  104. package/dist/cart.d.ts +4 -4
  105. package/dist/client/client.d.ts +12 -20
  106. package/dist/client/generated/error-codes.d.ts +1 -1
  107. package/dist/client/generated/routes.d.ts +10 -24
  108. package/dist/client/generated/wire.d.ts +75 -37
  109. package/dist/client/index.d.ts +1 -1
  110. package/dist/client/types.d.ts +17 -18
  111. package/dist/{client-DoRYitD3.js → client-HWr09qpX.js} +216 -233
  112. package/dist/client-HWr09qpX.js.map +1 -0
  113. package/dist/client.js +1 -1
  114. package/dist/embedded-payment.d.ts +25 -0
  115. package/dist/{hooks-DCSelDIZ.js → hooks-D9MR20BY.js} +7 -20
  116. package/dist/hooks-D9MR20BY.js.map +1 -0
  117. package/dist/index.d.ts +5 -5
  118. package/dist/index.js +284 -272
  119. package/dist/index.js.map +1 -1
  120. package/dist/locales/de.js +1 -1
  121. package/dist/locales/de.js.map +1 -1
  122. package/dist/locales/en.d.ts +1 -1
  123. package/dist/locales/en.js +1 -1
  124. package/dist/locales/en.js.map +1 -1
  125. package/dist/locales/es.js +1 -1
  126. package/dist/locales/es.js.map +1 -1
  127. package/dist/locales/ja.js +1 -1
  128. package/dist/locales/ja.js.map +1 -1
  129. package/dist/locales/ko.js +1 -1
  130. package/dist/locales/ko.js.map +1 -1
  131. package/dist/locales/zh.js +1 -1
  132. package/dist/locales/zh.js.map +1 -1
  133. package/dist/modal.d.ts +12 -3
  134. package/dist/{order-checkout.d.ts → order-payment.d.ts} +4 -4
  135. package/dist/product-list.d.ts +10 -16
  136. package/dist/psp.d.ts +2 -2
  137. package/dist/react/hooks.d.ts +2 -15
  138. package/dist/react/index.d.ts +1 -1
  139. package/dist/react/provider.d.ts +3 -3
  140. package/dist/react.js +2 -2
  141. package/dist/save-payment-method.d.ts +2 -2
  142. package/dist/scope-context.d.ts +4 -4
  143. package/dist/scope.d.ts +2 -2
  144. package/dist/tokenized-card-form.d.ts +2 -2
  145. package/docs/payment-form-contract.md +3 -3
  146. package/package.json +2 -2
  147. package/dist/admin-price-detail-DRquxpA9.js.map +0 -1
  148. package/dist/admin-utils-BK4Vh1o_.js.map +0 -1
  149. package/dist/client-DoRYitD3.js.map +0 -1
  150. package/dist/embedded-checkout.d.ts +0 -22
  151. package/dist/hooks-DCSelDIZ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"admin-price-wizard-CI7K-2Nz.js","names":[],"sources":["../src/admin/pages/catalog/price-wizard-logic.ts","../src/admin/pages/catalog/price-wizard.tsx"],"sourcesContent":["// Pure logic for the price-change wizard, kept apart from React for unit tests.\nimport { formatNativeAmount } from \"#orck/admin/lib/format\"\n\nexport type PriceDirection = \"increase\" | \"decrease\" | \"unchanged\"\n\nexport function detectDirection(\n newAmount: string,\n currentAmount: string\n): PriceDirection {\n const next = BigInt(newAmount)\n const current = BigInt(currentAmount)\n if (next > current) return \"increase\"\n if (next < current) return \"decrease\"\n return \"unchanged\"\n}\n\nexport type MigrationMode = \"grandfather\" | \"migrate\"\n\n// DEFAULT_NOTICE_WINDOW_DAYS is the fallback until the merchant's\n// reprice_notice_window_days loads; it mirrors\n// subscriptions.DefaultPriceIncreaseNoticeDays so the UI never under-gates.\n// The server enforces the window too (price_increase_notice_too_short).\nexport const DEFAULT_NOTICE_WINDOW_DAYS = 30\n\n// defaultMigrationMode is the direction-aware Step 2 default: an increase\n// grandfathers (no new action), a decrease migrates now (never grandfathered).\nexport function defaultMigrationMode(direction: PriceDirection): MigrationMode {\n return direction === \"decrease\" ? \"migrate\" : \"grandfather\"\n}\n\n// minEffectiveDate is the earliest allowed migration date, or null when there\n// is no minimum (a decrease needs no notice).\nexport function minEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date | null {\n if (direction !== \"increase\") return null\n const min = new Date(now)\n min.setUTCDate(min.getUTCDate() + noticeWindowDays)\n return min\n}\n\n// defaultEffectiveDate is Step 2's pre-filled migration date: the notice\n// window's floor for an increase, \"now\" for a decrease.\nexport function defaultEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date {\n return minEffectiveDate(direction, now, noticeWindowDays) ?? now\n}\n\n// isEffectiveDateValid enforces the console-side notice-window gate: an\n// increase+migrate plan may not pick a date inside the notice window.\nexport function isEffectiveDateValid(\n direction: PriceDirection,\n effectiveAt: Date,\n now: Date,\n noticeWindowDays: number\n): boolean {\n const min = minEffectiveDate(direction, now, noticeWindowDays)\n return !min || effectiveAt.getTime() >= min.getTime()\n}\n\nexport interface MigrationPlan {\n mode: MigrationMode\n // ISO instant; only meaningful when mode === \"migrate\".\n effectiveAt: string\n}\n\nconst dateLabel = (iso: string) =>\n new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n })\n\n// buildReviewText renders Step 3's plan in words, e.g. \"New subscribers pay $12\n// immediately. 1,204 existing subscribers keep $10 until Sep 1, then move to\n// $12 at their next renewal. Notices go out on confirm.\"\nexport function buildReviewText(params: {\n newAmount: string\n currentAmount: string\n currency: string\n affectedCount: number\n plan: MigrationPlan\n now: Date\n}): string {\n const { newAmount, currentAmount, currency, affectedCount, plan, now } =\n params\n const newLabel = formatNativeAmount(newAmount, currency)\n const oldLabel = formatNativeAmount(currentAmount, currency)\n const lead = `New subscribers pay ${newLabel} immediately.`\n\n if (affectedCount === 0) {\n return `${lead} No existing subscribers are on a prior version of this price.`\n }\n const subj = `${affectedCount.toLocaleString()} existing subscriber${affectedCount === 1 ? \"\" : \"s\"}`\n\n if (plan.mode === \"grandfather\") {\n return `${lead} ${subj} keep ${oldLabel} forever (grandfathered).`\n }\n\n // The engine notifies each affected subscriber at schedule time, however\n // far out effective_at is: always say so.\n const effective = new Date(plan.effectiveAt)\n const immediate = effective.getTime() <= now.getTime()\n if (immediate) {\n return `${lead} ${subj} move to ${newLabel} at their next renewal. Notices go out on confirm.`\n }\n return `${lead} ${subj} keep ${oldLabel} until ${dateLabel(plan.effectiveAt)}, then move to ${newLabel} at their next renewal. Notices go out on confirm.`\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\nimport * as React from \"react\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { FormFieldErrors } from \"#orck/admin/components/form-field-errors\"\nimport { Badge } from \"#orck/admin/components/ui/badge\"\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"#orck/admin/components/ui/dialog\"\nimport { Input } from \"#orck/admin/components/ui/input\"\nimport { Label } from \"#orck/admin/components/ui/label\"\nimport type { PriceChangeDraft } from \"#orck/admin/lib/api/copilot\"\nimport { DIALOG_FORM } from \"#orck/admin/lib/dialog-width\"\nimport type { Price } from \"#orck/client/generated/wire\"\nimport {\n formatNativeAmount,\n nativeAmountFromInput,\n nativeAmountToInput,\n} from \"#orck/admin/lib/format\"\n\nimport { priceIntervalLabel } from \"#orck/admin/pages/catalog/price-format\"\nimport {\n buildReviewText,\n defaultEffectiveDate,\n defaultMigrationMode,\n DEFAULT_NOTICE_WINDOW_DAYS,\n detectDirection,\n isEffectiveDateValid,\n minEffectiveDate,\n type MigrationMode,\n} from \"#orck/admin/pages/catalog/price-wizard-logic\"\n\nconst toDateInputValue = (d: Date) => d.toISOString().slice(0, 10)\n\nconst priceChangeFormValues = (price: Price, draft?: PriceChangeDraft) => ({\n amountInput: nativeAmountToInput(\n draft?.new_amount ?? price.unit_amount,\n price.currency\n ),\n mode: (draft?.migration ? \"migrate\" : \"grandfather\") as MigrationMode,\n effectiveAt: draft?.migration?.effective_at\n ? toDateInputValue(new Date(draft.migration.effective_at))\n : \"\",\n})\n\n// PriceChangeWizard is the #777 console price-change wizard — the ONLY\n// amount-edit affordance on a price. Three steps: new amount -> migration\n// plan (direction-aware defaults, live affected-count preview, notice-window\n// gate on increase+migrate) -> review in words, then confirm applies the\n// catalog price update followed (if migrating) by the price migration.\n//\n// draft (#779): when the catalog copilot proposed this change, the dialog\n// opens PRE-FILLED straight at Step 3 (review) — the affected count already\n// rode in the draft, so no extra preview call is needed. Confirm behaves\n// EXACTLY as a hand-typed change (the same create-price and price-migration\n// calls).\nexport function PriceChangeWizard({\n price,\n productName,\n productKey,\n draft,\n onDone,\n}: {\n price: Price\n productName: string\n productKey: string | undefined\n draft?: PriceChangeDraft\n onDone?: () => void\n}) {\n const { toast, toastApiError, adminMutations, adminQueries } =\n useAdminServices()\n\n const { id: adminId } = useAdminUI()\n\n const queryClient = useQueryClient()\n const [open, setOpen] = React.useState(() => !!draft)\n const [step, setStep] = React.useState<1 | 2 | 3>(() => (draft ? 3 : 1))\n const previewPriceChange = useMutation(adminMutations.previewPriceChange())\n const changePrice = useMutation(adminMutations.changePrice(queryClient))\n const affectedCount =\n previewPriceChange.data?.matched ?? draft?.affected_count ?? null\n const { data: configuration } = useQuery({\n ...adminQueries.merchantConfiguration(),\n enabled: open,\n })\n const noticeWindowDays =\n configuration?.settings.reprice_notice_window_days ??\n DEFAULT_NOTICE_WINDOW_DAYS\n\n const [now] = React.useState(() => new Date())\n const form = useForm({\n defaultValues: priceChangeFormValues(price, draft),\n onSubmit: async ({ value }) => {\n if (!productKey || price.customer_amount) return\n const newAmount = nativeAmountFromInput(value.amountInput, price.currency)\n if (newAmount === null || BigInt(newAmount) <= 0n) return\n\n try {\n const created = await changePrice.mutateAsync({\n price: {\n product_id: price.product_id,\n unit_amount: newAmount,\n currency: price.currency,\n access_duration_hours: price.access_duration_hours ?? undefined,\n billing_interval_hours: price.billing_interval_hours ?? undefined,\n trial_unit_amount: price.trial_unit_amount ?? undefined,\n trial_duration_hours: price.trial_duration_hours ?? undefined,\n key: price.key,\n psps: Object.keys(price.psps ?? {}),\n expected_revision: price.revision,\n },\n migration:\n value.mode === \"migrate\"\n ? {\n productKey,\n priceKey: price.key,\n effectiveAt: new Date(value.effectiveAt).toISOString(),\n }\n : undefined,\n })\n if (created.pending_manual_actions?.length) {\n toast.warning(\n `Price updated. One step is left for you: ${created.pending_manual_actions.map((action) => `${action.psp}: ${action.hint}`).join(\". \")}`\n )\n } else {\n toast.success(\n value.mode === \"migrate\"\n ? \"Price updated and migration scheduled\"\n : \"Price updated\"\n )\n }\n handleOpenChange(false)\n onDone?.()\n } catch (err) {\n toastApiError(err, \"Change price\")\n }\n },\n })\n\n const reset = () => {\n setStep(1)\n form.reset(priceChangeFormValues(price))\n previewPriceChange.reset()\n }\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n reset()\n }\n setOpen(next)\n }\n\n const enterStep2 = () => {\n if (!productKey || price.customer_amount) return\n const amount = nativeAmountFromInput(\n form.state.values.amountInput,\n price.currency\n )\n const direction = detectDirection(\n amount ?? price.unit_amount,\n price.unit_amount\n )\n form.setFieldValue(\"mode\", defaultMigrationMode(direction))\n form.setFieldValue(\n \"effectiveAt\",\n toDateInputValue(defaultEffectiveDate(direction, now, noticeWindowDays))\n )\n setStep(2)\n previewPriceChange.reset()\n previewPriceChange.mutate(\n { productKey, priceKey: price.key },\n { onError: (err) => toastApiError(err, \"Preview affected subscribers\") }\n )\n }\n\n // Archived (prior-version) rows are history, not the editable current\n // price. Each product/key pair has exactly one live edit target.\n // Wait for the product before allowing previews or scheduling a migration.\n if (price.archived || !productKey || price.customer_amount) {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title={\n price.customer_amount\n ? \"This price lets customers choose an amount. Change its limits through a catalog application or the client.\"\n : price.archived\n ? \"This is an archived version. Change the current price for this key instead.\"\n : \"Load the product before changing its price.\"\n }\n >\n <AdminMessage id={\"Change price\"} />\n </Button>\n )\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n {!draft && (\n <DialogTrigger\n render={\n <Button variant=\"outline\" size=\"sm\">\n <AdminMessage id={\"Change price\"} />\n </Button>\n }\n />\n )}\n <DialogContent className={DIALOG_FORM}>\n <form\n className=\"grid gap-4\"\n onSubmit={(event) => {\n event.preventDefault()\n event.stopPropagation()\n if (step !== 3) return\n void form.handleSubmit()\n }}\n >\n <form.Subscribe\n selector={(state) => [state.values, state.isSubmitting] as const}\n >\n {([values, isSubmitting]) => {\n const newAmount =\n nativeAmountFromInput(values.amountInput, price.currency) ??\n price.unit_amount\n const direction = detectDirection(newAmount, price.unit_amount)\n const minDate = minEffectiveDate(direction, now, noticeWindowDays)\n const dateValid =\n values.mode !== \"migrate\" ||\n (!!values.effectiveAt &&\n isEffectiveDateValid(\n direction,\n new Date(values.effectiveAt),\n now,\n noticeWindowDays\n ))\n\n return (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <AdminMessage id={\"Change price for \"} />\n {productName}\n {draft && (\n <Badge variant=\"secondary\">\n <AdminMessage id={\"drafted by copilot\"} />\n </Badge>\n )}\n </DialogTitle>\n <DialogDescription>\n <AdminMessage id={\"Step \"} />\n {step}\n <AdminMessage id={\" of 3:\"} />{\" \"}\n {step === 1\n ? \"new amount\"\n : step === 2\n ? \"migration plan\"\n : \"review\"}\n </DialogDescription>\n </DialogHeader>\n\n {step === 1 && (\n <div className=\"grid gap-3\">\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Current amount\"} />\n </p>\n <p className=\"font-medium\">\n {formatNativeAmount(\n price.unit_amount,\n price.currency\n )}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Currency · interval\"} />\n </p>\n <p className=\"font-medium\">\n {price.currency.toUpperCase()} ·{\" \"}\n {priceIntervalLabel(price)}\n </p>\n </div>\n </div>\n <form.Field\n name=\"amountInput\"\n validators={{\n onBlur: ({ value }) => {\n const amount = nativeAmountFromInput(\n value,\n price.currency\n )\n if (amount === null || BigInt(amount) <= 0n) {\n return \"Enter an amount greater than zero.\"\n }\n return undefined\n },\n }}\n >\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-amount\")}>\n <AdminMessage id={\"New amount (major units)\"} />\n </Label>\n <Input\n id={adminId(\"wiz-amount\")}\n type=\"number\"\n step=\"any\"\n min=\"0\"\n value={field.state.value}\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n autoFocus\n />\n <FormFieldErrors errors={field.state.meta.errors} />\n </div>\n )}\n </form.Field>\n {direction !== \"unchanged\" && (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage id={\"This is a price\"} />{\" \"}\n <span className=\"font-medium text-foreground\">\n {direction === \"increase\" ? \"increase\" : \"decrease\"}\n </span>\n <AdminMessage\n id={\n \". Currency and interval stay locked to the current price.\"\n }\n />\n </p>\n )}\n </div>\n )}\n\n {step === 2 && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm\">\n {affectedCount === null && previewPriceChange.isPending\n ? \"Checking affected subscribers…\"\n : `${(affectedCount ?? 0).toLocaleString()} active subscription${affectedCount === 1 ? \"\" : \"s\"} on prior versions of ${price.key}.`}\n </p>\n <form.Field name=\"mode\">\n {(field) => (\n <div className=\"grid gap-2\">\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"grandfather\"}\n onChange={() =>\n field.handleChange(\"grandfather\")\n }\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Grandfather.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Existing subscribers keep the current price.\"\n }\n />\n {direction === \"increase\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"migrate\"}\n onChange={() => field.handleChange(\"migrate\")}\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Migrate.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Move everyone to the new price at their first renewal on or after a date you choose.\"\n }\n />\n {direction === \"decrease\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n </div>\n )}\n </form.Field>\n {values.mode === \"migrate\" && (\n <form.Field name=\"effectiveAt\">\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-effective\")}>\n <AdminMessage id={\"Effective date\"} />\n </Label>\n <Input\n id={adminId(\"wiz-effective\")}\n type=\"date\"\n value={field.state.value}\n min={\n minDate\n ? toDateInputValue(minDate)\n : undefined\n }\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n />\n {direction === \"increase\" ? (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage id={\"Must be at least \"} />\n {noticeWindowDays}\n <AdminMessage\n id={\n \" days away. Card networks require that much notice before a price goes up. You can change the number in Settings.\"\n }\n />\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A price going down needs no notice, so today is fine.\"\n }\n />\n </p>\n )}\n {!dateValid && (\n <p\n className=\"text-xs text-destructive\"\n role=\"alert\"\n >\n <AdminMessage id={\"Pick a later date.\"} />\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A reprice_scheduled notice is queued for every affected subscriber as soon as you confirm.\"\n }\n />\n </p>\n </div>\n )}\n </form.Field>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"grid gap-3\">\n <p className=\"rounded-md border bg-muted/40 p-3 text-sm\">\n {buildReviewText({\n newAmount,\n currentAmount: price.unit_amount,\n currency: price.currency,\n affectedCount: affectedCount ?? 0,\n plan: {\n mode: values.mode,\n effectiveAt:\n values.mode === \"migrate\"\n ? new Date(values.effectiveAt).toISOString()\n : \"\",\n },\n now,\n })}\n </p>\n </div>\n )}\n\n <DialogFooter>\n {step > 1 && (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isSubmitting}\n onClick={() => setStep((value) => (value - 1) as 1 | 2)}\n >\n <AdminMessage id={\"Back\"} />\n </Button>\n )}\n {step === 1 && (\n <Button\n type=\"button\"\n disabled={\n !values.amountInput ||\n direction === \"unchanged\" ||\n BigInt(newAmount) <= 0n\n }\n onClick={enterStep2}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 2 && (\n <Button\n type=\"button\"\n disabled={!dateValid || previewPriceChange.isPending}\n onClick={() => setStep(3)}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 3 && (\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? \"Working…\" : \"Confirm\"}\n </Button>\n )}\n </DialogFooter>\n </>\n )\n }}\n </form.Subscribe>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-price-wizard-ferZSTfO.js","names":[],"sources":["../src/admin/pages/catalog/price-wizard-logic.ts","../src/admin/pages/catalog/price-wizard.tsx"],"sourcesContent":["// Pure logic for the price-change wizard, kept apart from React for unit tests.\nimport { formatNativeAmount } from \"#orck/admin/lib/format\"\n\nexport type PriceDirection = \"increase\" | \"decrease\" | \"unchanged\"\n\nexport function detectDirection(\n newAmount: string,\n currentAmount: string\n): PriceDirection {\n const next = BigInt(newAmount)\n const current = BigInt(currentAmount)\n if (next > current) return \"increase\"\n if (next < current) return \"decrease\"\n return \"unchanged\"\n}\n\nexport type MigrationMode = \"grandfather\" | \"migrate\"\n\n// DEFAULT_NOTICE_WINDOW_DAYS is the fallback until the merchant's\n// reprice_notice_window_days loads; it mirrors\n// subscriptions.DefaultPriceIncreaseNoticeDays so the UI never under-gates.\n// The server enforces the window too (price_increase_notice_too_short).\nexport const DEFAULT_NOTICE_WINDOW_DAYS = 30\n\n// defaultMigrationMode is the direction-aware Step 2 default: an increase\n// grandfathers (no new action), a decrease migrates now (never grandfathered).\nexport function defaultMigrationMode(direction: PriceDirection): MigrationMode {\n return direction === \"decrease\" ? \"migrate\" : \"grandfather\"\n}\n\n// minEffectiveDate is the earliest allowed migration date, or null when there\n// is no minimum (a decrease needs no notice).\nexport function minEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date | null {\n if (direction !== \"increase\") return null\n const min = new Date(now)\n min.setUTCDate(min.getUTCDate() + noticeWindowDays)\n return min\n}\n\n// defaultEffectiveDate is Step 2's pre-filled migration date: the notice\n// window's floor for an increase, \"now\" for a decrease.\nexport function defaultEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date {\n return minEffectiveDate(direction, now, noticeWindowDays) ?? now\n}\n\n// isEffectiveDateValid enforces the console-side notice-window gate: an\n// increase+migrate plan may not pick a date inside the notice window.\nexport function isEffectiveDateValid(\n direction: PriceDirection,\n effectiveAt: Date,\n now: Date,\n noticeWindowDays: number\n): boolean {\n const min = minEffectiveDate(direction, now, noticeWindowDays)\n return !min || effectiveAt.getTime() >= min.getTime()\n}\n\nexport interface MigrationPlan {\n mode: MigrationMode\n // ISO instant; only meaningful when mode === \"migrate\".\n effectiveAt: string\n}\n\nconst dateLabel = (iso: string) =>\n new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n })\n\n// buildReviewText renders Step 3's plan in words, e.g. \"New subscribers pay $12\n// immediately. 1,204 existing subscribers keep $10 until Sep 1, then move to\n// $12 at their next renewal. Notices go out on confirm.\"\nexport function buildReviewText(params: {\n newAmount: string\n currentAmount: string\n currency: string\n affectedCount: number\n plan: MigrationPlan\n now: Date\n}): string {\n const { newAmount, currentAmount, currency, affectedCount, plan, now } =\n params\n const newLabel = formatNativeAmount(newAmount, currency)\n const oldLabel = formatNativeAmount(currentAmount, currency)\n const lead = `New subscribers pay ${newLabel} immediately.`\n\n if (affectedCount === 0) {\n return `${lead} No existing subscribers are on a prior version of this price.`\n }\n const subj = `${affectedCount.toLocaleString()} existing subscriber${affectedCount === 1 ? \"\" : \"s\"}`\n\n if (plan.mode === \"grandfather\") {\n return `${lead} ${subj} keep ${oldLabel} forever (grandfathered).`\n }\n\n // The engine notifies each affected subscriber at schedule time, however\n // far out effective_at is: always say so.\n const effective = new Date(plan.effectiveAt)\n const immediate = effective.getTime() <= now.getTime()\n if (immediate) {\n return `${lead} ${subj} move to ${newLabel} at their next renewal. Notices go out on confirm.`\n }\n return `${lead} ${subj} keep ${oldLabel} until ${dateLabel(plan.effectiveAt)}, then move to ${newLabel} at their next renewal. Notices go out on confirm.`\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\nimport * as React from \"react\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { FormFieldErrors } from \"#orck/admin/components/form-field-errors\"\nimport { Badge } from \"#orck/admin/components/ui/badge\"\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"#orck/admin/components/ui/dialog\"\nimport { Input } from \"#orck/admin/components/ui/input\"\nimport { Label } from \"#orck/admin/components/ui/label\"\nimport type { PriceChangeDraft } from \"#orck/admin/lib/api/copilot\"\nimport { DIALOG_FORM } from \"#orck/admin/lib/dialog-width\"\nimport type { Price } from \"#orck/client/generated/wire\"\nimport {\n formatNativeAmount,\n nativeAmountFromInput,\n nativeAmountToInput,\n} from \"#orck/admin/lib/format\"\n\nimport { priceIntervalLabel } from \"#orck/admin/pages/catalog/price-format\"\nimport {\n buildReviewText,\n defaultEffectiveDate,\n defaultMigrationMode,\n DEFAULT_NOTICE_WINDOW_DAYS,\n detectDirection,\n isEffectiveDateValid,\n minEffectiveDate,\n type MigrationMode,\n} from \"#orck/admin/pages/catalog/price-wizard-logic\"\n\nconst toDateInputValue = (d: Date) => d.toISOString().slice(0, 10)\n\nconst priceChangeFormValues = (price: Price, draft?: PriceChangeDraft) => ({\n amountInput: nativeAmountToInput(\n draft?.new_amount ?? price.unit_amount,\n price.currency\n ),\n mode: (draft?.migration ? \"migrate\" : \"grandfather\") as MigrationMode,\n effectiveAt: draft?.migration?.effective_at\n ? toDateInputValue(new Date(draft.migration.effective_at))\n : \"\",\n})\n\n// PriceChangeWizard is the #777 console price-change wizard — the ONLY\n// amount-edit affordance on a price. Three steps: new amount -> migration\n// plan (direction-aware defaults, live affected-count preview, notice-window\n// gate on increase+migrate) -> review in words, then confirm applies the\n// catalog price update followed (if migrating) by the price migration.\n//\n// draft (#779): when the catalog copilot proposed this change, the dialog\n// opens PRE-FILLED straight at Step 3 (review) — the affected count already\n// rode in the draft, so no extra preview call is needed. Confirm behaves\n// EXACTLY as a hand-typed change (the same create-price and price-migration\n// calls).\nexport function PriceChangeWizard({\n price,\n productName,\n productKey,\n draft,\n onDone,\n}: {\n price: Price\n productName: string\n productKey: string | undefined\n draft?: PriceChangeDraft\n onDone?: () => void\n}) {\n const { toast, toastApiError, adminMutations, adminQueries } =\n useAdminServices()\n\n const { id: adminId } = useAdminUI()\n\n const queryClient = useQueryClient()\n const [open, setOpen] = React.useState(() => !!draft)\n const [step, setStep] = React.useState<1 | 2 | 3>(() => (draft ? 3 : 1))\n const previewPriceChange = useMutation(adminMutations.previewPriceChange())\n const changePrice = useMutation(adminMutations.changePrice(queryClient))\n const affectedCount =\n previewPriceChange.data?.matched ?? draft?.affected_count ?? null\n const { data: configuration } = useQuery({\n ...adminQueries.merchantConfiguration(),\n enabled: open,\n })\n const noticeWindowDays =\n configuration?.settings.reprice_notice_window_days ??\n DEFAULT_NOTICE_WINDOW_DAYS\n\n const [now] = React.useState(() => new Date())\n const form = useForm({\n defaultValues: priceChangeFormValues(price, draft),\n onSubmit: async ({ value }) => {\n if (!productKey || price.customer_amount) return\n const newAmount = nativeAmountFromInput(value.amountInput, price.currency)\n if (newAmount === null || BigInt(newAmount) <= 0n) return\n\n try {\n const created = await changePrice.mutateAsync({\n price: {\n product_id: price.product_id,\n unit_amount: newAmount,\n currency: price.currency,\n access_duration_hours: price.access_duration_hours ?? undefined,\n billing_interval_hours: price.billing_interval_hours ?? undefined,\n trial_unit_amount: price.trial_unit_amount ?? undefined,\n trial_duration_hours: price.trial_duration_hours ?? undefined,\n key: price.key,\n psps: Object.keys(price.psps ?? {}),\n expected_revision: price.revision,\n },\n migration:\n value.mode === \"migrate\"\n ? {\n productKey,\n priceKey: price.key,\n effectiveAt: new Date(value.effectiveAt).toISOString(),\n }\n : undefined,\n })\n if (created.pending_manual_actions?.length) {\n toast.warning(\n `Price updated. One step is left for you: ${created.pending_manual_actions.map((action) => `${action.psp}: ${action.hint}`).join(\". \")}`\n )\n } else {\n toast.success(\n value.mode === \"migrate\"\n ? \"Price updated and migration scheduled\"\n : \"Price updated\"\n )\n }\n handleOpenChange(false)\n onDone?.()\n } catch (err) {\n toastApiError(err, \"Change price\")\n }\n },\n })\n\n const reset = () => {\n setStep(1)\n form.reset(priceChangeFormValues(price))\n previewPriceChange.reset()\n }\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n reset()\n }\n setOpen(next)\n }\n\n const enterStep2 = () => {\n if (!productKey || price.customer_amount) return\n const amount = nativeAmountFromInput(\n form.state.values.amountInput,\n price.currency\n )\n const direction = detectDirection(\n amount ?? price.unit_amount,\n price.unit_amount\n )\n form.setFieldValue(\"mode\", defaultMigrationMode(direction))\n form.setFieldValue(\n \"effectiveAt\",\n toDateInputValue(defaultEffectiveDate(direction, now, noticeWindowDays))\n )\n setStep(2)\n previewPriceChange.reset()\n previewPriceChange.mutate(\n { productKey, priceKey: price.key },\n { onError: (err) => toastApiError(err, \"Preview affected subscribers\") }\n )\n }\n\n // Archived (prior-version) rows are history, not the editable current\n // price. Each product/key pair has exactly one live edit target.\n // Wait for the product before allowing previews or scheduling a migration.\n if (price.archived || !productKey || price.customer_amount) {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title={\n price.customer_amount\n ? \"This price lets customers choose an amount. Change its limits through a catalog application or the client.\"\n : price.archived\n ? \"This is an archived version. Change the current price for this key instead.\"\n : \"Load the product before changing its price.\"\n }\n >\n <AdminMessage id={\"Change price\"} />\n </Button>\n )\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n {!draft && (\n <DialogTrigger\n render={\n <Button variant=\"outline\" size=\"sm\">\n <AdminMessage id={\"Change price\"} />\n </Button>\n }\n />\n )}\n <DialogContent className={DIALOG_FORM}>\n <form\n className=\"grid gap-4\"\n onSubmit={(event) => {\n event.preventDefault()\n event.stopPropagation()\n if (step !== 3) return\n void form.handleSubmit()\n }}\n >\n <form.Subscribe\n selector={(state) => [state.values, state.isSubmitting] as const}\n >\n {([values, isSubmitting]) => {\n const newAmount =\n nativeAmountFromInput(values.amountInput, price.currency) ??\n price.unit_amount\n const direction = detectDirection(newAmount, price.unit_amount)\n const minDate = minEffectiveDate(direction, now, noticeWindowDays)\n const dateValid =\n values.mode !== \"migrate\" ||\n (!!values.effectiveAt &&\n isEffectiveDateValid(\n direction,\n new Date(values.effectiveAt),\n now,\n noticeWindowDays\n ))\n\n return (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <AdminMessage id={\"Change price for \"} />\n {productName}\n {draft && (\n <Badge variant=\"secondary\">\n <AdminMessage id={\"drafted by copilot\"} />\n </Badge>\n )}\n </DialogTitle>\n <DialogDescription>\n <AdminMessage id={\"Step \"} />\n {step}\n <AdminMessage id={\" of 3:\"} />{\" \"}\n {step === 1\n ? \"new amount\"\n : step === 2\n ? \"migration plan\"\n : \"review\"}\n </DialogDescription>\n </DialogHeader>\n\n {step === 1 && (\n <div className=\"grid gap-3\">\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Current amount\"} />\n </p>\n <p className=\"font-medium\">\n {formatNativeAmount(\n price.unit_amount,\n price.currency\n )}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Currency · interval\"} />\n </p>\n <p className=\"font-medium\">\n {price.currency.toUpperCase()} ·{\" \"}\n {priceIntervalLabel(price)}\n </p>\n </div>\n </div>\n <form.Field\n name=\"amountInput\"\n validators={{\n onBlur: ({ value }) => {\n const amount = nativeAmountFromInput(\n value,\n price.currency\n )\n if (amount === null || BigInt(amount) <= 0n) {\n return \"Enter an amount greater than zero.\"\n }\n return undefined\n },\n }}\n >\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-amount\")}>\n <AdminMessage id={\"New amount (major units)\"} />\n </Label>\n <Input\n id={adminId(\"wiz-amount\")}\n type=\"number\"\n step=\"any\"\n min=\"0\"\n value={field.state.value}\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n autoFocus\n />\n <FormFieldErrors errors={field.state.meta.errors} />\n </div>\n )}\n </form.Field>\n {direction !== \"unchanged\" && (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage id={\"This is a price\"} />{\" \"}\n <span className=\"font-medium text-foreground\">\n {direction === \"increase\" ? \"increase\" : \"decrease\"}\n </span>\n <AdminMessage\n id={\n \". Currency and interval stay locked to the current price.\"\n }\n />\n </p>\n )}\n </div>\n )}\n\n {step === 2 && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm\">\n {affectedCount === null && previewPriceChange.isPending\n ? \"Checking affected subscribers…\"\n : `${(affectedCount ?? 0).toLocaleString()} active subscription${affectedCount === 1 ? \"\" : \"s\"} on prior versions of ${price.key}.`}\n </p>\n <form.Field name=\"mode\">\n {(field) => (\n <div className=\"grid gap-2\">\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"grandfather\"}\n onChange={() =>\n field.handleChange(\"grandfather\")\n }\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Grandfather.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Existing subscribers keep the current price.\"\n }\n />\n {direction === \"increase\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"migrate\"}\n onChange={() => field.handleChange(\"migrate\")}\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Migrate.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Move everyone to the new price at their first renewal on or after a date you choose.\"\n }\n />\n {direction === \"decrease\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n </div>\n )}\n </form.Field>\n {values.mode === \"migrate\" && (\n <form.Field name=\"effectiveAt\">\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-effective\")}>\n <AdminMessage id={\"Effective date\"} />\n </Label>\n <Input\n id={adminId(\"wiz-effective\")}\n type=\"date\"\n value={field.state.value}\n min={\n minDate\n ? toDateInputValue(minDate)\n : undefined\n }\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n />\n {direction === \"increase\" ? (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage id={\"Must be at least \"} />\n {noticeWindowDays}\n <AdminMessage\n id={\n \" days away. Card networks require that much notice before a price goes up. You can change the number in Settings.\"\n }\n />\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A price going down needs no notice, so today is fine.\"\n }\n />\n </p>\n )}\n {!dateValid && (\n <p\n className=\"text-xs text-destructive\"\n role=\"alert\"\n >\n <AdminMessage id={\"Pick a later date.\"} />\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A reprice_scheduled notice is queued for every affected subscriber as soon as you confirm.\"\n }\n />\n </p>\n </div>\n )}\n </form.Field>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"grid gap-3\">\n <p className=\"rounded-md border bg-muted/40 p-3 text-sm\">\n {buildReviewText({\n newAmount,\n currentAmount: price.unit_amount,\n currency: price.currency,\n affectedCount: affectedCount ?? 0,\n plan: {\n mode: values.mode,\n effectiveAt:\n values.mode === \"migrate\"\n ? new Date(values.effectiveAt).toISOString()\n : \"\",\n },\n now,\n })}\n </p>\n </div>\n )}\n\n <DialogFooter>\n {step > 1 && (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isSubmitting}\n onClick={() => setStep((value) => (value - 1) as 1 | 2)}\n >\n <AdminMessage id={\"Back\"} />\n </Button>\n )}\n {step === 1 && (\n <Button\n type=\"button\"\n disabled={\n !values.amountInput ||\n direction === \"unchanged\" ||\n BigInt(newAmount) <= 0n\n }\n onClick={enterStep2}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 2 && (\n <Button\n type=\"button\"\n disabled={!dateValid || previewPriceChange.isPending}\n onClick={() => setStep(3)}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 3 && (\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? \"Working…\" : \"Confirm\"}\n </Button>\n )}\n </DialogFooter>\n </>\n )\n }}\n </form.Subscribe>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { E as e, l as t, w as n } from "./admin-utils-BK4Vh1o_.js";
2
- import { t as r } from "./admin-button-CTL3DgOt.js";
3
- import { t as i } from "./admin-input-CcfQfJFN.js";
4
- import { a, i as o, n as s, r as c, t as l } from "./admin-select-DBgprVW6.js";
1
+ import { E as e, l as t, w as n } from "./admin-utils-DnVY5fYd.js";
2
+ import { t as r } from "./admin-button-Oip73Plq.js";
3
+ import { t as i } from "./admin-input-DO5lvCLL.js";
4
+ import { a, i as o, n as s, r as c, t as l } from "./admin-select-CUXvh6j1.js";
5
5
  import { useRef as u } from "react";
6
6
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
7
7
  //#region src/admin/pages/dashboard/range.ts
@@ -189,4 +189,4 @@ function k() {
189
189
  //#endregion
190
190
  export { E as a, x as i, D as n, v as r, k as t };
191
191
 
192
- //# sourceMappingURL=admin-range-picker-Dr2M1EZr.js.map
192
+ //# sourceMappingURL=admin-range-picker-DaIvC1oC.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-range-picker-Dr2M1EZr.js","names":[],"sources":["../src/admin/pages/dashboard/range.ts","../src/admin/pages/dashboard/use-range.ts","../src/admin/pages/dashboard/range-picker.tsx"],"sourcesContent":["// The dashboard's range, kept in the URL: a preset (?range=7d) or a custom\n// span of UTC dates (?from=2026-09-01&to=2026-09-30, both inclusive). Every\n// widget runs over it except one that keeps its own (fixed_range).\nimport type {\n MetricsQuery,\n MetricsRange,\n Widget,\n} from \"#orck/admin/lib/api/metrics\"\n\nexport const RANGE_PRESETS = [\n { value: \"7d\", label: \"Last 7 days\" },\n { value: \"30d\", label: \"Last 30 days\" },\n { value: \"90d\", label: \"Last 3 months\" },\n { value: \"6m\", label: \"Last 6 months\" },\n { value: \"1y\", label: \"Last year\" },\n] as const\n\nexport const DEFAULT_PRESET = \"30d\"\n\nexport type DashboardRange = { preset: string } | { from: string; to: string }\n\nconst DATE = /^\\d{4}-\\d{2}-\\d{2}$/\n\nconst validDate = (s: string | null): s is string =>\n !!s && DATE.test(s) && !Number.isNaN(Date.parse(`${s}T00:00:00Z`))\n\nexport function rangeFromParams(params: URLSearchParams): DashboardRange {\n const from = params.get(\"from\")\n const to = params.get(\"to\")\n if (validDate(from) && validDate(to)) {\n return from <= to ? { from, to } : { from: to, to: from }\n }\n const preset = params.get(\"range\")\n return {\n preset: RANGE_PRESETS.some((p) => p.value === preset)\n ? preset!\n : DEFAULT_PRESET,\n }\n}\n\n// withRange is params showing range; the default preset leaves no trace.\nexport function withRange(\n params: URLSearchParams,\n range: DashboardRange\n): URLSearchParams {\n const p = new URLSearchParams(params)\n p.delete(\"range\")\n p.delete(\"from\")\n p.delete(\"to\")\n if (\"from\" in range) {\n p.set(\"from\", range.from)\n p.set(\"to\", range.to)\n } else if (range.preset !== DEFAULT_PRESET) {\n p.set(\"range\", range.preset)\n }\n return p\n}\n\nexport function metricsRange(range: DashboardRange): MetricsRange {\n return \"from\" in range\n ? { from: range.from, to: range.to }\n : { last: range.preset }\n}\n\nconst dayMs = 86_400_000\n\nconst utcDate = (d: Date) => d.toISOString().slice(0, 10)\n\n// rangeDates is the range as inclusive UTC dates. A preset ends today and\n// counts back as the API resolves \"last\": \"6m\" is six calendar months.\nexport function rangeDates(\n range: DashboardRange,\n now = new Date()\n): { from: string; to: string } {\n if (\"from\" in range) return range\n const [, n, unit] = /^(\\d+)([dwmy])$/.exec(range.preset) ?? [\"\", \"30\", \"d\"]\n const count = Number(n)\n const start = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)\n )\n if (unit === \"d\") start.setUTCDate(start.getUTCDate() - count)\n if (unit === \"w\") start.setUTCDate(start.getUTCDate() - 7 * count)\n if (unit === \"m\") start.setUTCMonth(start.getUTCMonth() - count)\n if (unit === \"y\") start.setUTCFullYear(start.getUTCFullYear() - count)\n return { from: utcDate(start), to: utcDate(now) }\n}\n\nexport function rangeLabel(range: DashboardRange, now = new Date()): string {\n const { from, to } = rangeDates(range, now)\n const fmt = (s: string) =>\n new Date(`${s}T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: s.slice(0, 4) === utcDate(now).slice(0, 4) ? undefined : \"numeric\",\n timeZone: \"UTC\",\n })\n return from === to ? fmt(from) : `${fmt(from)} – ${fmt(to)}`\n}\n\n// MAX_BUCKETS is the metrics API's limit on a time series.\nconst MAX_BUCKETS = 400\nconst GRAINS = [\"day\", \"week\", \"month\", \"quarter\", \"year\"]\nconst GRAIN_DAYS: Record<string, number> = {\n day: 1,\n week: 7,\n month: 28,\n quarter: 89,\n year: 365,\n}\n\n// fitGrain coarsens grain until the range fits the bucket limit, so a long\n// custom range never turns a daily chart into an error.\nexport function fitGrain(\n grain: string,\n range: DashboardRange,\n now = new Date()\n): string {\n const { from, to } = rangeDates(range, now)\n const days = (Date.parse(to) - Date.parse(from)) / dayMs + 1\n let i = Math.max(GRAINS.indexOf(grain), 0)\n while (\n i < GRAINS.length - 1 &&\n days / GRAIN_DAYS[GRAINS[i]] > MAX_BUCKETS - 2\n )\n i++\n return GRAINS[i]\n}\n\n// widgetQuery is the query a widget runs on a dashboard showing range.\nexport function widgetQuery(\n widget: Widget,\n range: DashboardRange\n): MetricsQuery {\n if (widget.fixed_range) return widget.query\n const query: MetricsQuery = { ...widget.query, range: metricsRange(range) }\n if (query.grain) query.grain = fitGrain(query.grain, range)\n return query\n}\n","import { useSearchParams } from \"#orck/admin/router\"\n\nimport { rangeFromParams, withRange, type DashboardRange } from \"./range\"\n\n// useDashboardRange reads the range from the URL and replaces it there.\nexport function useDashboardRange(): [\n DashboardRange,\n (range: DashboardRange) => void,\n] {\n const [params, setParams] = useSearchParams()\n return [\n rangeFromParams(params),\n (range) => setParams(withRange(params, range), { replace: true }),\n ]\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useRef } from \"react\"\n// Range picker for the dashboard and its reports: the presets, or a custom\n// start and end date. The choice lives in the URL, so a link shows the same\n// range.\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport { Input } from \"#orck/admin/components/ui/input\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"#orck/admin/components/ui/select\"\n\nimport { DEFAULT_PRESET, RANGE_PRESETS, rangeDates, rangeLabel } from \"./range\"\nimport { useDashboardRange } from \"./use-range\"\n\nconst CUSTOM = \"custom\"\n\nexport function RangePicker() {\n const { text: adminText } = useAdminUI()\n\n const [range, setRange] = useDashboardRange()\n // A host router can publish its new URL after the next input edit. Read\n // both live fields; the committed URL key resets them on back/forward.\n const startInput = useRef<HTMLInputElement>(null)\n const endInput = useRef<HTMLInputElement>(null)\n const custom = \"from\" in range\n const items = [\n ...RANGE_PRESETS.map((p) => ({ value: p.value, label: p.label })),\n { value: CUSTOM, label: \"Custom range\" },\n ]\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className=\"hidden text-sm text-muted-foreground tabular-nums sm:inline\"\n aria-live=\"polite\"\n >\n {rangeLabel(range)}\n </span>\n <Select\n items={items}\n value={custom ? CUSTOM : range.preset}\n onValueChange={(next) => {\n if (!next) return\n setRange(next === CUSTOM ? rangeDates(range) : { preset: next })\n }}\n >\n <SelectTrigger\n size=\"sm\"\n className=\"w-[150px]\"\n aria-label={adminText(\"Date range\")}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {items.map((item) => (\n <SelectItem key={item.value} value={item.value}>\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {custom && (\n <div key={`${range.from}/${range.to}`} className=\"contents\">\n <label className=\"flex items-center gap-1.5 text-xs font-medium\">\n <AdminMessage id={\"Start\"} />\n <Input\n ref={startInput}\n type=\"date\"\n className=\"h-8 w-auto\"\n defaultValue={range.from}\n max={range.to}\n onChange={(e) => {\n const from = e.target.value\n const to = endInput.current?.value || range.to\n if (from) setRange({ from, to: from > to ? from : to })\n }}\n />\n </label>\n <label className=\"flex items-center gap-1.5 text-xs font-medium\">\n <AdminMessage id={\"End\"} />\n <Input\n ref={endInput}\n type=\"date\"\n className=\"h-8 w-auto\"\n defaultValue={range.to}\n min={range.from}\n onChange={(e) => {\n const to = e.target.value\n const from = startInput.current?.value || range.from\n if (to) setRange({ from: to < from ? to : from, to })\n }}\n />\n </label>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setRange({ preset: DEFAULT_PRESET })}\n >\n <AdminMessage id={\"Reset\"} />\n </Button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-range-picker-DaIvC1oC.js","names":[],"sources":["../src/admin/pages/dashboard/range.ts","../src/admin/pages/dashboard/use-range.ts","../src/admin/pages/dashboard/range-picker.tsx"],"sourcesContent":["// The dashboard's range, kept in the URL: a preset (?range=7d) or a custom\n// span of UTC dates (?from=2026-09-01&to=2026-09-30, both inclusive). Every\n// widget runs over it except one that keeps its own (fixed_range).\nimport type {\n MetricsQuery,\n MetricsRange,\n Widget,\n} from \"#orck/admin/lib/api/metrics\"\n\nexport const RANGE_PRESETS = [\n { value: \"7d\", label: \"Last 7 days\" },\n { value: \"30d\", label: \"Last 30 days\" },\n { value: \"90d\", label: \"Last 3 months\" },\n { value: \"6m\", label: \"Last 6 months\" },\n { value: \"1y\", label: \"Last year\" },\n] as const\n\nexport const DEFAULT_PRESET = \"30d\"\n\nexport type DashboardRange = { preset: string } | { from: string; to: string }\n\nconst DATE = /^\\d{4}-\\d{2}-\\d{2}$/\n\nconst validDate = (s: string | null): s is string =>\n !!s && DATE.test(s) && !Number.isNaN(Date.parse(`${s}T00:00:00Z`))\n\nexport function rangeFromParams(params: URLSearchParams): DashboardRange {\n const from = params.get(\"from\")\n const to = params.get(\"to\")\n if (validDate(from) && validDate(to)) {\n return from <= to ? { from, to } : { from: to, to: from }\n }\n const preset = params.get(\"range\")\n return {\n preset: RANGE_PRESETS.some((p) => p.value === preset)\n ? preset!\n : DEFAULT_PRESET,\n }\n}\n\n// withRange is params showing range; the default preset leaves no trace.\nexport function withRange(\n params: URLSearchParams,\n range: DashboardRange\n): URLSearchParams {\n const p = new URLSearchParams(params)\n p.delete(\"range\")\n p.delete(\"from\")\n p.delete(\"to\")\n if (\"from\" in range) {\n p.set(\"from\", range.from)\n p.set(\"to\", range.to)\n } else if (range.preset !== DEFAULT_PRESET) {\n p.set(\"range\", range.preset)\n }\n return p\n}\n\nexport function metricsRange(range: DashboardRange): MetricsRange {\n return \"from\" in range\n ? { from: range.from, to: range.to }\n : { last: range.preset }\n}\n\nconst dayMs = 86_400_000\n\nconst utcDate = (d: Date) => d.toISOString().slice(0, 10)\n\n// rangeDates is the range as inclusive UTC dates. A preset ends today and\n// counts back as the API resolves \"last\": \"6m\" is six calendar months.\nexport function rangeDates(\n range: DashboardRange,\n now = new Date()\n): { from: string; to: string } {\n if (\"from\" in range) return range\n const [, n, unit] = /^(\\d+)([dwmy])$/.exec(range.preset) ?? [\"\", \"30\", \"d\"]\n const count = Number(n)\n const start = new Date(\n Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)\n )\n if (unit === \"d\") start.setUTCDate(start.getUTCDate() - count)\n if (unit === \"w\") start.setUTCDate(start.getUTCDate() - 7 * count)\n if (unit === \"m\") start.setUTCMonth(start.getUTCMonth() - count)\n if (unit === \"y\") start.setUTCFullYear(start.getUTCFullYear() - count)\n return { from: utcDate(start), to: utcDate(now) }\n}\n\nexport function rangeLabel(range: DashboardRange, now = new Date()): string {\n const { from, to } = rangeDates(range, now)\n const fmt = (s: string) =>\n new Date(`${s}T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: s.slice(0, 4) === utcDate(now).slice(0, 4) ? undefined : \"numeric\",\n timeZone: \"UTC\",\n })\n return from === to ? fmt(from) : `${fmt(from)} – ${fmt(to)}`\n}\n\n// MAX_BUCKETS is the metrics API's limit on a time series.\nconst MAX_BUCKETS = 400\nconst GRAINS = [\"day\", \"week\", \"month\", \"quarter\", \"year\"]\nconst GRAIN_DAYS: Record<string, number> = {\n day: 1,\n week: 7,\n month: 28,\n quarter: 89,\n year: 365,\n}\n\n// fitGrain coarsens grain until the range fits the bucket limit, so a long\n// custom range never turns a daily chart into an error.\nexport function fitGrain(\n grain: string,\n range: DashboardRange,\n now = new Date()\n): string {\n const { from, to } = rangeDates(range, now)\n const days = (Date.parse(to) - Date.parse(from)) / dayMs + 1\n let i = Math.max(GRAINS.indexOf(grain), 0)\n while (\n i < GRAINS.length - 1 &&\n days / GRAIN_DAYS[GRAINS[i]] > MAX_BUCKETS - 2\n )\n i++\n return GRAINS[i]\n}\n\n// widgetQuery is the query a widget runs on a dashboard showing range.\nexport function widgetQuery(\n widget: Widget,\n range: DashboardRange\n): MetricsQuery {\n if (widget.fixed_range) return widget.query\n const query: MetricsQuery = { ...widget.query, range: metricsRange(range) }\n if (query.grain) query.grain = fitGrain(query.grain, range)\n return query\n}\n","import { useSearchParams } from \"#orck/admin/router\"\n\nimport { rangeFromParams, withRange, type DashboardRange } from \"./range\"\n\n// useDashboardRange reads the range from the URL and replaces it there.\nexport function useDashboardRange(): [\n DashboardRange,\n (range: DashboardRange) => void,\n] {\n const [params, setParams] = useSearchParams()\n return [\n rangeFromParams(params),\n (range) => setParams(withRange(params, range), { replace: true }),\n ]\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useRef } from \"react\"\n// Range picker for the dashboard and its reports: the presets, or a custom\n// start and end date. The choice lives in the URL, so a link shows the same\n// range.\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport { Input } from \"#orck/admin/components/ui/input\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"#orck/admin/components/ui/select\"\n\nimport { DEFAULT_PRESET, RANGE_PRESETS, rangeDates, rangeLabel } from \"./range\"\nimport { useDashboardRange } from \"./use-range\"\n\nconst CUSTOM = \"custom\"\n\nexport function RangePicker() {\n const { text: adminText } = useAdminUI()\n\n const [range, setRange] = useDashboardRange()\n // A host router can publish its new URL after the next input edit. Read\n // both live fields; the committed URL key resets them on back/forward.\n const startInput = useRef<HTMLInputElement>(null)\n const endInput = useRef<HTMLInputElement>(null)\n const custom = \"from\" in range\n const items = [\n ...RANGE_PRESETS.map((p) => ({ value: p.value, label: p.label })),\n { value: CUSTOM, label: \"Custom range\" },\n ]\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className=\"hidden text-sm text-muted-foreground tabular-nums sm:inline\"\n aria-live=\"polite\"\n >\n {rangeLabel(range)}\n </span>\n <Select\n items={items}\n value={custom ? CUSTOM : range.preset}\n onValueChange={(next) => {\n if (!next) return\n setRange(next === CUSTOM ? rangeDates(range) : { preset: next })\n }}\n >\n <SelectTrigger\n size=\"sm\"\n className=\"w-[150px]\"\n aria-label={adminText(\"Date range\")}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {items.map((item) => (\n <SelectItem key={item.value} value={item.value}>\n {item.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {custom && (\n <div key={`${range.from}/${range.to}`} className=\"contents\">\n <label className=\"flex items-center gap-1.5 text-xs font-medium\">\n <AdminMessage id={\"Start\"} />\n <Input\n ref={startInput}\n type=\"date\"\n className=\"h-8 w-auto\"\n defaultValue={range.from}\n max={range.to}\n onChange={(e) => {\n const from = e.target.value\n const to = endInput.current?.value || range.to\n if (from) setRange({ from, to: from > to ? from : to })\n }}\n />\n </label>\n <label className=\"flex items-center gap-1.5 text-xs font-medium\">\n <AdminMessage id={\"End\"} />\n <Input\n ref={endInput}\n type=\"date\"\n className=\"h-8 w-auto\"\n defaultValue={range.to}\n min={range.from}\n onChange={(e) => {\n const to = e.target.value\n const from = startInput.current?.value || range.from\n if (to) setRange({ from: to < from ? to : from, to })\n }}\n />\n </label>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setRange({ preset: DEFAULT_PRESET })}\n >\n <AdminMessage id={\"Reset\"} />\n </Button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { E as e, S as t, i as n, p as r, w as i } from "./admin-utils-BK4Vh1o_.js";
2
- import { t as a } from "./admin-button-CTL3DgOt.js";
3
- import { t as o } from "./admin-skeleton-DsM7crYi.js";
4
- import { r as s } from "./admin-capabilities-BuigAgtm.js";
5
- import { a as c, i as l, n as u, o as d, r as f, s as p, t as m } from "./admin-table-CH9TrXJf.js";
6
- import { a as h, i as g, n as _, o as v, r as y, t as b } from "./admin-card-bzAks3-3.js";
1
+ import { E as e, S as t, i as n, p as r, w as i } from "./admin-utils-DnVY5fYd.js";
2
+ import { t as a } from "./admin-button-Oip73Plq.js";
3
+ import { t as o } from "./admin-skeleton-DEY3Q8Dg.js";
4
+ import { r as s } from "./admin-capabilities-BGv7oG8v.js";
5
+ import { a as c, i as l, n as u, o as d, r as f, s as p, t as m } from "./admin-table-CWhiCb9d.js";
6
+ import { a as h, i as g, n as _, o as v, r as y, t as b } from "./admin-card-DGbi6ZHZ.js";
7
7
  import { t as x } from "./admin-csv-CPoNZj2D.js";
8
- import { i as S, n as C, r as w, t as T } from "./admin-range-picker-Dr2M1EZr.js";
8
+ import { i as S, n as C, r as w, t as T } from "./admin-range-picker-DaIvC1oC.js";
9
9
  import { useQuery as E } from "@tanstack/react-query";
10
10
  import { jsx as D, jsxs as O } from "react/jsx-runtime";
11
11
  import { HugeiconsIcon as k } from "@hugeicons/react";
@@ -317,4 +317,4 @@ function Z({ range: t }) {
317
317
  //#endregion
318
318
  export { K as ReportsPage };
319
319
 
320
- //# sourceMappingURL=admin-reports-CywKLiSA.js.map
320
+ //# sourceMappingURL=admin-reports-CA-cnNur.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-reports-CywKLiSA.js","names":[],"sources":["../src/admin/pages/dashboard/reports-model.ts","../src/admin/pages/dashboard/reports.tsx"],"sourcesContent":["// The Reports page's tables, built from metrics queries over the dashboard's\n// range: subscriptions by UTC day, renewals and churn by UTC month.\nimport type {\n MetricsCell,\n MetricsQuery,\n MetricsResult,\n} from \"#orck/admin/lib/api/metrics\"\n\nimport { metricsRange, rangeDates, type DashboardRange } from \"./range\"\n\n// MAX_DAYS is the most days the daily report covers (the API's bucket limit).\nexport const MAX_DAYS = 400\n\nexport function rangeDays(range: DashboardRange, now = new Date()): number {\n const { from, to } = rangeDates(range, now)\n return (Date.parse(to) - Date.parse(from)) / 86_400_000 + 1\n}\n\nexport function dailyQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"new_subscriptions\", \"cancellations\", \"net_new_subscriptions\"],\n by: [\"time\"],\n grain: \"day\",\n range: metricsRange(range),\n }\n}\n\nexport function renewalsQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"payment_count\"],\n by: [\"time\"],\n grain: \"month\",\n range: metricsRange(range),\n filters: { attempt_kind: [\"renewal\"] },\n }\n}\n\nexport function churnQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"cancellations\", \"churn_rate\"],\n by: [\"time\"],\n grain: \"month\",\n range: metricsRange(range),\n }\n}\n\nexport interface DailyRow {\n // date is the UTC day, YYYY-MM-DD.\n date: string\n started: number\n canceled: number\n net: number\n}\n\nexport interface MonthlyRow {\n // month is the UTC month, YYYY-MM.\n month: string\n renewals: number\n canceled: number\n // churn is null for a month that began with no subscriptions.\n churn: number | null\n}\n\nconst count = (cell: MetricsCell | undefined) => Number(cell ?? 0)\n\n// cellsByBucket keys each row's measures by its time bucket.\nfunction cellsByBucket(\n result: MetricsResult\n): Map<string, Record<string, MetricsCell>> {\n const time = result.columns.findIndex((c) => c.kind === \"time\")\n const out = new Map<string, Record<string, MetricsCell>>()\n for (const row of result.rows) {\n const cells: Record<string, MetricsCell> = {}\n result.columns.forEach((c, i) => {\n cells[c.name] = row[i]\n })\n out.set(String(row[time]), cells)\n }\n return out\n}\n\n// dailyRows is the daily report, oldest day first.\nexport function dailyRows(result: MetricsResult): DailyRow[] {\n return [...cellsByBucket(result)]\n .sort(([a], [b]) => (a < b ? -1 : 1))\n .map(([bucket, cells]) => ({\n date: bucket.slice(0, 10),\n started: count(cells.new_subscriptions),\n canceled: count(cells.cancellations),\n net: count(cells.net_new_subscriptions),\n }))\n}\n\n// monthlyRows joins renewals with cancellations and churn, oldest month first.\nexport function monthlyRows(\n renewals: MetricsResult,\n churn: MetricsResult\n): MonthlyRow[] {\n const paid = cellsByBucket(renewals)\n const lost = cellsByBucket(churn)\n const buckets = [...new Set([...paid.keys(), ...lost.keys()])].sort()\n return buckets.map((bucket) => {\n const rate = lost.get(bucket)?.churn_rate\n return {\n month: bucket.slice(0, 7),\n renewals: count(paid.get(bucket)?.payment_count),\n canceled: count(lost.get(bucket)?.cancellations),\n churn: rate === null || rate === undefined ? null : Number(rate),\n }\n })\n}\n\nexport const dailyCsv = (rows: DailyRow[]) => ({\n header: [\"date\", \"new_subscriptions\", \"cancellations\", \"net_change\"],\n rows: rows.map((r) => [r.date, r.started, r.canceled, r.net]),\n})\n\nexport const monthlyCsv = (rows: MonthlyRow[]) => ({\n header: [\"month\", \"renewals\", \"cancellations\", \"churn_rate\"],\n rows: rows.map((r) => [r.month, r.renewals, r.canceled, r.churn ?? \"\"]),\n})\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\n// Dashboard › Reports: the daily subscription report and the monthly renewal\n// report over the dashboard's range, each a table with a CSV download.\nimport { HugeiconsIcon } from \"@hugeicons/react\"\nimport { Download01Icon } from \"@hugeicons/core-free-icons\"\nimport type * as React from \"react\"\nimport { Navigate } from \"#orck/admin/router\"\nimport { useQuery } from \"@tanstack/react-query\"\n\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"#orck/admin/components/ui/card\"\nimport { Skeleton } from \"#orck/admin/components/ui/skeleton\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"#orck/admin/components/ui/table\"\nimport { ApiError } from \"#orck/admin/lib/api/client\"\nimport { useAccess } from \"#orck/admin/lib/capabilities\"\nimport { downloadCsv } from \"#orck/admin/lib/csv\"\n\nimport { rangeDates, type DashboardRange } from \"./range\"\nimport { RangePicker } from \"./range-picker\"\nimport { useDashboardRange } from \"./use-range\"\nimport {\n MAX_DAYS,\n churnQuery,\n dailyCsv,\n dailyQuery,\n dailyRows,\n monthlyCsv,\n monthlyRows,\n rangeDays,\n renewalsQuery,\n} from \"./reports-model\"\n\nconst utcDay = (date: string) =>\n new Date(`${date}T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n timeZone: \"UTC\",\n })\n\nconst utcMonth = (month: string) =>\n new Date(`${month}-01T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n year: \"numeric\",\n timeZone: \"UTC\",\n })\n\nconst signed = (n: number) =>\n n > 0 ? `+${n.toLocaleString()}` : n.toLocaleString()\n\nconst percent = (ratio: number | null) =>\n ratio === null\n ? \"—\"\n : `${(ratio * 100).toLocaleString(undefined, { maximumFractionDigits: 2 })}%`\n\nconst suffix = (range: DashboardRange) => {\n const { from, to } = rangeDates(range)\n return `${from}-to-${to}`\n}\n\nexport function ReportsPage() {\n const access = useAccess()\n const [range] = useDashboardRange()\n if (!access) return null\n if (!access.metrics) return <Navigate to=\"/\" replace />\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <AdminMessage id={\"Reports\"} />\n </h1>\n <RangePicker />\n </div>\n <DailyReport range={range} />\n <MonthlyReport range={range} />\n </div>\n )\n}\n\nfunction failure(error: unknown) {\n if (error instanceof ApiError && error.isPermissionDenied)\n return \"Your role cannot read metrics.\"\n return error instanceof Error ? error.message : \"The report failed to load.\"\n}\n\nfunction ReportCard({\n title,\n description,\n onDownload,\n canDownload,\n children,\n}: {\n title: string\n description: string\n onDownload: () => void\n canDownload: boolean\n children: React.ReactNode\n}) {\n return (\n <Card>\n <CardHeader>\n <CardTitle>{title}</CardTitle>\n <CardDescription>{description}</CardDescription>\n <CardAction>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onDownload}\n disabled={!canDownload}\n >\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" />\n <AdminMessage id={\"Download CSV\"} />\n </Button>\n </CardAction>\n </CardHeader>\n <CardContent className=\"overflow-x-auto\">{children}</CardContent>\n </Card>\n )\n}\n\nfunction ReportState({\n loading,\n error,\n retry,\n empty,\n}: {\n loading: boolean\n error: unknown\n retry: () => void\n empty: boolean\n}) {\n if (loading)\n return (\n <div className=\"grid gap-2\" aria-busy>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-6 w-full\" />\n ))}\n </div>\n )\n if (error)\n return (\n <div\n role=\"alert\"\n className=\"flex flex-wrap items-center gap-3 text-sm text-destructive\"\n >\n {failure(error)}\n <Button size=\"sm\" variant=\"outline\" onClick={retry}>\n <AdminMessage id={\"Retry\"} />\n </Button>\n </div>\n )\n if (empty)\n return (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n <AdminMessage id={\"Nothing in this range. Pick a wider range.\"} />\n </p>\n )\n return null\n}\n\nfunction DailyReport({ range }: { range: DashboardRange }) {\n const { text: adminText } = useAdminUI()\n\n const { adminQueries } = useAdminServices()\n\n const tooLong = rangeDays(range) > MAX_DAYS\n const query = useQuery({\n ...adminQueries.widgetMetrics(dailyQuery(range)),\n enabled: !tooLong,\n })\n const rows = query.data ? dailyRows(query.data) : []\n const totals = rows.reduce(\n (t, r) => ({\n started: t.started + r.started,\n canceled: t.canceled + r.canceled,\n net: t.net + r.net,\n }),\n { started: 0, canceled: 0, net: 0 }\n )\n return (\n <ReportCard\n title={adminText(\"Daily subscriptions\")}\n description={adminText(\n \"Subscriptions started and canceled each day (UTC), newest first.\"\n )}\n canDownload={rows.length > 0}\n onDownload={() => {\n const csv = dailyCsv(rows)\n downloadCsv(\n `daily-subscriptions-${suffix(range)}.csv`,\n csv.header,\n csv.rows\n )\n }}\n >\n {tooLong ? (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n <AdminMessage id={\"The daily report covers at most \"} />\n {MAX_DAYS}\n <AdminMessage id={\" days. Pick a shorter range.\"} />\n </p>\n ) : rows.length === 0 || query.error ? (\n <ReportState\n loading={query.isPending}\n error={query.error}\n retry={() => void query.refetch()}\n empty={rows.length === 0}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead>\n <AdminMessage id={\"Date\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"New\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Canceled\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Net change\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {[...rows].reverse().map((r) => (\n <TableRow key={r.date}>\n <TableCell className=\"tabular-nums\">{utcDay(r.date)}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.started.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {signed(r.net)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n <TableFooter>\n <TableRow>\n <TableCell>\n <AdminMessage id={\"Total\"} />\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {totals.started.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {totals.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {signed(totals.net)}\n </TableCell>\n </TableRow>\n </TableFooter>\n </Table>\n )}\n </ReportCard>\n )\n}\n\nfunction MonthlyReport({ range }: { range: DashboardRange }) {\n const { text: adminText } = useAdminUI()\n\n const { adminQueries } = useAdminServices()\n\n const renewals = useQuery(adminQueries.widgetMetrics(renewalsQuery(range)))\n const churn = useQuery(adminQueries.widgetMetrics(churnQuery(range)))\n const rows =\n renewals.data && churn.data ? monthlyRows(renewals.data, churn.data) : []\n const error = renewals.error ?? churn.error\n return (\n <ReportCard\n title={adminText(\"Monthly renewals\")}\n description={adminText(\n \"Renewal payments, cancellations and churn each calendar month (UTC); churn is the month's cancellations over the subscriptions it began with.\"\n )}\n canDownload={rows.length > 0}\n onDownload={() => {\n const csv = monthlyCsv(rows)\n downloadCsv(\n `monthly-renewals-${suffix(range)}.csv`,\n csv.header,\n csv.rows\n )\n }}\n >\n {rows.length === 0 || error ? (\n <ReportState\n loading={renewals.isPending || churn.isPending}\n error={error}\n retry={() => {\n void renewals.refetch()\n void churn.refetch()\n }}\n empty={rows.length === 0}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead>\n <AdminMessage id={\"Month\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Renewals\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Canceled\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Churn\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {[...rows].reverse().map((r) => (\n <TableRow key={r.month}>\n <TableCell>{utcMonth(r.month)}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.renewals.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {percent(r.churn)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </ReportCard>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-reports-CA-cnNur.js","names":[],"sources":["../src/admin/pages/dashboard/reports-model.ts","../src/admin/pages/dashboard/reports.tsx"],"sourcesContent":["// The Reports page's tables, built from metrics queries over the dashboard's\n// range: subscriptions by UTC day, renewals and churn by UTC month.\nimport type {\n MetricsCell,\n MetricsQuery,\n MetricsResult,\n} from \"#orck/admin/lib/api/metrics\"\n\nimport { metricsRange, rangeDates, type DashboardRange } from \"./range\"\n\n// MAX_DAYS is the most days the daily report covers (the API's bucket limit).\nexport const MAX_DAYS = 400\n\nexport function rangeDays(range: DashboardRange, now = new Date()): number {\n const { from, to } = rangeDates(range, now)\n return (Date.parse(to) - Date.parse(from)) / 86_400_000 + 1\n}\n\nexport function dailyQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"new_subscriptions\", \"cancellations\", \"net_new_subscriptions\"],\n by: [\"time\"],\n grain: \"day\",\n range: metricsRange(range),\n }\n}\n\nexport function renewalsQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"payment_count\"],\n by: [\"time\"],\n grain: \"month\",\n range: metricsRange(range),\n filters: { attempt_kind: [\"renewal\"] },\n }\n}\n\nexport function churnQuery(range: DashboardRange): MetricsQuery {\n return {\n measures: [\"cancellations\", \"churn_rate\"],\n by: [\"time\"],\n grain: \"month\",\n range: metricsRange(range),\n }\n}\n\nexport interface DailyRow {\n // date is the UTC day, YYYY-MM-DD.\n date: string\n started: number\n canceled: number\n net: number\n}\n\nexport interface MonthlyRow {\n // month is the UTC month, YYYY-MM.\n month: string\n renewals: number\n canceled: number\n // churn is null for a month that began with no subscriptions.\n churn: number | null\n}\n\nconst count = (cell: MetricsCell | undefined) => Number(cell ?? 0)\n\n// cellsByBucket keys each row's measures by its time bucket.\nfunction cellsByBucket(\n result: MetricsResult\n): Map<string, Record<string, MetricsCell>> {\n const time = result.columns.findIndex((c) => c.kind === \"time\")\n const out = new Map<string, Record<string, MetricsCell>>()\n for (const row of result.rows) {\n const cells: Record<string, MetricsCell> = {}\n result.columns.forEach((c, i) => {\n cells[c.name] = row[i]\n })\n out.set(String(row[time]), cells)\n }\n return out\n}\n\n// dailyRows is the daily report, oldest day first.\nexport function dailyRows(result: MetricsResult): DailyRow[] {\n return [...cellsByBucket(result)]\n .sort(([a], [b]) => (a < b ? -1 : 1))\n .map(([bucket, cells]) => ({\n date: bucket.slice(0, 10),\n started: count(cells.new_subscriptions),\n canceled: count(cells.cancellations),\n net: count(cells.net_new_subscriptions),\n }))\n}\n\n// monthlyRows joins renewals with cancellations and churn, oldest month first.\nexport function monthlyRows(\n renewals: MetricsResult,\n churn: MetricsResult\n): MonthlyRow[] {\n const paid = cellsByBucket(renewals)\n const lost = cellsByBucket(churn)\n const buckets = [...new Set([...paid.keys(), ...lost.keys()])].sort()\n return buckets.map((bucket) => {\n const rate = lost.get(bucket)?.churn_rate\n return {\n month: bucket.slice(0, 7),\n renewals: count(paid.get(bucket)?.payment_count),\n canceled: count(lost.get(bucket)?.cancellations),\n churn: rate === null || rate === undefined ? null : Number(rate),\n }\n })\n}\n\nexport const dailyCsv = (rows: DailyRow[]) => ({\n header: [\"date\", \"new_subscriptions\", \"cancellations\", \"net_change\"],\n rows: rows.map((r) => [r.date, r.started, r.canceled, r.net]),\n})\n\nexport const monthlyCsv = (rows: MonthlyRow[]) => ({\n header: [\"month\", \"renewals\", \"cancellations\", \"churn_rate\"],\n rows: rows.map((r) => [r.month, r.renewals, r.canceled, r.churn ?? \"\"]),\n})\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\n// Dashboard › Reports: the daily subscription report and the monthly renewal\n// report over the dashboard's range, each a table with a CSV download.\nimport { HugeiconsIcon } from \"@hugeicons/react\"\nimport { Download01Icon } from \"@hugeicons/core-free-icons\"\nimport type * as React from \"react\"\nimport { Navigate } from \"#orck/admin/router\"\nimport { useQuery } from \"@tanstack/react-query\"\n\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"#orck/admin/components/ui/card\"\nimport { Skeleton } from \"#orck/admin/components/ui/skeleton\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n} from \"#orck/admin/components/ui/table\"\nimport { ApiError } from \"#orck/admin/lib/api/client\"\nimport { useAccess } from \"#orck/admin/lib/capabilities\"\nimport { downloadCsv } from \"#orck/admin/lib/csv\"\n\nimport { rangeDates, type DashboardRange } from \"./range\"\nimport { RangePicker } from \"./range-picker\"\nimport { useDashboardRange } from \"./use-range\"\nimport {\n MAX_DAYS,\n churnQuery,\n dailyCsv,\n dailyQuery,\n dailyRows,\n monthlyCsv,\n monthlyRows,\n rangeDays,\n renewalsQuery,\n} from \"./reports-model\"\n\nconst utcDay = (date: string) =>\n new Date(`${date}T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n timeZone: \"UTC\",\n })\n\nconst utcMonth = (month: string) =>\n new Date(`${month}-01T00:00:00Z`).toLocaleDateString(undefined, {\n month: \"short\",\n year: \"numeric\",\n timeZone: \"UTC\",\n })\n\nconst signed = (n: number) =>\n n > 0 ? `+${n.toLocaleString()}` : n.toLocaleString()\n\nconst percent = (ratio: number | null) =>\n ratio === null\n ? \"—\"\n : `${(ratio * 100).toLocaleString(undefined, { maximumFractionDigits: 2 })}%`\n\nconst suffix = (range: DashboardRange) => {\n const { from, to } = rangeDates(range)\n return `${from}-to-${to}`\n}\n\nexport function ReportsPage() {\n const access = useAccess()\n const [range] = useDashboardRange()\n if (!access) return null\n if (!access.metrics) return <Navigate to=\"/\" replace />\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <AdminMessage id={\"Reports\"} />\n </h1>\n <RangePicker />\n </div>\n <DailyReport range={range} />\n <MonthlyReport range={range} />\n </div>\n )\n}\n\nfunction failure(error: unknown) {\n if (error instanceof ApiError && error.isPermissionDenied)\n return \"Your role cannot read metrics.\"\n return error instanceof Error ? error.message : \"The report failed to load.\"\n}\n\nfunction ReportCard({\n title,\n description,\n onDownload,\n canDownload,\n children,\n}: {\n title: string\n description: string\n onDownload: () => void\n canDownload: boolean\n children: React.ReactNode\n}) {\n return (\n <Card>\n <CardHeader>\n <CardTitle>{title}</CardTitle>\n <CardDescription>{description}</CardDescription>\n <CardAction>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={onDownload}\n disabled={!canDownload}\n >\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" />\n <AdminMessage id={\"Download CSV\"} />\n </Button>\n </CardAction>\n </CardHeader>\n <CardContent className=\"overflow-x-auto\">{children}</CardContent>\n </Card>\n )\n}\n\nfunction ReportState({\n loading,\n error,\n retry,\n empty,\n}: {\n loading: boolean\n error: unknown\n retry: () => void\n empty: boolean\n}) {\n if (loading)\n return (\n <div className=\"grid gap-2\" aria-busy>\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-6 w-full\" />\n ))}\n </div>\n )\n if (error)\n return (\n <div\n role=\"alert\"\n className=\"flex flex-wrap items-center gap-3 text-sm text-destructive\"\n >\n {failure(error)}\n <Button size=\"sm\" variant=\"outline\" onClick={retry}>\n <AdminMessage id={\"Retry\"} />\n </Button>\n </div>\n )\n if (empty)\n return (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n <AdminMessage id={\"Nothing in this range. Pick a wider range.\"} />\n </p>\n )\n return null\n}\n\nfunction DailyReport({ range }: { range: DashboardRange }) {\n const { text: adminText } = useAdminUI()\n\n const { adminQueries } = useAdminServices()\n\n const tooLong = rangeDays(range) > MAX_DAYS\n const query = useQuery({\n ...adminQueries.widgetMetrics(dailyQuery(range)),\n enabled: !tooLong,\n })\n const rows = query.data ? dailyRows(query.data) : []\n const totals = rows.reduce(\n (t, r) => ({\n started: t.started + r.started,\n canceled: t.canceled + r.canceled,\n net: t.net + r.net,\n }),\n { started: 0, canceled: 0, net: 0 }\n )\n return (\n <ReportCard\n title={adminText(\"Daily subscriptions\")}\n description={adminText(\n \"Subscriptions started and canceled each day (UTC), newest first.\"\n )}\n canDownload={rows.length > 0}\n onDownload={() => {\n const csv = dailyCsv(rows)\n downloadCsv(\n `daily-subscriptions-${suffix(range)}.csv`,\n csv.header,\n csv.rows\n )\n }}\n >\n {tooLong ? (\n <p className=\"py-6 text-center text-sm text-muted-foreground\">\n <AdminMessage id={\"The daily report covers at most \"} />\n {MAX_DAYS}\n <AdminMessage id={\" days. Pick a shorter range.\"} />\n </p>\n ) : rows.length === 0 || query.error ? (\n <ReportState\n loading={query.isPending}\n error={query.error}\n retry={() => void query.refetch()}\n empty={rows.length === 0}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead>\n <AdminMessage id={\"Date\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"New\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Canceled\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Net change\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {[...rows].reverse().map((r) => (\n <TableRow key={r.date}>\n <TableCell className=\"tabular-nums\">{utcDay(r.date)}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.started.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {signed(r.net)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n <TableFooter>\n <TableRow>\n <TableCell>\n <AdminMessage id={\"Total\"} />\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {totals.started.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {totals.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {signed(totals.net)}\n </TableCell>\n </TableRow>\n </TableFooter>\n </Table>\n )}\n </ReportCard>\n )\n}\n\nfunction MonthlyReport({ range }: { range: DashboardRange }) {\n const { text: adminText } = useAdminUI()\n\n const { adminQueries } = useAdminServices()\n\n const renewals = useQuery(adminQueries.widgetMetrics(renewalsQuery(range)))\n const churn = useQuery(adminQueries.widgetMetrics(churnQuery(range)))\n const rows =\n renewals.data && churn.data ? monthlyRows(renewals.data, churn.data) : []\n const error = renewals.error ?? churn.error\n return (\n <ReportCard\n title={adminText(\"Monthly renewals\")}\n description={adminText(\n \"Renewal payments, cancellations and churn each calendar month (UTC); churn is the month's cancellations over the subscriptions it began with.\"\n )}\n canDownload={rows.length > 0}\n onDownload={() => {\n const csv = monthlyCsv(rows)\n downloadCsv(\n `monthly-renewals-${suffix(range)}.csv`,\n csv.header,\n csv.rows\n )\n }}\n >\n {rows.length === 0 || error ? (\n <ReportState\n loading={renewals.isPending || churn.isPending}\n error={error}\n retry={() => {\n void renewals.refetch()\n void churn.refetch()\n }}\n empty={rows.length === 0}\n />\n ) : (\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead>\n <AdminMessage id={\"Month\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Renewals\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Canceled\"} />\n </TableHead>\n <TableHead className=\"text-right\">\n <AdminMessage id={\"Churn\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {[...rows].reverse().map((r) => (\n <TableRow key={r.month}>\n <TableCell>{utcMonth(r.month)}</TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.renewals.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {r.canceled.toLocaleString()}\n </TableCell>\n <TableCell className=\"text-right tabular-nums\">\n {percent(r.churn)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </ReportCard>\n )\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,EAAU,GAAuB,oBAAM,IAAI,KAAK,GAAW;CACzE,IAAM,EAAE,SAAM,UAAO,EAAW,GAAO,CAAG;CAC1C,QAAQ,KAAK,MAAM,CAAE,IAAI,KAAK,MAAM,CAAI,KAAK,QAAa;AAC5D;AAEA,SAAgB,EAAW,GAAqC;CAC9D,OAAO;EACL,UAAU;GAAC;GAAqB;GAAiB;EAAuB;EACxE,IAAI,CAAC,MAAM;EACX,OAAO;EACP,OAAO,EAAa,CAAK;CAC3B;AACF;AAEA,SAAgB,EAAc,GAAqC;CACjE,OAAO;EACL,UAAU,CAAC,eAAe;EAC1B,IAAI,CAAC,MAAM;EACX,OAAO;EACP,OAAO,EAAa,CAAK;EACzB,SAAS,EAAE,cAAc,CAAC,SAAS,EAAE;CACvC;AACF;AAEA,SAAgB,EAAW,GAAqC;CAC9D,OAAO;EACL,UAAU,CAAC,iBAAiB,YAAY;EACxC,IAAI,CAAC,MAAM;EACX,OAAO;EACP,OAAO,EAAa,CAAK;CAC3B;AACF;AAmBA,IAAM,KAAS,MAAkC,OAAO,KAAQ,CAAC;AAGjE,SAAS,EACP,GAC0C;CAC1C,IAAM,IAAO,EAAO,QAAQ,WAAW,MAAM,EAAE,SAAS,MAAM,GACxD,oBAAM,IAAI,IAAyC;CACzD,KAAK,IAAM,KAAO,EAAO,MAAM;EAC7B,IAAM,IAAqC,CAAC;EAI5C,AAHA,EAAO,QAAQ,SAAS,GAAG,MAAM;GAC/B,EAAM,EAAE,QAAQ,EAAI;EACtB,CAAC,GACD,EAAI,IAAI,OAAO,EAAI,EAAK,GAAG,CAAK;CAClC;CACA,OAAO;AACT;AAGA,SAAgB,EAAU,GAAmC;CAC3D,OAAO,CAAC,GAAG,EAAc,CAAM,CAAC,CAAC,CAC9B,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,CAAE,CAAC,CACpC,KAAK,CAAC,GAAQ,QAAY;EACzB,MAAM,EAAO,MAAM,GAAG,EAAE;EACxB,SAAS,EAAM,EAAM,iBAAiB;EACtC,UAAU,EAAM,EAAM,aAAa;EACnC,KAAK,EAAM,EAAM,qBAAqB;CACxC,EAAE;AACN;AAGA,SAAgB,EACd,GACA,GACc;CACd,IAAM,IAAO,EAAc,CAAQ,GAC7B,IAAO,EAAc,CAAK;CAEhC,OADgB,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,EAAK,KAAK,GAAG,GAAG,EAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KACxD,CAAA,CAAQ,KAAK,MAAW;EAC7B,IAAM,IAAO,EAAK,IAAI,CAAM,CAAC,EAAE;EAC/B,OAAO;GACL,OAAO,EAAO,MAAM,GAAG,CAAC;GACxB,UAAU,EAAM,EAAK,IAAI,CAAM,CAAC,EAAE,aAAa;GAC/C,UAAU,EAAM,EAAK,IAAI,CAAM,CAAC,EAAE,aAAa;GAC/C,OAAO,KAAS,OAA6B,OAAO,OAAO,CAAI;EACjE;CACF,CAAC;AACH;AAEA,IAAa,KAAY,OAAsB;CAC7C,QAAQ;EAAC;EAAQ;EAAqB;EAAiB;CAAY;CACnE,MAAM,EAAK,KAAK,MAAM;EAAC,EAAE;EAAM,EAAE;EAAS,EAAE;EAAU,EAAE;CAAG,CAAC;AAC9D,IAEa,KAAc,OAAwB;CACjD,QAAQ;EAAC;EAAS;EAAY;EAAiB;CAAY;CAC3D,MAAM,EAAK,KAAK,MAAM;EAAC,EAAE;EAAO,EAAE;EAAU,EAAE;EAAU,EAAE,SAAS;CAAE,CAAC;AACxE,ICxEM,KAAU,uBACd,IAAI,KAAK,GAAG,EAAK,WAAW,EAAA,CAAE,mBAAmB,KAAA,GAAW;CAC1D,OAAO;CACP,KAAK;CACL,MAAM;CACN,UAAU;AACZ,CAAC,GAEG,KAAY,uBAChB,IAAI,KAAK,GAAG,EAAM,cAAc,EAAA,CAAE,mBAAmB,KAAA,GAAW;CAC9D,OAAO;CACP,MAAM;CACN,UAAU;AACZ,CAAC,GAEG,KAAU,MACd,IAAI,IAAI,IAAI,EAAE,eAAe,MAAM,EAAE,eAAe,GAEhD,KAAW,MACf,MAAU,OACN,MACA,IAAI,IAAQ,IAAA,CAAK,eAAe,KAAA,GAAW,EAAE,uBAAuB,EAAE,CAAC,EAAE,IAEzE,KAAU,MAA0B;CACxC,IAAM,EAAE,SAAM,UAAO,EAAW,CAAK;CACrC,OAAO,GAAG,EAAK,MAAM;AACvB;AAEA,SAAgB,IAAc;CAC5B,IAAM,IAAS,EAAU,GACnB,CAAC,KAAS,EAAkB;CAGlC,OAFK,IACA,EAAO,UAEV,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;KACZ,UAAA,kBAAC,GAAD,EAAc,IAAI,UAAY,CAAA;IAC5B,CAAA,GACJ,kBAAC,GAAD,CAAc,CAAA,CACX;;GACL,kBAAC,GAAD,EAAoB,SAAQ,CAAA;GAC5B,kBAAC,GAAD,EAAsB,SAAQ,CAAA;EAC3B;MAXqB,kBAAC,GAAD;EAAU,IAAG;EAAI,SAAA;CAAS,CAAA,IADlC;AActB;AAEA,SAAS,EAAQ,GAAgB;CAG/B,OAFI,aAAiB,KAAY,EAAM,qBAC9B,mCACF,aAAiB,QAAQ,EAAM,UAAU;AAClD;AAEA,SAAS,EAAW,EAClB,UACA,gBACA,eACA,gBACA,eAOC;CACD,OACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA;EACE,kBAAC,GAAD,EAAA,UAAY,EAAiB,CAAA;EAC7B,kBAAC,GAAD,EAAA,UAAkB,EAA6B,CAAA;EAC/C,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GACE,MAAK;GACL,SAAQ;GACR,SAAS;GACT,UAAU,CAAC;GAJb,UAAA,CAME,kBAAC,GAAD;IAAe,MAAM;IAAgB,WAAU;GAAU,CAAA,GACzD,kBAAC,GAAD,EAAc,IAAI,eAAiB,CAAA,CAC7B;EACE,CAAA,EAAA,CAAA;CACF,EAAA,CAAA,GACZ,kBAAC,GAAD;EAAa,WAAU;EAAmB;CAAsB,CAAA,CAC5D,EAAA,CAAA;AAEV;AAEA,SAAS,EAAY,EACnB,YACA,UACA,UACA,YAMC;CA2BD,OA1BI,IAEA,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAA;EACzB,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,GAAD,EAAkB,WAAU,aAAc,GAA3B,CAA2B,CAC3C;CACE,CAAA,IAEL,IAEA,kBAAC,OAAD;EACE,MAAK;EACL,WAAU;EAFZ,UAAA,CAIG,EAAQ,CAAK,GACd,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS;GAC3C,UAAA,kBAAC,GAAD,EAAc,IAAI,QAAU,CAAA;EACtB,CAAA,CACL;MAEL,IAEA,kBAAC,KAAD;EAAG,WAAU;EACX,UAAA,kBAAC,GAAD,EAAc,IAAI,6CAA+C,CAAA;CAChE,CAAA,IAEA;AACT;AAEA,SAAS,EAAY,EAAE,YAAoC;CACzD,IAAM,EAAE,MAAM,MAAc,EAAW,GAEjC,EAAE,oBAAiB,EAAiB,GAEpC,IAAU,EAAU,CAAK,IAAA,KACzB,IAAQ,EAAS;EACrB,GAAG,EAAa,cAAc,EAAW,CAAK,CAAC;EAC/C,SAAS,CAAC;CACZ,CAAC,GACK,IAAO,EAAM,OAAO,EAAU,EAAM,IAAI,IAAI,CAAC,GAC7C,IAAS,EAAK,QACjB,GAAG,OAAO;EACT,SAAS,EAAE,UAAU,EAAE;EACvB,UAAU,EAAE,WAAW,EAAE;EACzB,KAAK,EAAE,MAAM,EAAE;CACjB,IACA;EAAE,SAAS;EAAG,UAAU;EAAG,KAAK;CAAE,CACpC;CACA,OACE,kBAAC,GAAD;EACE,OAAO,EAAU,qBAAqB;EACtC,aAAa,EACX,kEACF;EACA,aAAa,EAAK,SAAS;EAC3B,kBAAkB;GAChB,IAAM,IAAM,EAAS,CAAI;GACzB,EACE,uBAAuB,EAAO,CAAK,EAAE,OACrC,EAAI,QACJ,EAAI,IACN;EACF;EAEC,UAAA,IACC,kBAAC,KAAD;GAAG,WAAU;GAAb,UAAA;IACE,kBAAC,GAAD,EAAc,IAAI,mCAAqC,CAAA;;IAEvD,kBAAC,GAAD,EAAc,IAAI,+BAAiC,CAAA;GAClD;EACD,CAAA,IAAA,EAAK,WAAW,KAAK,EAAM,QAC7B,kBAAC,GAAD;GACE,SAAS,EAAM;GACf,OAAO,EAAM;GACb,aAAa,KAAK,EAAM,QAAQ;GAChC,OAAO,EAAK,WAAW;EACxB,CAAA,IAED,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAU,WAAU;IAApB,UAAA;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAc,IAAI,OAAS,CAAA,EAClB,CAAA;KACX,kBAAC,GAAD;MAAW,WAAU;MACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,MAAQ,CAAA;KACjB,CAAA;KACX,kBAAC,GAAD;MAAW,WAAU;MACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,WAAa,CAAA;KACtB,CAAA;KACX,kBAAC,GAAD;MAAW,WAAU;MACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,aAAe,CAAA;KACxB,CAAA;IACH;GACC,CAAA,EAAA,CAAA;GACb,kBAAC,GAAD,EAAA,UACG,CAAC,GAAG,CAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,MACxB,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAW,WAAU;KAAgB,UAAA,EAAO,EAAE,IAAI;IAAa,CAAA;IAC/D,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAE,QAAQ,eAAe;IACjB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAE,SAAS,eAAe;IAClB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAO,EAAE,GAAG;IACJ,CAAA;GACH,EAAA,GAXK,EAAE,IAWP,CACX,EACQ,CAAA;GACX,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAc,IAAI,QAAU,CAAA,EACnB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAO,QAAQ,eAAe;IACtB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAO,SAAS,eAAe;IACvB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KAClB,UAAA,EAAO,EAAO,GAAG;IACT,CAAA;GACH,EAAA,CAAA,EACC,CAAA;EACR,EAAA,CAAA;CAEC,CAAA;AAEhB;AAEA,SAAS,EAAc,EAAE,YAAoC;CAC3D,IAAM,EAAE,MAAM,MAAc,EAAW,GAEjC,EAAE,oBAAiB,EAAiB,GAEpC,IAAW,EAAS,EAAa,cAAc,EAAc,CAAK,CAAC,CAAC,GACpE,IAAQ,EAAS,EAAa,cAAc,EAAW,CAAK,CAAC,CAAC,GAC9D,IACJ,EAAS,QAAQ,EAAM,OAAO,EAAY,EAAS,MAAM,EAAM,IAAI,IAAI,CAAC,GACpE,IAAQ,EAAS,SAAS,EAAM;CACtC,OACE,kBAAC,GAAD;EACE,OAAO,EAAU,kBAAkB;EACnC,aAAa,EACX,+IACF;EACA,aAAa,EAAK,SAAS;EAC3B,kBAAkB;GAChB,IAAM,IAAM,EAAW,CAAI;GAC3B,EACE,oBAAoB,EAAO,CAAK,EAAE,OAClC,EAAI,QACJ,EAAI,IACN;EACF;EAEC,UAAA,EAAK,WAAW,KAAK,IACpB,kBAAC,GAAD;GACE,SAAS,EAAS,aAAa,EAAM;GAC9B;GACP,aAAa;IAEX,AADA,EAAc,QAAQ,GACtB,EAAW,QAAQ;GACrB;GACA,OAAO,EAAK,WAAW;EACxB,CAAA,IAED,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAU,WAAU;GAApB,UAAA;IACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAc,IAAI,QAAU,CAAA,EACnB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,WAAa,CAAA;IACtB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,WAAa,CAAA;IACtB,CAAA;IACX,kBAAC,GAAD;KAAW,WAAU;KACnB,UAAA,kBAAC,GAAD,EAAc,IAAI,QAAU,CAAA;IACnB,CAAA;GACH;EACC,CAAA,EAAA,CAAA,GACb,kBAAC,GAAD,EAAA,UACG,CAAC,GAAG,CAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,MACxB,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,GAAD,EAAA,UAAY,EAAS,EAAE,KAAK,EAAa,CAAA;GACzC,kBAAC,GAAD;IAAW,WAAU;IAClB,UAAA,EAAE,SAAS,eAAe;GAClB,CAAA;GACX,kBAAC,GAAD;IAAW,WAAU;IAClB,UAAA,EAAE,SAAS,eAAe;GAClB,CAAA;GACX,kBAAC,GAAD;IAAW,WAAU;IAClB,UAAA,EAAQ,EAAE,KAAK;GACP,CAAA;EACH,EAAA,GAXK,EAAE,KAWP,CACX,EACQ,CAAA,CACN,EAAA,CAAA;CAEC,CAAA;AAEhB"}
@@ -1,4 +1,4 @@
1
- import { f as e, t } from "./admin-utils-BK4Vh1o_.js";
1
+ import { f as e, t } from "./admin-utils-DnVY5fYd.js";
2
2
  import "react";
3
3
  import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  import { CheckIcon as i, ChevronDownIcon as a, ChevronUpIcon as o } from "lucide-react";
@@ -78,4 +78,4 @@ function m({ className: e, ...r }) {
78
78
  //#endregion
79
79
  export { l as a, u as i, d as n, f as r, c as t };
80
80
 
81
- //# sourceMappingURL=admin-select-DBgprVW6.js.map
81
+ //# sourceMappingURL=admin-select-CUXvh6j1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-select-DBgprVW6.js","names":[],"sources":["../src/admin/components/ui/select.tsx"],"sourcesContent":["\"use client\"\nimport { useAdminContext } from \"#orck/admin/context\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\n\nimport { cn } from \"#orck/admin/lib/utils\"\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\"\n\nconst Select = SelectPrimitive.Root\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n return (\n <SelectPrimitive.Group\n data-slot=\"select-group\"\n className={cn(\"scroll-my-1 p-1\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n return (\n <SelectPrimitive.Value\n data-slot=\"select-value\"\n className={cn(\"flex flex-1 text-left\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectTrigger({\n className,\n size = \"default\",\n children,\n ...props\n}: SelectPrimitive.Trigger.Props & {\n size?: \"sm\" | \"default\"\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon\n render={\n <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n }\n />\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectContent({\n className,\n children,\n side = \"bottom\",\n sideOffset = 4,\n align = \"center\",\n alignOffset = 0,\n alignItemWithTrigger = true,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n >) {\n return (\n <SelectPrimitive.Portal container={useAdminContext().portal}>\n <SelectPrimitive.Positioner\n side={side}\n sideOffset={sideOffset}\n align={align}\n alignOffset={alignOffset}\n alignItemWithTrigger={alignItemWithTrigger}\n className=\"isolate z-50\"\n >\n <SelectPrimitive.Popup\n data-slot=\"select-content\"\n data-align-trigger={alignItemWithTrigger}\n className={cn(\n \"relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.List>{children}</SelectPrimitive.List>\n <SelectScrollDownButton />\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )\n}\n\nfunction SelectLabel({\n className,\n ...props\n}: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n data-slot=\"select-label\"\n className={cn(\"px-1.5 py-1 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: SelectPrimitive.Item.Props) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n {children}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator\n render={\n <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n }\n >\n <CheckIcon className=\"pointer-events-none\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n return (\n <SelectPrimitive.ScrollUpArrow\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronUpIcon />\n </SelectPrimitive.ScrollUpArrow>\n )\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n return (\n <SelectPrimitive.ScrollDownArrow\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronDownIcon />\n </SelectPrimitive.ScrollDownArrow>\n )\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n}\n"],"mappings":";;;;;;AASA,IAAM,IAAS,EAAgB;AAY/B,SAAS,EAAY,EAAE,cAAW,GAAG,KAAsC;CACzE,OACE,kBAAC,EAAgB,OAAjB;EACE,aAAU;EACV,WAAW,EAAG,yBAAyB,CAAS;EAChD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,EAAc,EACrB,cACA,UAAO,WACP,aACA,GAAG,KAGF;CACD,OACE,kBAAC,EAAgB,SAAjB;EACE,aAAU;EACV,aAAW;EACX,WAAW,EACT,u2BACA,CACF;EACA,GAAI;EAPN,UAAA,CASG,GACD,kBAAC,EAAgB,MAAjB,EACE,QACE,kBAAC,GAAD,EAAiB,WAAU,mDAAoD,CAAA,EAElF,CAAA,CACsB;;AAE7B;AAEA,SAAS,EAAc,EACrB,cACA,aACA,UAAO,UACP,gBAAa,GACb,WAAQ,UACR,iBAAc,GACd,0BAAuB,IACvB,GAAG,KAKA;CACH,OACE,kBAAC,EAAgB,QAAjB;EAAwB,WAAW,EAAgB,CAAC,CAAC;EACnD,UAAA,kBAAC,EAAgB,YAAjB;GACQ;GACM;GACL;GACM;GACS;GACtB,WAAU;GAEV,UAAA,kBAAC,EAAgB,OAAjB;IACE,aAAU;IACV,sBAAoB;IACpB,WAAW,EACT,gpBACA,CACF;IACA,GAAI;IAPN,UAAA;KASE,kBAAC,GAAD,CAAuB,CAAA;KACvB,kBAAC,EAAgB,MAAjB,EAAuB,YAA+B,CAAA;KACtD,kBAAC,GAAD,CAAyB,CAAA;IACJ;;EACG,CAAA;CACN,CAAA;AAE5B;AAeA,SAAS,EAAW,EAClB,cACA,aACA,GAAG,KAC0B;CAC7B,OACE,kBAAC,EAAgB,MAAjB;EACE,aAAU;EACV,WAAW,EACT,sbACA,CACF;EACA,GAAI;EANN,UAAA,CAQE,kBAAC,EAAgB,UAAjB;GAA0B,WAAU;GACjC;EACuB,CAAA,GAC1B,kBAAC,EAAgB,eAAjB;GACE,QACE,kBAAC,QAAD,EAAM,WAAU,+EAAgF,CAAA;GAGlG,UAAA,kBAAC,GAAD,EAAW,WAAU,sBAAuB,CAAA;EACf,CAAA,CACX;;AAE1B;AAeA,SAAS,EAAqB,EAC5B,cACA,GAAG,KAC0D;CAC7D,OACE,kBAAC,EAAgB,eAAjB;EACE,aAAU;EACV,WAAW,EACT,0HACA,CACF;EACA,GAAI;EAEJ,UAAA,kBAAC,GAAD,CAAgB,CAAA;CACa,CAAA;AAEnC;AAEA,SAAS,EAAuB,EAC9B,cACA,GAAG,KAC4D;CAC/D,OACE,kBAAC,EAAgB,iBAAjB;EACE,aAAU;EACV,WAAW,EACT,6HACA,CACF;EACA,GAAI;EAEJ,UAAA,kBAAC,GAAD,CAAkB,CAAA;CACa,CAAA;AAErC"}
1
+ {"version":3,"file":"admin-select-CUXvh6j1.js","names":[],"sources":["../src/admin/components/ui/select.tsx"],"sourcesContent":["\"use client\"\nimport { useAdminContext } from \"#orck/admin/context\"\n\nimport * as React from \"react\"\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\n\nimport { cn } from \"#orck/admin/lib/utils\"\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\"\n\nconst Select = SelectPrimitive.Root\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n return (\n <SelectPrimitive.Group\n data-slot=\"select-group\"\n className={cn(\"scroll-my-1 p-1\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n return (\n <SelectPrimitive.Value\n data-slot=\"select-value\"\n className={cn(\"flex flex-1 text-left\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectTrigger({\n className,\n size = \"default\",\n children,\n ...props\n}: SelectPrimitive.Trigger.Props & {\n size?: \"sm\" | \"default\"\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon\n render={\n <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n }\n />\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectContent({\n className,\n children,\n side = \"bottom\",\n sideOffset = 4,\n align = \"center\",\n alignOffset = 0,\n alignItemWithTrigger = true,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n >) {\n return (\n <SelectPrimitive.Portal container={useAdminContext().portal}>\n <SelectPrimitive.Positioner\n side={side}\n sideOffset={sideOffset}\n align={align}\n alignOffset={alignOffset}\n alignItemWithTrigger={alignItemWithTrigger}\n className=\"isolate z-50\"\n >\n <SelectPrimitive.Popup\n data-slot=\"select-content\"\n data-align-trigger={alignItemWithTrigger}\n className={cn(\n \"relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.List>{children}</SelectPrimitive.List>\n <SelectScrollDownButton />\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )\n}\n\nfunction SelectLabel({\n className,\n ...props\n}: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n data-slot=\"select-label\"\n className={cn(\"px-1.5 py-1 text-xs text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: SelectPrimitive.Item.Props) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n {children}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator\n render={\n <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n }\n >\n <CheckIcon className=\"pointer-events-none\" />\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"pointer-events-none -mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n return (\n <SelectPrimitive.ScrollUpArrow\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronUpIcon />\n </SelectPrimitive.ScrollUpArrow>\n )\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n return (\n <SelectPrimitive.ScrollDownArrow\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <ChevronDownIcon />\n </SelectPrimitive.ScrollDownArrow>\n )\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n}\n"],"mappings":";;;;;;AASA,IAAM,IAAS,EAAgB;AAY/B,SAAS,EAAY,EAAE,cAAW,GAAG,KAAsC;CACzE,OACE,kBAAC,EAAgB,OAAjB;EACE,aAAU;EACV,WAAW,EAAG,yBAAyB,CAAS;EAChD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,EAAc,EACrB,cACA,UAAO,WACP,aACA,GAAG,KAGF;CACD,OACE,kBAAC,EAAgB,SAAjB;EACE,aAAU;EACV,aAAW;EACX,WAAW,EACT,u2BACA,CACF;EACA,GAAI;EAPN,UAAA,CASG,GACD,kBAAC,EAAgB,MAAjB,EACE,QACE,kBAAC,GAAD,EAAiB,WAAU,mDAAoD,CAAA,EAElF,CAAA,CACsB;;AAE7B;AAEA,SAAS,EAAc,EACrB,cACA,aACA,UAAO,UACP,gBAAa,GACb,WAAQ,UACR,iBAAc,GACd,0BAAuB,IACvB,GAAG,KAKA;CACH,OACE,kBAAC,EAAgB,QAAjB;EAAwB,WAAW,EAAgB,CAAC,CAAC;EACnD,UAAA,kBAAC,EAAgB,YAAjB;GACQ;GACM;GACL;GACM;GACS;GACtB,WAAU;GAEV,UAAA,kBAAC,EAAgB,OAAjB;IACE,aAAU;IACV,sBAAoB;IACpB,WAAW,EACT,gpBACA,CACF;IACA,GAAI;IAPN,UAAA;KASE,kBAAC,GAAD,CAAuB,CAAA;KACvB,kBAAC,EAAgB,MAAjB,EAAuB,YAA+B,CAAA;KACtD,kBAAC,GAAD,CAAyB,CAAA;IACJ;;EACG,CAAA;CACN,CAAA;AAE5B;AAeA,SAAS,EAAW,EAClB,cACA,aACA,GAAG,KAC0B;CAC7B,OACE,kBAAC,EAAgB,MAAjB;EACE,aAAU;EACV,WAAW,EACT,sbACA,CACF;EACA,GAAI;EANN,UAAA,CAQE,kBAAC,EAAgB,UAAjB;GAA0B,WAAU;GACjC;EACuB,CAAA,GAC1B,kBAAC,EAAgB,eAAjB;GACE,QACE,kBAAC,QAAD,EAAM,WAAU,+EAAgF,CAAA;GAGlG,UAAA,kBAAC,GAAD,EAAW,WAAU,sBAAuB,CAAA;EACf,CAAA,CACX;;AAE1B;AAeA,SAAS,EAAqB,EAC5B,cACA,GAAG,KAC0D;CAC7D,OACE,kBAAC,EAAgB,eAAjB;EACE,aAAU;EACV,WAAW,EACT,0HACA,CACF;EACA,GAAI;EAEJ,UAAA,kBAAC,GAAD,CAAgB,CAAA;CACa,CAAA;AAEnC;AAEA,SAAS,EAAuB,EAC9B,cACA,GAAG,KAC4D;CAC/D,OACE,kBAAC,EAAgB,iBAAjB;EACE,aAAU;EACV,WAAW,EACT,6HACA,CACF;EACA,GAAI;EAEJ,UAAA,kBAAC,GAAD,CAAkB,CAAA;CACa,CAAA;AAErC"}
@@ -1,19 +1,19 @@
1
- import { E as e, S as t, f as n, l as r, p as i, w as a } from "./admin-utils-BK4Vh1o_.js";
2
- import { t as o } from "./admin-button-CTL3DgOt.js";
3
- import { t as s } from "./admin-input-CcfQfJFN.js";
4
- import { a as c, i as l, l as u, n as d, u as f } from "./admin-capabilities-BuigAgtm.js";
1
+ import { E as e, S as t, f as n, l as r, p as i, w as a } from "./admin-utils-DnVY5fYd.js";
2
+ import { t as o } from "./admin-button-Oip73Plq.js";
3
+ import { t as s } from "./admin-input-DO5lvCLL.js";
4
+ import { a as c, i as l, l as u, n as d, u as f } from "./admin-capabilities-BGv7oG8v.js";
5
5
  import { n as p, o as m, s as h, t as g } from "./admin-format-DoohJ7Fg.js";
6
6
  import { n as _ } from "./admin-confirm-katGq0Qo.js";
7
7
  import { t as v } from "./admin-form-field-errors-hbUMIriS.js";
8
- import { a as y, i as b, n as x, r as S, t as C } from "./admin-select-DBgprVW6.js";
9
- import { a as w, n as ee, o as te, r as T, s as E, t as ne } from "./admin-table-CH9TrXJf.js";
10
- import { t as D } from "./admin-badge-DR5-1qd8.js";
11
- import { a as O, i as k, n as A, o as j, r as M, s as N, t as P } from "./admin-dialog-BjuKI5Hp.js";
12
- import { t as F } from "./admin-label-BiEN5cjb.js";
13
- import { t as re } from "./admin-switch-BJUxLCsa.js";
8
+ import { a as y, i as b, n as x, r as S, t as C } from "./admin-select-CUXvh6j1.js";
9
+ import { a as w, n as ee, o as te, r as T, s as E, t as ne } from "./admin-table-CWhiCb9d.js";
10
+ import { t as D } from "./admin-badge-VCGbnfiN.js";
11
+ import { a as O, i as k, n as A, o as j, r as M, s as N, t as P } from "./admin-dialog-Ffx95AIT.js";
12
+ import { t as F } from "./admin-label-BzJpr3lg.js";
13
+ import { t as re } from "./admin-switch-S4g_ppaU.js";
14
14
  import { n as I } from "./admin-dialog-width-BPrgdYlj.js";
15
- import { i as L, n as R, r as ie, t as ae } from "./admin-tabs-528FJMjt.js";
16
- import { t as z } from "./admin-typed-confirm-dialog-DU7m9-O7.js";
15
+ import { i as L, n as R, r as ie, t as ae } from "./admin-tabs-Cjm5a6UJ.js";
16
+ import { t as z } from "./admin-typed-confirm-dialog-BloZvFPA.js";
17
17
  import * as B from "react";
18
18
  import { useMutation as V, useQuery as H, useQueryClient as U } from "@tanstack/react-query";
19
19
  import { Fragment as oe, jsx as W, jsxs as G } from "react/jsx-runtime";
@@ -840,7 +840,7 @@ function Se({ psp: n, railDefinitions: r, edits: s }) {
840
840
  let { toast: c, toastApiError: l, toastStaleEdit: u, adminMutations: d, selectedMerchant: f } = i(), { text: p } = e(), m = U(), h = V(d.updatePSP(m)), [g, v] = B.useState(!1), y = _(), b = r.find((e) => e.rail === n.rail), x = b?.display_name ?? n.rail, S = n.credentials ?? {}, C = async (e) => {
841
841
  if (!(!e && !await y({
842
842
  title: `Archive ${n.key}?`,
843
- description: "New checkout stops routing to it. Existing subscriptions, refunds and webhooks keep using it.",
843
+ description: "New purchase stops routing to it. Existing subscriptions, refunds and webhooks keep using it.",
844
844
  confirmLabel: "Archive PSP"
845
845
  }))) try {
846
846
  await h.mutateAsync({
@@ -941,7 +941,7 @@ function Se({ psp: n, railDefinitions: r, edits: s }) {
941
941
  open: g,
942
942
  onOpenChange: v,
943
943
  title: `Archive the last active ${x} PSP?`,
944
- description: p("New checkout on this rail is refused until another PSP is armed. Existing subscriptions, refunds and webhooks keep using this one."),
944
+ description: p("New purchase on this rail is refused until another PSP is armed. Existing subscriptions, refunds and webhooks keep using this one."),
945
945
  confirmationWord: "ARCHIVE",
946
946
  actionLabel: "Archive PSP",
947
947
  onConfirm: () => C(!0)
@@ -1142,7 +1142,7 @@ function ke({ railDefinitions: t }) {
1142
1142
  children: (e) => /* @__PURE__ */ G($, {
1143
1143
  label: u("Key"),
1144
1144
  id: "pv-key",
1145
- hint: "Your name for this PSP, such as mobius. Prices and checkout name it by this key.",
1145
+ hint: "Your name for this PSP, such as mobius. Prices and purchase name it by this key.",
1146
1146
  children: [/* @__PURE__ */ W(s, {
1147
1147
  id: d("pv-key"),
1148
1148
  value: e.state.value,
@@ -1431,4 +1431,4 @@ function $({ label: t, id: n, hint: r, children: i }) {
1431
1431
  //#endregion
1432
1432
  export { ge as SettingsPage };
1433
1433
 
1434
- //# sourceMappingURL=admin-settings-Bmoy95uO.js.map
1434
+ //# sourceMappingURL=admin-settings-C6eWFkDn.js.map