@burdenoff/microfe-billing 2026.604.2 → 2026.609.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +373 -272
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/shared/utils/format.js +11 -11
- package/dist/generated/global-operations.js +1 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-dom';\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 { useGetDefaultBillingAccountDashboardQuery } from '../../../../generated/global-operations';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n formatPercentage,\n} from '../../../shared/utils/format';\nimport {\n getSubscriptionStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n} from '../../../shared/utils/status';\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 type WorkspaceQuotaToAssign,\n} from '../../checkout/components/WorkspaceQuotaAssignmentModal';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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 // 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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground 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 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted 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 rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted 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-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\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-foreground\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\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-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\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 // 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 return (\n <div className=\"space-y-6 p-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(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md 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 <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-foreground\">{plan?.name || 'Subscription'}</h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground 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 </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT && (\n <button\n type=\"button\"\n onClick={() => setShowManageSeatsModal(true)}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-accent text-foreground transition-colors\"\n >\n Manage Seats\n </button>\n )}\n {isActive && permissions.canUpdateSubscription && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n )\n }\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Change Plan\n </button>\n )}\n {isActive && permissions.canCancelSubscription && !subscription.canceledAt && (\n <button\n type=\"button\"\n onClick={() => setShowCancelModal(true)}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium border border-border text-destructive rounded-md hover:bg-destructive/10 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Cancel Subscription'}\n </button>\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-md\">\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 */}\n {isActive && (\n <div className=\"border border-border rounded-lg bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">Current Period</h2>\n <span className=\"text-sm text-muted-foreground\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${progressPercentage}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* Quotas */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Quota Usage ({quotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No quotas assigned to this subscription.\n </p>\n ) : (\n <div className=\"space-y-4\">\n {quotas.map((quota) => (\n <QuotaUsageItem key={quota.id} quota={quota} />\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Workspace Quota Assignment */}\n {isActive &&\n workspaceId &&\n (() => {\n const assigned = subscription.assignedWorkspaceId ?? null;\n // Empty placeholder quotas — modal is display-only here; actual quota list\n // is determined server-side by activateSubscriptionForWorkspace.\n const wsQuotas: WorkspaceQuotaToAssign[] = [];\n\n return (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Workspace Quota Assignment\n </h2>\n {!assigned && (\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Assign to Workspace\n </button>\n )}\n </div>\n <div className=\"p-6\">\n {assigned ? (\n <p className=\"text-sm text-muted-foreground\">\n Workspace quotas are active on workspace{' '}\n <span className=\"font-medium text-foreground font-mono text-xs\">\n {assigned}\n </span>\n .\n </p>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n The workspace quotas included in this plan haven't been activated yet.\n Click{' '}\n <span className=\"font-medium text-foreground\">Assign to Workspace</span> to\n activate them.\n </p>\n )}\n </div>\n\n {showAssignWorkspaceModal && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={billingAccountId ?? subscription.billingAccountId}\n subscriptionId={subscription.id}\n quotas={wsQuotas}\n workspaces={[{ id: workspaceId, name: 'Current workspace' }]}\n currentWorkspaceId={workspaceId}\n onAssigned={() => {\n setShowAssignWorkspaceModal(false);\n void refetch();\n }}\n onSkip={() => setShowAssignWorkspaceModal(false)}\n />\n )}\n </div>\n );\n })()}\n\n {/* Addons */}\n {addons.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Addons ({addons.length})</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border\">\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\">\n <button type=\"button\" className=\"text-sm text-primary hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Subscription Details */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Details</h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Subscription ID</p>\n <p className=\"font-mono text-sm text-foreground mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Plan</p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Billing Account</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Start Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">End Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">{formatDate(subscription.endDate)}</p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Next Billing</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-muted-foreground\">Auto Renewal</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Payment Gateway</p>\n <p className=\"text-sm text-foreground mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </div>\n\n {/* History */}\n {history.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">History</h2>\n </div>\n <div className=\"p-6\">\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 </div>\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-card border border-border rounded-lg shadow-lg 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-foreground mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-muted-foreground 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-foreground\">\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-foreground 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 rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md\">\n <p className=\"text-sm text-destructive\">{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-muted-foreground hover:text-foreground 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-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors 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 limits = quota.limits as { value?: number; type?: string } | 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\n return (\n <div className=\"p-4 rounded-lg bg-muted/30\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"font-medium text-foreground\">{quota.name}</span>\n <span className=\"text-sm text-muted-foreground\">\n {used} / {limit > 0 ? limit : '∞'}\n </span>\n </div>\n {limit > 0 && (\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full transition-all duration-300 ${getQuotaProgressColor(percentage)}`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n )}\n <div className=\"flex items-center justify-between mt-2 text-xs text-muted-foreground\">\n <span>{quota.quotaType}</span>\n <span>{formatPercentage(percentage)} used</span>\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-lg bg-muted/30\">\n <div>\n <p className=\"font-medium text-foreground\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-muted-foreground\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-foreground\">\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-6 py-4\">\n <div>\n <p className=\"font-medium text-foreground capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-muted-foreground\">\n {formatDate(transaction.createdAt, 'short')}\n </p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-foreground'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-muted-foreground 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-muted-foreground mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-foreground\">\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-muted-foreground mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {formatDate(item.createdAt, 'short')}\n </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-dom';\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 { useGetDefaultBillingAccountDashboardQuery } 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 {\n getSubscriptionStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n} from '../../../shared/utils/status';\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 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 // 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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground 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 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted 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 rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted 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-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\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-foreground\">\n {tr('billing.subscriptions.notFound', 'Subscription not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\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-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\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 // Assigned if subscription field is set OR workspace assignments already exist\n const isAssignedToWorkspace =\n !!subscription.assignedWorkspaceId || wsAssignmentsForSub.length > 0;\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 return (\n <div className=\"space-y-6 p-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(withBillingAccountId('/subscriptions', billingAccountId))}\n className=\"p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md 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 <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl font-bold text-foreground\">{plan?.name || 'Subscription'}</h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatSubscriptionStatus(subscription.status)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground 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 </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {isActive &&\n permissions.canUpdateSubscription &&\n plan?.pricingModel === PricingModel.PER_SEAT && (\n <button\n type=\"button\"\n onClick={() => setShowManageSeatsModal(true)}\n className=\"px-4 py-2 text-sm font-medium border border-border rounded-md hover:bg-accent text-foreground transition-colors\"\n >\n Manage Seats\n </button>\n )}\n {isActive && permissions.canUpdateSubscription && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}/upgrade`,\n billingAccountId ?? subscription.billingAccountId\n )\n )\n }\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Change Plan\n </button>\n )}\n {isActive && permissions.canCancelSubscription && !subscription.canceledAt && (\n <button\n type=\"button\"\n onClick={() => setShowCancelModal(true)}\n disabled={isCanceling}\n className=\"px-4 py-2 text-sm font-medium border border-border text-destructive rounded-md hover:bg-destructive/10 transition-colors disabled:opacity-50\"\n >\n {isCanceling ? 'Canceling...' : 'Cancel Subscription'}\n </button>\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-md\">\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 */}\n {isActive && (\n <div className=\"border border-border rounded-lg bg-card p-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">Current Period</h2>\n <span className=\"text-sm text-muted-foreground\">\n {daysRemaining} days remaining\n </span>\n </div>\n <div className=\"space-y-2\">\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className=\"h-full bg-primary transition-all duration-300\"\n style={{ width: `${progressPercentage}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span>{formatDate(subscription.startDate, 'short')}</span>\n <span>{formatDate(subscription.endDate, 'short')}</span>\n </div>\n </div>\n </div>\n )}\n\n {/* Plan Quotas */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n Plan Quotas\n {quotas.length > 0 && (\n <span className=\"ml-2 text-sm font-normal text-muted-foreground\">\n ({quotas.length})\n </span>\n )}\n </h2>\n <p className=\"text-sm text-muted-foreground mt-0.5\">Limits included in your plan</p>\n </div>\n <div className=\"p-6\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\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 </div>\n\n {/* Workspace Quotas */}\n {isActive && workspaceId && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30 flex items-center justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-foreground\">Workspace Quotas</h2>\n <p className=\"text-sm text-muted-foreground mt-0.5\">\n Active limits for your workspace\n </p>\n </div>\n {isAssignedToWorkspace ? (\n <span className=\"flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full bg-status-success-bg-subtle text-status-success-text\">\n <span className=\"size-1.5 rounded-full bg-status-success-text inline-block\" />\n Active\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Activate for Workspace\n </button>\n )}\n </div>\n\n <div className=\"p-6\">\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-muted-foreground\">\n Quotas are activated on workspace{' '}\n <span className=\"font-mono text-xs text-foreground\">\n {subscription.assignedWorkspaceId}\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-foreground\">\n Workspace quotas not activated yet\n </p>\n <p className=\"text-sm text-muted-foreground mt-0.5\">\n Click{' '}\n <button\n type=\"button\"\n onClick={() => setShowAssignWorkspaceModal(true)}\n className=\"text-primary 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={[] as WorkspaceQuotaToAssign[]}\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 </div>\n )}\n\n {/* Addons */}\n {addons.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Addons ({addons.length})</h2>\n </div>\n <div className=\"p-6\">\n <div className=\"space-y-3\">\n {addons.map((addonSub) => (\n <AddonItem key={addonSub.id} addonSubscription={addonSub} />\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Recent Transactions */}\n {transactions.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Recent Transactions</h2>\n </div>\n <div className=\"divide-y divide-border\">\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\">\n <button type=\"button\" className=\"text-sm text-primary hover:underline\">\n View all transactions\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Subscription Details */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">Details</h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Subscription ID</p>\n <p className=\"font-mono text-sm text-foreground mt-0.5 break-all\">\n {subscription.id}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Plan</p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan?.name || 'Unknown'}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Billing Account</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.billingAccount?.name || 'Unknown'}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">Start Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.startDate)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">End Date</p>\n <p className=\"text-sm text-foreground mt-0.5\">{formatDate(subscription.endDate)}</p>\n </div>\n {subscription.nextBillingDate && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Next Billing</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n )}\n <div>\n <p className=\"text-sm text-muted-foreground\">Auto Renewal</p>\n <p className=\"text-sm text-foreground mt-0.5\">\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n {subscription.paymentGateway && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Payment Gateway</p>\n <p className=\"text-sm text-foreground mt-0.5 capitalize\">\n {subscription.paymentGateway}\n </p>\n </div>\n )}\n </div>\n </div>\n\n {/* History */}\n {history.length > 0 && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">History</h2>\n </div>\n <div className=\"p-6\">\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 </div>\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-card border border-border rounded-lg shadow-lg 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-foreground mb-2\"\n >\n Cancel Subscription\n </h3>\n <p className=\"text-sm text-muted-foreground 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-foreground\">\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-foreground 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 rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none\"\n rows={3}\n />\n </div>\n\n {cancelError && (\n <div className=\"mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md\">\n <p className=\"text-sm text-destructive\">{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-muted-foreground hover:text-foreground 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-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors 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 }\n | null\n | 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-lg border border-border bg-card\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-foreground truncate\">{displayName}</p>\n {description && (\n <p className=\"text-xs text-muted-foreground mt-0.5 line-clamp-2\">{description}</p>\n )}\n </div>\n <span className=\"text-sm font-medium text-foreground 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-muted overflow-hidden mb-2\">\n <div\n className={`h-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-muted-foreground\">\n <span className=\"px-1.5 py-0.5 rounded bg-muted 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-lg border border-border bg-card\">\n <div className=\"flex items-start justify-between gap-4 mb-2\">\n <div className=\"min-w-0\">\n <p className=\"font-medium text-foreground truncate\">{displayName}</p>\n <p className=\"text-xs text-muted-foreground 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-foreground 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-muted overflow-hidden mb-2\">\n <div\n className={`h-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-muted-foreground\">\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-lg bg-muted/30\">\n <div>\n <p className=\"font-medium text-foreground\">{addon?.name || 'Unknown Addon'}</p>\n <p className=\"text-sm text-muted-foreground\">Qty: {addonSubscription.quantity}</p>\n </div>\n {addon && (\n <p className=\"text-sm font-medium text-foreground\">\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-6 py-4\">\n <div>\n <p className=\"font-medium text-foreground capitalize\">{transaction.type}</p>\n <p className=\"text-sm text-muted-foreground\">\n {formatDate(transaction.createdAt, 'short')}\n </p>\n </div>\n <div className=\"text-right\">\n <p\n className={`font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : 'text-foreground'}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(transaction.amount, transaction.currency)}\n </p>\n <p className=\"text-sm text-muted-foreground 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-muted-foreground mt-2 shrink-0\" />\n <div>\n <p className=\"text-sm text-foreground\">\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-muted-foreground mt-0.5\">{item.reason}</p>}\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {formatDate(item.createdAt, 'short')}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { CreditTransactionType as e, InvoiceStatus as t, PlanDuration as n, SubscriptionStatus as r } from "../types/graphql.js";
|
|
2
|
-
import {
|
|
2
|
+
import { formatBytes as i, formatCurrency as a, formatDate as o, formatDateTime as s, formatNumber as c, formatPercentage as l, formatRelativeTime as u } from "@burdenoff/fe-libs/shared/utils";
|
|
3
3
|
//#region src/billing/shared/utils/format.ts
|
|
4
|
-
function
|
|
5
|
-
return t.toUpperCase() === "CREDITS" ?
|
|
4
|
+
function d(e, t = "USD", n = "en-US") {
|
|
5
|
+
return t.toUpperCase() === "CREDITS" ? f(e) : a(e, t, n);
|
|
6
6
|
}
|
|
7
|
-
function
|
|
7
|
+
function f(e) {
|
|
8
8
|
return Number.isInteger(e) ? `${e.toLocaleString()} Credits` : `${e.toLocaleString(void 0, {
|
|
9
9
|
minimumFractionDigits: 0,
|
|
10
10
|
maximumFractionDigits: 2
|
|
11
11
|
})} Credits`;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function p(e) {
|
|
14
14
|
return {
|
|
15
15
|
[r.ACTIVE]: "Active",
|
|
16
16
|
[r.CANCELED]: "Canceled",
|
|
@@ -18,7 +18,7 @@ function f(e) {
|
|
|
18
18
|
[r.UPGRADED]: "Upgraded"
|
|
19
19
|
}[e] || e;
|
|
20
20
|
}
|
|
21
|
-
function
|
|
21
|
+
function m(e) {
|
|
22
22
|
return {
|
|
23
23
|
[t.DRAFT]: "Draft",
|
|
24
24
|
[t.OPEN]: "Open",
|
|
@@ -27,19 +27,19 @@ function p(e) {
|
|
|
27
27
|
[t.UNCOLLECTIBLE]: "Uncollectible"
|
|
28
28
|
}[e] || e;
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function h(e) {
|
|
31
31
|
return {
|
|
32
32
|
[n.MONTHLY]: "month",
|
|
33
33
|
[n.YEARLY]: "year"
|
|
34
34
|
}[e] || e;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function g(e) {
|
|
37
37
|
return {
|
|
38
38
|
[n.MONTHLY]: "Monthly",
|
|
39
39
|
[n.YEARLY]: "Yearly"
|
|
40
40
|
}[e] || e;
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function _(t) {
|
|
43
43
|
return {
|
|
44
44
|
[e.PURCHASE]: "Purchase",
|
|
45
45
|
[e.USAGE]: "Usage",
|
|
@@ -47,10 +47,10 @@ function g(t) {
|
|
|
47
47
|
[e.GRANTED]: "Granted"
|
|
48
48
|
}[t] || t;
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function v(e) {
|
|
51
51
|
return `#${e}`;
|
|
52
52
|
}
|
|
53
53
|
//#endregion
|
|
54
|
-
export {
|
|
54
|
+
export { i as formatBytes, _ as formatCreditTransactionType, f as formatCredits, d as formatCurrency, o as formatDate, s as formatDateTime, v as formatInvoiceNumber, m as formatInvoiceStatus, c as formatNumber, l as formatPercentage, h as formatPlanDuration, g as formatPlanDurationLabel, u as formatRelativeTime, p as formatSubscriptionStatus };
|
|
55
55
|
|
|
56
56
|
//# sourceMappingURL=format.js.map
|