@burdenoff/microfe-billing 2026.916.1 → 2026.917.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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\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 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 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 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 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 {/* 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)]\"\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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"}
@@ -384,6 +384,8 @@ var y = t`
384
384
  applicableServices
385
385
  excludedProducts
386
386
  excludedServices
387
+ applicableProductIds
388
+ applicableEntityTypes
387
389
  campaignId
388
390
  campaignName
389
391
  metadata
@@ -1754,6 +1756,8 @@ var mt = t`
1754
1756
  applicableServices
1755
1757
  excludedProducts
1756
1758
  excludedServices
1759
+ applicableProductIds
1760
+ applicableEntityTypes
1757
1761
  campaignName
1758
1762
  createdAt
1759
1763
  updatedAt