@withpotter/components 0.13.0-next.124 → 0.13.0-next.58

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 (132) hide show
  1. package/dist/billing/overview-tab.js +3 -4
  2. package/dist/billing/overview-tab.js.map +1 -1
  3. package/dist/billing/plans-tab.js +12 -6
  4. package/dist/billing/plans-tab.js.map +1 -1
  5. package/dist/charts/index.d.ts +1 -1
  6. package/dist/charts/index.js +1 -3
  7. package/dist/charts/index.js.map +1 -1
  8. package/dist/dashboard/activity-feed.d.ts +0 -2
  9. package/dist/dashboard/activity-feed.js +1 -4
  10. package/dist/dashboard/activity-feed.js.map +1 -1
  11. package/dist/dashboard/home/ask-controls.js +0 -1
  12. package/dist/dashboard/home/ask-controls.js.map +1 -1
  13. package/dist/dashboard/home/ask-mode-chip.js +4 -4
  14. package/dist/dashboard/home/ask-mode-chip.js.map +1 -1
  15. package/dist/dashboard/home/home-ask.js +3 -3
  16. package/dist/dashboard/home/home-ask.js.map +1 -1
  17. package/dist/dashboard/home/home-quick-actions.js +1 -9
  18. package/dist/dashboard/home/home-quick-actions.js.map +1 -1
  19. package/dist/dashboard/home/widgets/activity-widget.js +43 -56
  20. package/dist/dashboard/home/widgets/activity-widget.js.map +1 -1
  21. package/dist/dashboard/home/widgets/home-widgets.js +2 -3
  22. package/dist/dashboard/home/widgets/home-widgets.js.map +1 -1
  23. package/dist/dashboard/home/widgets/money-widgets.js +4 -9
  24. package/dist/dashboard/home/widgets/money-widgets.js.map +1 -1
  25. package/dist/dashboard/home/widgets/registry.d.ts +3 -6
  26. package/dist/dashboard/home/widgets/registry.js +2 -12
  27. package/dist/dashboard/home/widgets/registry.js.map +1 -1
  28. package/dist/dashboard/home/widgets/team-widget.js +3 -15
  29. package/dist/dashboard/home/widgets/team-widget.js.map +1 -1
  30. package/dist/dashboard/home/widgets/types.d.ts +1 -13
  31. package/dist/dashboard/home/widgets/types.js.map +1 -1
  32. package/dist/dashboard/home/widgets/use-home-layout.js +4 -9
  33. package/dist/dashboard/home/widgets/use-home-layout.js.map +1 -1
  34. package/dist/dashboard/home/widgets/website-widget.js +1 -9
  35. package/dist/dashboard/home/widgets/website-widget.js.map +1 -1
  36. package/dist/dashboard/page-transition.d.ts +0 -11
  37. package/dist/dashboard/page-transition.js +2 -9
  38. package/dist/dashboard/page-transition.js.map +1 -1
  39. package/dist/features/gift-card-artwork.js +1 -1
  40. package/dist/features/gift-card-artwork.js.map +1 -1
  41. package/dist/features/gift-cards/gift-cards-feature.js +518 -656
  42. package/dist/features/gift-cards/gift-cards-feature.js.map +1 -1
  43. package/dist/features/product-form.js +1 -1
  44. package/dist/features/product-form.js.map +1 -1
  45. package/dist/mobile/screens/billing-flows.js +6 -7
  46. package/dist/mobile/screens/billing-flows.js.map +1 -1
  47. package/dist/mobile/screens/billing.js +2 -2
  48. package/dist/mobile/screens/billing.js.map +1 -1
  49. package/dist/notifications-dropdown.js +1 -11
  50. package/dist/notifications-dropdown.js.map +1 -1
  51. package/dist/shell/notification-kinds.d.ts +2 -11
  52. package/dist/shell/notification-kinds.js +1 -16
  53. package/dist/shell/notification-kinds.js.map +1 -1
  54. package/dist/shell/notification-row.d.ts +2 -6
  55. package/dist/shell/notification-row.js +33 -59
  56. package/dist/shell/notification-row.js.map +1 -1
  57. package/dist/shell/product-switcher.js +0 -3
  58. package/dist/shell/product-switcher.js.map +1 -1
  59. package/dist/shell/settings-modal-context.d.ts +0 -2
  60. package/dist/shell/settings-modal-context.js +2 -4
  61. package/dist/shell/settings-modal-context.js.map +1 -1
  62. package/dist/shell/settings-panels.js +9 -29
  63. package/dist/shell/settings-panels.js.map +1 -1
  64. package/dist/shell/settings-rows.d.ts +2 -7
  65. package/dist/shell/settings-rows.js +36 -93
  66. package/dist/shell/settings-rows.js.map +1 -1
  67. package/dist/shell/shell.css +0 -192
  68. package/dist/shell/sidebar-footer.js +2 -6
  69. package/dist/shell/sidebar-footer.js.map +1 -1
  70. package/dist/shell/unified-settings-modal.d.ts +2 -3
  71. package/dist/shell/unified-settings-modal.js +74 -151
  72. package/dist/shell/unified-settings-modal.js.map +1 -1
  73. package/dist/sidebar/config.js +3 -19
  74. package/dist/sidebar/config.js.map +1 -1
  75. package/dist/sidebar/more-group.js +1 -1
  76. package/dist/sidebar/more-group.js.map +1 -1
  77. package/dist/sidebar/nav-renderers.js +0 -2
  78. package/dist/sidebar/nav-renderers.js.map +1 -1
  79. package/dist/sidebar/nav-section.js +3 -5
  80. package/dist/sidebar/nav-section.js.map +1 -1
  81. package/dist/sidebar/setup-progress.d.ts +1 -3
  82. package/dist/sidebar/setup-progress.js +2 -2
  83. package/dist/sidebar/setup-progress.js.map +1 -1
  84. package/dist/sidebar/spine-groups.js +1 -7
  85. package/dist/sidebar/spine-groups.js.map +1 -1
  86. package/dist/sidebar.js +5 -5
  87. package/dist/sidebar.js.map +1 -1
  88. package/dist/templates/template-live-thumb.js +5 -16
  89. package/dist/templates/template-live-thumb.js.map +1 -1
  90. package/package.json +5 -5
  91. package/dist/dashboard/activity-paid.d.ts +0 -10
  92. package/dist/dashboard/activity-paid.js +0 -7
  93. package/dist/dashboard/activity-paid.js.map +0 -1
  94. package/dist/dashboard/home/host-quick-actions.d.ts +0 -19
  95. package/dist/dashboard/home/host-quick-actions.js +0 -12
  96. package/dist/dashboard/home/host-quick-actions.js.map +0 -1
  97. package/dist/dashboard/home/widgets/host-widgets.d.ts +0 -6
  98. package/dist/dashboard/home/widgets/host-widgets.js +0 -16
  99. package/dist/dashboard/home/widgets/host-widgets.js.map +0 -1
  100. package/dist/dashboard/home/widgets/member-label.d.ts +0 -11
  101. package/dist/dashboard/home/widgets/member-label.js +0 -10
  102. package/dist/dashboard/home/widgets/member-label.js.map +0 -1
  103. package/dist/dashboard/home/widgets/middle-ellipsis.d.ts +0 -5
  104. package/dist/dashboard/home/widgets/middle-ellipsis.js +0 -11
  105. package/dist/dashboard/home/widgets/middle-ellipsis.js.map +0 -1
  106. package/dist/dashboard/home/widgets/revenue-series.d.ts +0 -12
  107. package/dist/dashboard/home/widgets/revenue-series.js +0 -26
  108. package/dist/dashboard/home/widgets/revenue-series.js.map +0 -1
  109. package/dist/dashboard/page-transition-key.d.ts +0 -7
  110. package/dist/dashboard/page-transition-key.js +0 -11
  111. package/dist/dashboard/page-transition-key.js.map +0 -1
  112. package/dist/features/gift-cards/gift-card-logic.d.ts +0 -41
  113. package/dist/features/gift-cards/gift-card-logic.js +0 -65
  114. package/dist/features/gift-cards/gift-card-logic.js.map +0 -1
  115. package/dist/features/gift-cards/hint.d.ts +0 -9
  116. package/dist/features/gift-cards/hint.js +0 -25
  117. package/dist/features/gift-cards/hint.js.map +0 -1
  118. package/dist/shell/notification-actions.d.ts +0 -12
  119. package/dist/shell/notification-actions.js +0 -18
  120. package/dist/shell/notification-actions.js.map +0 -1
  121. package/dist/shell/settings-groups.d.ts +0 -23
  122. package/dist/shell/settings-groups.js +0 -22
  123. package/dist/shell/settings-groups.js.map +0 -1
  124. package/dist/shell/settings-host.d.ts +0 -39
  125. package/dist/shell/settings-host.js +0 -37
  126. package/dist/shell/settings-host.js.map +0 -1
  127. package/dist/shell/sidebar-cards.d.ts +0 -32
  128. package/dist/shell/sidebar-cards.js +0 -37
  129. package/dist/shell/sidebar-cards.js.map +0 -1
  130. package/dist/templates/template-tile-source.d.ts +0 -24
  131. package/dist/templates/template-tile-source.js +0 -18
  132. package/dist/templates/template-tile-source.js.map +0 -1
@@ -13,7 +13,6 @@ import {
13
13
  Plus
14
14
  } from "lucide-react";
15
15
  import { formatDate, formatBytes } from "@withpotter/lib/utils";
16
- import { pickPlanPrice } from "@withpotter/lib/plan-price";
17
16
  const STORAGE_BASELINE_BYTES = 500 * 1024 * 1024;
18
17
  function getStatusBadge(status) {
19
18
  switch (status) {
@@ -70,13 +69,13 @@ function OverviewTab({
70
69
  subscription ? getStatusBadge(subscription.status) : /* @__PURE__ */ jsx(Badge, { variant: "info", children: "Free" })
71
70
  ] }),
72
71
  /* @__PURE__ */ jsx("div", { className: "p-5", children: subscription ? (() => {
73
- const planPrice = pickPlanPrice(subscription.plan.prices);
74
- const isFreePlan = subscription.plan.slug === "free" || !planPrice || planPrice.amount === 0;
72
+ const planPriceUsd = subscription.plan.prices?.["USD"] ?? 0;
73
+ const isFreePlan = subscription.plan.slug === "free" || planPriceUsd === 0;
75
74
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
76
75
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
77
76
  /* @__PURE__ */ jsxs("div", { children: [
78
77
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-[var(--text-primary)]", children: (subscription.plan.name.split("\u2014")[0] || subscription.plan.name).trim() }),
79
- /* @__PURE__ */ jsx("p", { className: "text-[var(--text-tertiary)]", children: isFreePlan ? "Free plan" : `${formatCurrency(planPrice?.amount ?? 0, planPrice?.currency ?? "NGN")}/${subscription.plan.interval}` })
78
+ /* @__PURE__ */ jsx("p", { className: "text-[var(--text-tertiary)]", children: isFreePlan ? "Free plan" : `${formatCurrency(planPriceUsd, "USD")}/${subscription.plan.interval}` })
80
79
  ] }),
81
80
  /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
82
81
  !isFreePlan && subscription.currentPeriod.end && /* @__PURE__ */ jsxs("p", { className: "text-sm text-[var(--text-tertiary)]", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/billing/overview-tab.tsx"],"sourcesContent":["import { Badge } from '@withpotter/ui-kit/badge';\nimport { Button } from '@withpotter/ui-kit/button';\nimport {\n Wallet,\n AlertTriangle,\n Check,\n X,\n HardDrive,\n Wifi,\n MessageSquare,\n Mail,\n Plus,\n} from 'lucide-react';\nimport { formatDate, formatBytes } from '@withpotter/lib/utils';\nimport { pickPlanPrice } from '@withpotter/lib/plan-price';\nimport type {\n Subscription,\n BillingTab,\n CurrentUsage,\n MessagingCreditsInfo,\n EmailCreditsInfo,\n} from './helpers';\n\nexport const STORAGE_BASELINE_BYTES = 500 * 1024 * 1024; // 500 MB\n\n// Status badge helper (used only in overview)\nfunction getStatusBadge(status: string) {\n switch (status) {\n case 'active':\n case 'trialing':\n return <Badge variant=\"success\">Active</Badge>;\n case 'past_due':\n return <Badge variant=\"error\">Past due</Badge>;\n case 'cancelled':\n return <Badge variant=\"secondary\">Cancelled</Badge>;\n case 'non_renewing':\n return <Badge variant=\"warning\">Cancelling</Badge>;\n default:\n return <Badge variant=\"secondary\">{status}</Badge>;\n }\n}\n\ninterface OverviewTabProps {\n subscription: Subscription | null | undefined;\n currentUsage: CurrentUsage | null | undefined;\n addOnBytes: number;\n creditsInfo: MessagingCreditsInfo | null | undefined;\n emailCreditsInfo: EmailCreditsInfo | null | undefined;\n formatCurrency: (amount: number, currency: string) => string;\n onCancelClick: () => void;\n onUpgradeNow: () => void;\n onNavigateTab: (tab: BillingTab, subView?: string) => void;\n onBuyStorage: () => void;\n buyStoragePending: boolean;\n onTopUpCredits: () => void;\n topUpCreditsPending: boolean;\n onTopUpEmailCredits: () => void;\n topUpEmailCreditsPending: boolean;\n}\n\nexport function OverviewTab({\n subscription,\n currentUsage,\n addOnBytes,\n creditsInfo,\n emailCreditsInfo,\n formatCurrency,\n onCancelClick,\n onUpgradeNow,\n onNavigateTab,\n onBuyStorage,\n buyStoragePending,\n onTopUpCredits,\n topUpCreditsPending,\n onTopUpEmailCredits,\n topUpEmailCreditsPending,\n}: OverviewTabProps) {\n const storageUsedBytes = currentUsage?.storage.usedBytes ?? 0;\n const storageFileCount = currentUsage?.storage.fileCount ?? 0;\n const bandwidthUsedBytes = currentUsage?.bandwidth?.usedBytes ?? 0;\n const storageLimitBytes = STORAGE_BASELINE_BYTES + addOnBytes;\n const storagePct = Math.min(\n 100,\n Math.round((storageUsedBytes / storageLimitBytes) * 100),\n );\n const isOverBaseline = storageUsedBytes >= storageLimitBytes;\n const isApproaching = storagePct >= 80 && !isOverBaseline;\n\n return (\n <div className=\"space-y-6\">\n {/* Current Plan Card */}\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-x-auto\">\n <div className=\"px-5 py-4 border-b border-[var(--border-subtle)] flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-md bg-[var(--accent-secondary)] text-brand-500\">\n <Wallet className=\"w-5 h-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-medium text-[var(--text-primary)]\">\n Current plan\n </h2>\n <p className=\"text-sm text-[var(--text-tertiary)]\">\n Your subscription details\n </p>\n </div>\n </div>\n {subscription ? getStatusBadge(subscription.status) : <Badge variant=\"info\">Free</Badge>}\n </div>\n\n <div className=\"p-5\">\n {subscription ? (() => {\n const planPrice = pickPlanPrice(subscription.plan.prices);\n const isFreePlan =\n subscription.plan.slug === 'free' || !planPrice || planPrice.amount === 0;\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {(subscription.plan.name.split('—')[0] || subscription.plan.name).trim()}\n </h3>\n <p className=\"text-[var(--text-tertiary)]\">\n {isFreePlan\n ? 'Free plan'\n : `${formatCurrency(planPrice?.amount ?? 0, planPrice?.currency ?? 'NGN')}/${subscription.plan.interval}`}\n </p>\n </div>\n <div className=\"text-right\">\n {!isFreePlan && subscription.currentPeriod.end && (\n <p className=\"text-sm text-[var(--text-tertiary)]\">\n Renews on {formatDate(subscription.currentPeriod.end)}\n </p>\n )}\n {!isFreePlan && subscription.cancellation.cancelAtPeriodEnd && (\n <div className=\"flex items-center gap-1 text-[var(--error)]\">\n <AlertTriangle className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n Cancels on{' '}\n {formatDate(subscription.cancellation.endsAt!)}\n </span>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"h-px bg-[var(--bg-muted)]\" />\n\n {/* Plan-level transaction fees no longer apply — category fees\n are owned by PricingPolicy and surfaced in the Rates tab. */}\n <div className=\"text-sm\">\n <p className=\"text-[var(--text-tertiary)]\">Custom domain</p>\n <p className=\"text-xs text-[var(--text-muted)] mb-1\">\n Link your own domain\n </p>\n <p className=\"font-medium\">\n {subscription.plan.features.customDomain ? (\n <Check className=\"h-4 w-4 text-[var(--success)]\" />\n ) : (\n <X className=\"h-4 w-4 text-[var(--text-muted)]\" />\n )}\n </p>\n </div>\n\n {!isFreePlan && subscription.status !== 'cancelled' && (\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button\n variant=\"secondary\"\n onClick={onCancelClick}\n disabled={subscription.cancellation.cancelAtPeriodEnd}\n >\n {subscription.cancellation.cancelAtPeriodEnd\n ? 'Cancellation Pending'\n : 'Cancel Subscription'}\n </Button>\n </div>\n )}\n\n {isFreePlan && (\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button variant=\"primary\" onClick={onUpgradeNow}>\n Upgrade to Plus\n </Button>\n </div>\n )}\n </div>\n );\n })() : (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-[var(--text-primary)]\">\n Free plan\n </h3>\n <p className=\"text-[var(--text-tertiary)]\">\n $0/month\n </p>\n </div>\n </div>\n\n <div className=\"h-px bg-[var(--bg-muted)]\" />\n\n <div className=\"text-sm text-[var(--text-tertiary)]\">\n Activate a plan to unlock custom domains, lower transaction\n fees, and the full set of growth tools.\n </div>\n\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button\n variant=\"primary\"\n onClick={() => onNavigateTab('plan')}\n >\n Upgrade plan\n </Button>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Usage This Period — always shown so storage is visible from day one */}\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)]\">\n <div className=\"px-5 py-4 border-b border-[var(--border-subtle)] flex items-center justify-between\">\n <h2 className=\"text-base font-medium text-[var(--text-primary)]\">\n Usage this period\n </h2>\n {currentUsage && (\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {formatDate(currentUsage.period.start)} –{' '}\n {formatDate(currentUsage.period.end)}\n </span>\n )}\n </div>\n <div className=\"divide-y divide-[var(--border-subtle)]\">\n {/* Storage — always visible */}\n <div className=\"px-5 py-4 text-sm\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <HardDrive className=\"w-3.5 h-3.5\" />\n <span>Image storage</span>\n {addOnBytes > 0 && (\n <span className=\"text-xs text-brand-500 bg-brand-500/10 px-1.5 py-0.5 rounded\">\n +{formatBytes(addOnBytes)} purchased\n </span>\n )}\n </div>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {formatBytes(storageUsedBytes)} of{' '}\n {formatBytes(storageLimitBytes)} used\n </span>\n </div>\n {storageFileCount > 0 && (\n <p className=\"text-xs text-[var(--text-muted)] mb-1.5\">\n {storageFileCount} {storageFileCount === 1 ? 'file' : 'files'}{' '}\n uploaded this period\n </p>\n )}\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n isOverBaseline\n ? 'bg-[var(--error)]'\n : isApproaching\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width:\n storagePct === 0 ? '0%' : `${Math.max(storagePct, 1)}%`,\n }}\n />\n </div>\n {isApproaching && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Approaching your storage limit. Consider adding more storage.\n </p>\n )}\n {isOverBaseline && (\n <p className=\"text-xs text-[var(--error)] mt-1\">\n You&apos;ve used {formatBytes(storageUsedBytes)} (\n {formatBytes(storageLimitBytes)} limit). Overage of{' '}\n {formatBytes(storageUsedBytes - storageLimitBytes)} will be\n charged at end of month.\n </p>\n )}\n <button\n type=\"button\"\n onClick={onBuyStorage}\n disabled={buyStoragePending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Buy more storage\n </button>\n </div>\n\n {/* Bandwidth */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <Wifi className=\"w-3.5 h-3.5\" />\n <span>Bandwidth</span>\n </div>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {bandwidthUsedBytes > 0\n ? `${formatBytes(bandwidthUsedBytes)} used this period`\n : '—'}\n </span>\n </div>\n <p className=\"text-xs text-[var(--text-muted)] mt-1\">\n Served via CDN. Detailed tracking coming soon.\n </p>\n </div>\n\n {/* Messaging Credits */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <MessageSquare className=\"w-3.5 h-3.5\" />\n <span>Messaging credits</span>\n </div>\n <span className=\"font-medium text-[var(--text-primary)]\">\n {creditsInfo != null\n ? `${creditsInfo.balance.toLocaleString()} remaining`\n : '—'}\n </span>\n </div>\n\n {creditsInfo && (\n <>\n {/* Usage bar */}\n {creditsInfo.monthlyAllowance > 0 && (\n <>\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)] mb-1\">\n <span>\n {creditsInfo.usedThisPeriod.toLocaleString()} of{' '}\n {creditsInfo.monthlyAllowance.toLocaleString()} included\n used\n </span>\n </div>\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n creditsInfo.overage > 0\n ? 'bg-[var(--error)]'\n : creditsInfo.usedThisPeriod /\n creditsInfo.monthlyAllowance >=\n 0.8\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width: `${Math.min(100, Math.round((creditsInfo.usedThisPeriod / creditsInfo.monthlyAllowance) * 100))}%`,\n }}\n />\n </div>\n </>\n )}\n\n {/* Low balance warning */}\n {creditsInfo.isLowBalance && creditsInfo.overage === 0 && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Low balance — top up to avoid message failures.\n </p>\n )}\n\n {/* Overage */}\n {creditsInfo.overage > 0 && (\n <p className=\"text-xs text-[var(--error)] mt-1\">\n {creditsInfo.overage.toLocaleString()} credits over your\n included allowance. Est. a-la-carte charge:{' '}\n {formatCurrency(creditsInfo.overageCostNgn, 'NGN')}.\n </p>\n )}\n </>\n )}\n\n <button\n type=\"button\"\n onClick={onTopUpCredits}\n disabled={topUpCreditsPending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Top up credits\n </button>\n </div>\n\n {/* Email Credits */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <Mail className=\"w-3.5 h-3.5\" />\n <span>Email credits</span>\n </div>\n <span className=\"font-medium text-[var(--text-primary)]\">\n {emailCreditsInfo != null\n ? `${emailCreditsInfo.balance.toLocaleString()} remaining`\n : '—'}\n </span>\n </div>\n\n {emailCreditsInfo && (\n <>\n {emailCreditsInfo.monthlyAllowance > 0 && (\n <>\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)] mb-1\">\n <span>\n {emailCreditsInfo.usedThisPeriod.toLocaleString()} of{' '}\n {emailCreditsInfo.monthlyAllowance.toLocaleString()}{' '}\n included used\n </span>\n </div>\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n emailCreditsInfo.usedThisPeriod /\n emailCreditsInfo.monthlyAllowance >=\n 0.8\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width: `${Math.min(100, Math.round((emailCreditsInfo.usedThisPeriod / emailCreditsInfo.monthlyAllowance) * 100))}%`,\n }}\n />\n </div>\n </>\n )}\n\n {emailCreditsInfo.isLowBalance && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Low balance — top up to keep emails sending.\n </p>\n )}\n </>\n )}\n\n <button\n type=\"button\"\n onClick={onTopUpEmailCredits}\n disabled={topUpEmailCreditsPending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Top up email credits\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"AA8Ba,SA6SK,UA7SL,KAmED,YAnEC;AA9Bb,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY,mBAAmB;AACxC,SAAS,qBAAqB;AASvB,MAAM,yBAAyB,MAAM,OAAO;AAGnD,SAAS,eAAe,QAAgB;AACtC,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AACH,aAAO,oBAAC,SAAM,SAAQ,WAAU,oBAAM;AAAA,IACxC,KAAK;AACH,aAAO,oBAAC,SAAM,SAAQ,SAAQ,sBAAQ;AAAA,IACxC,KAAK;AACH,aAAO,oBAAC,SAAM,SAAQ,aAAY,uBAAS;AAAA,IAC7C,KAAK;AACH,aAAO,oBAAC,SAAM,SAAQ,WAAU,wBAAU;AAAA,IAC5C;AACE,aAAO,oBAAC,SAAM,SAAQ,aAAa,kBAAO;AAAA,EAC9C;AACF;AAoBO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,mBAAmB,cAAc,QAAQ,aAAa;AAC5D,QAAM,mBAAmB,cAAc,QAAQ,aAAa;AAC5D,QAAM,qBAAqB,cAAc,WAAW,aAAa;AACjE,QAAM,oBAAoB,yBAAyB;AACnD,QAAM,aAAa,KAAK;AAAA,IACtB;AAAA,IACA,KAAK,MAAO,mBAAmB,oBAAqB,GAAG;AAAA,EACzD;AACA,QAAM,iBAAiB,oBAAoB;AAC3C,QAAM,gBAAgB,cAAc,MAAM,CAAC;AAE3C,SACE,qBAAC,SAAI,WAAU,aAEb;AAAA,yBAAC,SAAI,WAAU,wFACb;AAAA,2BAAC,SAAI,WAAU,sFACb;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,SAAI,WAAU,8DACb,8BAAC,UAAO,WAAU,WAAU,GAC9B;AAAA,UACA,qBAAC,SACC;AAAA,gCAAC,QAAG,WAAU,oDAAmD,0BAEjE;AAAA,YACA,oBAAC,OAAE,WAAU,uCAAsC,uCAEnD;AAAA,aACF;AAAA,WACF;AAAA,QACC,eAAe,eAAe,aAAa,MAAM,IAAI,oBAAC,SAAM,SAAQ,QAAO,kBAAI;AAAA,SAClF;AAAA,MAEA,oBAAC,SAAI,WAAU,OACZ,0BAAgB,MAAM;AACrB,cAAM,YAAY,cAAc,aAAa,KAAK,MAAM;AACxD,cAAM,aACJ,aAAa,KAAK,SAAS,UAAU,CAAC,aAAa,UAAU,WAAW;AAC1E,eACA,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,qCACb;AAAA,iCAAC,SACC;AAAA,kCAAC,QAAG,WAAU,oDACV,wBAAa,KAAK,KAAK,MAAM,QAAG,EAAE,CAAC,KAAK,aAAa,KAAK,MAAM,KAAK,GACzE;AAAA,cACA,oBAAC,OAAE,WAAU,+BACV,uBACG,cACA,GAAG,eAAe,WAAW,UAAU,GAAG,WAAW,YAAY,KAAK,CAAC,IAAI,aAAa,KAAK,QAAQ,IAC3G;AAAA,eACF;AAAA,YACA,qBAAC,SAAI,WAAU,cACZ;AAAA,eAAC,cAAc,aAAa,cAAc,OACzC,qBAAC,OAAE,WAAU,uCAAsC;AAAA;AAAA,gBACtC,WAAW,aAAa,cAAc,GAAG;AAAA,iBACtD;AAAA,cAED,CAAC,cAAc,aAAa,aAAa,qBACxC,qBAAC,SAAI,WAAU,+CACb;AAAA,oCAAC,iBAAc,WAAU,WAAU;AAAA,gBACnC,qBAAC,UAAK,WAAU,WAAU;AAAA;AAAA,kBACb;AAAA,kBACV,WAAW,aAAa,aAAa,MAAO;AAAA,mBAC/C;AAAA,iBACF;AAAA,eAEJ;AAAA,aACF;AAAA,UAEA,oBAAC,SAAI,WAAU,6BAA4B;AAAA,UAI3C,qBAAC,SAAI,WAAU,WACb;AAAA,gCAAC,OAAE,WAAU,+BAA8B,2BAAa;AAAA,YACxD,oBAAC,OAAE,WAAU,yCAAwC,kCAErD;AAAA,YACA,oBAAC,OAAE,WAAU,eACV,uBAAa,KAAK,SAAS,eAC1B,oBAAC,SAAM,WAAU,iCAAgC,IAEjD,oBAAC,KAAE,WAAU,oCAAmC,GAEpD;AAAA,aACF;AAAA,UAEC,CAAC,cAAc,aAAa,WAAW,eACtC,oBAAC,SAAI,WAAU,iFACb;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,SAAS;AAAA,cACT,UAAU,aAAa,aAAa;AAAA,cAEnC,uBAAa,aAAa,oBACvB,yBACA;AAAA;AAAA,UACN,GACF;AAAA,UAGD,cACC,oBAAC,SAAI,WAAU,iFACb,8BAAC,UAAO,SAAQ,WAAU,SAAS,cAAc,6BAEjD,GACF;AAAA,WAEJ;AAAA,MAEF,GAAG,IACD,qBAAC,SAAI,WAAU,aACb;AAAA,4BAAC,SAAI,WAAU,qCACb,+BAAC,SACC;AAAA,8BAAC,QAAG,WAAU,oDAAmD,uBAEjE;AAAA,UACA,oBAAC,OAAE,WAAU,+BAA8B,sBAE3C;AAAA,WACF,GACF;AAAA,QAEA,oBAAC,SAAI,WAAU,6BAA4B;AAAA,QAE3C,oBAAC,SAAI,WAAU,uCAAsC,iHAGrD;AAAA,QAEA,oBAAC,SAAI,WAAU,iFACb;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,SAAS,MAAM,cAAc,MAAM;AAAA,YACpC;AAAA;AAAA,QAED,GACF;AAAA,SACF,GAEJ;AAAA,OACF;AAAA,IAGA,qBAAC,SAAI,WAAU,wEACb;AAAA,2BAAC,SAAI,WAAU,sFACb;AAAA,4BAAC,QAAG,WAAU,oDAAmD,+BAEjE;AAAA,QACC,gBACC,qBAAC,UAAK,WAAU,uCACb;AAAA,qBAAW,aAAa,OAAO,KAAK;AAAA,UAAE;AAAA,UAAG;AAAA,UACzC,WAAW,aAAa,OAAO,GAAG;AAAA,WACrC;AAAA,SAEJ;AAAA,MACA,qBAAC,SAAI,WAAU,0CAEb;AAAA,6BAAC,SAAI,WAAU,qBACb;AAAA,+BAAC,SAAI,WAAU,4CACb;AAAA,iCAAC,SAAI,WAAU,wDACb;AAAA,kCAAC,aAAU,WAAU,eAAc;AAAA,cACnC,oBAAC,UAAK,2BAAa;AAAA,cAClB,aAAa,KACZ,qBAAC,UAAK,WAAU,gEAA+D;AAAA;AAAA,gBAC3E,YAAY,UAAU;AAAA,gBAAE;AAAA,iBAC5B;AAAA,eAEJ;AAAA,YACA,qBAAC,UAAK,WAAU,uCACb;AAAA,0BAAY,gBAAgB;AAAA,cAAE;AAAA,cAAI;AAAA,cAClC,YAAY,iBAAiB;AAAA,cAAE;AAAA,eAClC;AAAA,aACF;AAAA,UACC,mBAAmB,KAClB,qBAAC,OAAE,WAAU,2CACV;AAAA;AAAA,YAAiB;AAAA,YAAE,qBAAqB,IAAI,SAAS;AAAA,YAAS;AAAA,YAAI;AAAA,aAErE;AAAA,UAEF,oBAAC,SAAI,WAAU,kEACb;AAAA,YAAC;AAAA;AAAA,cACC,WAAW,uDACT,iBACI,sBACA,gBACE,wBACA,cACR;AAAA,cACA,OAAO;AAAA,gBACL,OACE,eAAe,IAAI,OAAO,GAAG,KAAK,IAAI,YAAY,CAAC,CAAC;AAAA,cACxD;AAAA;AAAA,UACF,GACF;AAAA,UACC,iBACC,oBAAC,OAAE,WAAU,sCAAqC,2EAElD;AAAA,UAED,kBACC,qBAAC,OAAE,WAAU,oCAAmC;AAAA;AAAA,YAC5B,YAAY,gBAAgB;AAAA,YAAE;AAAA,YAC/C,YAAY,iBAAiB;AAAA,YAAE;AAAA,YAAoB;AAAA,YACnD,YAAY,mBAAmB,iBAAiB;AAAA,YAAE;AAAA,aAErD;AAAA,UAEF;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAU;AAAA,cAEV;AAAA,oCAAC,QAAK,WAAU,WAAU;AAAA,gBAAE;AAAA;AAAA;AAAA,UAE9B;AAAA,WACF;AAAA,QAGA,qBAAC,SAAI,WAAU,4DACb;AAAA,+BAAC,SAAI,WAAU,qCACb;AAAA,iCAAC,SAAI,WAAU,wDACb;AAAA,kCAAC,QAAK,WAAU,eAAc;AAAA,cAC9B,oBAAC,UAAK,uBAAS;AAAA,eACjB;AAAA,YACA,oBAAC,UAAK,WAAU,uCACb,+BAAqB,IAClB,GAAG,YAAY,kBAAkB,CAAC,sBAClC,UACN;AAAA,aACF;AAAA,UACA,oBAAC,OAAE,WAAU,yCAAwC,4DAErD;AAAA,WACF;AAAA,QAGA,qBAAC,SAAI,WAAU,4DACb;AAAA,+BAAC,SAAI,WAAU,4CACb;AAAA,iCAAC,SAAI,WAAU,wDACb;AAAA,kCAAC,iBAAc,WAAU,eAAc;AAAA,cACvC,oBAAC,UAAK,+BAAiB;AAAA,eACzB;AAAA,YACA,oBAAC,UAAK,WAAU,0CACb,yBAAe,OACZ,GAAG,YAAY,QAAQ,eAAe,CAAC,eACvC,UACN;AAAA,aACF;AAAA,UAEC,eACC,iCAEG;AAAA,wBAAY,mBAAmB,KAC9B,iCACE;AAAA,kCAAC,SAAI,WAAU,2EACb,+BAAC,UACE;AAAA,4BAAY,eAAe,eAAe;AAAA,gBAAE;AAAA,gBAAI;AAAA,gBAChD,YAAY,iBAAiB,eAAe;AAAA,gBAAE;AAAA,iBAEjD,GACF;AAAA,cACA,oBAAC,SAAI,WAAU,kEACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,uDACT,YAAY,UAAU,IAClB,sBACA,YAAY,iBACR,YAAY,oBACd,MACA,wBACA,cACR;AAAA,kBACA,OAAO;AAAA,oBACL,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,MAAO,YAAY,iBAAiB,YAAY,mBAAoB,GAAG,CAAC,CAAC;AAAA,kBACxG;AAAA;AAAA,cACF,GACF;AAAA,eACF;AAAA,YAID,YAAY,gBAAgB,YAAY,YAAY,KACnD,oBAAC,OAAE,WAAU,sCAAqC,kEAElD;AAAA,YAID,YAAY,UAAU,KACrB,qBAAC,OAAE,WAAU,oCACV;AAAA,0BAAY,QAAQ,eAAe;AAAA,cAAE;AAAA,cACM;AAAA,cAC3C,eAAe,YAAY,gBAAgB,KAAK;AAAA,cAAE;AAAA,eACrD;AAAA,aAEJ;AAAA,UAGF;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAU;AAAA,cAEV;AAAA,oCAAC,QAAK,WAAU,WAAU;AAAA,gBAAE;AAAA;AAAA;AAAA,UAE9B;AAAA,WACF;AAAA,QAGA,qBAAC,SAAI,WAAU,4DACb;AAAA,+BAAC,SAAI,WAAU,4CACb;AAAA,iCAAC,SAAI,WAAU,wDACb;AAAA,kCAAC,QAAK,WAAU,eAAc;AAAA,cAC9B,oBAAC,UAAK,2BAAa;AAAA,eACrB;AAAA,YACA,oBAAC,UAAK,WAAU,0CACb,8BAAoB,OACjB,GAAG,iBAAiB,QAAQ,eAAe,CAAC,eAC5C,UACN;AAAA,aACF;AAAA,UAEC,oBACC,iCACG;AAAA,6BAAiB,mBAAmB,KACnC,iCACE;AAAA,kCAAC,SAAI,WAAU,2EACb,+BAAC,UACE;AAAA,iCAAiB,eAAe,eAAe;AAAA,gBAAE;AAAA,gBAAI;AAAA,gBACrD,iBAAiB,iBAAiB,eAAe;AAAA,gBAAG;AAAA,gBAAI;AAAA,iBAE3D,GACF;AAAA,cACA,oBAAC,SAAI,WAAU,kEACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,uDACT,iBAAiB,iBACf,iBAAiB,oBACnB,MACI,wBACA,cACN;AAAA,kBACA,OAAO;AAAA,oBACL,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,MAAO,iBAAiB,iBAAiB,iBAAiB,mBAAoB,GAAG,CAAC,CAAC;AAAA,kBAClH;AAAA;AAAA,cACF,GACF;AAAA,eACF;AAAA,YAGD,iBAAiB,gBAChB,oBAAC,OAAE,WAAU,sCAAqC,+DAElD;AAAA,aAEJ;AAAA,UAGF;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAU;AAAA,cAEV;AAAA,oCAAC,QAAK,WAAU,WAAU;AAAA,gBAAE;AAAA;AAAA;AAAA,UAE9B;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/billing/overview-tab.tsx"],"sourcesContent":["import { Badge } from '@withpotter/ui-kit/badge';\nimport { Button } from '@withpotter/ui-kit/button';\nimport {\n Wallet,\n AlertTriangle,\n Check,\n X,\n HardDrive,\n Wifi,\n MessageSquare,\n Mail,\n Plus,\n} from 'lucide-react';\nimport { formatDate, formatBytes } from '@withpotter/lib/utils';\nimport type {\n Subscription,\n BillingTab,\n CurrentUsage,\n MessagingCreditsInfo,\n EmailCreditsInfo,\n} from './helpers';\n\nexport const STORAGE_BASELINE_BYTES = 500 * 1024 * 1024; // 500 MB\n\n// Status badge helper (used only in overview)\nfunction getStatusBadge(status: string) {\n switch (status) {\n case 'active':\n case 'trialing':\n return <Badge variant=\"success\">Active</Badge>;\n case 'past_due':\n return <Badge variant=\"error\">Past due</Badge>;\n case 'cancelled':\n return <Badge variant=\"secondary\">Cancelled</Badge>;\n case 'non_renewing':\n return <Badge variant=\"warning\">Cancelling</Badge>;\n default:\n return <Badge variant=\"secondary\">{status}</Badge>;\n }\n}\n\ninterface OverviewTabProps {\n subscription: Subscription | null | undefined;\n currentUsage: CurrentUsage | null | undefined;\n addOnBytes: number;\n creditsInfo: MessagingCreditsInfo | null | undefined;\n emailCreditsInfo: EmailCreditsInfo | null | undefined;\n formatCurrency: (amount: number, currency: string) => string;\n onCancelClick: () => void;\n onUpgradeNow: () => void;\n onNavigateTab: (tab: BillingTab, subView?: string) => void;\n onBuyStorage: () => void;\n buyStoragePending: boolean;\n onTopUpCredits: () => void;\n topUpCreditsPending: boolean;\n onTopUpEmailCredits: () => void;\n topUpEmailCreditsPending: boolean;\n}\n\nexport function OverviewTab({\n subscription,\n currentUsage,\n addOnBytes,\n creditsInfo,\n emailCreditsInfo,\n formatCurrency,\n onCancelClick,\n onUpgradeNow,\n onNavigateTab,\n onBuyStorage,\n buyStoragePending,\n onTopUpCredits,\n topUpCreditsPending,\n onTopUpEmailCredits,\n topUpEmailCreditsPending,\n}: OverviewTabProps) {\n const storageUsedBytes = currentUsage?.storage.usedBytes ?? 0;\n const storageFileCount = currentUsage?.storage.fileCount ?? 0;\n const bandwidthUsedBytes = currentUsage?.bandwidth?.usedBytes ?? 0;\n const storageLimitBytes = STORAGE_BASELINE_BYTES + addOnBytes;\n const storagePct = Math.min(\n 100,\n Math.round((storageUsedBytes / storageLimitBytes) * 100),\n );\n const isOverBaseline = storageUsedBytes >= storageLimitBytes;\n const isApproaching = storagePct >= 80 && !isOverBaseline;\n\n return (\n <div className=\"space-y-6\">\n {/* Current Plan Card */}\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-x-auto\">\n <div className=\"px-5 py-4 border-b border-[var(--border-subtle)] flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-md bg-[var(--accent-secondary)] text-brand-500\">\n <Wallet className=\"w-5 h-5\" />\n </div>\n <div>\n <h2 className=\"text-base font-medium text-[var(--text-primary)]\">\n Current plan\n </h2>\n <p className=\"text-sm text-[var(--text-tertiary)]\">\n Your subscription details\n </p>\n </div>\n </div>\n {subscription ? getStatusBadge(subscription.status) : <Badge variant=\"info\">Free</Badge>}\n </div>\n\n <div className=\"p-5\">\n {subscription ? (() => {\n const planPriceUsd = subscription.plan.prices?.['USD'] ?? 0;\n const isFreePlan =\n subscription.plan.slug === 'free' || planPriceUsd === 0;\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-[var(--text-primary)]\">\n {(subscription.plan.name.split('—')[0] || subscription.plan.name).trim()}\n </h3>\n <p className=\"text-[var(--text-tertiary)]\">\n {isFreePlan\n ? 'Free plan'\n : `${formatCurrency(planPriceUsd, 'USD')}/${subscription.plan.interval}`}\n </p>\n </div>\n <div className=\"text-right\">\n {!isFreePlan && subscription.currentPeriod.end && (\n <p className=\"text-sm text-[var(--text-tertiary)]\">\n Renews on {formatDate(subscription.currentPeriod.end)}\n </p>\n )}\n {!isFreePlan && subscription.cancellation.cancelAtPeriodEnd && (\n <div className=\"flex items-center gap-1 text-[var(--error)]\">\n <AlertTriangle className=\"h-4 w-4\" />\n <span className=\"text-sm\">\n Cancels on{' '}\n {formatDate(subscription.cancellation.endsAt!)}\n </span>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"h-px bg-[var(--bg-muted)]\" />\n\n {/* Plan-level transaction fees no longer apply — category fees\n are owned by PricingPolicy and surfaced in the Rates tab. */}\n <div className=\"text-sm\">\n <p className=\"text-[var(--text-tertiary)]\">Custom domain</p>\n <p className=\"text-xs text-[var(--text-muted)] mb-1\">\n Link your own domain\n </p>\n <p className=\"font-medium\">\n {subscription.plan.features.customDomain ? (\n <Check className=\"h-4 w-4 text-[var(--success)]\" />\n ) : (\n <X className=\"h-4 w-4 text-[var(--text-muted)]\" />\n )}\n </p>\n </div>\n\n {!isFreePlan && subscription.status !== 'cancelled' && (\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button\n variant=\"secondary\"\n onClick={onCancelClick}\n disabled={subscription.cancellation.cancelAtPeriodEnd}\n >\n {subscription.cancellation.cancelAtPeriodEnd\n ? 'Cancellation Pending'\n : 'Cancel Subscription'}\n </Button>\n </div>\n )}\n\n {isFreePlan && (\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button variant=\"primary\" onClick={onUpgradeNow}>\n Upgrade to Plus\n </Button>\n </div>\n )}\n </div>\n );\n })() : (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-[var(--text-primary)]\">\n Free plan\n </h3>\n <p className=\"text-[var(--text-tertiary)]\">\n $0/month\n </p>\n </div>\n </div>\n\n <div className=\"h-px bg-[var(--bg-muted)]\" />\n\n <div className=\"text-sm text-[var(--text-tertiary)]\">\n Activate a plan to unlock custom domains, lower transaction\n fees, and the full set of growth tools.\n </div>\n\n <div className=\"pt-4 flex items-center justify-between border-t border-[var(--border-subtle)]\">\n <Button\n variant=\"primary\"\n onClick={() => onNavigateTab('plan')}\n >\n Upgrade plan\n </Button>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Usage This Period — always shown so storage is visible from day one */}\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)]\">\n <div className=\"px-5 py-4 border-b border-[var(--border-subtle)] flex items-center justify-between\">\n <h2 className=\"text-base font-medium text-[var(--text-primary)]\">\n Usage this period\n </h2>\n {currentUsage && (\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {formatDate(currentUsage.period.start)} –{' '}\n {formatDate(currentUsage.period.end)}\n </span>\n )}\n </div>\n <div className=\"divide-y divide-[var(--border-subtle)]\">\n {/* Storage — always visible */}\n <div className=\"px-5 py-4 text-sm\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <HardDrive className=\"w-3.5 h-3.5\" />\n <span>Image storage</span>\n {addOnBytes > 0 && (\n <span className=\"text-xs text-brand-500 bg-brand-500/10 px-1.5 py-0.5 rounded\">\n +{formatBytes(addOnBytes)} purchased\n </span>\n )}\n </div>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {formatBytes(storageUsedBytes)} of{' '}\n {formatBytes(storageLimitBytes)} used\n </span>\n </div>\n {storageFileCount > 0 && (\n <p className=\"text-xs text-[var(--text-muted)] mb-1.5\">\n {storageFileCount} {storageFileCount === 1 ? 'file' : 'files'}{' '}\n uploaded this period\n </p>\n )}\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n isOverBaseline\n ? 'bg-[var(--error)]'\n : isApproaching\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width:\n storagePct === 0 ? '0%' : `${Math.max(storagePct, 1)}%`,\n }}\n />\n </div>\n {isApproaching && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Approaching your storage limit. Consider adding more storage.\n </p>\n )}\n {isOverBaseline && (\n <p className=\"text-xs text-[var(--error)] mt-1\">\n You&apos;ve used {formatBytes(storageUsedBytes)} (\n {formatBytes(storageLimitBytes)} limit). Overage of{' '}\n {formatBytes(storageUsedBytes - storageLimitBytes)} will be\n charged at end of month.\n </p>\n )}\n <button\n type=\"button\"\n onClick={onBuyStorage}\n disabled={buyStoragePending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Buy more storage\n </button>\n </div>\n\n {/* Bandwidth */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <Wifi className=\"w-3.5 h-3.5\" />\n <span>Bandwidth</span>\n </div>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {bandwidthUsedBytes > 0\n ? `${formatBytes(bandwidthUsedBytes)} used this period`\n : '—'}\n </span>\n </div>\n <p className=\"text-xs text-[var(--text-muted)] mt-1\">\n Served via CDN. Detailed tracking coming soon.\n </p>\n </div>\n\n {/* Messaging Credits */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <MessageSquare className=\"w-3.5 h-3.5\" />\n <span>Messaging credits</span>\n </div>\n <span className=\"font-medium text-[var(--text-primary)]\">\n {creditsInfo != null\n ? `${creditsInfo.balance.toLocaleString()} remaining`\n : '—'}\n </span>\n </div>\n\n {creditsInfo && (\n <>\n {/* Usage bar */}\n {creditsInfo.monthlyAllowance > 0 && (\n <>\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)] mb-1\">\n <span>\n {creditsInfo.usedThisPeriod.toLocaleString()} of{' '}\n {creditsInfo.monthlyAllowance.toLocaleString()} included\n used\n </span>\n </div>\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n creditsInfo.overage > 0\n ? 'bg-[var(--error)]'\n : creditsInfo.usedThisPeriod /\n creditsInfo.monthlyAllowance >=\n 0.8\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width: `${Math.min(100, Math.round((creditsInfo.usedThisPeriod / creditsInfo.monthlyAllowance) * 100))}%`,\n }}\n />\n </div>\n </>\n )}\n\n {/* Low balance warning */}\n {creditsInfo.isLowBalance && creditsInfo.overage === 0 && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Low balance — top up to avoid message failures.\n </p>\n )}\n\n {/* Overage */}\n {creditsInfo.overage > 0 && (\n <p className=\"text-xs text-[var(--error)] mt-1\">\n {creditsInfo.overage.toLocaleString()} credits over your\n included allowance. Est. a-la-carte charge:{' '}\n {formatCurrency(creditsInfo.overageCostNgn, 'NGN')}.\n </p>\n )}\n </>\n )}\n\n <button\n type=\"button\"\n onClick={onTopUpCredits}\n disabled={topUpCreditsPending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Top up credits\n </button>\n </div>\n\n {/* Email Credits */}\n <div className=\"px-5 py-4 text-sm border-t border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between mb-1.5\">\n <div className=\"flex items-center gap-2 text-[var(--text-secondary)]\">\n <Mail className=\"w-3.5 h-3.5\" />\n <span>Email credits</span>\n </div>\n <span className=\"font-medium text-[var(--text-primary)]\">\n {emailCreditsInfo != null\n ? `${emailCreditsInfo.balance.toLocaleString()} remaining`\n : '—'}\n </span>\n </div>\n\n {emailCreditsInfo && (\n <>\n {emailCreditsInfo.monthlyAllowance > 0 && (\n <>\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)] mb-1\">\n <span>\n {emailCreditsInfo.usedThisPeriod.toLocaleString()} of{' '}\n {emailCreditsInfo.monthlyAllowance.toLocaleString()}{' '}\n included used\n </span>\n </div>\n <div className=\"w-full h-1.5 bg-[var(--bg-muted)] rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-[width] duration-500 ${\n emailCreditsInfo.usedThisPeriod /\n emailCreditsInfo.monthlyAllowance >=\n 0.8\n ? 'bg-[var(--warning)]'\n : 'bg-brand-500'\n }`}\n style={{\n width: `${Math.min(100, Math.round((emailCreditsInfo.usedThisPeriod / emailCreditsInfo.monthlyAllowance) * 100))}%`,\n }}\n />\n </div>\n </>\n )}\n\n {emailCreditsInfo.isLowBalance && (\n <p className=\"text-xs text-[var(--warning)] mt-1\">\n Low balance — top up to keep emails sending.\n </p>\n )}\n </>\n )}\n\n <button\n type=\"button\"\n onClick={onTopUpEmailCredits}\n disabled={topUpEmailCreditsPending}\n className=\"mt-2 flex items-center gap-1 text-xs text-brand-500 hover:text-brand-600 hover:underline disabled:opacity-50 cursor-pointer\"\n >\n <Plus className=\"w-3 h-3\" />\n Top up email credits\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -11,7 +11,6 @@ import {
11
11
  Lock
12
12
  } from "lucide-react";
13
13
  import { useFormatCurrency } from "@withpotter/hooks/use-format-currency";
14
- import { pickPlanPrice } from "@withpotter/lib/plan-price";
15
14
  const PLUS_FEATURES = [
16
15
  {
17
16
  icon: ShoppingCart,
@@ -53,7 +52,7 @@ function PlansTab({
53
52
  subscribePending
54
53
  }
55
54
  ),
56
- /* @__PURE__ */ jsx(ProPlusCard, {}),
55
+ /* @__PURE__ */ jsx(ProPlusCard, { plusPlan }),
57
56
  /* @__PURE__ */ jsx(SuperProCard, {})
58
57
  ] }),
59
58
  /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-tertiary)] text-center", children: "Prices billed monthly per workspace \xB7 Cancel any time." })
@@ -70,7 +69,7 @@ function PlusCard({
70
69
  const rawName = plan?.name ?? "Plus";
71
70
  const displayName = rawName.split("\u2014")[0]?.trim() || rawName;
72
71
  const displayInterval = plan?.interval ?? "month";
73
- const displayPrice = pickPlanPrice(plan?.prices);
72
+ const displayAmount = plan?.prices?.["USD"];
74
73
  return /* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-base)] rounded-md border border-[var(--accent-primary)] overflow-hidden flex flex-col", children: [
75
74
  /* @__PURE__ */ jsxs("div", { className: "px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]", children: [
76
75
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
@@ -79,7 +78,7 @@ function PlusCard({
79
78
  ] }),
80
79
  /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-tertiary)] mt-1", children: "Everything you need to run your business on Box." }),
81
80
  /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-baseline gap-1", children: [
82
- /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold text-[var(--text-primary)]", children: displayPrice ? formatCurrency(displayPrice.amount, displayPrice.currency) : "\u2014" }),
81
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold text-[var(--text-primary)]", children: displayAmount !== void 0 ? formatCurrency(displayAmount, "USD") : "\u2014" }),
83
82
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-[var(--text-tertiary)]", children: [
84
83
  "/ ",
85
84
  displayInterval
@@ -111,7 +110,11 @@ function PlusCard({
111
110
  ) })
112
111
  ] });
113
112
  }
114
- function ProPlusCard() {
113
+ function ProPlusCard({ plusPlan }) {
114
+ const formatCurrency = useFormatCurrency();
115
+ const plusAmount = plusPlan?.prices?.["USD"];
116
+ const upgradeUsd = 5;
117
+ const proPlusAmount = plusAmount === void 0 ? void 0 : plusAmount + upgradeUsd;
115
118
  return /* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-hidden flex flex-col", children: [
116
119
  /* @__PURE__ */ jsxs("div", { className: "px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]", children: [
117
120
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
@@ -119,7 +122,10 @@ function ProPlusCard() {
119
122
  /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: "Coming soon" })
120
123
  ] }),
121
124
  /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-tertiary)] mt-1", children: "Plus, plus our team helping you grow." }),
122
- /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-baseline gap-1", children: /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold text-[var(--text-primary)]", children: "Priced on top of Plus" }) })
125
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-baseline gap-1", children: [
126
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold text-[var(--text-primary)]", children: proPlusAmount !== void 0 ? formatCurrency(proPlusAmount, "USD") : "+$5" }),
127
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--text-tertiary)]", children: proPlusAmount !== void 0 ? "/ month" : "/ month over Plus" })
128
+ ] })
123
129
  ] }),
124
130
  /* @__PURE__ */ jsx("div", { className: "px-5 py-4 flex-1", children: /* @__PURE__ */ jsx("ul", { className: "space-y-2.5", children: PRO_PLUS_FEATURES.map((text) => /* @__PURE__ */ jsxs(
125
131
  "li",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/billing/plans-tab.tsx"],"sourcesContent":["import { Button } from '@withpotter/ui-kit/button';\nimport { Badge } from '@withpotter/ui-kit/badge';\nimport {\n Check,\n Globe,\n MessageSquare,\n ShoppingCart,\n BarChart3,\n Loader2,\n Lock,\n} from 'lucide-react';\nimport { useFormatCurrency } from '@withpotter/hooks/use-format-currency';\nimport { pickPlanPrice } from '@withpotter/lib/plan-price';\nimport type { BillingPlan, Subscription } from './helpers';\n\n// ── Plus tier — included features ──────────────────────────────────────\nconst PLUS_FEATURES = [\n {\n icon: ShoppingCart,\n text: 'All modules — orders, bookings, inventory, subscriptions',\n },\n { icon: Globe, text: 'Custom domain support' },\n { icon: MessageSquare, text: '200 transactional emails / month included' },\n { icon: BarChart3, text: 'Full analytics and reporting' },\n];\n\n// ── Pro Plus tier — value-added services preview ───────────────────────\nconst PRO_PLUS_FEATURES = [\n 'User-generated content (UGC) campaigns produced for you',\n 'Distribution support across owned and paid channels',\n 'Branding & business-strategy office hours',\n];\n\n// ── Super Pro tier — premium / agency-grade services ───────────────────\nconst SUPER_PRO_FEATURES = [\n 'Full brand system — identity, kit, guidelines',\n 'In-person terminals & POS integration',\n 'Priority human support and account management',\n];\n\ninterface PlansTabProps {\n /** Region-resolved Plus plan returned by /billing/plans/active. */\n plusPlan: BillingPlan | undefined;\n subscription: Subscription | null | undefined;\n onSubscribe: () => void;\n subscribePending: boolean;\n}\n\nexport function PlansTab({\n plusPlan,\n subscription,\n onSubscribe,\n subscribePending,\n}: PlansTabProps) {\n return (\n <div className=\"space-y-6\">\n {/* Promise banner */}\n <div className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-subtle)] px-4 py-3 text-sm text-[var(--text-secondary)]\">\n <span className=\"font-medium text-[var(--text-primary)]\">\n Cheaper than IG ads that might not convert.\n </span>{' '}\n Pay for the platform, keep what you make.\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <PlusCard\n plan={plusPlan}\n subscription={subscription}\n onSubscribe={onSubscribe}\n subscribePending={subscribePending}\n />\n <ProPlusCard />\n <SuperProCard />\n </div>\n\n <p className=\"text-xs text-[var(--text-tertiary)] text-center\">\n Prices billed monthly per workspace · Cancel any time.\n </p>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Plus — today's live tier\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction PlusCard({\n plan,\n subscription,\n onSubscribe,\n subscribePending,\n}: {\n plan: BillingPlan | undefined;\n subscription: Subscription | null | undefined;\n onSubscribe: () => void;\n subscribePending: boolean;\n}) {\n const formatCurrency = useFormatCurrency();\n\n const isActive = subscription && !['cancelled'].includes(subscription.status);\n const rawName = plan?.name ?? 'Plus';\n // Plan names sometimes encode region (e.g. \"Plus — Nigeria\"). The card\n // shows the naira price when the plan has one, so strip the region suffix\n // to avoid showing \"Plus — Nigeria\" next to the amount.\n const displayName = rawName.split('—')[0]?.trim() || rawName;\n const displayInterval = plan?.interval ?? 'month';\n const displayPrice = pickPlanPrice(plan?.prices);\n\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--accent-primary)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n {displayName}\n </h2>\n {isActive ? (\n <Badge variant=\"success\">Active</Badge>\n ) : (\n <Badge variant=\"info\">Available now</Badge>\n )}\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n Everything you need to run your business on Box.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-3xl font-bold text-[var(--text-primary)]\">\n {displayPrice ? formatCurrency(displayPrice.amount, displayPrice.currency) : '—'}\n </span>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n / {displayInterval}\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {PLUS_FEATURES.map(({ text }) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--success)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n {isActive ? (\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)]\">\n Workspace is active\n </div>\n ) : (\n <Button\n className=\"w-full\"\n onClick={onSubscribe}\n disabled={subscribePending || !plan}\n >\n {subscribePending ? (\n <Loader2 className=\"w-4 h-4 animate-spin mr-2\" />\n ) : null}\n Activate workspace\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Pro Plus — Plus + value-added services (coming soon)\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction ProPlusCard() {\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n Pro Plus\n </h2>\n <Badge variant=\"secondary\">Coming soon</Badge>\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n Plus, plus our team helping you grow.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-lg font-semibold text-[var(--text-primary)]\">\n Priced on top of Plus\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {PRO_PLUS_FEATURES.map((text) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--text-muted)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)] flex items-center justify-center gap-2\">\n <Lock className=\"w-3.5 h-3.5\" />\n Coming soon\n </div>\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Super Pro — branding, terminals, advanced (coming soon)\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction SuperProCard() {\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n Super Pro\n </h2>\n <Badge variant=\"secondary\">Coming soon</Badge>\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n For brands operating at scale.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-3xl font-bold text-[var(--text-primary)]\">\n Custom\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {SUPER_PRO_FEATURES.map((text) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--text-muted)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)] flex items-center justify-center gap-2\">\n <Lock className=\"w-3.5 h-3.5\" />\n Coming soon\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"AAyDM,SACE,KADF;AAzDN,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAI9B,MAAM,gBAAgB;AAAA,EACpB;AAAA,IACE,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAAA,EACA,EAAE,MAAM,OAAO,MAAM,wBAAwB;AAAA,EAC7C,EAAE,MAAM,eAAe,MAAM,4CAA4C;AAAA,EACzE,EAAE,MAAM,WAAW,MAAM,+BAA+B;AAC1D;AAGA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF;AAGA,MAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkB;AAChB,SACE,qBAAC,SAAI,WAAU,aAEb;AAAA,yBAAC,SAAI,WAAU,yHACb;AAAA,0BAAC,UAAK,WAAU,0CAAyC,yDAEzD;AAAA,MAAQ;AAAA,MAAI;AAAA,OAEd;AAAA,IAEA,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,MACA,oBAAC,eAAY;AAAA,MACb,oBAAC,gBAAa;AAAA,OAChB;AAAA,IAEA,oBAAC,OAAE,WAAU,mDAAkD,uEAE/D;AAAA,KACF;AAEJ;AAMA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,iBAAiB,kBAAkB;AAEzC,QAAM,WAAW,gBAAgB,CAAC,CAAC,WAAW,EAAE,SAAS,aAAa,MAAM;AAC5E,QAAM,UAAU,MAAM,QAAQ;AAI9B,QAAM,cAAc,QAAQ,MAAM,QAAG,EAAE,CAAC,GAAG,KAAK,KAAK;AACrD,QAAM,kBAAkB,MAAM,YAAY;AAC1C,QAAM,eAAe,cAAc,MAAM,MAAM;AAE/C,SACE,qBAAC,SAAI,WAAU,sGACb;AAAA,yBAAC,SAAI,WAAU,yDACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA,4BAAC,QAAG,WAAU,sDACX,uBACH;AAAA,QACC,WACC,oBAAC,SAAM,SAAQ,WAAU,oBAAM,IAE/B,oBAAC,SAAM,SAAQ,QAAO,2BAAa;AAAA,SAEvC;AAAA,MACA,oBAAC,OAAE,WAAU,4CAA2C,8DAExD;AAAA,MAEA,qBAAC,SAAI,WAAU,kCACb;AAAA,4BAAC,UAAK,WAAU,iDACb,yBAAe,eAAe,aAAa,QAAQ,aAAa,QAAQ,IAAI,UAC/E;AAAA,QACA,qBAAC,UAAK,WAAU,uCAAsC;AAAA;AAAA,UACjD;AAAA,WACL;AAAA,SACF;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,oBACb,8BAAC,QAAG,WAAU,eACX,wBAAc,IAAI,CAAC,EAAE,KAAK,MACzB;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,8BAAC,SAAM,WAAU,sDAAqD;AAAA,UACtE,oBAAC,UAAM,gBAAK;AAAA;AAAA;AAAA,MAJP;AAAA,IAKP,CACD,GACH,GACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACZ,qBACC,oBAAC,SAAI,WAAU,oJAAmJ,iCAElK,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS;AAAA,QACT,UAAU,oBAAoB,CAAC;AAAA,QAE9B;AAAA,6BACC,oBAAC,WAAQ,WAAU,6BAA4B,IAC7C;AAAA,UAAK;AAAA;AAAA;AAAA,IAEX,GAEJ;AAAA,KACF;AAEJ;AAMA,SAAS,cAAc;AACrB,SACE,qBAAC,SAAI,WAAU,sGACb;AAAA,yBAAC,SAAI,WAAU,yDACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA,4BAAC,QAAG,WAAU,sDAAqD,sBAEnE;AAAA,QACA,oBAAC,SAAM,SAAQ,aAAY,yBAAW;AAAA,SACxC;AAAA,MACA,oBAAC,OAAE,WAAU,4CAA2C,mDAExD;AAAA,MAEA,oBAAC,SAAI,WAAU,kCACb,8BAAC,UAAK,WAAU,oDAAmD,mCAEnE,GACF;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,oBACb,8BAAC,QAAG,WAAU,eACX,4BAAkB,IAAI,CAAC,SACtB;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,8BAAC,SAAM,WAAU,yDAAwD;AAAA,UACzE,oBAAC,UAAM,gBAAK;AAAA;AAAA;AAAA,MAJP;AAAA,IAKP,CACD,GACH,GACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACb,+BAAC,SAAI,WAAU,2LACb;AAAA,0BAAC,QAAK,WAAU,eAAc;AAAA,MAAE;AAAA,OAElC,GACF;AAAA,KACF;AAEJ;AAMA,SAAS,eAAe;AACtB,SACE,qBAAC,SAAI,WAAU,sGACb;AAAA,yBAAC,SAAI,WAAU,yDACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA,4BAAC,QAAG,WAAU,sDAAqD,uBAEnE;AAAA,QACA,oBAAC,SAAM,SAAQ,aAAY,yBAAW;AAAA,SACxC;AAAA,MACA,oBAAC,OAAE,WAAU,4CAA2C,4CAExD;AAAA,MAEA,oBAAC,SAAI,WAAU,kCACb,8BAAC,UAAK,WAAU,iDAAgD,oBAEhE,GACF;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,oBACb,8BAAC,QAAG,WAAU,eACX,6BAAmB,IAAI,CAAC,SACvB;AAAA,MAAC;AAAA;AAAA,QAEC,WAAU;AAAA,QAEV;AAAA,8BAAC,SAAM,WAAU,yDAAwD;AAAA,UACzE,oBAAC,UAAM,gBAAK;AAAA;AAAA;AAAA,MAJP;AAAA,IAKP,CACD,GACH,GACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACb,+BAAC,SAAI,WAAU,2LACb;AAAA,0BAAC,QAAK,WAAU,eAAc;AAAA,MAAE;AAAA,OAElC,GACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/billing/plans-tab.tsx"],"sourcesContent":["import { Button } from '@withpotter/ui-kit/button';\nimport { Badge } from '@withpotter/ui-kit/badge';\nimport {\n Check,\n Globe,\n MessageSquare,\n ShoppingCart,\n BarChart3,\n Loader2,\n Lock,\n} from 'lucide-react';\nimport { useFormatCurrency } from '@withpotter/hooks/use-format-currency';\nimport type { BillingPlan, Subscription } from './helpers';\n\n// ── Plus tier — included features ──────────────────────────────────────\nconst PLUS_FEATURES = [\n {\n icon: ShoppingCart,\n text: 'All modules — orders, bookings, inventory, subscriptions',\n },\n { icon: Globe, text: 'Custom domain support' },\n { icon: MessageSquare, text: '200 transactional emails / month included' },\n { icon: BarChart3, text: 'Full analytics and reporting' },\n];\n\n// ── Pro Plus tier — value-added services preview ───────────────────────\nconst PRO_PLUS_FEATURES = [\n 'User-generated content (UGC) campaigns produced for you',\n 'Distribution support across owned and paid channels',\n 'Branding & business-strategy office hours',\n];\n\n// ── Super Pro tier — premium / agency-grade services ───────────────────\nconst SUPER_PRO_FEATURES = [\n 'Full brand system — identity, kit, guidelines',\n 'In-person terminals & POS integration',\n 'Priority human support and account management',\n];\n\ninterface PlansTabProps {\n /** Region-resolved Plus plan returned by /billing/plans/active. */\n plusPlan: BillingPlan | undefined;\n subscription: Subscription | null | undefined;\n onSubscribe: () => void;\n subscribePending: boolean;\n}\n\nexport function PlansTab({\n plusPlan,\n subscription,\n onSubscribe,\n subscribePending,\n}: PlansTabProps) {\n return (\n <div className=\"space-y-6\">\n {/* Promise banner */}\n <div className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-subtle)] px-4 py-3 text-sm text-[var(--text-secondary)]\">\n <span className=\"font-medium text-[var(--text-primary)]\">\n Cheaper than IG ads that might not convert.\n </span>{' '}\n Pay for the platform, keep what you make.\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <PlusCard\n plan={plusPlan}\n subscription={subscription}\n onSubscribe={onSubscribe}\n subscribePending={subscribePending}\n />\n <ProPlusCard plusPlan={plusPlan} />\n <SuperProCard />\n </div>\n\n <p className=\"text-xs text-[var(--text-tertiary)] text-center\">\n Prices billed monthly per workspace · Cancel any time.\n </p>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Plus — today's live tier\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction PlusCard({\n plan,\n subscription,\n onSubscribe,\n subscribePending,\n}: {\n plan: BillingPlan | undefined;\n subscription: Subscription | null | undefined;\n onSubscribe: () => void;\n subscribePending: boolean;\n}) {\n const formatCurrency = useFormatCurrency();\n\n const isActive = subscription && !['cancelled'].includes(subscription.status);\n const rawName = plan?.name ?? 'Plus';\n // Plan names sometimes encode region (e.g. \"Plus — Nigeria\"). We always\n // bill in USD list price and convert at checkout, so strip the region\n // suffix to avoid showing \"Plus — Nigeria\" next to a $ amount.\n const displayName = rawName.split('—')[0]?.trim() || rawName;\n const displayInterval = plan?.interval ?? 'month';\n const displayAmount = plan?.prices?.['USD'];\n\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--accent-primary)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n {displayName}\n </h2>\n {isActive ? (\n <Badge variant=\"success\">Active</Badge>\n ) : (\n <Badge variant=\"info\">Available now</Badge>\n )}\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n Everything you need to run your business on Box.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-3xl font-bold text-[var(--text-primary)]\">\n {displayAmount !== undefined ? formatCurrency(displayAmount, 'USD') : '—'}\n </span>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n / {displayInterval}\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {PLUS_FEATURES.map(({ text }) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--success)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n {isActive ? (\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)]\">\n Workspace is active\n </div>\n ) : (\n <Button\n className=\"w-full\"\n onClick={onSubscribe}\n disabled={subscribePending || !plan}\n >\n {subscribePending ? (\n <Loader2 className=\"w-4 h-4 animate-spin mr-2\" />\n ) : null}\n Activate workspace\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Pro Plus — Plus + value-added services (coming soon)\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction ProPlusCard({ plusPlan }: { plusPlan: BillingPlan | undefined }) {\n const formatCurrency = useFormatCurrency();\n const plusAmount = plusPlan?.prices?.['USD'];\n\n // Pro Plus is roughly +$5 / month on top of Plus (USD list price; currency\n // conversion happens at checkout).\n const upgradeUsd = 5;\n const proPlusAmount =\n plusAmount === undefined ? undefined : plusAmount + upgradeUsd;\n\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n Pro Plus\n </h2>\n <Badge variant=\"secondary\">Coming soon</Badge>\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n Plus, plus our team helping you grow.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-3xl font-bold text-[var(--text-primary)]\">\n {proPlusAmount !== undefined ? formatCurrency(proPlusAmount, 'USD') : '+$5'}\n </span>\n <span className=\"text-xs text-[var(--text-tertiary)]\">\n {proPlusAmount !== undefined ? '/ month' : '/ month over Plus'}\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {PRO_PLUS_FEATURES.map((text) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--text-muted)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)] flex items-center justify-center gap-2\">\n <Lock className=\"w-3.5 h-3.5\" />\n Coming soon\n </div>\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Super Pro — branding, terminals, advanced (coming soon)\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction SuperProCard() {\n return (\n <div className=\"bg-[var(--bg-base)] rounded-md border border-[var(--border-default)] overflow-hidden flex flex-col\">\n <div className=\"px-5 pt-5 pb-4 border-b border-[var(--border-subtle)]\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n Super Pro\n </h2>\n <Badge variant=\"secondary\">Coming soon</Badge>\n </div>\n <p className=\"text-xs text-[var(--text-tertiary)] mt-1\">\n For brands operating at scale.\n </p>\n\n <div className=\"mt-4 flex items-baseline gap-1\">\n <span className=\"text-3xl font-bold text-[var(--text-primary)]\">\n Custom\n </span>\n </div>\n </div>\n\n <div className=\"px-5 py-4 flex-1\">\n <ul className=\"space-y-2.5\">\n {SUPER_PRO_FEATURES.map((text) => (\n <li\n key={text}\n className=\"flex items-start gap-2 text-sm text-[var(--text-secondary)]\"\n >\n <Check className=\"w-4 h-4 flex-shrink-0 text-[var(--text-muted)] mt-0.5\" />\n <span>{text}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <div className=\"px-5 pb-5\">\n <div className=\"w-full py-2.5 rounded-md bg-[var(--bg-subtle)] border border-[var(--border-default)] text-center text-sm font-medium text-[var(--text-tertiary)] flex items-center justify-center gap-2\">\n <Lock className=\"w-3.5 h-3.5\" />\n Coming soon\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -28,4 +28,4 @@ export { TimeSeriesChart } from './time-series-chart';
28
28
  export { SeriesBreakdown } from './series-breakdown';
29
29
  export type { SeriesBreakdownProps, BreakdownSeries, BreakdownPoint, } from './series-breakdown';
30
30
  export { EmptyState as ChartEmptyState } from './chart-empty-state';
31
- export { useChartTheme, CHART_GRID, CHART_TICK, CHART_TICK_STYLE, CHART_TOOLTIP, } from './chart-theme';
31
+ export { CHART_GRID, CHART_TICK, CHART_TICK_STYLE, CHART_TOOLTIP, } from './chart-theme';
@@ -7,7 +7,6 @@ import { TimeSeriesChart } from "./time-series-chart";
7
7
  import { SeriesBreakdown } from "./series-breakdown";
8
8
  import { EmptyState } from "./chart-empty-state";
9
9
  import {
10
- useChartTheme,
11
10
  CHART_GRID,
12
11
  CHART_TICK,
13
12
  CHART_TICK_STYLE,
@@ -25,7 +24,6 @@ export {
25
24
  DonutChart,
26
25
  RevenueSpineChart,
27
26
  SeriesBreakdown,
28
- TimeSeriesChart,
29
- useChartTheme
27
+ TimeSeriesChart
30
28
  };
31
29
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/charts/index.ts"],"sourcesContent":["/**\n * Every chart in the app, in one importable place.\n *\n * This folder held four finished chart components — `AreaMetricCard`,\n * `DonutChart`, `AreaChart`, `BarChart` — that **nothing had ever imported**,\n * while `components/dashboard/home/` reached sideways into\n * `app/dashboard/analytics/components/` for the one chart it needed. The\n * shared folder was invisible; a route folder was the de facto shared one.\n *\n * That is the same failure the design system had: the components were fine,\n * nobody could find them, so every new surface drew its own. A barrel is the\n * cheapest fix for it — before writing a chart, look here.\n *\n * The charts that were stranded in the analytics route now live here too, with\n * their theme, their empty state and their formatters pulled out to shared\n * modules so nothing in this folder points back at a route.\n */\n\nexport { AreaChart } from './area-chart';\nexport type { ChartPoint } from './area-chart';\nexport { AreaMetricCard } from './area-metric-card';\nexport type { MetricRange } from './area-metric-card';\nexport { BarChart } from './bar-chart';\nexport { DonutChart } from './donut-chart';\nexport type { DonutSlice } from './donut-chart';\nexport { RevenueSpineChart } from './revenue-spine-chart';\nexport { TimeSeriesChart } from './time-series-chart';\n/** A measure split by a dimension: chart and breakdown as one object. */\nexport { SeriesBreakdown } from './series-breakdown';\nexport type {\n SeriesBreakdownProps,\n BreakdownSeries,\n BreakdownPoint,\n} from './series-breakdown';\nexport { EmptyState as ChartEmptyState } from './chart-empty-state';\nexport {\n useChartTheme,\n CHART_GRID,\n CHART_TICK,\n CHART_TICK_STYLE,\n CHART_TOOLTIP,\n} from './chart-theme';\n"],"mappings":"AAkBA,SAAS,iBAAiB;AAE1B,SAAS,sBAAsB;AAE/B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAE3B,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAEhC,SAAS,uBAAuB;AAMhC,SAAuB,kBAAuB;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../../src/charts/index.ts"],"sourcesContent":["/**\n * Every chart in the app, in one importable place.\n *\n * This folder held four finished chart components — `AreaMetricCard`,\n * `DonutChart`, `AreaChart`, `BarChart` — that **nothing had ever imported**,\n * while `components/dashboard/home/` reached sideways into\n * `app/dashboard/analytics/components/` for the one chart it needed. The\n * shared folder was invisible; a route folder was the de facto shared one.\n *\n * That is the same failure the design system had: the components were fine,\n * nobody could find them, so every new surface drew its own. A barrel is the\n * cheapest fix for it — before writing a chart, look here.\n *\n * The charts that were stranded in the analytics route now live here too, with\n * their theme, their empty state and their formatters pulled out to shared\n * modules so nothing in this folder points back at a route.\n */\n\nexport { AreaChart } from './area-chart';\nexport type { ChartPoint } from './area-chart';\nexport { AreaMetricCard } from './area-metric-card';\nexport type { MetricRange } from './area-metric-card';\nexport { BarChart } from './bar-chart';\nexport { DonutChart } from './donut-chart';\nexport type { DonutSlice } from './donut-chart';\nexport { RevenueSpineChart } from './revenue-spine-chart';\nexport { TimeSeriesChart } from './time-series-chart';\n/** A measure split by a dimension: chart and breakdown as one object. */\nexport { SeriesBreakdown } from './series-breakdown';\nexport type {\n SeriesBreakdownProps,\n BreakdownSeries,\n BreakdownPoint,\n} from './series-breakdown';\nexport { EmptyState as ChartEmptyState } from './chart-empty-state';\nexport {\n CHART_GRID,\n CHART_TICK,\n CHART_TICK_STYLE,\n CHART_TOOLTIP,\n} from './chart-theme';\n"],"mappings":"AAkBA,SAAS,iBAAiB;AAE1B,SAAS,sBAAsB;AAE/B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAE3B,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAEhC,SAAS,uBAAuB;AAMhC,SAAuB,kBAAuB;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -17,7 +17,5 @@ export interface ActivityItem {
17
17
  /** Retained for the mobile feed; the desktop timeline renders an icon. */
18
18
  emoji: string;
19
19
  href?: string;
20
- /** A placed order whose payment has not arrived; the Home card quiets it. */
21
- unpaid?: boolean;
22
20
  }
23
21
  export declare function buildActivityItems(recentContent: ContentItem[], recentOrders: Order[], packages: PackageType[], contentTypeSingular: string, primaryContentKey: string, recentMembers: Member[], recentSubscriptions: CustomerSubscription[] | undefined, inventoryProducts: InventoryProduct[] | undefined, recentInvoices: Invoice[] | undefined, formatCurrency: (amount: number, currencyOverride?: string) => string, industryKey?: string | null): ActivityItem[];
@@ -1,4 +1,3 @@
1
- import { isOrderPaid } from "./activity-paid";
2
1
  import * as copy from "@withpotter/lib/timeline-copy";
3
2
  function formatRelativeTime(dateStr) {
4
3
  const now = Date.now();
@@ -65,13 +64,11 @@ function buildActivityItems(recentContent, recentOrders, packages, contentTypeSi
65
64
  customerName,
66
65
  order.orderNumber,
67
66
  formatCurrency(order.total),
68
- industryKey,
69
- isOrderPaid(order.paymentStatus)
67
+ industryKey
70
68
  );
71
69
  items.push({
72
70
  id: `order-${order.id}`,
73
71
  type: isCompleted ? "order_completed" : "order_placed",
74
- ...!isCompleted && !isOrderPaid(order.paymentStatus) ? { unpaid: true } : {},
75
72
  ...msg,
76
73
  timestamp: isCompleted ? order.completedAt || order.updatedAt : order.createdAt,
77
74
  href: `/dashboard/orders/${order.id}`
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dashboard/activity-feed.tsx"],"sourcesContent":["import type {\n Order,\n Package as PackageType,\n ContentItem,\n Member,\n CustomerSubscription,\n InventoryProduct,\n Invoice,\n} from '@withpotter/lib/api/types';\nimport { isOrderPaid } from './activity-paid';\nimport * as copy from '@withpotter/lib/timeline-copy';\n\n// ─── Types ──────────────────────────────────────────────────────────\n\n/**\n * A single thing that happened to the business. Built here, rendered by\n * `home/home-timeline.tsx`.\n *\n * The React component that used to live in this file is gone: the home merged\n * its feed with \"Next up\" and \"Needs you\" into one timeline, so the events and\n * the open work read as a single list with now as the pivot. What survives is\n * the builder, which is the part several surfaces actually share.\n */\nexport interface ActivityItem {\n id: string;\n type:\n | 'product_added'\n | 'product_updated'\n | 'order_placed'\n | 'order_completed'\n | 'member_signup'\n | 'member_converted'\n | 'subscription_created'\n | 'invoice_paid'\n | 'invoice_sent';\n title: string;\n description: string;\n timestamp: string;\n /** Retained for the mobile feed; the desktop timeline renders an icon. */\n emoji: string;\n href?: string;\n /** A placed order whose payment has not arrived; the Home card quiets it. */\n unpaid?: boolean;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction formatRelativeTime(dateStr: string): string {\n const now = Date.now();\n const date = new Date(dateStr).getTime();\n const diffMs = now - date;\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHr = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHr / 24);\n\n if (diffSec < 60) return 'Just now';\n if (diffMin < 60) return `${diffMin}m ago`;\n if (diffHr < 24) return `${diffHr}h ago`;\n if (diffDay < 7) return `${diffDay}d ago`;\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function buildActivityItems(\n recentContent: ContentItem[],\n recentOrders: Order[],\n packages: PackageType[],\n contentTypeSingular: string,\n primaryContentKey: string,\n recentMembers: Member[],\n recentSubscriptions: CustomerSubscription[] = [],\n inventoryProducts: InventoryProduct[] = [],\n recentInvoices: Invoice[] = [],\n formatCurrency: (amount: number, currencyOverride?: string) => string,\n industryKey?: string | null,\n): ActivityItem[] {\n const items: ActivityItem[] = [];\n\n // Product / content activity\n for (const item of recentContent) {\n const isUpdate = item.updatedAt !== item.createdAt;\n const msg = isUpdate\n ? copy.productUpdated(item.title, contentTypeSingular)\n : copy.productAdded(item.title, contentTypeSingular);\n items.push({\n id: `content-${item.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: item.updatedAt || item.createdAt,\n href: `/dashboard/content/${primaryContentKey}/${item.id}/edit`,\n });\n }\n\n // Inventory product activity (deduplicate against content items by title)\n const contentTitles = new Set(recentContent.map((c) => c.title));\n for (const prod of inventoryProducts) {\n if (contentTitles.has(prod.title)) continue;\n const created = prod.createdAt ? String(prod.createdAt) : '';\n const updated = prod.updatedAt ? String(prod.updatedAt) : created;\n const isUpdate = updated !== created;\n const msg = isUpdate\n ? copy.productUpdated(prod.title, contentTypeSingular)\n : copy.productAdded(prod.title, contentTypeSingular);\n items.push({\n id: `inv-${prod.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: updated || created,\n href: `/dashboard/catalog/${prod.id}`,\n });\n }\n\n // Package activity (only if no content items — avoids duplicates)\n if (recentContent.length === 0 && inventoryProducts.length === 0) {\n for (const pkg of packages) {\n const isUpdate = pkg.updatedAt !== pkg.createdAt;\n const msg = isUpdate\n ? copy.productUpdated(pkg.title, contentTypeSingular)\n : copy.productAdded(pkg.title, contentTypeSingular);\n items.push({\n id: `pkg-${pkg.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: pkg.updatedAt || pkg.createdAt,\n href: `/dashboard/content/${primaryContentKey}/${pkg.documentId}/edit`,\n });\n }\n }\n\n // Order activity\n for (const order of recentOrders) {\n const isCompleted = order.status === 'completed';\n const customerName =\n order.customerName || order.customerEmail?.split('@')[0] || 'Someone';\n const msg = isCompleted\n ? copy.orderCompleted(customerName, order.orderNumber)\n : copy.orderPlaced(\n customerName,\n order.orderNumber,\n formatCurrency(order.total),\n industryKey,\n isOrderPaid(order.paymentStatus),\n );\n items.push({\n id: `order-${order.id}`,\n type: isCompleted ? 'order_completed' : 'order_placed',\n ...(!isCompleted && !isOrderPaid(order.paymentStatus)\n ? { unpaid: true }\n : {}),\n ...msg,\n timestamp: isCompleted\n ? order.completedAt || order.updatedAt\n : order.createdAt,\n href: `/dashboard/orders/${order.id}`,\n });\n }\n\n // Member signup / conversion activity\n for (const member of recentMembers) {\n const isConverted = !!member.convertedAt;\n const name = member.name || member.email?.split('@')[0] || 'A visitor';\n const msg = isConverted\n ? copy.memberConverted(name, member.email)\n : copy.memberSignup(name, member.email, industryKey);\n items.push({\n id: `member-${member.id}`,\n type: isConverted ? 'member_converted' : 'member_signup',\n ...msg,\n timestamp: isConverted ? member.convertedAt! : member.createdAt,\n href: `/dashboard/customers/${member.id}`,\n });\n }\n\n // Subscription activity\n for (const sub of recentSubscriptions) {\n const name = sub.customerEmail?.split('@')[0] || 'Someone';\n const msg = copy.subscriptionCreated(\n name,\n sub.plan?.name || 'a plan',\n formatCurrency(sub.amount / 100),\n );\n items.push({\n id: `sub-${sub.id}`,\n type: 'subscription_created',\n ...msg,\n timestamp: sub.createdAt,\n href: '/dashboard/subscriptions',\n });\n }\n\n // Invoice activity\n for (const inv of recentInvoices) {\n const customerName =\n inv.customerName || inv.customerEmail?.split('@')[0] || 'Someone';\n const isPaid = inv.status === 'paid' && !!inv.paidAt;\n const isSent = inv.status === 'sent' && !!inv.sentAt;\n if (isPaid) {\n const msg = copy.invoicePaid(\n customerName,\n inv.invoiceNumber,\n formatCurrency(inv.amountPaid || inv.total, inv.currency),\n );\n items.push({\n id: `inv-paid-${inv.id}`,\n type: 'invoice_paid',\n ...msg,\n timestamp: inv.paidAt!,\n href: `/dashboard/invoices/${inv.id}`,\n });\n } else if (isSent) {\n const msg = copy.invoiceSent(\n customerName,\n inv.invoiceNumber,\n formatCurrency(inv.total, inv.currency),\n );\n items.push({\n id: `inv-sent-${inv.id}`,\n type: 'invoice_sent',\n ...msg,\n timestamp: inv.sentAt!,\n href: `/dashboard/invoices/${inv.id}`,\n });\n }\n }\n\n // Sort by timestamp descending\n items.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n );\n return items;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/dashboard/activity-feed.tsx"],"sourcesContent":["import type {\n Order,\n Package as PackageType,\n ContentItem,\n Member,\n CustomerSubscription,\n InventoryProduct,\n Invoice,\n} from '@withpotter/lib/api/types';\nimport * as copy from '@withpotter/lib/timeline-copy';\n\n// ─── Types ──────────────────────────────────────────────────────────\n\n/**\n * A single thing that happened to the business. Built here, rendered by\n * `home/home-timeline.tsx`.\n *\n * The React component that used to live in this file is gone: the home merged\n * its feed with \"Next up\" and \"Needs you\" into one timeline, so the events and\n * the open work read as a single list with now as the pivot. What survives is\n * the builder, which is the part several surfaces actually share.\n */\nexport interface ActivityItem {\n id: string;\n type:\n | 'product_added'\n | 'product_updated'\n | 'order_placed'\n | 'order_completed'\n | 'member_signup'\n | 'member_converted'\n | 'subscription_created'\n | 'invoice_paid'\n | 'invoice_sent';\n title: string;\n description: string;\n timestamp: string;\n /** Retained for the mobile feed; the desktop timeline renders an icon. */\n emoji: string;\n href?: string;\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction formatRelativeTime(dateStr: string): string {\n const now = Date.now();\n const date = new Date(dateStr).getTime();\n const diffMs = now - date;\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHr = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHr / 24);\n\n if (diffSec < 60) return 'Just now';\n if (diffMin < 60) return `${diffMin}m ago`;\n if (diffHr < 24) return `${diffHr}h ago`;\n if (diffDay < 7) return `${diffDay}d ago`;\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function buildActivityItems(\n recentContent: ContentItem[],\n recentOrders: Order[],\n packages: PackageType[],\n contentTypeSingular: string,\n primaryContentKey: string,\n recentMembers: Member[],\n recentSubscriptions: CustomerSubscription[] = [],\n inventoryProducts: InventoryProduct[] = [],\n recentInvoices: Invoice[] = [],\n formatCurrency: (amount: number, currencyOverride?: string) => string,\n industryKey?: string | null,\n): ActivityItem[] {\n const items: ActivityItem[] = [];\n\n // Product / content activity\n for (const item of recentContent) {\n const isUpdate = item.updatedAt !== item.createdAt;\n const msg = isUpdate\n ? copy.productUpdated(item.title, contentTypeSingular)\n : copy.productAdded(item.title, contentTypeSingular);\n items.push({\n id: `content-${item.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: item.updatedAt || item.createdAt,\n href: `/dashboard/content/${primaryContentKey}/${item.id}/edit`,\n });\n }\n\n // Inventory product activity (deduplicate against content items by title)\n const contentTitles = new Set(recentContent.map((c) => c.title));\n for (const prod of inventoryProducts) {\n if (contentTitles.has(prod.title)) continue;\n const created = prod.createdAt ? String(prod.createdAt) : '';\n const updated = prod.updatedAt ? String(prod.updatedAt) : created;\n const isUpdate = updated !== created;\n const msg = isUpdate\n ? copy.productUpdated(prod.title, contentTypeSingular)\n : copy.productAdded(prod.title, contentTypeSingular);\n items.push({\n id: `inv-${prod.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: updated || created,\n href: `/dashboard/catalog/${prod.id}`,\n });\n }\n\n // Package activity (only if no content items — avoids duplicates)\n if (recentContent.length === 0 && inventoryProducts.length === 0) {\n for (const pkg of packages) {\n const isUpdate = pkg.updatedAt !== pkg.createdAt;\n const msg = isUpdate\n ? copy.productUpdated(pkg.title, contentTypeSingular)\n : copy.productAdded(pkg.title, contentTypeSingular);\n items.push({\n id: `pkg-${pkg.id}`,\n type: isUpdate ? 'product_updated' : 'product_added',\n ...msg,\n timestamp: pkg.updatedAt || pkg.createdAt,\n href: `/dashboard/content/${primaryContentKey}/${pkg.documentId}/edit`,\n });\n }\n }\n\n // Order activity\n for (const order of recentOrders) {\n const isCompleted = order.status === 'completed';\n const customerName =\n order.customerName || order.customerEmail?.split('@')[0] || 'Someone';\n const msg = isCompleted\n ? copy.orderCompleted(customerName, order.orderNumber)\n : copy.orderPlaced(\n customerName,\n order.orderNumber,\n formatCurrency(order.total),\n industryKey,\n );\n items.push({\n id: `order-${order.id}`,\n type: isCompleted ? 'order_completed' : 'order_placed',\n ...msg,\n timestamp: isCompleted\n ? order.completedAt || order.updatedAt\n : order.createdAt,\n href: `/dashboard/orders/${order.id}`,\n });\n }\n\n // Member signup / conversion activity\n for (const member of recentMembers) {\n const isConverted = !!member.convertedAt;\n const name = member.name || member.email?.split('@')[0] || 'A visitor';\n const msg = isConverted\n ? copy.memberConverted(name, member.email)\n : copy.memberSignup(name, member.email, industryKey);\n items.push({\n id: `member-${member.id}`,\n type: isConverted ? 'member_converted' : 'member_signup',\n ...msg,\n timestamp: isConverted ? member.convertedAt! : member.createdAt,\n href: `/dashboard/customers/${member.id}`,\n });\n }\n\n // Subscription activity\n for (const sub of recentSubscriptions) {\n const name = sub.customerEmail?.split('@')[0] || 'Someone';\n const msg = copy.subscriptionCreated(\n name,\n sub.plan?.name || 'a plan',\n formatCurrency(sub.amount / 100),\n );\n items.push({\n id: `sub-${sub.id}`,\n type: 'subscription_created',\n ...msg,\n timestamp: sub.createdAt,\n href: '/dashboard/subscriptions',\n });\n }\n\n // Invoice activity\n for (const inv of recentInvoices) {\n const customerName =\n inv.customerName || inv.customerEmail?.split('@')[0] || 'Someone';\n const isPaid = inv.status === 'paid' && !!inv.paidAt;\n const isSent = inv.status === 'sent' && !!inv.sentAt;\n if (isPaid) {\n const msg = copy.invoicePaid(\n customerName,\n inv.invoiceNumber,\n formatCurrency(inv.amountPaid || inv.total, inv.currency),\n );\n items.push({\n id: `inv-paid-${inv.id}`,\n type: 'invoice_paid',\n ...msg,\n timestamp: inv.paidAt!,\n href: `/dashboard/invoices/${inv.id}`,\n });\n } else if (isSent) {\n const msg = copy.invoiceSent(\n customerName,\n inv.invoiceNumber,\n formatCurrency(inv.total, inv.currency),\n );\n items.push({\n id: `inv-sent-${inv.id}`,\n type: 'invoice_sent',\n ...msg,\n timestamp: inv.sentAt!,\n href: `/dashboard/invoices/${inv.id}`,\n });\n }\n }\n\n // Sort by timestamp descending\n items.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n );\n return items;\n}\n"],"mappings":"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","names":[]}
@@ -28,7 +28,6 @@ function AskControls({
28
28
  },
29
29
  disabled: voice.disabled && !voice.active,
30
30
  "aria-label": voice.active ? voiceLabels.stop : voiceLabels.start,
31
- title: voice.active ? voiceLabels.stop : voiceLabels.start,
32
31
  "aria-pressed": voice.active,
33
32
  className: `grid size-8 shrink-0 place-items-center rounded-lg border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${voice.active ? "border-transparent bg-[var(--error)] text-white" : "border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] text-[var(--text-tertiary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]"}`,
34
33
  children: voice.active ? /* @__PURE__ */ jsx(Square, { className: "h-[9px] w-[9px] fill-current" }) : /* @__PURE__ */ jsx(Mic, {})
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/dashboard/home/ask-controls.tsx"],"sourcesContent":["'use client';\n\nimport { ArrowUp, Mic, Square, X } from 'lucide-react';\n\n/**\n * The three controls at the right end of the ask bar.\n *\n * Lifted out of `home-ask` for room, and they belong together anyway: they are\n * the only things in that bar that are pressed rather than typed into, they\n * share one frame and one size, and the rules about which of them is showing\n * are rules about the same cluster.\n */\nexport function AskControls({\n voice,\n value,\n canSend,\n engaged,\n inputRef,\n onSubmit,\n onClose,\n send,\n voiceLabels = { start: 'Talk to Box', stop: 'Stop voice mode' },\n}: {\n voice: {\n supported: boolean;\n active: boolean;\n start: () => Promise<void> | void;\n stop: () => void;\n /** Pressing it does nothing, e.g. a clip is already waiting to be sent. */\n disabled?: boolean;\n };\n /** Replaces the default round send button. */\n send?: React.ReactNode;\n /** What the mic says it will do, for the person using a screen reader. */\n voiceLabels?: { start: string; stop: string };\n /** There is a sentence or an attachment to send. */\n canSend: boolean;\n value: string;\n /** Anything is open under the bar: suggestions, results or a conversation. */\n engaged: boolean;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n onSubmit: (q: string) => void;\n onClose: () => void;\n}) {\n return (\n <>\n {/* Hidden where the browser has no recogniser, rather than shown and\n dead. The icon becomes a stop square so the control says what\n pressing it will do — and in voice mode that is a bigger claim than\n it was for dictation, because pressing it ends a conversation rather\n than closing a microphone. */}\n {voice.supported && (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // Both branches play their sound on the PRESS rather than on the\n // recogniser being ready — `getUserMedia` can sit behind a\n // permission prompt, and a confirmation that arrives late\n // confirms nothing. Owned by the hook so every entrance to voice\n // mode sounds the same, including the automatic one at the end of\n // Box's reply, which has no button to hang a sound on.\n if (voice.active) {\n voice.stop();\n } else {\n inputRef.current?.focus();\n void voice.start();\n }\n }}\n disabled={voice.disabled && !voice.active}\n aria-label={voice.active ? voiceLabels.stop : voiceLabels.start}\n title={voice.active ? voiceLabels.stop : voiceLabels.start}\n aria-pressed={voice.active}\n // FRAMED, like Send. Two adjacent controls where one sits in a\n // circle and the other floats bare read as two different KINDS of\n // control — one a button, one a decoration — when they are both\n // buttons. Same frame, same size, different job.\n className={`grid size-8 shrink-0 place-items-center rounded-lg border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${\n voice.active\n ? 'border-transparent bg-[var(--error)] text-white'\n : 'border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] text-[var(--text-tertiary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]'\n }`}\n >\n {voice.active ? (\n <Square className=\"h-[9px] w-[9px] fill-current\" />\n ) : (\n <Mic />\n )}\n </button>\n )}\n\n {/* THE WAY BACK, on the bar, in every engaged state.\n\n Focusing hides the whole board — deliberately; the suggestions want\n the room. But the exits were Escape and a click on dead space, and\n once you typed a word the reward for losing the board was one grey\n line of \"Nothing matched\". John, twice: *\"i cant seem to close a\n chat\"*, then *\"you cant close it when you start prompting\"*. The\n first answer was an X above the thread, which exists only once a\n thread does.\n\n The bar is the one element present in all four engaged states, and\n a control that ends something must not be rendered by the thing it\n ends. */}\n {engaged && (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={onClose}\n aria-label=\"Close\"\n /* RED ON HOVER, like every other control in this product that undoes\n something. The board's own remove control already answers a hover\n with `--error-bg` and `--error`; this one answered with the same\n neutral wash as the mic beside it, so the one control in the row\n that ENDS something looked exactly like the two that start\n something. A hover state is where a control says what it is about\n to do, and this one was declining to. */\n className=\"grid size-8 shrink-0 place-items-center rounded-lg bg-[var(--bg-muted)] text-[var(--text-tertiary)] transition-[background-color,color] duration-[var(--motion-fast)] hover:bg-[var(--error-bg)] hover:text-[var(--error)]\"\n >\n <X />\n </button>\n )}\n\n {send ?? (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onSubmit(value)}\n aria-label=\"Send\"\n disabled={!canSend}\n // LIVE means BLUE. It was muted in both states, so the one control\n // that commits looked equally dead whether or not it would do\n // anything — the single most useful thing a send button can say is\n // \"there is something to send now\", and colour is how it says it.\n className={`grid size-8 shrink-0 place-items-center rounded-full transition-[background-color,color] duration-[var(--motion-fast)] ${\n canSend\n ? 'bg-brand-500 text-white hover:bg-brand-600'\n : 'bg-[var(--bg-muted)] text-[var(--text-muted)]'\n }`}\n >\n <ArrowUp />\n </button>\n )}\n </>\n );\n}\n"],"mappings":";AA6CI,mBAuCQ,KAvCR;AA3CJ,SAAS,SAAS,KAAK,QAAQ,SAAS;AAUjC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc,EAAE,OAAO,eAAe,MAAM,kBAAkB;AAChE,GAqBG;AACD,SACE,iCAMG;AAAA,UAAM,aACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM;AAOb,cAAI,MAAM,QAAQ;AAChB,kBAAM,KAAK;AAAA,UACb,OAAO;AACL,qBAAS,SAAS,MAAM;AACxB,iBAAK,MAAM,MAAM;AAAA,UACnB;AAAA,QACF;AAAA,QACA,UAAU,MAAM,YAAY,CAAC,MAAM;AAAA,QACnC,cAAY,MAAM,SAAS,YAAY,OAAO,YAAY;AAAA,QAC1D,OAAO,MAAM,SAAS,YAAY,OAAO,YAAY;AAAA,QACrD,gBAAc,MAAM;AAAA,QAKpB,WAAW,+HACT,MAAM,SACF,oDACA,wLACN;AAAA,QAEC,gBAAM,SACL,oBAAC,UAAO,WAAU,gCAA+B,IAEjD,oBAAC,OAAI;AAAA;AAAA,IAET;AAAA,IAgBD,WACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS;AAAA,QACT,cAAW;AAAA,QAQX,WAAU;AAAA,QAEV,8BAAC,KAAE;AAAA;AAAA,IACL;AAAA,IAGD,QACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,SAAS,KAAK;AAAA,QAC7B,cAAW;AAAA,QACX,UAAU,CAAC;AAAA,QAKX,WAAW,0HACT,UACI,+CACA,+CACN;AAAA,QAEA,8BAAC,WAAQ;AAAA;AAAA,IACX;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/dashboard/home/ask-controls.tsx"],"sourcesContent":["'use client';\n\nimport { ArrowUp, Mic, Square, X } from 'lucide-react';\n\n/**\n * The three controls at the right end of the ask bar.\n *\n * Lifted out of `home-ask` for room, and they belong together anyway: they are\n * the only things in that bar that are pressed rather than typed into, they\n * share one frame and one size, and the rules about which of them is showing\n * are rules about the same cluster.\n */\nexport function AskControls({\n voice,\n value,\n canSend,\n engaged,\n inputRef,\n onSubmit,\n onClose,\n send,\n voiceLabels = { start: 'Talk to Box', stop: 'Stop voice mode' },\n}: {\n voice: {\n supported: boolean;\n active: boolean;\n start: () => Promise<void> | void;\n stop: () => void;\n /** Pressing it does nothing, e.g. a clip is already waiting to be sent. */\n disabled?: boolean;\n };\n /** Replaces the default round send button. */\n send?: React.ReactNode;\n /** What the mic says it will do, for the person using a screen reader. */\n voiceLabels?: { start: string; stop: string };\n /** There is a sentence or an attachment to send. */\n canSend: boolean;\n value: string;\n /** Anything is open under the bar: suggestions, results or a conversation. */\n engaged: boolean;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n onSubmit: (q: string) => void;\n onClose: () => void;\n}) {\n return (\n <>\n {/* Hidden where the browser has no recogniser, rather than shown and\n dead. The icon becomes a stop square so the control says what\n pressing it will do — and in voice mode that is a bigger claim than\n it was for dictation, because pressing it ends a conversation rather\n than closing a microphone. */}\n {voice.supported && (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n // Both branches play their sound on the PRESS rather than on the\n // recogniser being ready — `getUserMedia` can sit behind a\n // permission prompt, and a confirmation that arrives late\n // confirms nothing. Owned by the hook so every entrance to voice\n // mode sounds the same, including the automatic one at the end of\n // Box's reply, which has no button to hang a sound on.\n if (voice.active) {\n voice.stop();\n } else {\n inputRef.current?.focus();\n void voice.start();\n }\n }}\n disabled={voice.disabled && !voice.active}\n aria-label={voice.active ? voiceLabels.stop : voiceLabels.start}\n aria-pressed={voice.active}\n // FRAMED, like Send. Two adjacent controls where one sits in a\n // circle and the other floats bare read as two different KINDS of\n // control — one a button, one a decoration — when they are both\n // buttons. Same frame, same size, different job.\n className={`grid size-8 shrink-0 place-items-center rounded-lg border transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${\n voice.active\n ? 'border-transparent bg-[var(--error)] text-white'\n : 'border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] text-[var(--text-tertiary)] hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]'\n }`}\n >\n {voice.active ? (\n <Square className=\"h-[9px] w-[9px] fill-current\" />\n ) : (\n <Mic />\n )}\n </button>\n )}\n\n {/* THE WAY BACK, on the bar, in every engaged state.\n\n Focusing hides the whole board — deliberately; the suggestions want\n the room. But the exits were Escape and a click on dead space, and\n once you typed a word the reward for losing the board was one grey\n line of \"Nothing matched\". John, twice: *\"i cant seem to close a\n chat\"*, then *\"you cant close it when you start prompting\"*. The\n first answer was an X above the thread, which exists only once a\n thread does.\n\n The bar is the one element present in all four engaged states, and\n a control that ends something must not be rendered by the thing it\n ends. */}\n {engaged && (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={onClose}\n aria-label=\"Close\"\n /* RED ON HOVER, like every other control in this product that undoes\n something. The board's own remove control already answers a hover\n with `--error-bg` and `--error`; this one answered with the same\n neutral wash as the mic beside it, so the one control in the row\n that ENDS something looked exactly like the two that start\n something. A hover state is where a control says what it is about\n to do, and this one was declining to. */\n className=\"grid size-8 shrink-0 place-items-center rounded-lg bg-[var(--bg-muted)] text-[var(--text-tertiary)] transition-[background-color,color] duration-[var(--motion-fast)] hover:bg-[var(--error-bg)] hover:text-[var(--error)]\"\n >\n <X />\n </button>\n )}\n\n {send ?? (\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onSubmit(value)}\n aria-label=\"Send\"\n disabled={!canSend}\n // LIVE means BLUE. It was muted in both states, so the one control\n // that commits looked equally dead whether or not it would do\n // anything — the single most useful thing a send button can say is\n // \"there is something to send now\", and colour is how it says it.\n className={`grid size-8 shrink-0 place-items-center rounded-full transition-[background-color,color] duration-[var(--motion-fast)] ${\n canSend\n ? 'bg-brand-500 text-white hover:bg-brand-600'\n : 'bg-[var(--bg-muted)] text-[var(--text-muted)]'\n }`}\n >\n <ArrowUp />\n </button>\n )}\n </>\n );\n}\n"],"mappings":";AA6CI,mBAsCQ,KAtCR;AA3CJ,SAAS,SAAS,KAAK,QAAQ,SAAS;AAUjC,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc,EAAE,OAAO,eAAe,MAAM,kBAAkB;AAChE,GAqBG;AACD,SACE,iCAMG;AAAA,UAAM,aACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM;AAOb,cAAI,MAAM,QAAQ;AAChB,kBAAM,KAAK;AAAA,UACb,OAAO;AACL,qBAAS,SAAS,MAAM;AACxB,iBAAK,MAAM,MAAM;AAAA,UACnB;AAAA,QACF;AAAA,QACA,UAAU,MAAM,YAAY,CAAC,MAAM;AAAA,QACnC,cAAY,MAAM,SAAS,YAAY,OAAO,YAAY;AAAA,QAC1D,gBAAc,MAAM;AAAA,QAKpB,WAAW,+HACT,MAAM,SACF,oDACA,wLACN;AAAA,QAEC,gBAAM,SACL,oBAAC,UAAO,WAAU,gCAA+B,IAEjD,oBAAC,OAAI;AAAA;AAAA,IAET;AAAA,IAgBD,WACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS;AAAA,QACT,cAAW;AAAA,QAQX,WAAU;AAAA,QAEV,8BAAC,KAAE;AAAA;AAAA,IACL;AAAA,IAGD,QACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,SAAS,MAAM,SAAS,KAAK;AAAA,QAC7B,cAAW;AAAA,QACX,UAAU,CAAC;AAAA,QAKX,WAAW,0HACT,UACI,+CACA,+CACN;AAAA,QAEA,8BAAC,WAAQ;AAAA;AAAA,IACX;AAAA,KAEJ;AAEJ;","names":[]}
@@ -3,8 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as Popover from "@radix-ui/react-popover";
4
4
  import { Check, ChevronDown, Headset, Search } from "lucide-react";
5
5
  const MODES = [
6
- { id: "search", label: "Search your business", Icon: Search },
7
- { id: "support", label: "Message support", Icon: Headset }
6
+ { id: "search", label: "Search", Icon: Search },
7
+ { id: "support", label: "Support", Icon: Headset }
8
8
  ];
9
9
  function AskModeChip({
10
10
  mode,
@@ -19,7 +19,7 @@ function AskModeChip({
19
19
  {
20
20
  type: "button",
21
21
  onMouseDown: (e) => e.preventDefault(),
22
- "aria-label": "Choose between searching your business and messaging support",
22
+ "aria-label": "Choose what this box does",
23
23
  title: locked ? "Remove the attachment to search again" : void 0,
24
24
  className: "flex h-[30px] shrink-0 items-center gap-1 rounded-lg border border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] px-2 text-xs text-[var(--text-primary)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--interactive-hover)] disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-[var(--bg-muted)]",
25
25
  children: [
@@ -40,7 +40,7 @@ function AskModeChip({
40
40
  {
41
41
  align: "start",
42
42
  sideOffset: 6,
43
- className: "surface-pinned z-[100] w-56 !p-1",
43
+ className: "surface-pinned z-[100] w-44 !p-1",
44
44
  children: MODES.map(({ id, label, Icon: ModeIcon }) => /* @__PURE__ */ jsx(Popover.Close, { asChild: true, children: /* @__PURE__ */ jsxs(
45
45
  "button",
46
46
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/dashboard/home/ask-mode-chip.tsx"],"sourcesContent":["'use client';\n\nimport * as Popover from '@radix-ui/react-popover';\nimport { Check, ChevronDown, Headset, Search } from 'lucide-react';\n\nexport type AskMode = 'search' | 'support';\n\nconst MODES = [\n { id: 'search', label: 'Search your business', Icon: Search },\n { id: 'support', label: 'Message support', Icon: Headset },\n] as const;\n\n/**\n * The picker beside the paperclip: what the box is for right now.\n *\n * It is the pill AI products put in their input to change model, with the\n * same job done honestly for this box: it does not choose a model, it chooses\n * where the words go. Search finds records and takes you to them; Support\n * opens a case with whatever is in the box.\n *\n * IT LOCKS TO SUPPORT while a voice note or a picture is on the card, because\n * neither can be searched for and neither can be sent anywhere else. A pill\n * that let you flip back to Search would silently park the attachment and\n * turn a message to support into a query with a picture stuck to it.\n */\nexport function AskModeChip({\n mode,\n onChange,\n locked,\n}: {\n mode: AskMode;\n onChange: (next: AskMode) => void;\n /** An attachment is on the card, so the mode cannot be changed. */\n locked?: boolean;\n}) {\n const selected = MODES.find((m) => m.id === mode) ?? MODES[0];\n const Icon = selected.Icon;\n return (\n <Popover.Root>\n <Popover.Trigger asChild disabled={locked}>\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n aria-label=\"Choose between searching your business and messaging support\"\n title={locked ? 'Remove the attachment to search again' : undefined}\n className=\"flex h-[30px] shrink-0 items-center gap-1 rounded-lg border border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] px-2 text-xs text-[var(--text-primary)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--interactive-hover)] disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-[var(--bg-muted)]\"\n >\n <Icon aria-hidden className=\"size-3.5 shrink-0 text-brand-500\" />\n {selected.label}\n {!locked && (\n <ChevronDown\n aria-hidden\n className=\"size-3.5 shrink-0 text-[var(--text-tertiary)]\"\n />\n )}\n </button>\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Content\n align=\"start\"\n sideOffset={6}\n className=\"surface-pinned z-[100] w-56 !p-1\"\n >\n {MODES.map(({ id, label, Icon: ModeIcon }) => (\n <Popover.Close asChild key={id}>\n <button\n type=\"button\"\n onClick={() => onChange(id)}\n className=\"flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-sm text-[var(--text-primary)] hover:bg-[var(--interactive-hover)]\"\n >\n <ModeIcon\n aria-hidden\n className=\"size-4 shrink-0 text-[var(--text-tertiary)]\"\n />\n <span className=\"flex-1\">{label}</span>\n {id === mode && <Check aria-hidden className=\"size-4\" />}\n </button>\n </Popover.Close>\n ))}\n </Popover.Content>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n"],"mappings":";AAwCQ,SAOE,KAPF;AAtCR,YAAY,aAAa;AACzB,SAAS,OAAO,aAAa,SAAS,cAAc;AAIpD,MAAM,QAAQ;AAAA,EACZ,EAAE,IAAI,UAAU,OAAO,wBAAwB,MAAM,OAAO;AAAA,EAC5D,EAAE,IAAI,WAAW,OAAO,mBAAmB,MAAM,QAAQ;AAC3D;AAeO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,WAAW,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,KAAK,MAAM,CAAC;AAC5D,QAAM,OAAO,SAAS;AACtB,SACE,qBAAC,QAAQ,MAAR,EACC;AAAA,wBAAC,QAAQ,SAAR,EAAgB,SAAO,MAAC,UAAU,QACjC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,cAAW;AAAA,QACX,OAAO,SAAS,0CAA0C;AAAA,QAC1D,WAAU;AAAA,QAEV;AAAA,8BAAC,QAAK,eAAW,MAAC,WAAU,oCAAmC;AAAA,UAC9D,SAAS;AAAA,UACT,CAAC,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,IAEJ,GACF;AAAA,IACA,oBAAC,QAAQ,QAAR,EACC;AAAA,MAAC,QAAQ;AAAA,MAAR;AAAA,QACC,OAAM;AAAA,QACN,YAAY;AAAA,QACZ,WAAU;AAAA,QAET,gBAAM,IAAI,CAAC,EAAE,IAAI,OAAO,MAAM,SAAS,MACtC,oBAAC,QAAQ,OAAR,EAAc,SAAO,MACpB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,SAAS,EAAE;AAAA,YAC1B,WAAU;AAAA,YAEV;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAW;AAAA,kBACX,WAAU;AAAA;AAAA,cACZ;AAAA,cACA,oBAAC,UAAK,WAAU,UAAU,iBAAM;AAAA,cAC/B,OAAO,QAAQ,oBAAC,SAAM,eAAW,MAAC,WAAU,UAAS;AAAA;AAAA;AAAA,QACxD,KAZ0B,EAa5B,CACD;AAAA;AAAA,IACH,GACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/dashboard/home/ask-mode-chip.tsx"],"sourcesContent":["'use client';\n\nimport * as Popover from '@radix-ui/react-popover';\nimport { Check, ChevronDown, Headset, Search } from 'lucide-react';\n\nexport type AskMode = 'search' | 'support';\n\nconst MODES = [\n { id: 'search', label: 'Search', Icon: Search },\n { id: 'support', label: 'Support', Icon: Headset },\n] as const;\n\n/**\n * The picker beside the paperclip: what the box is for right now.\n *\n * It is the pill AI products put in their input to change model, with the\n * same job done honestly for this box: it does not choose a model, it chooses\n * where the words go. Search finds records and takes you to them; Support\n * opens a case with whatever is in the box.\n *\n * IT LOCKS TO SUPPORT while a voice note or a picture is on the card, because\n * neither can be searched for and neither can be sent anywhere else. A pill\n * that let you flip back to Search would silently park the attachment and\n * turn a message to support into a query with a picture stuck to it.\n */\nexport function AskModeChip({\n mode,\n onChange,\n locked,\n}: {\n mode: AskMode;\n onChange: (next: AskMode) => void;\n /** An attachment is on the card, so the mode cannot be changed. */\n locked?: boolean;\n}) {\n const selected = MODES.find((m) => m.id === mode) ?? MODES[0];\n const Icon = selected.Icon;\n return (\n <Popover.Root>\n <Popover.Trigger asChild disabled={locked}>\n <button\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n aria-label=\"Choose what this box does\"\n title={locked ? 'Remove the attachment to search again' : undefined}\n className=\"flex h-[30px] shrink-0 items-center gap-1 rounded-lg border border-[color-mix(in_srgb,var(--border-subtle)_55%,transparent)] bg-[var(--bg-muted)] px-2 text-xs text-[var(--text-primary)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--interactive-hover)] disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-[var(--bg-muted)]\"\n >\n <Icon aria-hidden className=\"size-3.5 shrink-0 text-brand-500\" />\n {selected.label}\n {!locked && (\n <ChevronDown\n aria-hidden\n className=\"size-3.5 shrink-0 text-[var(--text-tertiary)]\"\n />\n )}\n </button>\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Content\n align=\"start\"\n sideOffset={6}\n className=\"surface-pinned z-[100] w-44 !p-1\"\n >\n {MODES.map(({ id, label, Icon: ModeIcon }) => (\n <Popover.Close asChild key={id}>\n <button\n type=\"button\"\n onClick={() => onChange(id)}\n className=\"flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-sm text-[var(--text-primary)] hover:bg-[var(--interactive-hover)]\"\n >\n <ModeIcon\n aria-hidden\n className=\"size-4 shrink-0 text-[var(--text-tertiary)]\"\n />\n <span className=\"flex-1\">{label}</span>\n {id === mode && <Check aria-hidden className=\"size-4\" />}\n </button>\n </Popover.Close>\n ))}\n </Popover.Content>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n"],"mappings":";AAwCQ,SAOE,KAPF;AAtCR,YAAY,aAAa;AACzB,SAAS,OAAO,aAAa,SAAS,cAAc;AAIpD,MAAM,QAAQ;AAAA,EACZ,EAAE,IAAI,UAAU,OAAO,UAAU,MAAM,OAAO;AAAA,EAC9C,EAAE,IAAI,WAAW,OAAO,WAAW,MAAM,QAAQ;AACnD;AAeO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,WAAW,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,KAAK,MAAM,CAAC;AAC5D,QAAM,OAAO,SAAS;AACtB,SACE,qBAAC,QAAQ,MAAR,EACC;AAAA,wBAAC,QAAQ,SAAR,EAAgB,SAAO,MAAC,UAAU,QACjC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,QACrC,cAAW;AAAA,QACX,OAAO,SAAS,0CAA0C;AAAA,QAC1D,WAAU;AAAA,QAEV;AAAA,8BAAC,QAAK,eAAW,MAAC,WAAU,oCAAmC;AAAA,UAC9D,SAAS;AAAA,UACT,CAAC,UACA;AAAA,YAAC;AAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,IAEJ,GACF;AAAA,IACA,oBAAC,QAAQ,QAAR,EACC;AAAA,MAAC,QAAQ;AAAA,MAAR;AAAA,QACC,OAAM;AAAA,QACN,YAAY;AAAA,QACZ,WAAU;AAAA,QAET,gBAAM,IAAI,CAAC,EAAE,IAAI,OAAO,MAAM,SAAS,MACtC,oBAAC,QAAQ,OAAR,EAAc,SAAO,MACpB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,SAAS,EAAE;AAAA,YAC1B,WAAU;AAAA,YAEV;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,eAAW;AAAA,kBACX,WAAU;AAAA;AAAA,cACZ;AAAA,cACA,oBAAC,UAAK,WAAU,UAAU,iBAAM;AAAA,cAC/B,OAAO,QAAQ,oBAAC,SAAM,eAAW,MAAC,WAAU,UAAS;AAAA;AAAA;AAAA,QACxD,KAZ0B,EAa5B,CACD;AAAA;AAAA,IACH,GACF;AAAA,KACF;AAEJ;","names":[]}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { Info, X } from "lucide-react";
@@ -201,7 +201,7 @@ function HomeAsk({
201
201
  inputRef,
202
202
  onSubmit: submit,
203
203
  onClose: () => inputRef.current?.blur(),
204
- send: !inSupport ? /* @__PURE__ */ jsx(Fragment, {}) : /* @__PURE__ */ jsx(
204
+ send: /* @__PURE__ */ jsx(
205
205
  AskSendButton,
206
206
  {
207
207
  support: inSupport,
@@ -209,7 +209,7 @@ function HomeAsk({
209
209
  sending: support.sending,
210
210
  sent: support.sent,
211
211
  title: support.needsCaption ? "Add a few words about the picture first" : void 0,
212
- onClick: () => void support.send()
212
+ onClick: inSupport ? () => void support.send() : submit
213
213
  }
214
214
  )
215
215
  }