@burdenoff/microfe-billing 2026.828.1 → 2026.828.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +18 -3
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/shared/types/graphql.js.map +1 -1
- package/dist/generated/global-operations.js +2 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -28,7 +28,8 @@ function x(e) {
|
|
|
28
28
|
quotaType: n.quota.quotaType ?? "",
|
|
29
29
|
limitType: r,
|
|
30
30
|
totalValue: i,
|
|
31
|
-
unit: s(n)
|
|
31
|
+
unit: s(n),
|
|
32
|
+
perWorkspace: n.quota.scope === "WORKSPACE"
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
return Array.from(t.values());
|
|
@@ -184,9 +185,12 @@ var S = () => {
|
|
|
184
185
|
children: [/* @__PURE__ */ m("p", {
|
|
185
186
|
className: "font-medium text-text-primary",
|
|
186
187
|
children: e.name
|
|
187
|
-
}), /* @__PURE__ */
|
|
188
|
+
}), /* @__PURE__ */ h("span", {
|
|
188
189
|
className: "font-semibold text-text-primary",
|
|
189
|
-
children: i(e.totalValue, e.unit)
|
|
190
|
+
children: [i(e.totalValue, e.unit), e.perWorkspace && /* @__PURE__ */ h("span", {
|
|
191
|
+
className: "font-normal text-text-secondary",
|
|
192
|
+
children: [" ", s("billing.plans.perWorkspace", "per workspace")]
|
|
193
|
+
})]
|
|
190
194
|
})]
|
|
191
195
|
}), /* @__PURE__ */ h("div", {
|
|
192
196
|
className: "flex items-center gap-2 mt-1",
|
|
@@ -235,6 +239,17 @@ var S = () => {
|
|
|
235
239
|
className: "text-sm text-text-primary mt-0.5",
|
|
236
240
|
children: r(y.duration)
|
|
237
241
|
})] }),
|
|
242
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
243
|
+
className: "text-sm text-text-secondary",
|
|
244
|
+
children: s("billing.plans.maxWorkspaces", "Max Workspaces")
|
|
245
|
+
}), /* @__PURE__ */ h("p", {
|
|
246
|
+
className: "text-sm font-medium text-text-primary mt-0.5",
|
|
247
|
+
children: [
|
|
248
|
+
y.maxWorkspaces ?? 1,
|
|
249
|
+
" ",
|
|
250
|
+
(y.maxWorkspaces ?? 1) === 1 ? s("billing.plans.workspaceSingular", "workspace") : s("billing.plans.workspacePlural", "workspaces")
|
|
251
|
+
]
|
|
252
|
+
})] }),
|
|
238
253
|
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
239
254
|
className: "text-sm text-text-secondary",
|
|
240
255
|
children: s("billing.plans.quotasIncluded", "Quotas Included")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Browse Detail Page (User-facing)\n * View plan details and subscribe. No edit/toggle actions.\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n formatQuotaValue,\n getPlanFeatureQuantity,\n getPlanFeatureUnit,\n getQuotaDisplayName,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ShowMore, PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport type { ReactNode } from 'react';\n\n/** Wraps children in a ShowMore progressive-disclosure region only when `when` is true. */\nconst ShowMoreIf: FC<{ when: boolean; collapsedHeight?: number; children: ReactNode }> = ({\n when,\n collapsedHeight,\n children,\n}) => (when ? <ShowMore collapsedHeight={collapsedHeight}>{children}</ShowMore> : <>{children}</>);\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n unit?: string;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: getQuotaDisplayName(feature),\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n unit: getPlanFeatureUnit(feature),\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlanBrowseDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const { plan, isLoading, error, refetch } = usePlan(planId);\n\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-10 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlanDetails', 'Unable to load plan details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {error?.message ||\n tr(\n 'billing.plans.planNotFoundMessage',\n 'The plan you are looking for does not exist.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"p-2 -ml-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.browseDetailPurpose',\n 'Everything this plan includes — its price, billing period and the full list of features and quotas — so you can confirm it fits before subscribing. Hit Subscribe when you are ready to check out.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Subscribe to Plan\n </button>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-6 border-b border-border-seam\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(plan.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.plans.billed', 'Billed')}{' '}\n {plan.duration === 'monthly'\n ? tr('billing.plans.everyMonth', 'every month')\n : tr('billing.plans.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Features Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-6 border-b border-border-seam bg-bg-sunken\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.featuresAndQuotas', 'Features & Quotas')} (\n {combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.noFeatures', 'No features configured for this plan.')}\n </p>\n ) : (\n <ShowMoreIf when={combinedQuotas.length > 4} collapsedHeight={280}>\n <ul className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <li\n key={quota.quotaId}\n className=\"flex items-start gap-3 p-3 rounded-lg bg-bg-sunken\"\n >\n <div className=\"size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0\">\n <svg\n className=\"size-4 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <span className=\"font-semibold text-text-primary\">\n {formatQuotaValue(quota.totalValue, quota.unit)}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-text-secondary capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-secondary rounded\">\n {quota.quotaType}\n </span>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n </ShowMoreIf>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6 lg:sticky lg:top-6 lg:self-start\">\n {/* Subscribe Card — primary emphasis zone */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface bg-accent-wash shadow-[var(--shadow-pop)] overflow-hidden\">\n <div className=\"p-6 border-b border-border-seam bg-bg-sunken\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.subscribeTitle', 'Subscribe')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">{tr('billing.plans.price', 'Price')}</p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatPlanDurationLabel(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.quotasIncluded', 'Quotas Included')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {combinedQuotas.length}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"w-full px-4 py-2.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Subscribe to Plan\n </button>\n </div>\n </div>\n\n {/* Back */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Browse Detail Page (User-facing)\n * View plan details and subscribe. No edit/toggle actions.\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n formatQuotaValue,\n getPlanFeatureQuantity,\n getPlanFeatureUnit,\n getQuotaDisplayName,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ShowMore, PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport type { ReactNode } from 'react';\n\n/** Wraps children in a ShowMore progressive-disclosure region only when `when` is true. */\nconst ShowMoreIf: FC<{ when: boolean; collapsedHeight?: number; children: ReactNode }> = ({\n when,\n collapsedHeight,\n children,\n}) => (when ? <ShowMore collapsedHeight={collapsedHeight}>{children}</ShowMore> : <>{children}</>);\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n unit?: string;\n /** True for WORKSPACE-scoped quotas — the full amount applies to EACH\n * activated workspace, not divided across them (see plan.maxWorkspaces). */\n perWorkspace: boolean;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: getQuotaDisplayName(feature),\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n unit: getPlanFeatureUnit(feature),\n perWorkspace: feature.quota.scope === 'WORKSPACE',\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlanBrowseDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const { plan, isLoading, error, refetch } = usePlan(planId);\n\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-10 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlanDetails', 'Unable to load plan details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {error?.message ||\n tr(\n 'billing.plans.planNotFoundMessage',\n 'The plan you are looking for does not exist.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"p-2 -ml-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan.name}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.browseDetailPurpose',\n 'Everything this plan includes — its price, billing period and the full list of features and quotas — so you can confirm it fits before subscribing. Hit Subscribe when you are ready to check out.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Subscribe to Plan\n </button>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-6 border-b border-border-seam\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(plan.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.plans.billed', 'Billed')}{' '}\n {plan.duration === 'monthly'\n ? tr('billing.plans.everyMonth', 'every month')\n : tr('billing.plans.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Features Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-6 border-b border-border-seam bg-bg-sunken\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.featuresAndQuotas', 'Features & Quotas')} (\n {combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.noFeatures', 'No features configured for this plan.')}\n </p>\n ) : (\n <ShowMoreIf when={combinedQuotas.length > 4} collapsedHeight={280}>\n <ul className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <li\n key={quota.quotaId}\n className=\"flex items-start gap-3 p-3 rounded-lg bg-bg-sunken\"\n >\n <div className=\"size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0\">\n <svg\n className=\"size-4 text-status-success-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <span className=\"font-semibold text-text-primary\">\n {formatQuotaValue(quota.totalValue, quota.unit)}\n {quota.perWorkspace && (\n <span className=\"font-normal text-text-secondary\">\n {' '}\n {tr('billing.plans.perWorkspace', 'per workspace')}\n </span>\n )}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-text-secondary capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-secondary rounded\">\n {quota.quotaType}\n </span>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n </ShowMoreIf>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6 lg:sticky lg:top-6 lg:self-start\">\n {/* Subscribe Card — primary emphasis zone */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface bg-accent-wash shadow-[var(--shadow-pop)] overflow-hidden\">\n <div className=\"p-6 border-b border-border-seam bg-bg-sunken\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.subscribeTitle', 'Subscribe')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">{tr('billing.plans.price', 'Price')}</p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatPlanDurationLabel(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.maxWorkspaces', 'Max Workspaces')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {plan.maxWorkspaces ?? 1}{' '}\n {(plan.maxWorkspaces ?? 1) === 1\n ? tr('billing.plans.workspaceSingular', 'workspace')\n : tr('billing.plans.workspacePlural', 'workspaces')}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.plans.quotasIncluded', 'Quotas Included')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {combinedQuotas.length}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"w-full px-4 py-2.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Subscribe to Plan\n </button>\n </div>\n </div>\n\n {/* Back */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriptionDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionDetailPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscription Detail Page\n * Displays detailed information about a single subscription\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useSubscription, useSubscriptionMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useGetDefaultBillingAccountDashboardQuery,\n useGetPlanQuery,\n} from '../../../../generated/global-operations';\nimport { useWorkspaceQuotaOverview } from '../../usage/hooks/useUsage';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n formatPercentage,\n formatBytes,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor, getQuotaProgressColor } from '../../../shared/utils/status';\nimport { Card, StatusPill, ProgressBar } from '../../../shared/ui';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n PagePurpose,\n NextSteps,\n type CTAAction,\n type NextStep,\n} from '@burdenoff/fe-libs/ui';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n SubscriptionStatus,\n QuotaAssignment,\n AddonSubscription,\n SubscriptionHistory,\n Transaction,\n} from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { ManageSeatsModal } from '../components/ManageSeatsModal';\nimport {\n WorkspaceQuotaAssignmentModal,\n isWorkspaceScopedQuota,\n type WorkspaceQuotaToAssign,\n} from '../../checkout/components/WorkspaceQuotaAssignmentModal';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { WorkspaceQuotaOverviewItem } from '../../usage/hooks/useUsage';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction formatQuotaDisplayName(name: string, context?: Record<string, unknown> | null): string {\n if (context?.displayName && typeof context.displayName === 'string') return context.displayName;\n if (context?.label && typeof context.label === 'string') return context.label;\n const parts = name.split('.');\n const meaningful = parts.length > 1 ? parts.slice(1) : parts;\n return meaningful\n .map((p) => p.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()))\n .join(' ');\n}\n\nfunction formatResetPeriod(period: string): string {\n if (period === 'DAILY') return 'Resets daily';\n if (period === 'MONTHLY') return 'Resets monthly';\n return '';\n}\n\n// ============================================================================\n// Page Component\n// ============================================================================\n\nexport const SubscriptionDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { subscriptionId } = useParams<{ subscriptionId: string }>();\n const navigateTo = useBillingNavigate();\n const { emit } = useBillingEventEmitter();\n const { orgId } = useBilling();\n const permissions = useBillingPermissions();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const { data: defaultBillingAccountData } = useGetDefaultBillingAccountDashboardQuery({\n fetchPolicy: 'cache-first',\n });\n const billingAccountId =\n selectedAccountId ?? defaultBillingAccountData?.getDefaultBillingAccount?.id ?? undefined;\n const { subscription, isLoading, error, refetch } = useSubscription(\n subscriptionId,\n billingAccountId\n );\n const { cancelSubscription, isCanceling, updateSubscriptionSeats, isUpdatingSeats } =\n useSubscriptionMutations();\n\n const { workspaceId } = useBilling();\n\n // Workspace quota overview — used to detect if quotas are already active and show live usage\n const { overview: wsQuotaOverview, refetch: refetchWsOverview } = useWorkspaceQuotaOverview(\n workspaceId ?? undefined\n );\n\n // Plan features — the source of the workspace-scoped quotas included in this\n // plan. Subscription-level `quotas` only covers global-scope QuotaAssignment\n // rows; workspace-scoped quotas aren't provisioned as rows anywhere until\n // this subscription is activated for a workspace, so the \"Included quotas\"\n // preview in the assign modal has to be derived from the plan catalog.\n const { data: planFeaturesData } = useGetPlanQuery({\n variables: { id: subscription?.plan?.id ?? '' },\n skip: !subscription?.plan?.id,\n fetchPolicy: 'cache-first',\n });\n\n // Modal state\n const [showCancelModal, setShowCancelModal] = useState(false);\n const [showManageSeatsModal, setShowManageSeatsModal] = useState(false);\n const [showAssignWorkspaceModal, setShowAssignWorkspaceModal] = useState(false);\n const [cancelReason, setCancelReason] = useState('');\n const [cancelError, setCancelError] = useState<string | null>(null);\n\n // Handle cancel subscription\n const handleCancelSubscription = async () => {\n if (!subscription) return;\n\n setCancelError(null);\n try {\n await cancelSubscription({\n billingAccountId: billingAccountId ?? subscription.billingAccountId,\n subscriptionId: subscription.id,\n reason: cancelReason || 'User requested cancellation',\n });\n emit('billing.subscription.cancelled', {\n route: '/billing/subscriptions',\n entityId: subscription.id,\n source: 'subscription-detail',\n reason: cancelReason || 'User requested cancellation',\n });\n setShowCancelModal(false);\n setCancelReason('');\n await refetch();\n } catch (err) {\n setCancelError(err instanceof Error ? err.message : 'Failed to cancel subscription');\n }\n };\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n You don't have permission to view this subscription.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded-button\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !subscription) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {error?.message || 'The subscription you are looking for does not exist.'}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Back to Subscriptions\n </button>\n </div>\n </div>\n );\n }\n\n const plan = subscription.plan;\n const quotas = subscription.quotas || [];\n const addons = subscription.addonSubscriptions || [];\n const transactions = subscription.transactions || [];\n const history = subscription.history || [];\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Workspace quota assignments that belong to this subscription\n const wsAssignmentsForSub = wsQuotaOverview.filter(\n (item) => item.assignment.subscriptionId === subscription.id && item.assignment.isActive\n );\n // `assignedWorkspaceId` is legacy and last-write-wins across every workspace\n // this subscription has ever been activated for — with multiple workspaces\n // per account, it stays truthy forever after the first activation and\n // permanently hides the \"Activate for Workspace\" action on every OTHER\n // workspace. `activatedWorkspaceIds` is the authoritative, multi-workspace-\n // aware set (backed by SubscriptionWorkspace); fall back to the workspace\n // assignments already loaded for this workspace in case the field isn't\n // populated yet.\n const isAssignedToWorkspace = workspaceId\n ? (subscription.activatedWorkspaceIds?.includes(workspaceId) ?? false) ||\n wsAssignmentsForSub.length > 0\n : false;\n\n // Workspace-scoped quotas included in this plan, for the \"Included quotas\"\n // preview in the assign modal (see planFeaturesData above).\n type PlanFeature = {\n quota?: { name?: string | null; limits?: Record<string, unknown> | null; quotaType?: string | null } | null;\n };\n const rawPlanFeatures: Array<PlanFeature | null | undefined> = planFeaturesData?.getPlan?.features ?? [];\n const planFeatures: PlanFeature[] = rawPlanFeatures.filter((f): f is PlanFeature => f != null);\n const workspaceQuotasToAssign: WorkspaceQuotaToAssign[] = planFeatures\n .filter((f: PlanFeature) => f.quota?.name && isWorkspaceScopedQuota(f.quota.name))\n .map((f: PlanFeature) => {\n const limits = f.quota?.limits;\n const limitsValue = limits && 'value' in limits ? Number(limits.value) : null;\n const limitsType = limits && 'type' in limits ? String(limits.type) : 'number';\n return {\n quotaName: f.quota!.name!,\n productId: plan?.productID ?? 'platform',\n limit: limitsType === 'number' ? limitsValue : null,\n noLimit: limitsType !== 'number',\n resetPeriod: 'NEVER',\n quotaMode: f.quota?.quotaType === 'POOLED' ? ('pooled' as const) : ('dedicated' as const),\n };\n });\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const startDate = new Date(subscription.startDate);\n const now = new Date();\n const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n const progressPercentage = Math.round(((totalDays - daysRemaining) / totalDays) * 100);\n\n // Recommended next actions for this subscription\n const nextSteps: NextStep[] = [\n ...(isActive && permissions.canUpdateSubscription\n ? [\n {\n id: 'change-plan',\n label: 'Review or change your plan',\n description: 'Upgrade, downgrade, or compare what each tier includes.',\n onClick: () =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n ),\n },\n ]\n : []),\n ...(isActive && workspaceId && !isAssignedToWorkspace\n ? [\n {\n id: 'activate-workspace',\n label: 'Activate quotas for your workspace',\n description: 'Turn on the workspace-scoped limits included in this plan.',\n onClick: () => setShowAssignWorkspaceModal(true),\n },\n ]\n : []),\n {\n id: 'track-usage',\n label: 'Track your usage',\n description: 'See how much of your included quota you have consumed.',\n onClick: () =>\n navigateTo(\n withBillingAccountId('/usage', billingAccountId ?? subscription.billingAccountId)\n ),\n },\n {\n id: 'view-invoices',\n label: 'Review invoices & payments',\n description: 'Check what has been billed for this subscription.',\n onClick: () =>\n navigateTo(\n withBillingAccountId('/invoices', billingAccountId ?? subscription.billingAccountId)\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n aria-label=\"Back to subscriptions\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <div className=\"flex items-center gap-3 flex-wrap\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan?.name || 'Subscription'}\n </h1>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <p className=\"text-sm text-text-secondary mt-1\">\n {plan?.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? `${formatCurrency(plan.pricePerSeat, plan.currency)} / seat / ${formatPlanDuration(plan.duration)} · ${subscription.seatCount ?? plan.minSeats ?? 1} seats = ${formatCurrency(plan.pricePerSeat * (subscription.seatCount ?? plan.minSeats ?? 1), plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : plan\n ? `${formatCurrency(plan.price, plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : 'Unknown plan'}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.subscriptions.detailPurpose',\n 'Everything about this subscription in one view — its billing period, the quotas and add-ons it includes, recent charges and its full status history. Use it to track what you are getting, activate workspace quotas, change your plan, or cancel.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <CTAOverflowMenu\n primary={\n isActive && permissions.canUpdateSubscription\n ? {\n label: 'Change Plan',\n onSelect: () =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n ),\n }\n : undefined\n }\n actions={\n [\n ...(isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT\n ? [\n {\n label: 'Manage Seats',\n onSelect: () => setShowManageSeatsModal(true),\n },\n ]\n : []),\n ...(isActive && permissions.canCancelSubscription && !subscription.canceledAt\n ? [\n {\n label: isCanceling ? 'Canceling…' : 'Cancel Subscription',\n onSelect: () => setShowCancelModal(true),\n disabled: isCanceling,\n intent: 'destructive' as const,\n },\n ]\n : []),\n ] satisfies CTAAction[]\n }\n />\n {subscription.canceledAt && (\n <span className=\"px-4 py-2 text-sm font-medium text-status-warning-text bg-status-warning-bg-subtle rounded-button\">\n Cancellation Scheduled\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Period Progress — primary status emphasis zone */}\n {isActive && (\n <EmphasisPanel className=\"p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-base font-semibold text-text-primary\">Current Period</h2>\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <ProgressBar\n value={progressPercentage}\n status=\"primary\"\n size=\"md\"\n aria-label=\"Billing period progress\"\n />\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Plan Quotas */}\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Plan Quotas\n {quotas.length > 0 && (\n <span className=\"ml-2 text-sm font-normal text-text-muted tabular-nums\">\n ({quotas.length})\n </span>\n )}\n </h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">Limits included in your plan</p>\n </div>\n <div className=\"p-5\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No quotas assigned to this subscription.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {quotas.map((quota) => (\n <QuotaUsageItem key={quota.id} quota={quota} />\n ))}\n </div>\n )}\n </div>\n </Card>\n\n {/* Workspace Quotas */}\n {isActive && workspaceId && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle flex items-center justify-between gap-4\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Workspace Quotas</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Active limits for your workspace\n </p>\n </div>\n {isAssignedToWorkspace ? (\n <StatusPill status=\"success\" dot>\n Active\n </StatusPill>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Activate for Workspace\n </button>\n )}\n </div>\n\n <div className=\"p-5\">\n {isAssignedToWorkspace ? (\n wsAssignmentsForSub.length > 0 ? (\n <div className=\"space-y-3\">\n {wsAssignmentsForSub.map((item) => (\n <WorkspaceQuotaItem key={item.assignment.id} item={item} />\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Quotas are activated on workspace{' '}\n <span className=\"font-mono text-xs text-text-primary bg-bg-sunken/60 rounded px-1.5 py-0.5\">\n {workspaceId}\n </span>\n . Usage data will appear here once quotas are consumed.\n </p>\n )\n ) : (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-8 rounded-full bg-status-warning-bg-subtle flex items-center justify-center shrink-0 mt-0.5\">\n <svg\n className=\"size-4 text-status-warning-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Workspace quotas not activated yet\n </p>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Click{' '}\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"text-text-link hover:underline font-medium\"\n >\n Activate for Workspace\n </button>{' '}\n to enable the workspace-scoped limits included in this plan.\n </p>\n </div>\n </div>\n )}\n </div>\n\n {showAssignWorkspaceModal && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={billingAccountId ?? subscription.billingAccountId}\n subscriptionId={subscription.id}\n quotas={workspaceQuotasToAssign}\n workspaces={[{ id: workspaceId, name: 'Current workspace' }]}\n currentWorkspaceId={workspaceId}\n onAssigned={() => {\n setShowAssignWorkspaceModal(false);\n void refetch();\n void refetchWsOverview();\n }}\n onSkip={() => setShowAssignWorkspaceModal(false)}\n />\n )}\n </Card>\n )}\n\n {/* Addons */}\n {addons.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Addons{' '}\n <span className=\"text-sm font-normal text-text-muted tabular-nums\">\n ({addons.length})\n </span>\n </h2>\n </div>\n <div className=\"p-5\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </Card>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {transactions.slice(0, 5).map((transaction) => (\n <TransactionItem key={transaction.id} transaction={transaction} />\n ))}\n </div>\n {transactions.length > 5 && (\n <div className=\"p-4 text-center border-t border-border-subtle\">\n <button type=\"button\" className=\"text-sm text-text-link hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </Card>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6 lg:sticky lg:top-6 lg:self-start\">\n {/* Next steps guidance */}\n <NextSteps storageKey=\"billing-subscription-detail\" steps={nextSteps} />\n\n {/* Subscription Details */}\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">Details</h2>\n </div>\n <div className=\"p-5 space-y-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Subscription ID</p>\n <p className=\"font-mono text-sm text-text-primary mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Plan</p>\n <p className=\"text-sm text-text-primary mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Billing Account</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Start Date</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">End Date</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.endDate)}\n </p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-text-secondary\">Next Billing</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-secondary\">Auto Renewal</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-text-secondary\">Payment Gateway</p>\n <p className=\"text-sm text-text-primary mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </Card>\n\n {/* History */}\n {history.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">History</h2>\n </div>\n <div className=\"p-5\">\n <div className=\"space-y-3\">\n {history.slice(0, 5).map((item) => (\n <HistoryItem key={item.id} item={item} />\n ))}\n </div>\n </div>\n </Card>\n )}\n </div>\n </div>\n\n {/* Manage Seats Modal */}\n {showManageSeatsModal && subscription && (\n <ManageSeatsModal\n subscription={subscription}\n onClose={() => setShowManageSeatsModal(false)}\n onConfirm={async (seatCount) => {\n await updateSubscriptionSeats({ subscriptionId: subscription.id, seatCount });\n await refetch();\n }}\n isLoading={isUpdatingSeats}\n />\n )}\n\n {/* Cancel Subscription Modal */}\n {showCancelModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-overlay-scrim\"\n onClick={() => setShowCancelModal(false)}\n />\n <div\n className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-md mx-4 p-6\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"cancel-subscription-title\"\n >\n <h3\n id=\"cancel-subscription-title\"\n className=\"text-lg font-semibold text-text-primary mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n Are you sure you want to cancel this subscription? Your subscription will remain\n active until{' '}\n <span className=\"font-medium text-text-primary\">\n {formatDate(subscription.endDate)}\n </span>\n , but will not renew automatically.\n </p>\n\n <div className=\"mb-4\">\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Reason for cancellation (optional)\n </label>\n <textarea\n value={cancelReason}\n onChange={(e) => setCancelReason(e.target.value)}\n placeholder=\"Please share why you're canceling...\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-card\">\n <p className=\"text-sm text-status-error-text\">{cancelError}</p>\n </div>\n )}\n\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowCancelModal(false);\n setCancelReason('');\n setCancelError(null);\n }}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary transition-colors\"\n >\n Keep Subscription\n </button>\n <button\n type=\"button\"\n onClick={handleCancelSubscription}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium bg-action-danger-bg text-action-danger-text rounded-button hover:bg-action-danger-bgHover transition-colors duration-200 disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Confirm Cancellation'}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Sub-components\n// ============================================================================\n\nconst QuotaUsageItem: FC<{ quota: QuotaAssignment }> = ({ quota }) => {\n const context = quota.context as\n { displayName?: string; label?: string; description?: string } | null | undefined;\n const displayName = formatQuotaDisplayName(quota.name, context as Record<string, unknown> | null);\n const description =\n context?.description && typeof context.description === 'string' ? context.description : null;\n const limits = quota.limits as { value?: number } | null;\n const limit = limits?.value ?? 0;\n const used = quota.currentUsageSum ?? 0;\n const percentage = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;\n const isBytes = quota.name.includes('bytes') || quota.name.includes('storage');\n const isPooled = quota.quotaType === 'POOLED';\n\n return (\n <div className=\"p-4 rounded-card border border-border-seam bg-bg-surface\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{displayName}</p>\n {description && (\n <p className=\"text-xs text-text-secondary mt-0.5 line-clamp-2\">{description}</p>\n )}\n </div>\n <span className=\"text-sm font-medium text-text-primary shrink-0 tabular-nums\">\n {isBytes ? formatBytes(used) : used.toLocaleString()}\n {' / '}\n {limit > 0 ? (isBytes ? formatBytes(limit) : limit.toLocaleString()) : '∞'}\n </span>\n </div>\n\n {limit > 0 && (\n <div className=\"h-1.5 rounded-full bg-bg-sunken overflow-hidden mb-2\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${getQuotaProgressColor(percentage)}`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"px-1.5 py-0.5 rounded bg-bg-sunken font-medium\">\n {isPooled ? 'Shared' : 'Per Workspace'}\n </span>\n <div className=\"flex items-center gap-3\">\n {quota.endtime && <span>Expires {formatDate(quota.endtime, 'short')}</span>}\n {limit > 0 && (\n <span\n className={\n percentage >= 90\n ? 'text-status-error-text font-medium'\n : percentage >= 70\n ? 'text-status-warning-text font-medium'\n : ''\n }\n >\n {formatPercentage(percentage)} used\n </span>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nconst WorkspaceQuotaItem: FC<{ item: WorkspaceQuotaOverviewItem }> = ({ item }) => {\n const { assignment, effectiveUsed, usagePercentage } = item;\n const displayName = formatQuotaDisplayName(assignment.quotaName);\n const hasLimit = !assignment.noLimit && assignment.limit !== null;\n const limit = assignment.limit ?? 0;\n const isBytes =\n assignment.quotaName.includes('bytes') || assignment.quotaName.includes('storage');\n const resetLabel = formatResetPeriod(assignment.resetPeriod);\n\n return (\n <div className=\"p-4 rounded-card border border-border-seam bg-bg-surface\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{displayName}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {assignment.quotaMode === 'pooled' ? 'Pooled across workspaces' : 'Dedicated'}\n {resetLabel && ` · ${resetLabel}`}\n </p>\n </div>\n <span className=\"text-sm font-medium text-text-primary shrink-0 tabular-nums\">\n {isBytes ? formatBytes(effectiveUsed) : effectiveUsed.toLocaleString()}\n {' / '}\n {assignment.noLimit\n ? '∞'\n : hasLimit\n ? isBytes\n ? formatBytes(limit)\n : limit.toLocaleString()\n : '∞'}\n </span>\n </div>\n\n {hasLimit && (\n <div className=\"h-1.5 rounded-full bg-bg-sunken overflow-hidden mb-2\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${getQuotaProgressColor(usagePercentage)}`}\n style={{ width: `${Math.min(100, usagePercentage)}%` }}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Since {formatDate(assignment.createdAt, 'short')}</span>\n {hasLimit && (\n <span\n className={\n usagePercentage >= 90\n ? 'text-status-error-text font-medium'\n : usagePercentage >= 70\n ? 'text-status-warning-text font-medium'\n : ''\n }\n >\n {formatPercentage(usagePercentage)} used\n </span>\n )}\n </div>\n </div>\n );\n};\n\nconst AddonItem: FC<{ addonSubscription: AddonSubscription }> = ({ addonSubscription }) => {\n const addon = addonSubscription.addon;\n return (\n <div className=\"flex items-center justify-between p-3 rounded-card border border-border-seam bg-bg-sunken\">\n <div>\n <p className=\"font-medium text-text-primary\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-text-secondary\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-text-primary tabular-nums\">\n {formatCurrency(addon.price * addonSubscription.quantity, addon.currency)}\n </p>\n )}\n </div>\n );\n};\n\nconst TransactionItem: FC<{ transaction: Transaction }> = ({ transaction }) => {\n return (\n <div className=\"flex items-center justify-between px-5 py-4 hover:bg-bg-sunken transition-colors\">\n <div>\n <p className=\"font-medium text-text-primary capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-text-secondary\">{formatDate(transaction.createdAt, 'short')}</p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium tabular-nums ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-text-primary'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-text-secondary capitalize\">{transaction.status}</p>\n </div>\n </div>\n );\n};\n\nconst HistoryItem: FC<{ item: SubscriptionHistory }> = ({ item }) => {\n return (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-2 rounded-full bg-text-muted mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-text-primary\">\n <span className=\"capitalize\">{item.fromStatus}</span>\n {' → '}\n <span className=\"capitalize font-medium\">{item.toStatus}</span>\n </p>\n {item.reason && <p className=\"text-xs text-text-secondary mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-text-secondary mt-0.5\">{formatDate(item.createdAt, 'short')}</p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SubscriptionDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionDetailPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscription Detail Page\n * Displays detailed information about a single subscription\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useSubscription, useSubscriptionMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useGetDefaultBillingAccountDashboardQuery,\n useGetPlanQuery,\n} from '../../../../generated/global-operations';\nimport { useWorkspaceQuotaOverview } from '../../usage/hooks/useUsage';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n formatPercentage,\n formatBytes,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor, getQuotaProgressColor } from '../../../shared/utils/status';\nimport { Card, StatusPill, ProgressBar } from '../../../shared/ui';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n PagePurpose,\n NextSteps,\n type CTAAction,\n type NextStep,\n} from '@burdenoff/fe-libs/ui';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n SubscriptionStatus,\n QuotaAssignment,\n AddonSubscription,\n SubscriptionHistory,\n Transaction,\n} from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { ManageSeatsModal } from '../components/ManageSeatsModal';\nimport {\n WorkspaceQuotaAssignmentModal,\n isWorkspaceScopedQuota,\n type WorkspaceQuotaToAssign,\n} from '../../checkout/components/WorkspaceQuotaAssignmentModal';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { WorkspaceQuotaOverviewItem } from '../../usage/hooks/useUsage';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction formatQuotaDisplayName(name: string, context?: Record<string, unknown> | null): string {\n if (context?.displayName && typeof context.displayName === 'string') return context.displayName;\n if (context?.label && typeof context.label === 'string') return context.label;\n const parts = name.split('.');\n const meaningful = parts.length > 1 ? parts.slice(1) : parts;\n return meaningful\n .map((p) => p.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase()))\n .join(' ');\n}\n\nfunction formatResetPeriod(period: string): string {\n if (period === 'DAILY') return 'Resets daily';\n if (period === 'MONTHLY') return 'Resets monthly';\n return '';\n}\n\n// ============================================================================\n// Page Component\n// ============================================================================\n\nexport const SubscriptionDetailPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { subscriptionId } = useParams<{ subscriptionId: string }>();\n const navigateTo = useBillingNavigate();\n const { emit } = useBillingEventEmitter();\n const { orgId } = useBilling();\n const permissions = useBillingPermissions();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const { data: defaultBillingAccountData } = useGetDefaultBillingAccountDashboardQuery({\n fetchPolicy: 'cache-first',\n });\n const billingAccountId =\n selectedAccountId ?? defaultBillingAccountData?.getDefaultBillingAccount?.id ?? undefined;\n const { subscription, isLoading, error, refetch } = useSubscription(\n subscriptionId,\n billingAccountId\n );\n const { cancelSubscription, isCanceling, updateSubscriptionSeats, isUpdatingSeats } =\n useSubscriptionMutations();\n\n const { workspaceId } = useBilling();\n\n // Workspace quota overview — used to detect if quotas are already active and show live usage\n const { overview: wsQuotaOverview, refetch: refetchWsOverview } = useWorkspaceQuotaOverview(\n workspaceId ?? undefined\n );\n\n // Plan features — the source of the workspace-scoped quotas included in this\n // plan. Subscription-level `quotas` only covers global-scope QuotaAssignment\n // rows; workspace-scoped quotas aren't provisioned as rows anywhere until\n // this subscription is activated for a workspace, so the \"Included quotas\"\n // preview in the assign modal has to be derived from the plan catalog.\n const { data: planFeaturesData } = useGetPlanQuery({\n variables: { id: subscription?.plan?.id ?? '' },\n skip: !subscription?.plan?.id,\n fetchPolicy: 'cache-first',\n });\n\n // Modal state\n const [showCancelModal, setShowCancelModal] = useState(false);\n const [showManageSeatsModal, setShowManageSeatsModal] = useState(false);\n const [showAssignWorkspaceModal, setShowAssignWorkspaceModal] = useState(false);\n const [cancelReason, setCancelReason] = useState('');\n const [cancelError, setCancelError] = useState<string | null>(null);\n\n // Handle cancel subscription\n const handleCancelSubscription = async () => {\n if (!subscription) return;\n\n setCancelError(null);\n try {\n await cancelSubscription({\n billingAccountId: billingAccountId ?? subscription.billingAccountId,\n subscriptionId: subscription.id,\n reason: cancelReason || 'User requested cancellation',\n });\n emit('billing.subscription.cancelled', {\n route: '/billing/subscriptions',\n entityId: subscription.id,\n source: 'subscription-detail',\n reason: cancelReason || 'User requested cancellation',\n });\n setShowCancelModal(false);\n setCancelReason('');\n await refetch();\n } catch (err) {\n setCancelError(err instanceof Error ? err.message : 'Failed to cancel subscription');\n }\n };\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n You don't have permission to view this subscription.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded-button\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !subscription) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {error?.message || 'The subscription you are looking for does not exist.'}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Back to Subscriptions\n </button>\n </div>\n </div>\n );\n }\n\n const plan = subscription.plan;\n const quotas = subscription.quotas || [];\n const addons = subscription.addonSubscriptions || [];\n const transactions = subscription.transactions || [];\n const history = subscription.history || [];\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Workspace quota assignments that belong to this subscription\n const wsAssignmentsForSub = wsQuotaOverview.filter(\n (item) => item.assignment.subscriptionId === subscription.id && item.assignment.isActive\n );\n // `assignedWorkspaceId` is legacy and last-write-wins across every workspace\n // this subscription has ever been activated for — with multiple workspaces\n // per account, it stays truthy forever after the first activation and\n // permanently hides the \"Activate for Workspace\" action on every OTHER\n // workspace. `activatedWorkspaceIds` is the authoritative, multi-workspace-\n // aware set (backed by SubscriptionWorkspace); fall back to the workspace\n // assignments already loaded for this workspace in case the field isn't\n // populated yet.\n const isAssignedToWorkspace = workspaceId\n ? (subscription.activatedWorkspaceIds?.includes(workspaceId) ?? false) ||\n wsAssignmentsForSub.length > 0\n : false;\n\n // Workspace-scoped quotas included in this plan, for the \"Included quotas\"\n // preview in the assign modal (see planFeaturesData above).\n type PlanFeature = {\n quota?: {\n name?: string | null;\n limits?: Record<string, unknown> | null;\n quotaType?: string | null;\n } | null;\n };\n const rawPlanFeatures: Array<PlanFeature | null | undefined> =\n planFeaturesData?.getPlan?.features ?? [];\n const planFeatures: PlanFeature[] = rawPlanFeatures.filter((f): f is PlanFeature => f != null);\n const workspaceQuotasToAssign: WorkspaceQuotaToAssign[] = planFeatures\n .filter((f: PlanFeature) => f.quota?.name && isWorkspaceScopedQuota(f.quota.name))\n .map((f: PlanFeature) => {\n const limits = f.quota?.limits;\n const limitsValue = limits && 'value' in limits ? Number(limits.value) : null;\n const limitsType = limits && 'type' in limits ? String(limits.type) : 'number';\n return {\n quotaName: f.quota!.name!,\n productId: plan?.productID ?? 'platform',\n limit: limitsType === 'number' ? limitsValue : null,\n noLimit: limitsType !== 'number',\n resetPeriod: 'NEVER',\n quotaMode: f.quota?.quotaType === 'POOLED' ? ('pooled' as const) : ('dedicated' as const),\n };\n });\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const startDate = new Date(subscription.startDate);\n const now = new Date();\n const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n const progressPercentage = Math.round(((totalDays - daysRemaining) / totalDays) * 100);\n\n // Recommended next actions for this subscription\n const nextSteps: NextStep[] = [\n ...(isActive && permissions.canUpdateSubscription\n ? [\n {\n id: 'change-plan',\n label: 'Review or change your plan',\n description: 'Upgrade, downgrade, or compare what each tier includes.',\n onClick: () =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n ),\n },\n ]\n : []),\n ...(isActive && workspaceId && !isAssignedToWorkspace\n ? [\n {\n id: 'activate-workspace',\n label: 'Activate quotas for your workspace',\n description: 'Turn on the workspace-scoped limits included in this plan.',\n onClick: () => setShowAssignWorkspaceModal(true),\n },\n ]\n : []),\n {\n id: 'track-usage',\n label: 'Track your usage',\n description: 'See how much of your included quota you have consumed.',\n onClick: () =>\n navigateTo(\n withBillingAccountId('/usage', billingAccountId ?? subscription.billingAccountId)\n ),\n },\n {\n id: 'view-invoices',\n label: 'Review invoices & payments',\n description: 'Check what has been billed for this subscription.',\n onClick: () =>\n navigateTo(\n withBillingAccountId('/invoices', billingAccountId ?? subscription.billingAccountId)\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n aria-label=\"Back to subscriptions\"\n onClick={() => navigateTo(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button transition-colors duration-200\"\n >\n <svg className=\"size-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n <div>\n <div className=\"flex items-center gap-3 flex-wrap\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan?.name || 'Subscription'}\n </h1>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <p className=\"text-sm text-text-secondary mt-1\">\n {plan?.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? `${formatCurrency(plan.pricePerSeat, plan.currency)} / seat / ${formatPlanDuration(plan.duration)} · ${subscription.seatCount ?? plan.minSeats ?? 1} seats = ${formatCurrency(plan.pricePerSeat * (subscription.seatCount ?? plan.minSeats ?? 1), plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : plan\n ? `${formatCurrency(plan.price, plan.currency)} / ${formatPlanDuration(plan.duration)}`\n : 'Unknown plan'}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.subscriptions.detailPurpose',\n 'Everything about this subscription in one view — its billing period, the quotas and add-ons it includes, recent charges and its full status history. Use it to track what you are getting, activate workspace quotas, change your plan, or cancel.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <CTAOverflowMenu\n primary={\n isActive && permissions.canUpdateSubscription\n ? {\n label: 'Change Plan',\n onSelect: () =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n ),\n }\n : undefined\n }\n actions={\n [\n ...(isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT\n ? [\n {\n label: 'Manage Seats',\n onSelect: () => setShowManageSeatsModal(true),\n },\n ]\n : []),\n ...(isActive && permissions.canCancelSubscription && !subscription.canceledAt\n ? [\n {\n label: isCanceling ? 'Canceling…' : 'Cancel Subscription',\n onSelect: () => setShowCancelModal(true),\n disabled: isCanceling,\n intent: 'destructive' as const,\n },\n ]\n : []),\n ] satisfies CTAAction[]\n }\n />\n {subscription.canceledAt && (\n <span className=\"px-4 py-2 text-sm font-medium text-status-warning-text bg-status-warning-bg-subtle rounded-button\">\n Cancellation Scheduled\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Period Progress — primary status emphasis zone */}\n {isActive && (\n <EmphasisPanel className=\"p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-base font-semibold text-text-primary\">Current Period</h2>\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <ProgressBar\n value={progressPercentage}\n status=\"primary\"\n size=\"md\"\n aria-label=\"Billing period progress\"\n />\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Plan Quotas */}\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Plan Quotas\n {quotas.length > 0 && (\n <span className=\"ml-2 text-sm font-normal text-text-muted tabular-nums\">\n ({quotas.length})\n </span>\n )}\n </h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">Limits included in your plan</p>\n </div>\n <div className=\"p-5\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No quotas assigned to this subscription.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {quotas.map((quota) => (\n <QuotaUsageItem key={quota.id} quota={quota} />\n ))}\n </div>\n )}\n </div>\n </Card>\n\n {/* Workspace Quotas */}\n {isActive && workspaceId && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle flex items-center justify-between gap-4\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Workspace Quotas</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Active limits for your workspace\n </p>\n </div>\n {isAssignedToWorkspace ? (\n <StatusPill status=\"success\" dot>\n Active\n </StatusPill>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Activate for Workspace\n </button>\n )}\n </div>\n\n <div className=\"p-5\">\n {isAssignedToWorkspace ? (\n wsAssignmentsForSub.length > 0 ? (\n <div className=\"space-y-3\">\n {wsAssignmentsForSub.map((item) => (\n <WorkspaceQuotaItem key={item.assignment.id} item={item} />\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Quotas are activated on workspace{' '}\n <span className=\"font-mono text-xs text-text-primary bg-bg-sunken/60 rounded px-1.5 py-0.5\">\n {workspaceId}\n </span>\n . Usage data will appear here once quotas are consumed.\n </p>\n )\n ) : (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-8 rounded-full bg-status-warning-bg-subtle flex items-center justify-center shrink-0 mt-0.5\">\n <svg\n className=\"size-4 text-status-warning-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Workspace quotas not activated yet\n </p>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Click{' '}\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"text-text-link hover:underline font-medium\"\n >\n Activate for Workspace\n </button>{' '}\n to enable the workspace-scoped limits included in this plan.\n </p>\n </div>\n </div>\n )}\n </div>\n\n {showAssignWorkspaceModal && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={billingAccountId ?? subscription.billingAccountId}\n subscriptionId={subscription.id}\n quotas={workspaceQuotasToAssign}\n workspaces={[{ id: workspaceId, name: 'Current workspace' }]}\n currentWorkspaceId={workspaceId}\n onAssigned={() => {\n setShowAssignWorkspaceModal(false);\n void refetch();\n void refetchWsOverview();\n }}\n onSkip={() => setShowAssignWorkspaceModal(false)}\n />\n )}\n </Card>\n )}\n\n {/* Addons */}\n {addons.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n Addons{' '}\n <span className=\"text-sm font-normal text-text-muted tabular-nums\">\n ({addons.length})\n </span>\n </h2>\n </div>\n <div className=\"p-5\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </Card>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {transactions.slice(0, 5).map((transaction) => (\n <TransactionItem key={transaction.id} transaction={transaction} />\n ))}\n </div>\n {transactions.length > 5 && (\n <div className=\"p-4 text-center border-t border-border-subtle\">\n <button type=\"button\" className=\"text-sm text-text-link hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </Card>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6 lg:sticky lg:top-6 lg:self-start\">\n {/* Next steps guidance */}\n <NextSteps storageKey=\"billing-subscription-detail\" steps={nextSteps} />\n\n {/* Subscription Details */}\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">Details</h2>\n </div>\n <div className=\"p-5 space-y-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Subscription ID</p>\n <p className=\"font-mono text-sm text-text-primary mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Plan</p>\n <p className=\"text-sm text-text-primary mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Billing Account</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Start Date</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">End Date</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.endDate)}\n </p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-text-secondary\">Next Billing</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-text-secondary\">Auto Renewal</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-text-secondary\">Payment Gateway</p>\n <p className=\"text-sm text-text-primary mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </Card>\n\n {/* History */}\n {history.length > 0 && (\n <Card className=\"overflow-hidden\">\n <div className=\"p-5 border-b border-border-subtle\">\n <h2 className=\"text-base font-semibold text-text-primary\">History</h2>\n </div>\n <div className=\"p-5\">\n <div className=\"space-y-3\">\n {history.slice(0, 5).map((item) => (\n <HistoryItem key={item.id} item={item} />\n ))}\n </div>\n </div>\n </Card>\n )}\n </div>\n </div>\n\n {/* Manage Seats Modal */}\n {showManageSeatsModal && subscription && (\n <ManageSeatsModal\n subscription={subscription}\n onClose={() => setShowManageSeatsModal(false)}\n onConfirm={async (seatCount) => {\n await updateSubscriptionSeats({ subscriptionId: subscription.id, seatCount });\n await refetch();\n }}\n isLoading={isUpdatingSeats}\n />\n )}\n\n {/* Cancel Subscription Modal */}\n {showCancelModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div\n className=\"absolute inset-0 bg-overlay-scrim\"\n onClick={() => setShowCancelModal(false)}\n />\n <div\n className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-md mx-4 p-6\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"cancel-subscription-title\"\n >\n <h3\n id=\"cancel-subscription-title\"\n className=\"text-lg font-semibold text-text-primary mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n Are you sure you want to cancel this subscription? Your subscription will remain\n active until{' '}\n <span className=\"font-medium text-text-primary\">\n {formatDate(subscription.endDate)}\n </span>\n , but will not renew automatically.\n </p>\n\n <div className=\"mb-4\">\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Reason for cancellation (optional)\n </label>\n <textarea\n value={cancelReason}\n onChange={(e) => setCancelReason(e.target.value)}\n placeholder=\"Please share why you're canceling...\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-card\">\n <p className=\"text-sm text-status-error-text\">{cancelError}</p>\n </div>\n )}\n\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowCancelModal(false);\n setCancelReason('');\n setCancelError(null);\n }}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary transition-colors\"\n >\n Keep Subscription\n </button>\n <button\n type=\"button\"\n onClick={handleCancelSubscription}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium bg-action-danger-bg text-action-danger-text rounded-button hover:bg-action-danger-bgHover transition-colors duration-200 disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Confirm Cancellation'}\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Sub-components\n// ============================================================================\n\nconst QuotaUsageItem: FC<{ quota: QuotaAssignment }> = ({ quota }) => {\n const context = quota.context as\n { displayName?: string; label?: string; description?: string } | null | undefined;\n const displayName = formatQuotaDisplayName(quota.name, context as Record<string, unknown> | null);\n const description =\n context?.description && typeof context.description === 'string' ? context.description : null;\n const limits = quota.limits as { value?: number } | null;\n const limit = limits?.value ?? 0;\n const used = quota.currentUsageSum ?? 0;\n const percentage = limit > 0 ? Math.min(100, (used / limit) * 100) : 0;\n const isBytes = quota.name.includes('bytes') || quota.name.includes('storage');\n const isPooled = quota.quotaType === 'POOLED';\n\n return (\n <div className=\"p-4 rounded-card border border-border-seam bg-bg-surface\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{displayName}</p>\n {description && (\n <p className=\"text-xs text-text-secondary mt-0.5 line-clamp-2\">{description}</p>\n )}\n </div>\n <span className=\"text-sm font-medium text-text-primary shrink-0 tabular-nums\">\n {isBytes ? formatBytes(used) : used.toLocaleString()}\n {' / '}\n {limit > 0 ? (isBytes ? formatBytes(limit) : limit.toLocaleString()) : '∞'}\n </span>\n </div>\n\n {limit > 0 && (\n <div className=\"h-1.5 rounded-full bg-bg-sunken overflow-hidden mb-2\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${getQuotaProgressColor(percentage)}`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"px-1.5 py-0.5 rounded bg-bg-sunken font-medium\">\n {isPooled ? 'Shared' : 'Per Workspace'}\n </span>\n <div className=\"flex items-center gap-3\">\n {quota.endtime && <span>Expires {formatDate(quota.endtime, 'short')}</span>}\n {limit > 0 && (\n <span\n className={\n percentage >= 90\n ? 'text-status-error-text font-medium'\n : percentage >= 70\n ? 'text-status-warning-text font-medium'\n : ''\n }\n >\n {formatPercentage(percentage)} used\n </span>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nconst WorkspaceQuotaItem: FC<{ item: WorkspaceQuotaOverviewItem }> = ({ item }) => {\n const { assignment, effectiveUsed, usagePercentage } = item;\n const displayName = formatQuotaDisplayName(assignment.quotaName);\n const hasLimit = !assignment.noLimit && assignment.limit !== null;\n const limit = assignment.limit ?? 0;\n const isBytes =\n assignment.quotaName.includes('bytes') || assignment.quotaName.includes('storage');\n const resetLabel = formatResetPeriod(assignment.resetPeriod);\n\n return (\n <div className=\"p-4 rounded-card border border-border-seam bg-bg-surface\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{displayName}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {assignment.quotaMode === 'pooled' ? 'Pooled across workspaces' : 'Dedicated'}\n {resetLabel && ` · ${resetLabel}`}\n </p>\n </div>\n <span className=\"text-sm font-medium text-text-primary shrink-0 tabular-nums\">\n {isBytes ? formatBytes(effectiveUsed) : effectiveUsed.toLocaleString()}\n {' / '}\n {assignment.noLimit\n ? '∞'\n : hasLimit\n ? isBytes\n ? formatBytes(limit)\n : limit.toLocaleString()\n : '∞'}\n </span>\n </div>\n\n {hasLimit && (\n <div className=\"h-1.5 rounded-full bg-bg-sunken overflow-hidden mb-2\">\n <div\n className={`h-full rounded-full transition-all duration-300 ${getQuotaProgressColor(usagePercentage)}`}\n style={{ width: `${Math.min(100, usagePercentage)}%` }}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Since {formatDate(assignment.createdAt, 'short')}</span>\n {hasLimit && (\n <span\n className={\n usagePercentage >= 90\n ? 'text-status-error-text font-medium'\n : usagePercentage >= 70\n ? 'text-status-warning-text font-medium'\n : ''\n }\n >\n {formatPercentage(usagePercentage)} used\n </span>\n )}\n </div>\n </div>\n );\n};\n\nconst AddonItem: FC<{ addonSubscription: AddonSubscription }> = ({ addonSubscription }) => {\n const addon = addonSubscription.addon;\n return (\n <div className=\"flex items-center justify-between p-3 rounded-card border border-border-seam bg-bg-sunken\">\n <div>\n <p className=\"font-medium text-text-primary\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-text-secondary\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-text-primary tabular-nums\">\n {formatCurrency(addon.price * addonSubscription.quantity, addon.currency)}\n </p>\n )}\n </div>\n );\n};\n\nconst TransactionItem: FC<{ transaction: Transaction }> = ({ transaction }) => {\n return (\n <div className=\"flex items-center justify-between px-5 py-4 hover:bg-bg-sunken transition-colors\">\n <div>\n <p className=\"font-medium text-text-primary capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-text-secondary\">{formatDate(transaction.createdAt, 'short')}</p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium tabular-nums ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-text-primary'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-text-secondary capitalize\">{transaction.status}</p>\n </div>\n </div>\n );\n};\n\nconst HistoryItem: FC<{ item: SubscriptionHistory }> = ({ item }) => {\n return (\n <div className=\"flex items-start gap-3\">\n <div className=\"size-2 rounded-full bg-text-muted mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-text-primary\">\n <span className=\"capitalize\">{item.fromStatus}</span>\n {' → '}\n <span className=\"capitalize font-medium\">{item.toStatus}</span>\n </p>\n {item.reason && <p className=\"text-xs text-text-secondary mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-text-secondary mt-0.5\">{formatDate(item.createdAt, 'short')}</p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -112,7 +112,7 @@ var O = (e) => typeof e == "boolean" ? e ? "Yes" : "No" : typeof e == "number" ?
|
|
|
112
112
|
return H.find((t) => t.id === e) || null;
|
|
113
113
|
}, [H, P?.planId]), W = u(() => {
|
|
114
114
|
let e = P?.planId, t = P?.plan?.productID;
|
|
115
|
-
return H.filter((n) => n.id === e || !n.isActive ? !1 : t ? n.productID === t : !0).sort((e, t) => e.price - t.price);
|
|
115
|
+
return H.filter((n) => n.id === e || !n.isActive || n.isPurchasable === !1 ? !1 : t ? n.productID === t : !0).sort((e, t) => e.price - t.price);
|
|
116
116
|
}, [H, P]), G = u(() => W.find((e) => e.id === R) || null, [W, R]), K = G !== null && G.price < (P?.plan?.price ?? 0), q = (e) => {
|
|
117
117
|
V((t) => ({
|
|
118
118
|
...t,
|