@burdenoff/microfe-billing 2026.625.3 → 2026.625.5
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/lib/native-purchases.js +2 -44
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +192 -187
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +322 -317
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +190 -185
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +297 -292
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +60 -75
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +86 -101
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +124 -121
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +103 -111
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +196 -160
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +58 -73
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +65 -75
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +399 -399
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +49 -48
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +144 -151
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +430 -425
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +121 -93
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +154 -152
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +467 -462
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +294 -286
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +123 -117
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +43 -42
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +192 -207
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +40 -56
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +128 -121
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +165 -180
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +103 -100
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +175 -175
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +96 -96
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +42 -40
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +45 -38
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +339 -334
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +80 -74
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +342 -337
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +38 -53
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +60 -75
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +242 -237
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +29 -39
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +1 -0
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +299 -294
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +191 -186
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +308 -303
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +57 -78
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +79 -78
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +15 -13
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +338 -299
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +82 -103
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +59 -54
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +172 -215
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/package.json +2 -2
- package/dist/billing/lib/native-purchases.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditCouponPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/EditCouponPage.tsx"],"sourcesContent":["/**\n * Edit Coupon Page\n *\n * Form for editing existing coupon codes\n * Note: Only certain fields can be edited (name, description, status, usage limits, expiration)\n * Code, type, value, and scope cannot be changed after creation\n */\n\nimport { type FC, useState, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, AlertCircle, CheckCircle } from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useGetCouponLazyQuery,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { Coupon, CouponStatus } from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface EditFormData {\n name: string;\n description: string;\n status: CouponStatus;\n maxTotalUses: string;\n maxUsesPerUser: string;\n validUntil: string;\n}\n\nexport const EditCouponPage: 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 navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n // Get coupon ID from URL\n const couponId = window.location.pathname.split('/').slice(-2)[0];\n\n const [getCoupon, { data, loading: loadingCoupon, error: loadError }] = useGetCouponLazyQuery();\n const [updateCoupon, { loading: updating }] = useUpdateCouponMutation();\n\n const [formData, setFormData] = useState<EditFormData>({\n name: '',\n description: '',\n status: CouponStatus.Active,\n maxTotalUses: '',\n maxUsesPerUser: '',\n validUntil: '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [submitError, setSubmitError] = useState('');\n const [submitSuccess, setSubmitSuccess] = useState(false);\n\n // Load coupon data\n useEffect(() => {\n if (couponId) {\n getCoupon({ variables: { id: couponId } });\n }\n }, [couponId, getCoupon]);\n\n // Populate form when data loads\n useEffect(() => {\n if (data?.getCoupon) {\n const coupon = data.getCoupon as Coupon;\n setFormData({\n name: coupon.name,\n description: coupon.description || '',\n status: coupon.status,\n maxTotalUses: coupon.maxTotalUses?.toString() || '',\n maxUsesPerUser: coupon.maxUsesPerUser.toString(),\n validUntil: coupon.validUntil\n ? new Date(coupon.validUntil).toISOString().split('T')[0]\n : '',\n });\n }\n }, [data]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n const handleInputChange = (field: keyof EditFormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field]) {\n setErrors((prev) => ({ ...prev, [field]: '' }));\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: Record<string, string> = {};\n\n if (!formData.name.trim()) {\n newErrors.name = 'Name is required';\n }\n\n if (formData.maxTotalUses && parseInt(formData.maxTotalUses) < 1) {\n newErrors.maxTotalUses = 'Must be at least 1';\n }\n\n if (formData.maxUsesPerUser && parseInt(formData.maxUsesPerUser) < 1) {\n newErrors.maxUsesPerUser = 'Must be at least 1';\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError('');\n setSubmitSuccess(false);\n\n if (!validateForm()) {\n return;\n }\n\n if (!couponId) {\n setSubmitError('Coupon ID is missing');\n return;\n }\n\n try {\n const input: {\n name?: string;\n description?: string;\n status?: CouponStatus;\n maxTotalUses?: number;\n maxUsesPerUser?: number;\n validUntil?: string;\n } = {};\n\n if (formData.name.trim()) input.name = formData.name.trim();\n if (formData.description.trim()) input.description = formData.description.trim();\n if (formData.status) input.status = formData.status;\n if (formData.maxTotalUses) input.maxTotalUses = parseInt(formData.maxTotalUses);\n if (formData.maxUsesPerUser) input.maxUsesPerUser = parseInt(formData.maxUsesPerUser);\n if (formData.validUntil) input.validUntil = new Date(formData.validUntil).toISOString();\n\n await updateCoupon({\n variables: {\n id: couponId,\n input,\n },\n });\n\n setSubmitSuccess(true);\n setTimeout(() => {\n navigateTo('/coupons');\n }, 1500);\n } catch (error) {\n console.error('Failed to update coupon:', error);\n setSubmitError(error instanceof Error ? error.message : 'Failed to update coupon');\n }\n };\n\n if (loadingCoupon) {\n return (\n <div className=\"p-8\">\n <PageHeader\n title={tr('billing.coupons.editTitle', 'Edit Coupon')}\n description=\"Loading coupon data...\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n }\n />\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"text-text-muted\">Loading coupon…</div>\n </div>\n </div>\n );\n }\n\n if (loadError || !data?.getCoupon) {\n return (\n <div className=\"p-8\">\n <PageHeader\n title=\"Edit Coupon\"\n description=\"Coupon not found\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n }\n />\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"text-status-error-text\">\n {loadError ? `Error: ${loadError.message}` : 'Coupon not found'}\n </div>\n </div>\n </div>\n );\n }\n\n const coupon = data.getCoupon as Coupon;\n\n return (\n <div className=\"p-8\">\n <PageHeader\n title=\"Edit Coupon\"\n description={`Editing: ${coupon.code}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Coupons\n </button>\n }\n />\n\n <div className=\"max-w-3xl mx-auto mt-8\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Read-only Information Card */}\n <div className=\"bg-bg-sunken/30 border border-border-subtle rounded-lg p-6\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n Coupon Details (Read-Only)\n </h3>\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Code:</span>\n <span className=\"ml-2 font-mono font-medium text-text-primary\">{coupon.code}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Type:</span>\n <span className=\"ml-2 text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Value:</span>\n <span className=\"ml-2 text-text-primary\">\n {coupon.type === 'PERCENTAGE' ? `${coupon.value}%` : `$${coupon.value}`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Scope:</span>\n <span className=\"ml-2 text-text-primary\">{coupon.scope}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Current Uses:</span>\n <span className=\"ml-2 text-text-primary\">\n {coupon.currentUses}\n {coupon.maxTotalUses ? ` / ${coupon.maxTotalUses}` : ''}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Created:</span>\n <span className=\"ml-2 text-text-primary\">\n {new Date(coupon.createdAt).toLocaleDateString()}\n </span>\n </div>\n </div>\n </div>\n\n {/* Editable Fields Card */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-6\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Editable Fields</h3>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary\">\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => handleInputChange('name', e.target.value)}\n placeholder=\"e.g., Welcome Discount\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.name && <p className=\"text-sm text-status-error-text\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => handleInputChange('description', e.target.value)}\n placeholder=\"Optional description\"\n rows={3}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Status */}\n <div className=\"space-y-2\">\n <label htmlFor=\"status\" className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n id=\"status\"\n value={formData.status}\n onChange={(e) => handleInputChange('status', e.target.value)}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n <option value=\"DEPLETED\">Depleted</option>\n </select>\n </div>\n\n {/* Max Total Uses */}\n <div className=\"space-y-2\">\n <label htmlFor=\"maxTotalUses\" className=\"block text-sm font-medium text-text-primary\">\n Maximum Total Uses\n </label>\n <input\n id=\"maxTotalUses\"\n type=\"number\"\n min=\"1\"\n value={formData.maxTotalUses}\n onChange={(e) => handleInputChange('maxTotalUses', e.target.value)}\n placeholder=\"Leave empty for unlimited\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.maxTotalUses && (\n <p className=\"text-sm text-status-error-text\">{errors.maxTotalUses}</p>\n )}\n <p className=\"text-sm text-text-muted\">\n Current uses: {coupon.currentUses}\n {coupon.maxTotalUses ? ` / ${coupon.maxTotalUses}` : ''}\n </p>\n </div>\n\n {/* Max Uses Per User */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"maxUsesPerUser\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Maximum Uses Per User\n </label>\n <input\n id=\"maxUsesPerUser\"\n type=\"number\"\n min=\"1\"\n value={formData.maxUsesPerUser}\n onChange={(e) => handleInputChange('maxUsesPerUser', e.target.value)}\n placeholder=\"e.g., 1\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.maxUsesPerUser && (\n <p className=\"text-sm text-status-error-text\">{errors.maxUsesPerUser}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div className=\"space-y-2\">\n <label htmlFor=\"validUntil\" className=\"block text-sm font-medium text-text-primary\">\n Valid Until\n </label>\n <input\n id=\"validUntil\"\n type=\"date\"\n value={formData.validUntil}\n onChange={(e) => handleInputChange('validUntil', e.target.value)}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-sm text-text-muted\">Leave empty for no expiration</p>\n </div>\n </div>\n\n {/* Error/Success Messages */}\n {submitError && (\n <div className=\"flex items-start gap-3 p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border\">\n <AlertCircle className=\"size-5 text-status-error-text mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-error-text\">Error</p>\n <p className=\"text-sm text-status-error-text/90 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n {submitSuccess && (\n <div className=\"flex items-start gap-3 p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-bg-subtle\">\n <CheckCircle className=\"size-5 text-status-success-text mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">Success</p>\n <p className=\"text-sm text-status-success-text/90 mt-1\">\n Coupon updated successfully! Redirecting…\n </p>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex gap-4 pt-4 border-t border-border-subtle\">\n <button\n type=\"submit\"\n disabled={updating || submitSuccess}\n className=\"px-6 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2\"\n >\n <Save className=\"size-4\" />\n {updating ? 'Updating...' : 'Update Coupon'}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-6 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditCouponPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/EditCouponPage.tsx"],"sourcesContent":["/**\n * Edit Coupon Page\n *\n * Form for editing existing coupon codes\n * Note: Only certain fields can be edited (name, description, status, usage limits, expiration)\n * Code, type, value, and scope cannot be changed after creation\n */\n\nimport { type FC, useState, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, AlertCircle, CheckCircle } from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n useGetCouponLazyQuery,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { Coupon, CouponStatus } from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface EditFormData {\n name: string;\n description: string;\n status: CouponStatus;\n maxTotalUses: string;\n maxUsesPerUser: string;\n validUntil: string;\n}\n\nexport const EditCouponPage: 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 navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n // Get coupon ID from URL\n const couponId = window.location.pathname.split('/').slice(-2)[0];\n\n const [getCoupon, { data, loading: loadingCoupon, error: loadError }] = useGetCouponLazyQuery();\n const [updateCoupon, { loading: updating }] = useUpdateCouponMutation();\n\n const [formData, setFormData] = useState<EditFormData>({\n name: '',\n description: '',\n status: CouponStatus.Active,\n maxTotalUses: '',\n maxUsesPerUser: '',\n validUntil: '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [submitError, setSubmitError] = useState('');\n const [submitSuccess, setSubmitSuccess] = useState(false);\n\n // Load coupon data\n useEffect(() => {\n if (couponId) {\n getCoupon({ variables: { id: couponId } });\n }\n }, [couponId, getCoupon]);\n\n // Populate form when data loads\n useEffect(() => {\n if (data?.getCoupon) {\n const coupon = data.getCoupon as Coupon;\n setFormData({\n name: coupon.name,\n description: coupon.description || '',\n status: coupon.status,\n maxTotalUses: coupon.maxTotalUses?.toString() || '',\n maxUsesPerUser: coupon.maxUsesPerUser.toString(),\n validUntil: coupon.validUntil\n ? new Date(coupon.validUntil).toISOString().split('T')[0]\n : '',\n });\n }\n }, [data]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n const handleInputChange = (field: keyof EditFormData, value: string) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field]) {\n setErrors((prev) => ({ ...prev, [field]: '' }));\n }\n };\n\n const validateForm = (): boolean => {\n const newErrors: Record<string, string> = {};\n\n if (!formData.name.trim()) {\n newErrors.name = 'Name is required';\n }\n\n if (formData.maxTotalUses && parseInt(formData.maxTotalUses) < 1) {\n newErrors.maxTotalUses = 'Must be at least 1';\n }\n\n if (formData.maxUsesPerUser && parseInt(formData.maxUsesPerUser) < 1) {\n newErrors.maxUsesPerUser = 'Must be at least 1';\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError('');\n setSubmitSuccess(false);\n\n if (!validateForm()) {\n return;\n }\n\n if (!couponId) {\n setSubmitError('Coupon ID is missing');\n return;\n }\n\n try {\n const input: {\n name?: string;\n description?: string;\n status?: CouponStatus;\n maxTotalUses?: number;\n maxUsesPerUser?: number;\n validUntil?: string;\n } = {};\n\n if (formData.name.trim()) input.name = formData.name.trim();\n if (formData.description.trim()) input.description = formData.description.trim();\n if (formData.status) input.status = formData.status;\n if (formData.maxTotalUses) input.maxTotalUses = parseInt(formData.maxTotalUses);\n if (formData.maxUsesPerUser) input.maxUsesPerUser = parseInt(formData.maxUsesPerUser);\n if (formData.validUntil) input.validUntil = new Date(formData.validUntil).toISOString();\n\n await updateCoupon({\n variables: {\n id: couponId,\n input,\n },\n });\n\n setSubmitSuccess(true);\n setTimeout(() => {\n navigateTo('/coupons');\n }, 1500);\n } catch (error) {\n console.error('Failed to update coupon:', error);\n setSubmitError(error instanceof Error ? error.message : 'Failed to update coupon');\n }\n };\n\n if (loadingCoupon) {\n return (\n <div className=\"p-8\">\n <PageHeader\n title={tr('billing.coupons.editTitle', 'Edit Coupon')}\n description=\"Loading coupon data...\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n }\n />\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"text-text-muted\">Loading coupon…</div>\n </div>\n </div>\n );\n }\n\n if (loadError || !data?.getCoupon) {\n return (\n <div className=\"p-8\">\n <PageHeader\n title=\"Edit Coupon\"\n description=\"Coupon not found\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n }\n />\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"text-status-error-text\">\n {loadError ? `Error: ${loadError.message}` : 'Coupon not found'}\n </div>\n </div>\n </div>\n );\n }\n\n const coupon = data.getCoupon as Coupon;\n\n return (\n <div className=\"p-8\">\n <PageHeader\n title=\"Edit Coupon\"\n description={`Editing: ${coupon.code}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Coupons\n </button>\n }\n />\n\n <PagePurpose className=\"mt-2\">\n Update an existing coupon's discount, limits, expiry or applicable products. The code\n itself and its usage history are read-only; deactivate the coupon if you want to stop it\n being redeemed.\n </PagePurpose>\n\n <div className=\"max-w-3xl mx-auto mt-8\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Read-only Information Card */}\n <div className=\"bg-bg-sunken/30 border border-border-subtle rounded-lg p-6\">\n <h3 className=\"text-lg font-semibold text-text-primary mb-4\">\n Coupon Details (Read-Only)\n </h3>\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Code:</span>\n <span className=\"ml-2 font-mono font-medium text-text-primary\">{coupon.code}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Type:</span>\n <span className=\"ml-2 text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Value:</span>\n <span className=\"ml-2 text-text-primary\">\n {coupon.type === 'PERCENTAGE' ? `${coupon.value}%` : `$${coupon.value}`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Scope:</span>\n <span className=\"ml-2 text-text-primary\">{coupon.scope}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">Current Uses:</span>\n <span className=\"ml-2 text-text-primary\">\n {coupon.currentUses}\n {coupon.maxTotalUses ? ` / ${coupon.maxTotalUses}` : ''}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Created:</span>\n <span className=\"ml-2 text-text-primary\">\n {new Date(coupon.createdAt).toLocaleDateString()}\n </span>\n </div>\n </div>\n </div>\n\n {/* Editable Fields Card */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-6\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Editable Fields</h3>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary\">\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => handleInputChange('name', e.target.value)}\n placeholder=\"e.g., Welcome Discount\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.name && <p className=\"text-sm text-status-error-text\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => handleInputChange('description', e.target.value)}\n placeholder=\"Optional description\"\n rows={3}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Status */}\n <div className=\"space-y-2\">\n <label htmlFor=\"status\" className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n id=\"status\"\n value={formData.status}\n onChange={(e) => handleInputChange('status', e.target.value)}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n <option value=\"DEPLETED\">Depleted</option>\n </select>\n </div>\n\n {/* Max Total Uses */}\n <div className=\"space-y-2\">\n <label htmlFor=\"maxTotalUses\" className=\"block text-sm font-medium text-text-primary\">\n Maximum Total Uses\n </label>\n <input\n id=\"maxTotalUses\"\n type=\"number\"\n min=\"1\"\n value={formData.maxTotalUses}\n onChange={(e) => handleInputChange('maxTotalUses', e.target.value)}\n placeholder=\"Leave empty for unlimited\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.maxTotalUses && (\n <p className=\"text-sm text-status-error-text\">{errors.maxTotalUses}</p>\n )}\n <p className=\"text-sm text-text-muted\">\n Current uses: {coupon.currentUses}\n {coupon.maxTotalUses ? ` / ${coupon.maxTotalUses}` : ''}\n </p>\n </div>\n\n {/* Max Uses Per User */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"maxUsesPerUser\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Maximum Uses Per User\n </label>\n <input\n id=\"maxUsesPerUser\"\n type=\"number\"\n min=\"1\"\n value={formData.maxUsesPerUser}\n onChange={(e) => handleInputChange('maxUsesPerUser', e.target.value)}\n placeholder=\"e.g., 1\"\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {errors.maxUsesPerUser && (\n <p className=\"text-sm text-status-error-text\">{errors.maxUsesPerUser}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div className=\"space-y-2\">\n <label htmlFor=\"validUntil\" className=\"block text-sm font-medium text-text-primary\">\n Valid Until\n </label>\n <input\n id=\"validUntil\"\n type=\"date\"\n value={formData.validUntil}\n onChange={(e) => handleInputChange('validUntil', e.target.value)}\n className=\"w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-sm text-text-muted\">Leave empty for no expiration</p>\n </div>\n </div>\n\n {/* Error/Success Messages */}\n {submitError && (\n <div className=\"flex items-start gap-3 p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border\">\n <AlertCircle className=\"size-5 text-status-error-text mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-error-text\">Error</p>\n <p className=\"text-sm text-status-error-text/90 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n {submitSuccess && (\n <div className=\"flex items-start gap-3 p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-bg-subtle\">\n <CheckCircle className=\"size-5 text-status-success-text mt-0.5\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">Success</p>\n <p className=\"text-sm text-status-success-text/90 mt-1\">\n Coupon updated successfully! Redirecting…\n </p>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex gap-4 pt-4 border-t border-border-subtle\">\n <button\n type=\"submit\"\n disabled={updating || submitSuccess}\n className=\"px-6 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2\"\n >\n <Save className=\"size-4\" />\n {updating ? 'Updating...' : 'Update Coupon'}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-6 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -6,135 +6,141 @@ import { useGetBillingAccountsSettingsQuery as r } from "../../../../generated/g
|
|
|
6
6
|
import { useCreditMutations as i, useCreditTransactions as a } from "../hooks/useCredits.js";
|
|
7
7
|
import { useMemo as o, useState as s } from "react";
|
|
8
8
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
9
|
+
import { EmphasisPanel as u, IllustratedEmptyState as d, PagePurpose as f } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/billing/modules/credits/pages/AdminCreditsPage.tsx
|
|
10
|
-
var
|
|
11
|
-
let
|
|
12
|
-
billingAccountId:
|
|
11
|
+
var p = () => {
|
|
12
|
+
let p = n(), { grantCredits: m } = i(), { data: h, loading: g, refetch: _ } = r(), v = o(() => h?.getBillingAccountsByOrg || [], [h]), [y, b] = s(""), [x, S] = s(""), [C, w] = s(""), [T, E] = s(!1), [D, O] = s(null), [k, A] = s(null), j = y || v[0]?.id || "", { transactions: M, isLoading: N, refetch: P } = a({
|
|
13
|
+
billingAccountId: j,
|
|
13
14
|
days: 30,
|
|
14
15
|
page: 1,
|
|
15
16
|
pageSize: 10
|
|
16
17
|
});
|
|
17
|
-
return
|
|
18
|
+
return p.canGrantCredits ? /* @__PURE__ */ l("div", {
|
|
18
19
|
className: "space-y-6 p-6",
|
|
19
|
-
children: [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
className: "text-right",
|
|
123
|
-
children: [/* @__PURE__ */ l("p", {
|
|
124
|
-
className: "text-sm font-semibold text-text-primary",
|
|
125
|
-
children: [e.amount > 0 ? "+" : "", e.amount]
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ c(t, {
|
|
22
|
+
title: "Credit Grants",
|
|
23
|
+
description: "Grant credits to billing accounts and review the latest credit ledger activity."
|
|
24
|
+
}),
|
|
25
|
+
/* @__PURE__ */ c(f, { children: "Give credits to a customer's billing account — for promotions, service recovery or manual adjustments — and immediately see them reflected in that account's credit ledger. Pick an account, enter an amount and a reason, and grant." }),
|
|
26
|
+
/* @__PURE__ */ l("div", {
|
|
27
|
+
className: "grid grid-cols-1 xl:grid-cols-[360px_minmax(0,1fr)] gap-6",
|
|
28
|
+
children: [/* @__PURE__ */ l(u, {
|
|
29
|
+
className: "p-6 space-y-4 h-fit",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("label", {
|
|
32
|
+
className: "block text-sm font-medium text-text-primary mb-1",
|
|
33
|
+
children: "Billing Account"
|
|
34
|
+
}), /* @__PURE__ */ c("select", {
|
|
35
|
+
value: j,
|
|
36
|
+
onChange: (e) => b(e.target.value),
|
|
37
|
+
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)]",
|
|
38
|
+
children: v.map((e) => /* @__PURE__ */ c("option", {
|
|
39
|
+
value: e.id,
|
|
40
|
+
children: e.name
|
|
41
|
+
}, e.id))
|
|
42
|
+
})] }),
|
|
43
|
+
/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("label", {
|
|
44
|
+
className: "block text-sm font-medium text-text-primary mb-1",
|
|
45
|
+
children: "Credits"
|
|
46
|
+
}), /* @__PURE__ */ c("input", {
|
|
47
|
+
type: "number",
|
|
48
|
+
min: "1",
|
|
49
|
+
step: "1",
|
|
50
|
+
value: x,
|
|
51
|
+
onChange: (e) => S(e.target.value),
|
|
52
|
+
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)]",
|
|
53
|
+
placeholder: "500"
|
|
54
|
+
})] }),
|
|
55
|
+
/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("label", {
|
|
56
|
+
className: "block text-sm font-medium text-text-primary mb-1",
|
|
57
|
+
children: "Reason"
|
|
58
|
+
}), /* @__PURE__ */ c("textarea", {
|
|
59
|
+
value: C,
|
|
60
|
+
onChange: (e) => w(e.target.value),
|
|
61
|
+
className: "w-full min-h-24 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)]",
|
|
62
|
+
placeholder: "Promotional credit grant, service recovery, manual adjustment..."
|
|
63
|
+
})] }),
|
|
64
|
+
D ? /* @__PURE__ */ c("p", {
|
|
65
|
+
className: "text-sm text-status-success-text",
|
|
66
|
+
children: D
|
|
67
|
+
}) : null,
|
|
68
|
+
k ? /* @__PURE__ */ c("p", {
|
|
69
|
+
className: "text-sm text-status-error-text",
|
|
70
|
+
children: k
|
|
71
|
+
}) : null,
|
|
72
|
+
/* @__PURE__ */ c("button", {
|
|
73
|
+
type: "button",
|
|
74
|
+
onClick: async () => {
|
|
75
|
+
if (!j) {
|
|
76
|
+
A("Select a billing account first.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!x || Number(x) <= 0) {
|
|
80
|
+
A("Enter a valid credit amount.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
E(!0), A(null), O(null);
|
|
84
|
+
try {
|
|
85
|
+
await m({
|
|
86
|
+
billingAccountId: j,
|
|
87
|
+
amount: Number(x),
|
|
88
|
+
reason: C
|
|
89
|
+
}), await Promise.all([_(), P()]), S(""), w(""), O("Credits granted successfully.");
|
|
90
|
+
} catch (e) {
|
|
91
|
+
A(e instanceof Error ? e.message : "Failed to grant credits.");
|
|
92
|
+
} finally {
|
|
93
|
+
E(!1);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
disabled: g || T || v.length === 0,
|
|
97
|
+
className: "w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
98
|
+
children: T ? "Granting Credits..." : "Grant Credits"
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
}), /* @__PURE__ */ l("div", {
|
|
102
|
+
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
103
|
+
children: [/* @__PURE__ */ c("div", {
|
|
104
|
+
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
105
|
+
children: /* @__PURE__ */ c("h2", {
|
|
106
|
+
className: "font-semibold text-text-primary",
|
|
107
|
+
children: "Recent Credit Ledger"
|
|
108
|
+
})
|
|
109
|
+
}), N ? /* @__PURE__ */ c("div", {
|
|
110
|
+
className: "p-6 text-sm text-text-muted",
|
|
111
|
+
children: "Loading recent credit activity…"
|
|
112
|
+
}) : M.length === 0 ? /* @__PURE__ */ c(d, {
|
|
113
|
+
illustration: "empty-data",
|
|
114
|
+
title: "No recent credit activity",
|
|
115
|
+
description: "Credit grants and other ledger entries for this account will show up here."
|
|
116
|
+
}) : /* @__PURE__ */ c("div", {
|
|
117
|
+
className: "divide-y divide-border-subtle",
|
|
118
|
+
children: M.map((e) => /* @__PURE__ */ l("div", {
|
|
119
|
+
className: "p-4 flex items-center justify-between gap-4",
|
|
120
|
+
children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("p", {
|
|
121
|
+
className: "text-sm font-medium text-text-primary",
|
|
122
|
+
children: e.type
|
|
126
123
|
}), /* @__PURE__ */ c("p", {
|
|
127
124
|
className: "text-xs text-text-muted",
|
|
128
|
-
children: e.
|
|
125
|
+
children: e.context?.reason || "No reason"
|
|
126
|
+
})] }), /* @__PURE__ */ l("div", {
|
|
127
|
+
className: "text-right",
|
|
128
|
+
children: [/* @__PURE__ */ l("p", {
|
|
129
|
+
className: "text-sm font-semibold text-text-primary",
|
|
130
|
+
children: [e.amount > 0 ? "+" : "", e.amount]
|
|
131
|
+
}), /* @__PURE__ */ c("p", {
|
|
132
|
+
className: "text-xs text-text-muted",
|
|
133
|
+
children: e.status
|
|
134
|
+
})]
|
|
129
135
|
})]
|
|
130
|
-
})
|
|
131
|
-
}
|
|
136
|
+
}, e.id))
|
|
137
|
+
})]
|
|
132
138
|
})]
|
|
133
|
-
})
|
|
134
|
-
|
|
139
|
+
})
|
|
140
|
+
]
|
|
135
141
|
}) : /* @__PURE__ */ c(e, { message: "You don't have permission to grant credits." });
|
|
136
142
|
};
|
|
137
143
|
//#endregion
|
|
138
|
-
export {
|
|
144
|
+
export { p as AdminCreditsPage };
|
|
139
145
|
|
|
140
146
|
//# sourceMappingURL=AdminCreditsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminCreditsPage.js","names":[],"sources":["../../../../../src/billing/modules/credits/pages/AdminCreditsPage.tsx"],"sourcesContent":["import { useMemo, useState, type FC } from 'react';\n\nimport { useGetBillingAccountsSettingsQuery } from '../../../../generated/global-operations';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport type { BillingAccount } from '../../../shared/types';\nimport { useCreditMutations, useCreditTransactions } from '../hooks/useCredits';\n\nexport const AdminCreditsPage: FC = () => {\n const permissions = useBillingPermissions();\n const { grantCredits } = useCreditMutations();\n const { data, loading, refetch } = useGetBillingAccountsSettingsQuery();\n const billingAccounts = useMemo<BillingAccount[]>(\n () => data?.getBillingAccountsByOrg || [],\n [data]\n );\n const [selectedBillingAccountId, setSelectedBillingAccountId] = useState<string>('');\n const [amount, setAmount] = useState<string>('');\n const [reason, setReason] = useState<string>('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const activeBillingAccountId = selectedBillingAccountId || billingAccounts[0]?.id || '';\n const {\n transactions,\n isLoading: isLoadingTransactions,\n refetch: refetchTransactions,\n } = useCreditTransactions({\n billingAccountId: activeBillingAccountId,\n days: 30,\n page: 1,\n pageSize: 10,\n });\n\n if (!permissions.canGrantCredits) {\n return <AccessDenied message=\"You don't have permission to grant credits.\" />;\n }\n\n const handleGrantCredits = async () => {\n if (!activeBillingAccountId) {\n setError('Select a billing account first.');\n return;\n }\n\n if (!amount || Number(amount) <= 0) {\n setError('Enter a valid credit amount.');\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n setMessage(null);\n\n try {\n await grantCredits({\n billingAccountId: activeBillingAccountId,\n amount: Number(amount),\n reason,\n });\n\n await Promise.all([refetch(), refetchTransactions()]);\n setAmount('');\n setReason('');\n setMessage('Credits granted successfully.');\n } catch (grantError) {\n setError(grantError instanceof Error ? grantError.message : 'Failed to grant credits.');\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Credit Grants\"\n description=\"Grant credits to billing accounts and review the latest credit ledger activity.\"\n />\n\n <div className=\"grid grid-cols-1 xl:grid-cols-[360px_minmax(0,1fr)] gap-6\">\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface p-6 space-y-4 h-fit\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Billing Account\n </label>\n <select\n value={activeBillingAccountId}\n onChange={(event) => setSelectedBillingAccountId(event.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 >\n {billingAccounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.name}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Credits</label>\n <input\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={amount}\n onChange={(event) => setAmount(event.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 placeholder=\"500\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Reason</label>\n <textarea\n value={reason}\n onChange={(event) => setReason(event.target.value)}\n className=\"w-full min-h-24 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 placeholder=\"Promotional credit grant, service recovery, manual adjustment...\"\n />\n </div>\n\n {message ? <p className=\"text-sm text-status-success-text\">{message}</p> : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n\n <button\n type=\"button\"\n onClick={handleGrantCredits}\n disabled={loading || isSubmitting || billingAccounts.length === 0}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting ? 'Granting Credits...' : 'Grant Credits'}\n </button>\n </div>\n\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Recent Credit Ledger</h2>\n </div>\n\n {isLoadingTransactions ? (\n <div className=\"p-6 text-sm text-text-muted\">Loading recent credit activity…</div>\n ) : transactions.length === 0 ? (\n <div className=\"p-6 text-sm text-text-muted\">No recent credit transactions found.</div>\n ) : (\n <div className=\"divide-y divide-border-subtle\">\n {transactions.map((transaction) => (\n <div key={transaction.id} className=\"p-4 flex items-center justify-between gap-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{transaction.type}</p>\n <p className=\"text-xs text-text-muted\">\n {(transaction.context as { reason?: string } | undefined)?.reason ||\n 'No reason'}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {transaction.amount > 0 ? '+' : ''}\n {transaction.amount}\n </p>\n <p className=\"text-xs text-text-muted\">{transaction.status}</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminCreditsPage.js","names":[],"sources":["../../../../../src/billing/modules/credits/pages/AdminCreditsPage.tsx"],"sourcesContent":["import { useMemo, useState, type FC } from 'react';\n\nimport { useGetBillingAccountsSettingsQuery } from '../../../../generated/global-operations';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport type { BillingAccount } from '../../../shared/types';\nimport { useCreditMutations, useCreditTransactions } from '../hooks/useCredits';\nimport { PagePurpose, EmphasisPanel, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nexport const AdminCreditsPage: FC = () => {\n const permissions = useBillingPermissions();\n const { grantCredits } = useCreditMutations();\n const { data, loading, refetch } = useGetBillingAccountsSettingsQuery();\n const billingAccounts = useMemo<BillingAccount[]>(\n () => data?.getBillingAccountsByOrg || [],\n [data]\n );\n const [selectedBillingAccountId, setSelectedBillingAccountId] = useState<string>('');\n const [amount, setAmount] = useState<string>('');\n const [reason, setReason] = useState<string>('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const activeBillingAccountId = selectedBillingAccountId || billingAccounts[0]?.id || '';\n const {\n transactions,\n isLoading: isLoadingTransactions,\n refetch: refetchTransactions,\n } = useCreditTransactions({\n billingAccountId: activeBillingAccountId,\n days: 30,\n page: 1,\n pageSize: 10,\n });\n\n if (!permissions.canGrantCredits) {\n return <AccessDenied message=\"You don't have permission to grant credits.\" />;\n }\n\n const handleGrantCredits = async () => {\n if (!activeBillingAccountId) {\n setError('Select a billing account first.');\n return;\n }\n\n if (!amount || Number(amount) <= 0) {\n setError('Enter a valid credit amount.');\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n setMessage(null);\n\n try {\n await grantCredits({\n billingAccountId: activeBillingAccountId,\n amount: Number(amount),\n reason,\n });\n\n await Promise.all([refetch(), refetchTransactions()]);\n setAmount('');\n setReason('');\n setMessage('Credits granted successfully.');\n } catch (grantError) {\n setError(grantError instanceof Error ? grantError.message : 'Failed to grant credits.');\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Credit Grants\"\n description=\"Grant credits to billing accounts and review the latest credit ledger activity.\"\n />\n\n <PagePurpose>\n Give credits to a customer's billing account — for promotions, service recovery or\n manual adjustments — and immediately see them reflected in that account's credit\n ledger. Pick an account, enter an amount and a reason, and grant.\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 xl:grid-cols-[360px_minmax(0,1fr)] gap-6\">\n <EmphasisPanel className=\"p-6 space-y-4 h-fit\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Billing Account\n </label>\n <select\n value={activeBillingAccountId}\n onChange={(event) => setSelectedBillingAccountId(event.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 >\n {billingAccounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.name}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Credits</label>\n <input\n type=\"number\"\n min=\"1\"\n step=\"1\"\n value={amount}\n onChange={(event) => setAmount(event.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 placeholder=\"500\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Reason</label>\n <textarea\n value={reason}\n onChange={(event) => setReason(event.target.value)}\n className=\"w-full min-h-24 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 placeholder=\"Promotional credit grant, service recovery, manual adjustment...\"\n />\n </div>\n\n {message ? <p className=\"text-sm text-status-success-text\">{message}</p> : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n\n <button\n type=\"button\"\n onClick={handleGrantCredits}\n disabled={loading || isSubmitting || billingAccounts.length === 0}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isSubmitting ? 'Granting Credits...' : 'Grant Credits'}\n </button>\n </EmphasisPanel>\n\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Recent Credit Ledger</h2>\n </div>\n\n {isLoadingTransactions ? (\n <div className=\"p-6 text-sm text-text-muted\">Loading recent credit activity…</div>\n ) : transactions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No recent credit activity\"\n description=\"Credit grants and other ledger entries for this account will show up here.\"\n />\n ) : (\n <div className=\"divide-y divide-border-subtle\">\n {transactions.map((transaction) => (\n <div key={transaction.id} className=\"p-4 flex items-center justify-between gap-4\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{transaction.type}</p>\n <p className=\"text-xs text-text-muted\">\n {(transaction.context as { reason?: string } | undefined)?.reason ||\n 'No reason'}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {transaction.amount > 0 ? '+' : ''}\n {transaction.amount}\n </p>\n <p className=\"text-xs text-text-muted\">{transaction.status}</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AASA,IAAa,UAA6B;CACxC,IAAM,IAAc,GAAuB,EACrC,EAAE,oBAAiB,GAAoB,EACvC,EAAE,SAAM,YAAS,eAAY,GAAoC,EACjE,IAAkB,QAChB,GAAM,2BAA2B,EAAE,EACzC,CAAC,EAAK,CACP,EACK,CAAC,GAA0B,KAA+B,EAAiB,GAAG,EAC9E,CAAC,GAAQ,KAAa,EAAiB,GAAG,EAC1C,CAAC,GAAQ,KAAa,EAAiB,GAAG,EAC1C,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAS,KAAc,EAAwB,KAAK,EACrD,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAyB,KAA4B,EAAgB,IAAI,MAAM,IAC/E,EACJ,iBACA,WAAW,GACX,SAAS,MACP,EAAsB;EACxB,kBAAkB;EAClB,MAAM;EACN,MAAM;EACN,UAAU;EACX,CAAC;AAuCF,QArCK,EAAY,kBAsCf,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,CAAA;GAEF,kBAAC,GAAD,EAAA,UAAa,yOAIC,CAAA;GAEd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAe,WAAU;eAAzB;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAmD;OAE5D,CAAA,EACR,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAU,EAA4B,EAAM,OAAO,MAAM;OACpE,WAAU;iBAET,EAAgB,KAAK,MACpB,kBAAC,UAAD;QAAyB,OAAO,EAAQ;kBACrC,EAAQ;QACF,EAFI,EAAQ,GAEZ,CACT;OACK,CAAA,CACL,EAAA,CAAA;MAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAmD;OAAe,CAAA,EACnF,kBAAC,SAAD;OACE,MAAK;OACL,KAAI;OACJ,MAAK;OACL,OAAO;OACP,WAAW,MAAU,EAAU,EAAM,OAAO,MAAM;OAClD,WAAU;OACV,aAAY;OACZ,CAAA,CACE,EAAA,CAAA;MAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAmD;OAAc,CAAA,EAClF,kBAAC,YAAD;OACE,OAAO;OACP,WAAW,MAAU,EAAU,EAAM,OAAO,MAAM;OAClD,WAAU;OACV,aAAY;OACZ,CAAA,CACE,EAAA,CAAA;MAEL,IAAU,kBAAC,KAAD;OAAG,WAAU;iBAAoC;OAAY,CAAA,GAAG;MAC1E,IAAQ,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAAU,CAAA,GAAG;MAErE,kBAAC,UAAD;OACE,MAAK;OACL,SA7FiB,YAAY;AACrC,YAAI,CAAC,GAAwB;AAC3B,WAAS,kCAAkC;AAC3C;;AAGF,YAAI,CAAC,KAAU,OAAO,EAAO,IAAI,GAAG;AAClC,WAAS,+BAA+B;AACxC;;AAKF,QAFA,EAAgB,GAAK,EACrB,EAAS,KAAK,EACd,EAAW,KAAK;AAEhB,YAAI;AAUF,SATA,MAAM,EAAa;UACjB,kBAAkB;UAClB,QAAQ,OAAO,EAAO;UACtB;UACD,CAAC,EAEF,MAAM,QAAQ,IAAI,CAAC,GAAS,EAAE,GAAqB,CAAC,CAAC,EACrD,EAAU,GAAG,EACb,EAAU,GAAG,EACb,EAAW,gCAAgC;iBACpC,GAAY;AACnB,WAAS,aAAsB,QAAQ,EAAW,UAAU,2BAA2B;kBAC/E;AACR,WAAgB,GAAM;;;OAiEhB,UAAU,KAAW,KAAgB,EAAgB,WAAW;OAChE,WAAU;iBAET,IAAe,wBAAwB;OACjC,CAAA;MACK;QAEhB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAkC;OAAyB,CAAA;MACrE,CAAA,EAEL,IACC,kBAAC,OAAD;MAAK,WAAU;gBAA8B;MAAqC,CAAA,GAChF,EAAa,WAAW,IAC1B,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAa,KAAK,MACjB,kBAAC,OAAD;OAA0B,WAAU;iBAApC,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAyC,EAAY;QAAS,CAAA,EAC3E,kBAAC,KAAD;QAAG,WAAU;kBACT,EAAY,SAA6C,UACzD;QACA,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACG,EAAY,SAAS,IAAI,MAAM,IAC/B,EAAY,OACX;YACJ,kBAAC,KAAD;SAAG,WAAU;mBAA2B,EAAY;SAAW,CAAA,CAC3D;UACF;SAfI,EAAY,GAehB,CACN;MACE,CAAA,CAEJ;OACF;;GACF;MA7IC,kBAAC,GAAD,EAAc,SAAQ,+CAAgD,CAAA"}
|