@burdenoff/microfe-billing 2026.1013.3 → 2026.1014.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/billing/modules/addons/pages/AddonCreatePage.js +4 -4
  2. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -3
  4. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  5. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +1 -1
  6. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  7. package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js +3 -3
  8. package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js.map +1 -1
  9. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  10. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js +2 -2
  12. package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js.map +1 -1
  13. package/dist/billing/modules/billing/pages/RefundsPage.js +2 -2
  14. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  15. package/dist/billing/modules/billing/pages/UserRefundsPage.js +1 -1
  16. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  17. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
  18. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  19. package/dist/billing/modules/checkout/components/StoreShippingSection.js +1 -1
  20. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  21. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +1 -1
  22. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  23. package/dist/billing/modules/coupons/pages/CouponsListPage.js +2 -2
  24. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  25. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +2 -2
  26. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  27. package/dist/billing/modules/coupons/pages/EditCouponPage.js +1 -1
  28. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  29. package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
  30. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  31. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +2 -2
  32. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  33. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
  34. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  35. package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
  36. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  37. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +1 -1
  38. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -1
  39. package/dist/billing/modules/earnings/pages/EarningsPage.js +1 -1
  40. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  41. package/dist/billing/modules/earnings/pages/PayoutsPage.js +3 -3
  42. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  43. package/dist/billing/modules/plans/pages/PlanCreatePage.js +3 -3
  44. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  45. package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -3
  46. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  47. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
  48. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  49. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +2 -2
  50. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  51. package/dist/billing/modules/settings/pages/SettingsPage.js +1 -1
  52. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  53. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +1 -1
  54. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  55. package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
  56. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  57. package/dist/generated/global-operations.js +1 -1
  58. package/dist/generated/global-operations.js.map +1 -1
  59. package/dist/generated/global-types.js.map +1 -1
  60. package/dist/generated/wspace-operations.js +1 -1
  61. package/dist/generated/wspace-operations.js.map +1 -1
  62. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CreateCouponPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CreateCouponPage.tsx"],"sourcesContent":["/**\n * Create Coupon Page\n *\n * Form for creating new coupon codes with validation\n */\n\nimport { type FC, useState, useRef, type KeyboardEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, Tag, Percent, Package, X, Users } from 'lucide-react';\n\nconst CURRENCIES: { code: string; symbol: string; name: string }[] = [\n { code: 'USD', symbol: '$', name: 'US Dollar' },\n { code: 'EUR', symbol: '€', name: 'Euro' },\n { code: 'GBP', symbol: '£', name: 'British Pound' },\n { code: 'INR', symbol: '₹', name: 'Indian Rupee' },\n { code: 'AUD', symbol: 'A$', name: 'Australian Dollar' },\n { code: 'CAD', symbol: 'C$', name: 'Canadian Dollar' },\n { code: 'SGD', symbol: 'S$', name: 'Singapore Dollar' },\n { code: 'JPY', symbol: '¥', name: 'Japanese Yen' },\n { code: 'MXN', symbol: 'MX$', name: 'Mexican Peso' },\n { code: 'BRL', symbol: 'R$', name: 'Brazilian Real' },\n { code: 'CHF', symbol: 'Fr', name: 'Swiss Franc' },\n { code: 'HKD', symbol: 'HK$', name: 'Hong Kong Dollar' },\n { code: 'NZD', symbol: 'NZ$', name: 'New Zealand Dollar' },\n { code: 'SEK', symbol: 'kr', name: 'Swedish Krona' },\n { code: 'KRW', symbol: '₩', name: 'South Korean Won' },\n];\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useCreateCouponMutation } from '../../../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// Product slugs a coupon can be scoped to (Plan.productID / Addon.productID),\n// same convention CreatePromotionPage.tsx uses for Promotion.applicableProductIds.\nconst KNOWN_PRODUCTS = [\n 'workspaces',\n 'bigconsole',\n 'botlit',\n 'fluidgrids',\n 'vibecontrols',\n 'healthybowl',\n 'adaptercloud',\n 'atheletebridge',\n 'coolandlovely',\n 'hookmovies',\n 'labsofscience',\n 'subscriberbot',\n 'artistrybase',\n 'assethandler',\n 'brainyrich',\n 'buildmyiq',\n 'collabkin',\n 'comradecircle',\n 'cosmicintersection',\n 'crewfoundry',\n 'ecoimpacthub',\n 'eventfullymanaged',\n 'ggnomad',\n 'govcitizenhub',\n 'headshotmarketing',\n 'housingvista',\n 'intelwatchtower',\n 'kadaikodi',\n 'manufacturedops',\n 'movethewheels',\n 'mybodyshield',\n 'planmagnet',\n 'proserviceworld',\n 'semanticfed',\n 'technospam',\n 'theculturepost',\n 'theglobalfuel',\n 'timecampus',\n];\n\nconst ENTITY_TYPES: { value: 'PLAN' | 'ADD_ON' | 'STORE'; label: string }[] = [\n { value: 'PLAN', label: 'Plan purchases' },\n { value: 'ADD_ON', label: 'Add-on purchases' },\n { value: 'STORE', label: 'Store purchases' },\n];\n\ninterface CouponFormData {\n code: string;\n name: string;\n description: string;\n scope: 'GENERIC' | 'USER_SPECIFIC' | 'REFERRAL' | 'CAMPAIGN';\n type: 'PERCENTAGE' | 'FIXED_AMOUNT' | 'FREE_SHIPPING';\n value: number;\n currency: string;\n minPurchaseAmount: number | null;\n maxDiscountAmount: number | null;\n maxTotalUses: number | null;\n maxUsesPerUser: number;\n validFrom: string;\n validUntil: string;\n restrictedToUserIds: string[];\n applicableProducts: string[];\n applicableServices: string[];\n applicableProductIds: string[];\n applicableEntityTypes: ('PLAN' | 'ADD_ON' | 'STORE')[];\n campaignName: string;\n}\n\nexport const CreateCouponPage: 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 const [userIdInput, setUserIdInput] = useState('');\n const userIdInputRef = useRef<HTMLInputElement>(null);\n\n const [formData, setFormData] = useState<CouponFormData>({\n code: '',\n name: '',\n description: '',\n scope: 'GENERIC',\n type: 'PERCENTAGE',\n value: 0,\n currency: 'USD',\n minPurchaseAmount: null,\n maxDiscountAmount: null,\n maxTotalUses: null,\n maxUsesPerUser: 1,\n validFrom: new Date().toISOString().split('T')[0],\n validUntil: '',\n restrictedToUserIds: [],\n applicableProducts: [],\n applicableServices: [],\n applicableProductIds: [],\n applicableEntityTypes: [],\n campaignName: '',\n });\n\n const [errors, setErrors] = useState<Partial<Record<keyof CouponFormData, string>>>({});\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const currencySymbol =\n CURRENCIES.find((c) => c.code === formData.currency)?.symbol ?? formData.currency;\n\n // GraphQL mutation\n const [createCouponMutation] = useCreateCouponMutation({ refetchQueries: ['ListCoupons'] });\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to create coupons.\" />;\n }\n\n // Handle input change\n const handleChange = (\n field: keyof CouponFormData,\n value: CouponFormData[keyof CouponFormData]\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n\n // Clear error for this field\n if (errors[field]) {\n setErrors((prev) => {\n const newErrors = { ...prev };\n delete newErrors[field];\n return newErrors;\n });\n }\n };\n\n // Validate form\n const validateForm = (): boolean => {\n const newErrors: Partial<Record<keyof CouponFormData, string>> = {};\n\n // Code validation\n if (!formData.code.trim()) {\n newErrors.code = 'Coupon code is required';\n } else if (!/^[A-Z0-9-_]+$/.test(formData.code)) {\n newErrors.code =\n 'Code must contain only uppercase letters, numbers, hyphens, and underscores';\n } else if (formData.code.length < 3) {\n newErrors.code = 'Code must be at least 3 characters';\n } else if (formData.code.length > 50) {\n newErrors.code = 'Code must be at most 50 characters';\n }\n\n // Name validation\n if (!formData.name.trim()) {\n newErrors.name = 'Coupon name is required';\n }\n\n // Value validation\n if (formData.value <= 0) {\n newErrors.value = 'Value must be greater than 0';\n }\n if (formData.type === 'PERCENTAGE' && formData.value > 100) {\n newErrors.value = 'Percentage cannot exceed 100%';\n }\n\n // User-specific validation\n if (formData.scope === 'USER_SPECIFIC' && formData.restrictedToUserIds.length === 0) {\n newErrors.restrictedToUserIds = 'Add at least one user email or ID';\n }\n\n // Date validation\n if (!formData.validFrom) {\n newErrors.validFrom = 'Start date is required';\n }\n if (formData.validUntil && formData.validUntil < formData.validFrom) {\n newErrors.validUntil = 'End date must be after start date';\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const addUserId = (value: string) => {\n const trimmed = value.trim();\n if (!trimmed) return;\n if (!formData.restrictedToUserIds.includes(trimmed)) {\n handleChange('restrictedToUserIds', [...formData.restrictedToUserIds, trimmed]);\n }\n setUserIdInput('');\n };\n\n const removeUserId = (id: string) => {\n handleChange(\n 'restrictedToUserIds',\n formData.restrictedToUserIds.filter((u) => u !== id)\n );\n };\n\n const toggleProductId = (productId: string) => {\n handleChange(\n 'applicableProductIds',\n formData.applicableProductIds.includes(productId)\n ? formData.applicableProductIds.filter((p) => p !== productId)\n : [...formData.applicableProductIds, productId]\n );\n };\n\n const toggleEntityType = (entityType: 'PLAN' | 'ADD_ON' | 'STORE') => {\n handleChange(\n 'applicableEntityTypes',\n formData.applicableEntityTypes.includes(entityType)\n ? formData.applicableEntityTypes.filter((e) => e !== entityType)\n : [...formData.applicableEntityTypes, entityType]\n );\n };\n\n const handleUserIdKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n addUserId(userIdInput);\n } else if (e.key === 'Backspace' && !userIdInput && formData.restrictedToUserIds.length > 0) {\n removeUserId(formData.restrictedToUserIds[formData.restrictedToUserIds.length - 1]);\n }\n };\n\n // Handle submit\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm()) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n // Prepare input data - convert dates to ISO format\n const input = {\n code: formData.code,\n name: formData.name,\n description: formData.description,\n scope: formData.scope,\n type: formData.type,\n value: formData.value,\n currency: formData.currency,\n minPurchaseAmount: formData.minPurchaseAmount,\n maxDiscountAmount: formData.maxDiscountAmount,\n maxTotalUses: formData.maxTotalUses,\n maxUsesPerUser: formData.maxUsesPerUser,\n validFrom: new Date(formData.validFrom).toISOString(),\n validUntil: formData.validUntil ? new Date(formData.validUntil).toISOString() : undefined,\n restrictedToUserIds: formData.restrictedToUserIds,\n applicableProducts: formData.applicableProducts,\n applicableServices: formData.applicableServices,\n applicableProductIds: formData.applicableProductIds,\n applicableEntityTypes: formData.applicableEntityTypes,\n excludedProducts: [],\n excludedServices: [],\n campaignName: formData.campaignName || undefined,\n };\n\n // Call GraphQL mutation\n const result = await createCouponMutation({\n variables: { input },\n });\n\n if (result.data?.createCoupon) {\n // Success - navigate to coupons list\n navigateTo('/coupons');\n } else {\n throw new Error('Failed to create coupon');\n }\n } catch (error) {\n console.error('Error creating coupon:', error);\n const errorMessage =\n error instanceof Error ? error.message : 'Failed to create coupon. Please try again.';\n setErrors({ code: errorMessage });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-4xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title={tr('billing.coupons.createTitle', 'Create Coupon')}\n description=\"Create a new discount code or promotional offer\"\n icon={<Tag className=\"size-6\" />}\n />\n </div>\n\n <PagePurpose>\n {tr(\n 'billing.coupons.createPurpose',\n 'Define a new discount code customers can redeem at checkout. Choose whether it takes a percentage or fixed amount off, set any usage limit and expiry, and pick which products it applies to.'\n )}\n </PagePurpose>\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Basic Information */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Basic Information</h3>\n\n {/* Code */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Coupon Code *\n </label>\n <input\n type=\"text\"\n value={formData.code}\n onChange={(e) => handleChange('code', e.target.value.toUpperCase())}\n placeholder=\"e.g., WELCOME20\"\n className={`w-full px-4 py-2 rounded-input border ${\n errors.code ? 'border-status-error-border' : 'border-border-subtle'\n } 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.code && <p className=\"text-sm text-status-error-text mt-1\">{errors.code}</p>}\n <p className=\"text-xs text-text-muted mt-1\">\n Use uppercase letters, numbers, hyphens, and underscores\n </p>\n </div>\n\n {/* Name */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Display Name *\n </label>\n <input\n type=\"text\"\n value={formData.name}\n onChange={(e) => handleChange('name', e.target.value)}\n placeholder=\"e.g., Welcome 20% Off\"\n className={`w-full px-4 py-2 rounded-input border ${\n errors.name ? 'border-status-error-border' : 'border-border-subtle'\n } 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 mt-1\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Description</label>\n <textarea\n value={formData.description}\n onChange={(e) => handleChange('description', e.target.value)}\n placeholder=\"Optional description for internal use\"\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)]\"\n />\n </div>\n </div>\n\n {/* Discount Configuration */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Discount Configuration</h3>\n\n {/* Currency */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Coupon Currency *\n </label>\n <select\n value={formData.currency}\n onChange={(e) => handleChange('currency', 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 {CURRENCIES.map(({ code, symbol, name }) => (\n <option key={code} value={code}>\n {symbol} {code} — {name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n Amounts below are in this currency. Users paying in a different currency (e.g. INR)\n will have the amounts automatically converted using live exchange rates at validation\n time.\n </p>\n </div>\n\n {/* Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Discount Type *\n </label>\n <div className=\"grid grid-cols-3 gap-3\">\n {[\n { value: 'PERCENTAGE', label: 'Percentage', icon: Percent },\n { value: 'FIXED_AMOUNT', label: `Fixed (${currencySymbol})`, icon: null },\n { value: 'FREE_SHIPPING', label: 'Free Shipping', icon: Package },\n ].map(({ value, label, icon: Icon }) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => handleChange('type', value)}\n className={`p-4 rounded-lg border-2 transition-colors ${\n formData.type === value\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {Icon ? (\n <Icon className=\"size-5 mx-auto mb-2\" />\n ) : (\n <span className=\"block text-lg font-bold mb-2\">{currencySymbol}</span>\n )}\n <p className=\"text-sm font-medium\">{label}</p>\n </button>\n ))}\n </div>\n </div>\n\n {/* Value */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Discount Value *\n </label>\n <div className=\"relative\">\n {formData.type === 'PERCENTAGE' ? (\n <>\n <Percent className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"number\"\n value={formData.value}\n onChange={(e) => handleChange('value', parseFloat(e.target.value))}\n min=\"0\"\n max=\"100\"\n step=\"0.01\"\n className={`w-full pl-10 pr-4 py-2 rounded-input border ${\n errors.value ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n </>\n ) : (\n <>\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.value}\n onChange={(e) => handleChange('value', parseFloat(e.target.value))}\n min=\"0\"\n step=\"0.01\"\n className={`w-full pl-10 pr-4 py-2 rounded-input border ${\n errors.value ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n </>\n )}\n </div>\n {errors.value && <p className=\"text-sm text-status-error-text mt-1\">{errors.value}</p>}\n </div>\n\n {/* Min Purchase Amount */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Minimum Purchase Amount ({formData.currency})\n </label>\n <div className=\"relative\">\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.minPurchaseAmount || ''}\n onChange={(e) =>\n handleChange(\n 'minPurchaseAmount',\n e.target.value ? parseFloat(e.target.value) : null\n )\n }\n min=\"0\"\n step=\"0.01\"\n placeholder=\"No minimum\"\n className=\"w-full pl-10 pr-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 </div>\n <p className=\"text-xs text-text-muted mt-1\">\n Order must be at least this amount in {formData.currency} (auto-converted for other\n currencies)\n </p>\n </div>\n\n {/* Max Discount Amount */}\n {formData.type === 'PERCENTAGE' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Maximum Discount Amount ({formData.currency})\n </label>\n <div className=\"relative\">\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.maxDiscountAmount || ''}\n onChange={(e) =>\n handleChange(\n 'maxDiscountAmount',\n e.target.value ? parseFloat(e.target.value) : null\n )\n }\n min=\"0\"\n step=\"0.01\"\n placeholder=\"No maximum\"\n className=\"w-full pl-10 pr-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 </div>\n <p className=\"text-xs text-text-muted mt-1\">\n Cap the discount in {formData.currency} — auto-converted to the user's payment\n currency\n </p>\n </div>\n )}\n </div>\n\n {/* Usage Limits */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Usage Limits</h3>\n\n {/* Max Total Uses */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Total Usage Limit\n </label>\n <input\n type=\"number\"\n value={formData.maxTotalUses || ''}\n onChange={(e) =>\n handleChange('maxTotalUses', e.target.value ? parseInt(e.target.value) : null)\n }\n min=\"1\"\n placeholder=\"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 <p className=\"text-xs text-text-muted mt-1\">\n Maximum number of times this coupon can be used across all users\n </p>\n </div>\n\n {/* Max Uses Per User */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Per-User Limit *\n </label>\n <input\n type=\"number\"\n value={formData.maxUsesPerUser}\n onChange={(e) => handleChange('maxUsesPerUser', parseInt(e.target.value))}\n min=\"1\"\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-xs text-text-muted mt-1\">\n Maximum times a single user can use this coupon\n </p>\n </div>\n </div>\n\n {/* Validity Period */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Validity Period</h3>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {/* Valid From */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Start Date *\n </label>\n <input\n type=\"date\"\n value={formData.validFrom}\n onChange={(e) => handleChange('validFrom', e.target.value)}\n className={`w-full px-4 py-2 rounded-input border ${\n errors.validFrom ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.validFrom && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.validFrom}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">End Date</label>\n <input\n type=\"date\"\n value={formData.validUntil}\n onChange={(e) => handleChange('validUntil', e.target.value)}\n className={`w-full px-4 py-2 rounded-input border ${\n errors.validUntil ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.validUntil && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.validUntil}</p>\n )}\n <p className=\"text-xs text-text-muted mt-1\">Leave empty for no expiry</p>\n </div>\n </div>\n </div>\n\n {/* Applicability */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Applicability</h3>\n\n {/* Applicable Products */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((productId) => (\n <button\n key={productId}\n type=\"button\"\n onClick={() => toggleProductId(productId)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n formData.applicableProductIds.includes(productId)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {productId}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {formData.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${formData.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n {/* Applicable Entity Types */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Purchase Types\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {ENTITY_TYPES.map(({ value, label }) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => toggleEntityType(value)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n formData.applicableEntityTypes.includes(value)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {formData.applicableEntityTypes.length === 0\n ? 'No selection = applies to plan, add-on, and store purchases'\n : `Applies to: ${formData.applicableEntityTypes.join(', ')}`}\n </p>\n </div>\n </div>\n\n {/* Scope */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Coupon Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Who can use this coupon? *\n </label>\n <select\n value={formData.scope}\n onChange={(e) => handleChange('scope', 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=\"GENERIC\">Anyone (Public)</option>\n <option value=\"USER_SPECIFIC\">Specific Users Only</option>\n <option value=\"REFERRAL\">Referral Code</option>\n <option value=\"CAMPAIGN\">Campaign-Specific</option>\n </select>\n </div>\n\n {/* Campaign Name (if scope is CAMPAIGN) */}\n {formData.scope === 'CAMPAIGN' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Campaign Name\n </label>\n <input\n type=\"text\"\n value={formData.campaignName}\n onChange={(e) => handleChange('campaignName', e.target.value)}\n placeholder=\"e.g., Summer Sale 2024\"\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 </div>\n )}\n\n {/* Restricted Users (if scope is USER_SPECIFIC) */}\n {formData.scope === 'USER_SPECIFIC' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n <span className=\"flex items-center gap-1.5\">\n <Users className=\"size-4\" />\n Restricted Users *\n </span>\n </label>\n {/* Tag input container */}\n <div\n className={`min-h-[42px] flex flex-wrap gap-2 px-3 py-2 rounded-lg border ${\n errors.restrictedToUserIds ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface cursor-text`}\n onClick={() => userIdInputRef.current?.focus()}\n >\n {formData.restrictedToUserIds.map((id) => (\n <span\n key={id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--color-accent-soft)] text-text-link text-sm font-medium\"\n >\n {id}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n removeUserId(id);\n }}\n className=\"hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <input\n ref={userIdInputRef}\n type=\"text\"\n value={userIdInput}\n onChange={(e) => setUserIdInput(e.target.value)}\n onKeyDown={handleUserIdKeyDown}\n onBlur={() => addUserId(userIdInput)}\n placeholder={\n formData.restrictedToUserIds.length === 0\n ? 'Enter user email or ID, press Enter to add'\n : 'Add another…'\n }\n className=\"flex-1 min-w-[200px] bg-transparent text-text-primary placeholder:text-text-muted text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n </div>\n {errors.restrictedToUserIds && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.restrictedToUserIds}</p>\n )}\n <p className=\"text-xs text-text-muted mt-1\">\n Type an email or user ID and press Enter or comma to add. Only these users can\n redeem this coupon.\n </p>\n </div>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-6 py-3 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-6 py-3 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? (\n <>\n <div className=\"size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin\" />\n Creating…\n </>\n ) : (\n <>\n <Save className=\"size-4\" />\n Create Coupon\n </>\n )}\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreateCouponPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CreateCouponPage.tsx"],"sourcesContent":["/**\n * Create Coupon Page\n *\n * Form for creating new coupon codes with validation\n */\n\nimport { type FC, useState, useRef, type KeyboardEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, Tag, Percent, Package, X, Users } from 'lucide-react';\n\nconst CURRENCIES: { code: string; symbol: string; name: string }[] = [\n { code: 'USD', symbol: '$', name: 'US Dollar' },\n { code: 'EUR', symbol: '€', name: 'Euro' },\n { code: 'GBP', symbol: '£', name: 'British Pound' },\n { code: 'INR', symbol: '₹', name: 'Indian Rupee' },\n { code: 'AUD', symbol: 'A$', name: 'Australian Dollar' },\n { code: 'CAD', symbol: 'C$', name: 'Canadian Dollar' },\n { code: 'SGD', symbol: 'S$', name: 'Singapore Dollar' },\n { code: 'JPY', symbol: '¥', name: 'Japanese Yen' },\n { code: 'MXN', symbol: 'MX$', name: 'Mexican Peso' },\n { code: 'BRL', symbol: 'R$', name: 'Brazilian Real' },\n { code: 'CHF', symbol: 'Fr', name: 'Swiss Franc' },\n { code: 'HKD', symbol: 'HK$', name: 'Hong Kong Dollar' },\n { code: 'NZD', symbol: 'NZ$', name: 'New Zealand Dollar' },\n { code: 'SEK', symbol: 'kr', name: 'Swedish Krona' },\n { code: 'KRW', symbol: '₩', name: 'South Korean Won' },\n];\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useCreateCouponMutation } from '../../../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// Product slugs a coupon can be scoped to (Plan.productID / Addon.productID),\n// same convention CreatePromotionPage.tsx uses for Promotion.applicableProductIds.\nconst KNOWN_PRODUCTS = [\n 'workspaces',\n 'bigconsole',\n 'botlit',\n 'fluidgrids',\n 'vibecontrols',\n 'healthybowl',\n 'adaptercloud',\n 'atheletebridge',\n 'coolandlovely',\n 'hookmovies',\n 'labsofscience',\n 'subscriberbot',\n 'artistrybase',\n 'assethandler',\n 'brainyrich',\n 'buildmyiq',\n 'collabkin',\n 'comradecircle',\n 'cosmicintersection',\n 'crewfoundry',\n 'ecoimpacthub',\n 'eventfullymanaged',\n 'ggnomad',\n 'govcitizenhub',\n 'headshotmarketing',\n 'housingvista',\n 'intelwatchtower',\n 'kadaikodi',\n 'manufacturedops',\n 'movethewheels',\n 'mybodyshield',\n 'planmagnet',\n 'proserviceworld',\n 'semanticfed',\n 'technospam',\n 'theculturepost',\n 'theglobalfuel',\n 'timecampus',\n];\n\nconst ENTITY_TYPES: { value: 'PLAN' | 'ADD_ON' | 'STORE'; label: string }[] = [\n { value: 'PLAN', label: 'Plan purchases' },\n { value: 'ADD_ON', label: 'Add-on purchases' },\n { value: 'STORE', label: 'Store purchases' },\n];\n\ninterface CouponFormData {\n code: string;\n name: string;\n description: string;\n scope: 'GENERIC' | 'USER_SPECIFIC' | 'REFERRAL' | 'CAMPAIGN';\n type: 'PERCENTAGE' | 'FIXED_AMOUNT' | 'FREE_SHIPPING';\n value: number;\n currency: string;\n minPurchaseAmount: number | null;\n maxDiscountAmount: number | null;\n maxTotalUses: number | null;\n maxUsesPerUser: number;\n validFrom: string;\n validUntil: string;\n restrictedToUserIds: string[];\n applicableProducts: string[];\n applicableServices: string[];\n applicableProductIds: string[];\n applicableEntityTypes: ('PLAN' | 'ADD_ON' | 'STORE')[];\n campaignName: string;\n}\n\nexport const CreateCouponPage: 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 const [userIdInput, setUserIdInput] = useState('');\n const userIdInputRef = useRef<HTMLInputElement>(null);\n\n const [formData, setFormData] = useState<CouponFormData>({\n code: '',\n name: '',\n description: '',\n scope: 'GENERIC',\n type: 'PERCENTAGE',\n value: 0,\n currency: 'USD',\n minPurchaseAmount: null,\n maxDiscountAmount: null,\n maxTotalUses: null,\n maxUsesPerUser: 1,\n validFrom: new Date().toISOString().split('T')[0],\n validUntil: '',\n restrictedToUserIds: [],\n applicableProducts: [],\n applicableServices: [],\n applicableProductIds: [],\n applicableEntityTypes: [],\n campaignName: '',\n });\n\n const [errors, setErrors] = useState<Partial<Record<keyof CouponFormData, string>>>({});\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const currencySymbol =\n CURRENCIES.find((c) => c.code === formData.currency)?.symbol ?? formData.currency;\n\n // GraphQL mutation\n const [createCouponMutation] = useCreateCouponMutation({ refetchQueries: ['ListCoupons'] });\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to create coupons.\" />;\n }\n\n // Handle input change\n const handleChange = (\n field: keyof CouponFormData,\n value: CouponFormData[keyof CouponFormData]\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n\n // Clear error for this field\n if (errors[field]) {\n setErrors((prev) => {\n const newErrors = { ...prev };\n delete newErrors[field];\n return newErrors;\n });\n }\n };\n\n // Validate form\n const validateForm = (): boolean => {\n const newErrors: Partial<Record<keyof CouponFormData, string>> = {};\n\n // Code validation\n if (!formData.code.trim()) {\n newErrors.code = 'Coupon code is required';\n } else if (!/^[A-Z0-9-_]+$/.test(formData.code)) {\n newErrors.code =\n 'Code must contain only uppercase letters, numbers, hyphens, and underscores';\n } else if (formData.code.length < 3) {\n newErrors.code = 'Code must be at least 3 characters';\n } else if (formData.code.length > 50) {\n newErrors.code = 'Code must be at most 50 characters';\n }\n\n // Name validation\n if (!formData.name.trim()) {\n newErrors.name = 'Coupon name is required';\n }\n\n // Value validation\n if (formData.value <= 0) {\n newErrors.value = 'Value must be greater than 0';\n }\n if (formData.type === 'PERCENTAGE' && formData.value > 100) {\n newErrors.value = 'Percentage cannot exceed 100%';\n }\n\n // User-specific validation\n if (formData.scope === 'USER_SPECIFIC' && formData.restrictedToUserIds.length === 0) {\n newErrors.restrictedToUserIds = 'Add at least one user email or ID';\n }\n\n // Date validation\n if (!formData.validFrom) {\n newErrors.validFrom = 'Start date is required';\n }\n if (formData.validUntil && formData.validUntil < formData.validFrom) {\n newErrors.validUntil = 'End date must be after start date';\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const addUserId = (value: string) => {\n const trimmed = value.trim();\n if (!trimmed) return;\n if (!formData.restrictedToUserIds.includes(trimmed)) {\n handleChange('restrictedToUserIds', [...formData.restrictedToUserIds, trimmed]);\n }\n setUserIdInput('');\n };\n\n const removeUserId = (id: string) => {\n handleChange(\n 'restrictedToUserIds',\n formData.restrictedToUserIds.filter((u) => u !== id)\n );\n };\n\n const toggleProductId = (productId: string) => {\n handleChange(\n 'applicableProductIds',\n formData.applicableProductIds.includes(productId)\n ? formData.applicableProductIds.filter((p) => p !== productId)\n : [...formData.applicableProductIds, productId]\n );\n };\n\n const toggleEntityType = (entityType: 'PLAN' | 'ADD_ON' | 'STORE') => {\n handleChange(\n 'applicableEntityTypes',\n formData.applicableEntityTypes.includes(entityType)\n ? formData.applicableEntityTypes.filter((e) => e !== entityType)\n : [...formData.applicableEntityTypes, entityType]\n );\n };\n\n const handleUserIdKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n addUserId(userIdInput);\n } else if (e.key === 'Backspace' && !userIdInput && formData.restrictedToUserIds.length > 0) {\n removeUserId(formData.restrictedToUserIds[formData.restrictedToUserIds.length - 1]);\n }\n };\n\n // Handle submit\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validateForm()) {\n return;\n }\n\n setIsSubmitting(true);\n\n try {\n // Prepare input data - convert dates to ISO format\n const input = {\n code: formData.code,\n name: formData.name,\n description: formData.description,\n scope: formData.scope,\n type: formData.type,\n value: formData.value,\n currency: formData.currency,\n minPurchaseAmount: formData.minPurchaseAmount,\n maxDiscountAmount: formData.maxDiscountAmount,\n maxTotalUses: formData.maxTotalUses,\n maxUsesPerUser: formData.maxUsesPerUser,\n validFrom: new Date(formData.validFrom).toISOString(),\n validUntil: formData.validUntil ? new Date(formData.validUntil).toISOString() : undefined,\n restrictedToUserIds: formData.restrictedToUserIds,\n applicableProducts: formData.applicableProducts,\n applicableServices: formData.applicableServices,\n applicableProductIds: formData.applicableProductIds,\n applicableEntityTypes: formData.applicableEntityTypes,\n excludedProducts: [],\n excludedServices: [],\n campaignName: formData.campaignName || undefined,\n };\n\n // Call GraphQL mutation\n const result = await createCouponMutation({\n variables: { input },\n });\n\n if (result.data?.createCoupon) {\n // Success - navigate to coupons list\n navigateTo('/coupons');\n } else {\n throw new Error('Failed to create coupon');\n }\n } catch (error) {\n console.error('Error creating coupon:', error);\n const errorMessage =\n error instanceof Error ? error.message : 'Failed to create coupon. Please try again.';\n setErrors({ code: errorMessage });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-4xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title={tr('billing.coupons.createTitle', 'Create Coupon')}\n description=\"Create a new discount code or promotional offer\"\n icon={<Tag className=\"size-6\" />}\n />\n </div>\n\n <PagePurpose>\n {tr(\n 'billing.coupons.createPurpose',\n 'Define a new discount code customers can redeem at checkout. Choose whether it takes a percentage or fixed amount off, set any usage limit and expiry, and pick which products it applies to.'\n )}\n </PagePurpose>\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Basic Information */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Basic Information</h3>\n\n {/* Code */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Coupon Code *\n </label>\n <input\n type=\"text\"\n value={formData.code}\n onChange={(e) => handleChange('code', e.target.value.toUpperCase())}\n placeholder=\"e.g., WELCOME20\"\n className={`w-full px-4 py-2 rounded-input border ${\n errors.code ? 'border-status-error-border' : 'border-border-subtle'\n } 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.code && <p className=\"text-sm text-status-error-text mt-1\">{errors.code}</p>}\n <p className=\"text-xs text-text-muted mt-1\">\n Use uppercase letters, numbers, hyphens, and underscores\n </p>\n </div>\n\n {/* Name */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Display Name *\n </label>\n <input\n type=\"text\"\n value={formData.name}\n onChange={(e) => handleChange('name', e.target.value)}\n placeholder=\"e.g., Welcome 20% Off\"\n className={`w-full px-4 py-2 rounded-input border ${\n errors.name ? 'border-status-error-border' : 'border-border-subtle'\n } 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 mt-1\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Description</label>\n <textarea\n value={formData.description}\n onChange={(e) => handleChange('description', e.target.value)}\n placeholder=\"Optional description for internal use\"\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)]\"\n />\n </div>\n </div>\n\n {/* Discount Configuration */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Discount Configuration</h3>\n\n {/* Currency */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Coupon Currency *\n </label>\n <select\n value={formData.currency}\n onChange={(e) => handleChange('currency', 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {CURRENCIES.map(({ code, symbol, name }) => (\n <option key={code} value={code}>\n {symbol} {code} — {name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n Amounts below are in this currency. Users paying in a different currency (e.g. INR)\n will have the amounts automatically converted using live exchange rates at validation\n time.\n </p>\n </div>\n\n {/* Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Discount Type *\n </label>\n <div className=\"grid grid-cols-3 gap-3\">\n {[\n { value: 'PERCENTAGE', label: 'Percentage', icon: Percent },\n { value: 'FIXED_AMOUNT', label: `Fixed (${currencySymbol})`, icon: null },\n { value: 'FREE_SHIPPING', label: 'Free Shipping', icon: Package },\n ].map(({ value, label, icon: Icon }) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => handleChange('type', value)}\n className={`p-4 rounded-lg border-2 transition-colors ${\n formData.type === value\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {Icon ? (\n <Icon className=\"size-5 mx-auto mb-2\" />\n ) : (\n <span className=\"block text-lg font-bold mb-2\">{currencySymbol}</span>\n )}\n <p className=\"text-sm font-medium\">{label}</p>\n </button>\n ))}\n </div>\n </div>\n\n {/* Value */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Discount Value *\n </label>\n <div className=\"relative\">\n {formData.type === 'PERCENTAGE' ? (\n <>\n <Percent className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"number\"\n value={formData.value}\n onChange={(e) => handleChange('value', parseFloat(e.target.value))}\n min=\"0\"\n max=\"100\"\n step=\"0.01\"\n className={`w-full pl-10 pr-4 py-2 rounded-input border ${\n errors.value ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n </>\n ) : (\n <>\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.value}\n onChange={(e) => handleChange('value', parseFloat(e.target.value))}\n min=\"0\"\n step=\"0.01\"\n className={`w-full pl-10 pr-4 py-2 rounded-input border ${\n errors.value ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n </>\n )}\n </div>\n {errors.value && <p className=\"text-sm text-status-error-text mt-1\">{errors.value}</p>}\n </div>\n\n {/* Min Purchase Amount */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Minimum Purchase Amount ({formData.currency})\n </label>\n <div className=\"relative\">\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.minPurchaseAmount || ''}\n onChange={(e) =>\n handleChange(\n 'minPurchaseAmount',\n e.target.value ? parseFloat(e.target.value) : null\n )\n }\n min=\"0\"\n step=\"0.01\"\n placeholder=\"No minimum\"\n className=\"w-full pl-10 pr-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 </div>\n <p className=\"text-xs text-text-muted mt-1\">\n Order must be at least this amount in {formData.currency} (auto-converted for other\n currencies)\n </p>\n </div>\n\n {/* Max Discount Amount */}\n {formData.type === 'PERCENTAGE' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Maximum Discount Amount ({formData.currency})\n </label>\n <div className=\"relative\">\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-muted font-medium\">\n {currencySymbol}\n </span>\n <input\n type=\"number\"\n value={formData.maxDiscountAmount || ''}\n onChange={(e) =>\n handleChange(\n 'maxDiscountAmount',\n e.target.value ? parseFloat(e.target.value) : null\n )\n }\n min=\"0\"\n step=\"0.01\"\n placeholder=\"No maximum\"\n className=\"w-full pl-10 pr-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 </div>\n <p className=\"text-xs text-text-muted mt-1\">\n Cap the discount in {formData.currency} — auto-converted to the user's payment\n currency\n </p>\n </div>\n )}\n </div>\n\n {/* Usage Limits */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Usage Limits</h3>\n\n {/* Max Total Uses */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Total Usage Limit\n </label>\n <input\n type=\"number\"\n value={formData.maxTotalUses || ''}\n onChange={(e) =>\n handleChange('maxTotalUses', e.target.value ? parseInt(e.target.value) : null)\n }\n min=\"1\"\n placeholder=\"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 <p className=\"text-xs text-text-muted mt-1\">\n Maximum number of times this coupon can be used across all users\n </p>\n </div>\n\n {/* Max Uses Per User */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Per-User Limit *\n </label>\n <input\n type=\"number\"\n value={formData.maxUsesPerUser}\n onChange={(e) => handleChange('maxUsesPerUser', parseInt(e.target.value))}\n min=\"1\"\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-xs text-text-muted mt-1\">\n Maximum times a single user can use this coupon\n </p>\n </div>\n </div>\n\n {/* Validity Period */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Validity Period</h3>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {/* Valid From */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Start Date *\n </label>\n <input\n type=\"date\"\n value={formData.validFrom}\n onChange={(e) => handleChange('validFrom', e.target.value)}\n className={`w-full px-4 py-2 rounded-input border ${\n errors.validFrom ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.validFrom && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.validFrom}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">End Date</label>\n <input\n type=\"date\"\n value={formData.validUntil}\n onChange={(e) => handleChange('validUntil', e.target.value)}\n className={`w-full px-4 py-2 rounded-input border ${\n errors.validUntil ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.validUntil && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.validUntil}</p>\n )}\n <p className=\"text-xs text-text-muted mt-1\">Leave empty for no expiry</p>\n </div>\n </div>\n </div>\n\n {/* Applicability */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Applicability</h3>\n\n {/* Applicable Products */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((productId) => (\n <button\n key={productId}\n type=\"button\"\n onClick={() => toggleProductId(productId)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n formData.applicableProductIds.includes(productId)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {productId}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {formData.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${formData.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n {/* Applicable Entity Types */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Purchase Types\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {ENTITY_TYPES.map(({ value, label }) => (\n <button\n key={value}\n type=\"button\"\n onClick={() => toggleEntityType(value)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n formData.applicableEntityTypes.includes(value)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {formData.applicableEntityTypes.length === 0\n ? 'No selection = applies to plan, add-on, and store purchases'\n : `Applies to: ${formData.applicableEntityTypes.join(', ')}`}\n </p>\n </div>\n </div>\n\n {/* Scope */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Coupon Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Who can use this coupon? *\n </label>\n <select\n value={formData.scope}\n onChange={(e) => handleChange('scope', 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"GENERIC\">Anyone (Public)</option>\n <option value=\"USER_SPECIFIC\">Specific Users Only</option>\n <option value=\"REFERRAL\">Referral Code</option>\n <option value=\"CAMPAIGN\">Campaign-Specific</option>\n </select>\n </div>\n\n {/* Campaign Name (if scope is CAMPAIGN) */}\n {formData.scope === 'CAMPAIGN' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Campaign Name\n </label>\n <input\n type=\"text\"\n value={formData.campaignName}\n onChange={(e) => handleChange('campaignName', e.target.value)}\n placeholder=\"e.g., Summer Sale 2024\"\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 </div>\n )}\n\n {/* Restricted Users (if scope is USER_SPECIFIC) */}\n {formData.scope === 'USER_SPECIFIC' && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n <span className=\"flex items-center gap-1.5\">\n <Users className=\"size-4\" />\n Restricted Users *\n </span>\n </label>\n {/* Tag input container */}\n <div\n className={`min-h-[42px] flex flex-wrap gap-2 px-3 py-2 rounded-lg border ${\n errors.restrictedToUserIds ? 'border-status-error-border' : 'border-border-subtle'\n } bg-bg-surface cursor-text`}\n onClick={() => userIdInputRef.current?.focus()}\n >\n {formData.restrictedToUserIds.map((id) => (\n <span\n key={id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-[var(--color-accent-soft)] text-text-link text-sm font-medium\"\n >\n {id}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n removeUserId(id);\n }}\n className=\"hover:text-status-error-text\"\n >\n <X className=\"size-3\" />\n </button>\n </span>\n ))}\n <input\n ref={userIdInputRef}\n type=\"text\"\n value={userIdInput}\n onChange={(e) => setUserIdInput(e.target.value)}\n onKeyDown={handleUserIdKeyDown}\n onBlur={() => addUserId(userIdInput)}\n placeholder={\n formData.restrictedToUserIds.length === 0\n ? 'Enter user email or ID, press Enter to add'\n : 'Add another…'\n }\n className=\"flex-1 min-w-[200px] bg-transparent text-text-primary placeholder:text-text-muted text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n </div>\n {errors.restrictedToUserIds && (\n <p className=\"text-sm text-status-error-text mt-1\">{errors.restrictedToUserIds}</p>\n )}\n <p className=\"text-xs text-text-muted mt-1\">\n Type an email or user ID and press Enter or comma to add. Only these users can\n redeem this coupon.\n </p>\n </div>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons')}\n className=\"px-6 py-3 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-6 py-3 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? (\n <>\n <div className=\"size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin\" />\n Creating…\n </>\n ) : (\n <>\n <Save className=\"size-4\" />\n Create Coupon\n </>\n )}\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -240,7 +240,7 @@ var _ = () => {
240
240
  id: "status",
241
241
  value: O.status,
242
242
  onChange: (e) => I("status", e.target.value),
243
- 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)]",
243
+ 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
244
244
  children: [
245
245
  /* @__PURE__ */ p("option", {
246
246
  value: "ACTIVE",
@@ -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 { 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&apos;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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"}
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&apos;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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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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"}
@@ -34,7 +34,7 @@ var p = () => {
34
34
  }), /* @__PURE__ */ c("select", {
35
35
  value: j,
36
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)]",
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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
38
38
  children: v.map((e) => /* @__PURE__ */ c("option", {
39
39
  value: e.id,
40
40
  children: e.name
@@ -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';\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&apos;s billing account — for promotions, service recovery or\n manual adjustments — and immediately see them reflected in that account&apos;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":"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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&apos;s billing account — for promotions, service recovery or\n manual adjustments — and immediately see them reflected in that account&apos;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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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":"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"}
@@ -294,7 +294,7 @@ var m = () => {
294
294
  value: h?.id ?? "",
295
295
  onChange: (e) => te(e.target.value),
296
296
  "aria-label": "Switch billing account",
297
- className: "px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] w-full sm:min-w-[200px] sm:w-auto",
297
+ className: "px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] w-full sm:min-w-[200px] sm:w-auto",
298
298
  children: o.map((e) => /* @__PURE__ */ d("option", {
299
299
  value: e.id,
300
300
  children: e.name
@@ -440,7 +440,7 @@ var m = () => {
440
440
  ...t,
441
441
  type: e.target.value
442
442
  })),
443
- className: "w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
443
+ className: "w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
444
444
  children: [/* @__PURE__ */ d("option", {
445
445
  value: "BANK_ACCOUNT",
446
446
  children: "Bank Account"