@burdenoff/microfe-billing 2026.624.3 → 2026.625.2
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/BillingAdminRoutes.js +1 -1
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +2 -2
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +4 -4
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +2 -2
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +98 -99
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +88 -85
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +6 -6
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +2 -2
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +360 -370
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +2 -2
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +2 -2
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +102 -104
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +5 -5
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +29 -28
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +2 -2
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +3 -3
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +139 -142
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +6 -6
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +4 -4
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +142 -133
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +2 -2
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +4 -4
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +2 -2
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +4 -4
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +2 -2
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +2 -2
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +5 -5
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -265
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +7 -7
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +13 -13
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +1 -1
- package/dist/billing/shared/ui/Card.js.map +1 -1
- package/dist/billing/shared/ui/MetricCard.js +28 -21
- package/dist/billing/shared/ui/MetricCard.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: 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\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\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-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-subtle rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label htmlFor=\"workspaceId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\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 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: 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\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\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-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label htmlFor=\"workspaceId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\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 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -9,10 +9,15 @@ import { usePlan as s } from "../hooks/usePlans.js";
|
|
|
9
9
|
import "../hooks/index.js";
|
|
10
10
|
import { useMemo as c } from "react";
|
|
11
11
|
import { useParams as l } from "react-router-dom";
|
|
12
|
-
import {
|
|
13
|
-
import { useI18n as
|
|
12
|
+
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
13
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
14
|
+
import { ShowMore as m } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx
|
|
15
|
-
|
|
16
|
+
var h = ({ when: e, collapsedHeight: t, children: n }) => e ? /* @__PURE__ */ d(m, {
|
|
17
|
+
collapsedHeight: t,
|
|
18
|
+
children: n
|
|
19
|
+
}) : /* @__PURE__ */ d(u, { children: n });
|
|
20
|
+
function g(e) {
|
|
16
21
|
let t = /* @__PURE__ */ new Map();
|
|
17
22
|
for (let n of e) {
|
|
18
23
|
if (!n.quota) continue;
|
|
@@ -27,236 +32,240 @@ function p(e) {
|
|
|
27
32
|
}
|
|
28
33
|
return Array.from(t.values());
|
|
29
34
|
}
|
|
30
|
-
var
|
|
31
|
-
let { t: a } =
|
|
35
|
+
var _ = () => {
|
|
36
|
+
let { t: a } = p(), u = (e, t) => {
|
|
32
37
|
let n = a(e);
|
|
33
38
|
return n === e ? t : n;
|
|
34
|
-
}, { planId:
|
|
35
|
-
return
|
|
39
|
+
}, { planId: m } = l(), _ = o(), { plan: v, isLoading: y, error: b, refetch: x } = s(m), S = c(() => g(v?.features || []), [v?.features]);
|
|
40
|
+
return y ? /* @__PURE__ */ f("div", {
|
|
36
41
|
className: "space-y-6 px-6 lg:px-8 py-6",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ f("div", {
|
|
38
43
|
className: "flex items-center gap-4",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
-
}), /* @__PURE__ */
|
|
44
|
+
children: [/* @__PURE__ */ d("div", { className: "size-10 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ d("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" })]
|
|
45
|
+
}), /* @__PURE__ */ f("div", {
|
|
41
46
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
42
|
-
children: [/* @__PURE__ */
|
|
47
|
+
children: [/* @__PURE__ */ d("div", {
|
|
43
48
|
className: "lg:col-span-2 space-y-6",
|
|
44
|
-
children: /* @__PURE__ */
|
|
49
|
+
children: /* @__PURE__ */ f("div", {
|
|
45
50
|
className: "border border-border-subtle rounded-lg p-6 space-y-4",
|
|
46
|
-
children: [/* @__PURE__ */
|
|
51
|
+
children: [/* @__PURE__ */ d("div", { className: "h-6 w-32 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ d("div", { className: "h-4 w-full bg-bg-sunken animate-pulse rounded" })]
|
|
47
52
|
})
|
|
48
|
-
}), /* @__PURE__ */
|
|
53
|
+
}), /* @__PURE__ */ f("div", {
|
|
49
54
|
className: "border border-border-subtle rounded-lg p-6 space-y-4",
|
|
50
|
-
children: [/* @__PURE__ */
|
|
55
|
+
children: [/* @__PURE__ */ d("div", { className: "h-6 w-24 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ d("div", { className: "h-10 w-full bg-bg-sunken animate-pulse rounded" })]
|
|
51
56
|
})]
|
|
52
57
|
})]
|
|
53
|
-
}) :
|
|
58
|
+
}) : b && i(b) ? /* @__PURE__ */ d("div", {
|
|
54
59
|
className: "p-6",
|
|
55
|
-
children: /* @__PURE__ */
|
|
56
|
-
title:
|
|
57
|
-
message:
|
|
58
|
-
onRetry: () =>
|
|
60
|
+
children: /* @__PURE__ */ d(e, {
|
|
61
|
+
title: u("billing.plans.serverUnavailable", "Server Unavailable"),
|
|
62
|
+
message: u("billing.plans.unableToLoadPlanDetails", "Unable to load plan details."),
|
|
63
|
+
onRetry: () => x(),
|
|
59
64
|
showRetry: !0
|
|
60
65
|
})
|
|
61
|
-
}) :
|
|
66
|
+
}) : v ? /* @__PURE__ */ f("div", {
|
|
62
67
|
className: "space-y-6 px-6 lg:px-8 py-6",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
68
|
+
children: [/* @__PURE__ */ f("div", {
|
|
64
69
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
65
|
-
children: [/* @__PURE__ */
|
|
70
|
+
children: [/* @__PURE__ */ f("div", {
|
|
66
71
|
className: "flex items-start gap-4",
|
|
67
|
-
children: [/* @__PURE__ */
|
|
72
|
+
children: [/* @__PURE__ */ d("button", {
|
|
68
73
|
type: "button",
|
|
69
|
-
onClick: () =>
|
|
74
|
+
onClick: () => _("/plans"),
|
|
70
75
|
className: "p-2 -ml-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button transition-colors",
|
|
71
|
-
children: /* @__PURE__ */
|
|
76
|
+
children: /* @__PURE__ */ d("svg", {
|
|
72
77
|
className: "size-5",
|
|
73
78
|
fill: "none",
|
|
74
79
|
viewBox: "0 0 24 24",
|
|
75
80
|
stroke: "currentColor",
|
|
76
|
-
children: /* @__PURE__ */
|
|
81
|
+
children: /* @__PURE__ */ d("path", {
|
|
77
82
|
strokeLinecap: "round",
|
|
78
83
|
strokeLinejoin: "round",
|
|
79
84
|
strokeWidth: 2,
|
|
80
85
|
d: "M15 19l-7-7 7-7"
|
|
81
86
|
})
|
|
82
87
|
})
|
|
83
|
-
}), /* @__PURE__ */
|
|
88
|
+
}), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("h1", {
|
|
84
89
|
className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
|
|
85
|
-
children:
|
|
86
|
-
}), /* @__PURE__ */
|
|
90
|
+
children: v.name
|
|
91
|
+
}), /* @__PURE__ */ f("p", {
|
|
87
92
|
className: "text-sm text-text-secondary mt-1",
|
|
88
|
-
children: [r(
|
|
93
|
+
children: [r(v.duration), " billing plan"]
|
|
89
94
|
})] })]
|
|
90
|
-
}), /* @__PURE__ */
|
|
95
|
+
}), /* @__PURE__ */ d("button", {
|
|
91
96
|
type: "button",
|
|
92
|
-
onClick: () =>
|
|
97
|
+
onClick: () => _(`/checkout/${v.id}`),
|
|
93
98
|
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",
|
|
94
99
|
children: "Subscribe to Plan"
|
|
95
100
|
})]
|
|
96
|
-
}), /* @__PURE__ */
|
|
101
|
+
}), /* @__PURE__ */ f("div", {
|
|
97
102
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
98
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ f("div", {
|
|
99
104
|
className: "lg:col-span-2 space-y-6",
|
|
100
|
-
children: [/* @__PURE__ */
|
|
101
|
-
className: "border border-border-
|
|
102
|
-
children: [/* @__PURE__ */
|
|
103
|
-
className: "p-6 border-b border-border-
|
|
104
|
-
children: /* @__PURE__ */
|
|
105
|
+
children: [/* @__PURE__ */ f("div", {
|
|
106
|
+
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden",
|
|
107
|
+
children: [/* @__PURE__ */ d("div", {
|
|
108
|
+
className: "p-6 border-b border-border-seam bg-bg-sunken",
|
|
109
|
+
children: /* @__PURE__ */ d("h2", {
|
|
105
110
|
className: "text-lg font-semibold text-text-primary",
|
|
106
|
-
children:
|
|
111
|
+
children: u("billing.plans.pricing", "Pricing")
|
|
107
112
|
})
|
|
108
|
-
}), /* @__PURE__ */
|
|
113
|
+
}), /* @__PURE__ */ f("div", {
|
|
109
114
|
className: "p-6",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
115
|
+
children: [/* @__PURE__ */ f("div", {
|
|
111
116
|
className: "flex items-baseline gap-1",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
117
|
+
children: [/* @__PURE__ */ d("span", {
|
|
113
118
|
className: "text-4xl font-bold text-text-primary",
|
|
114
|
-
children: t(
|
|
115
|
-
}), /* @__PURE__ */
|
|
119
|
+
children: t(v.price, v.currency)
|
|
120
|
+
}), /* @__PURE__ */ f("span", {
|
|
116
121
|
className: "text-lg text-text-secondary",
|
|
117
|
-
children: ["/ ", n(
|
|
122
|
+
children: ["/ ", n(v.duration)]
|
|
118
123
|
})]
|
|
119
|
-
}), /* @__PURE__ */
|
|
124
|
+
}), /* @__PURE__ */ f("p", {
|
|
120
125
|
className: "text-sm text-text-secondary mt-2",
|
|
121
126
|
children: [
|
|
122
|
-
|
|
127
|
+
u("billing.plans.billed", "Billed"),
|
|
123
128
|
" ",
|
|
124
|
-
|
|
129
|
+
v.duration === "monthly" ? u("billing.plans.everyMonth", "every month") : u("billing.plans.annually", "annually")
|
|
125
130
|
]
|
|
126
131
|
})]
|
|
127
132
|
})]
|
|
128
|
-
}), /* @__PURE__ */
|
|
129
|
-
className: "border border-border-
|
|
130
|
-
children: [/* @__PURE__ */
|
|
131
|
-
className: "p-6 border-b border-border-
|
|
132
|
-
children: /* @__PURE__ */
|
|
133
|
+
}), /* @__PURE__ */ f("div", {
|
|
134
|
+
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden",
|
|
135
|
+
children: [/* @__PURE__ */ d("div", {
|
|
136
|
+
className: "p-6 border-b border-border-seam bg-bg-sunken",
|
|
137
|
+
children: /* @__PURE__ */ f("h2", {
|
|
133
138
|
className: "text-lg font-semibold text-text-primary",
|
|
134
139
|
children: [
|
|
135
|
-
|
|
140
|
+
u("billing.plans.featuresAndQuotas", "Features & Quotas"),
|
|
136
141
|
" (",
|
|
137
|
-
|
|
142
|
+
S.length,
|
|
138
143
|
")"
|
|
139
144
|
]
|
|
140
145
|
})
|
|
141
|
-
}), /* @__PURE__ */
|
|
146
|
+
}), /* @__PURE__ */ d("div", {
|
|
142
147
|
className: "p-6",
|
|
143
|
-
children:
|
|
148
|
+
children: S.length === 0 ? /* @__PURE__ */ d("p", {
|
|
144
149
|
className: "text-sm text-text-secondary",
|
|
145
|
-
children:
|
|
146
|
-
}) : /* @__PURE__ */
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
className: "size-4 text-status-success-text",
|
|
154
|
-
fill: "none",
|
|
155
|
-
viewBox: "0 0 24 24",
|
|
156
|
-
stroke: "currentColor",
|
|
157
|
-
children: /* @__PURE__ */ u("path", {
|
|
158
|
-
strokeLinecap: "round",
|
|
159
|
-
strokeLinejoin: "round",
|
|
160
|
-
strokeWidth: 2,
|
|
161
|
-
d: "M5 13l4 4L19 7"
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
}), /* @__PURE__ */ d("div", {
|
|
165
|
-
className: "flex-1",
|
|
150
|
+
children: u("billing.plans.noFeatures", "No features configured for this plan.")
|
|
151
|
+
}) : /* @__PURE__ */ d(h, {
|
|
152
|
+
when: S.length > 4,
|
|
153
|
+
collapsedHeight: 280,
|
|
154
|
+
children: /* @__PURE__ */ d("ul", {
|
|
155
|
+
className: "space-y-3",
|
|
156
|
+
children: S.map((e) => /* @__PURE__ */ f("li", {
|
|
157
|
+
className: "flex items-start gap-3 p-3 rounded-lg bg-bg-sunken",
|
|
166
158
|
children: [/* @__PURE__ */ d("div", {
|
|
167
|
-
className: "flex items-center justify-
|
|
168
|
-
children:
|
|
169
|
-
className: "
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
children:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
159
|
+
className: "size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0",
|
|
160
|
+
children: /* @__PURE__ */ d("svg", {
|
|
161
|
+
className: "size-4 text-status-success-text",
|
|
162
|
+
fill: "none",
|
|
163
|
+
viewBox: "0 0 24 24",
|
|
164
|
+
stroke: "currentColor",
|
|
165
|
+
children: /* @__PURE__ */ d("path", {
|
|
166
|
+
strokeLinecap: "round",
|
|
167
|
+
strokeLinejoin: "round",
|
|
168
|
+
strokeWidth: 2,
|
|
169
|
+
d: "M5 13l4 4L19 7"
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
}), /* @__PURE__ */ f("div", {
|
|
173
|
+
className: "flex-1",
|
|
174
|
+
children: [/* @__PURE__ */ f("div", {
|
|
175
|
+
className: "flex items-center justify-between",
|
|
176
|
+
children: [/* @__PURE__ */ d("p", {
|
|
177
|
+
className: "font-medium text-text-primary",
|
|
178
|
+
children: e.name
|
|
179
|
+
}), /* @__PURE__ */ d("span", {
|
|
180
|
+
className: "font-semibold text-text-primary",
|
|
181
|
+
children: e.totalValue.toLocaleString()
|
|
182
|
+
})]
|
|
183
|
+
}), /* @__PURE__ */ f("div", {
|
|
184
|
+
className: "flex items-center gap-2 mt-1",
|
|
185
|
+
children: [/* @__PURE__ */ f("span", {
|
|
186
|
+
className: "text-sm text-text-secondary capitalize",
|
|
187
|
+
children: ["Type: ", e.limitType]
|
|
188
|
+
}), e.quotaType && /* @__PURE__ */ d("span", {
|
|
189
|
+
className: "px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-secondary rounded",
|
|
190
|
+
children: e.quotaType
|
|
191
|
+
})]
|
|
183
192
|
})]
|
|
184
193
|
})]
|
|
185
|
-
})
|
|
186
|
-
}
|
|
194
|
+
}, e.quotaId))
|
|
195
|
+
})
|
|
187
196
|
})
|
|
188
197
|
})]
|
|
189
198
|
})]
|
|
190
|
-
}), /* @__PURE__ */
|
|
191
|
-
className: "space-y-6",
|
|
192
|
-
children: [/* @__PURE__ */
|
|
193
|
-
className: "border border-border-
|
|
194
|
-
children: [/* @__PURE__ */
|
|
195
|
-
className: "p-6 border-b border-border-
|
|
196
|
-
children: /* @__PURE__ */
|
|
199
|
+
}), /* @__PURE__ */ f("div", {
|
|
200
|
+
className: "space-y-6 lg:sticky lg:top-6 lg:self-start",
|
|
201
|
+
children: [/* @__PURE__ */ f("div", {
|
|
202
|
+
className: "border border-border-seam rounded-card bg-bg-surface bg-accent-wash shadow-[var(--shadow-pop)] overflow-hidden",
|
|
203
|
+
children: [/* @__PURE__ */ d("div", {
|
|
204
|
+
className: "p-6 border-b border-border-seam bg-bg-sunken",
|
|
205
|
+
children: /* @__PURE__ */ d("h2", {
|
|
197
206
|
className: "text-lg font-semibold text-text-primary",
|
|
198
|
-
children:
|
|
207
|
+
children: u("billing.plans.subscribeTitle", "Subscribe")
|
|
199
208
|
})
|
|
200
|
-
}), /* @__PURE__ */
|
|
209
|
+
}), /* @__PURE__ */ f("div", {
|
|
201
210
|
className: "p-6 space-y-4",
|
|
202
211
|
children: [
|
|
203
|
-
/* @__PURE__ */
|
|
212
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
204
213
|
className: "text-sm text-text-secondary",
|
|
205
|
-
children:
|
|
206
|
-
}), /* @__PURE__ */
|
|
214
|
+
children: u("billing.plans.price", "Price")
|
|
215
|
+
}), /* @__PURE__ */ f("p", {
|
|
207
216
|
className: "text-sm font-medium text-text-primary mt-0.5",
|
|
208
217
|
children: [
|
|
209
|
-
t(
|
|
218
|
+
t(v.price, v.currency),
|
|
210
219
|
" / ",
|
|
211
|
-
n(
|
|
220
|
+
n(v.duration)
|
|
212
221
|
]
|
|
213
222
|
})] }),
|
|
214
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
215
224
|
className: "text-sm text-text-secondary",
|
|
216
|
-
children:
|
|
217
|
-
}), /* @__PURE__ */
|
|
225
|
+
children: u("billing.plans.billingPeriod", "Billing Period")
|
|
226
|
+
}), /* @__PURE__ */ d("p", {
|
|
218
227
|
className: "text-sm text-text-primary mt-0.5",
|
|
219
|
-
children: r(
|
|
228
|
+
children: r(v.duration)
|
|
220
229
|
})] }),
|
|
221
|
-
/* @__PURE__ */
|
|
230
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
222
231
|
className: "text-sm text-text-secondary",
|
|
223
|
-
children:
|
|
224
|
-
}), /* @__PURE__ */
|
|
232
|
+
children: u("billing.plans.quotasIncluded", "Quotas Included")
|
|
233
|
+
}), /* @__PURE__ */ d("p", {
|
|
225
234
|
className: "text-sm font-medium text-text-primary mt-0.5",
|
|
226
|
-
children:
|
|
235
|
+
children: S.length
|
|
227
236
|
})] }),
|
|
228
|
-
/* @__PURE__ */
|
|
237
|
+
/* @__PURE__ */ d("button", {
|
|
229
238
|
type: "button",
|
|
230
|
-
onClick: () =>
|
|
239
|
+
onClick: () => _(`/checkout/${v.id}`),
|
|
231
240
|
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",
|
|
232
241
|
children: "Subscribe to Plan"
|
|
233
242
|
})
|
|
234
243
|
]
|
|
235
244
|
})]
|
|
236
|
-
}), /* @__PURE__ */
|
|
245
|
+
}), /* @__PURE__ */ d("button", {
|
|
237
246
|
type: "button",
|
|
238
|
-
onClick: () =>
|
|
247
|
+
onClick: () => _("/plans"),
|
|
239
248
|
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",
|
|
240
249
|
children: "Back to Plans"
|
|
241
250
|
})]
|
|
242
251
|
})]
|
|
243
252
|
})]
|
|
244
|
-
}) : /* @__PURE__ */
|
|
253
|
+
}) : /* @__PURE__ */ d("div", {
|
|
245
254
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
246
|
-
children: /* @__PURE__ */
|
|
255
|
+
children: /* @__PURE__ */ f("div", {
|
|
247
256
|
className: "text-center space-y-4",
|
|
248
257
|
children: [
|
|
249
|
-
/* @__PURE__ */
|
|
258
|
+
/* @__PURE__ */ d("h2", {
|
|
250
259
|
className: "text-lg font-semibold text-text-primary",
|
|
251
|
-
children:
|
|
260
|
+
children: u("billing.plans.planNotFound", "Plan not found")
|
|
252
261
|
}),
|
|
253
|
-
/* @__PURE__ */
|
|
262
|
+
/* @__PURE__ */ d("p", {
|
|
254
263
|
className: "text-sm text-text-secondary",
|
|
255
|
-
children:
|
|
264
|
+
children: b?.message || u("billing.plans.planNotFoundMessage", "The plan you are looking for does not exist.")
|
|
256
265
|
}),
|
|
257
|
-
/* @__PURE__ */
|
|
266
|
+
/* @__PURE__ */ d("button", {
|
|
258
267
|
type: "button",
|
|
259
|
-
onClick: () =>
|
|
268
|
+
onClick: () => _("/plans"),
|
|
260
269
|
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",
|
|
261
270
|
children: "Back to Plans"
|
|
262
271
|
})
|
|
@@ -265,6 +274,6 @@ var m = () => {
|
|
|
265
274
|
});
|
|
266
275
|
};
|
|
267
276
|
//#endregion
|
|
268
|
-
export {
|
|
277
|
+
export { _ as PlanBrowseDetailPage };
|
|
269
278
|
|
|
270
279
|
//# sourceMappingURL=PlanBrowseDetailPage.js.map
|
|
@@ -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-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\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: feature.quota.name ?? 'Unknown Quota',\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\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 </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 */}\n <div className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken\">\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 </div>\n\n {/* Features Card */}\n <div className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-6 border-b border-border-subtle 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 <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 {quota.totalValue.toLocaleString()}\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 )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Subscribe Card */}\n <div className=\"border border-border-subtle rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-6 border-b border-border-subtle 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-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ShowMore } 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}\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: feature.quota.name ?? 'Unknown Quota',\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\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 </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 */}\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.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 </div>\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 {quota.totalValue.toLocaleString()}\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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"}
|
|
@@ -107,7 +107,7 @@ var p = () => {
|
|
|
107
107
|
/* @__PURE__ */ d("div", {
|
|
108
108
|
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
109
109
|
children: [/* @__PURE__ */ d("div", {
|
|
110
|
-
className: "border border-border-
|
|
110
|
+
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit",
|
|
111
111
|
children: [/* @__PURE__ */ u("div", {
|
|
112
112
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
113
113
|
children: /* @__PURE__ */ u("h2", {
|
|
@@ -248,7 +248,7 @@ var p = () => {
|
|
|
248
248
|
]
|
|
249
249
|
})]
|
|
250
250
|
}), /* @__PURE__ */ d("div", {
|
|
251
|
-
className: "border border-border-
|
|
251
|
+
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit",
|
|
252
252
|
children: [/* @__PURE__ */ d("div", {
|
|
253
253
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between",
|
|
254
254
|
children: [/* @__PURE__ */ d("h2", {
|