@burdenoff/microfe-billing 2026.601.3 → 2026.601.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,7 +107,7 @@ var v = [
107
107
  applicableProducts: [],
108
108
  applicableServices: [],
109
109
  campaignName: ""
110
- }), [O, k] = o({}), [A, j] = o(!1), M = v.find((e) => e.code === E.currency)?.symbol ?? E.currency, [N] = r();
110
+ }), [O, k] = o({}), [A, j] = o(!1), M = v.find((e) => e.code === E.currency)?.symbol ?? E.currency, [N] = r({ refetchQueries: ["ListCoupons"] });
111
111
  if (!S.canManageDiscounts) return /* @__PURE__ */ h(e, { message: "You don't have permission to create coupons." });
112
112
  let P = (e, t) => {
113
113
  D((n) => ({
@@ -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 { 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();\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-muted text-muted-foreground hover:text-foreground\"\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 {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Basic Information */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Basic Information</h3>\n\n {/* Code */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Coupon Code *</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-lg border ${\n errors.code ? 'border-destructive' : 'border-border'\n } bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.code && <p className=\"text-sm text-destructive mt-1\">{errors.code}</p>}\n <p className=\"text-xs text-muted-foreground 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-foreground mb-1\">Display Name *</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-lg border ${\n errors.name ? 'border-destructive' : 'border-border'\n } bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.name && <p className=\"text-sm text-destructive mt-1\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n </div>\n\n {/* Discount Configuration */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Discount Configuration</h3>\n\n {/* Currency */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-muted-foreground 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-foreground 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-primary bg-primary/10'\n : 'border-border hover:border-primary/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-foreground 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-muted-foreground\" />\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-lg border ${\n errors.value ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n </>\n ) : (\n <>\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground 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-lg border ${\n errors.value ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n </>\n )}\n </div>\n {errors.value && <p className=\"text-sm text-destructive mt-1\">{errors.value}</p>}\n </div>\n\n {/* Min Purchase Amount */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-muted-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n <p className=\"text-xs text-muted-foreground 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-foreground 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-muted-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n <p className=\"text-xs text-muted-foreground 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-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Usage Limits</h3>\n\n {/* Max Total Uses */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground 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-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground 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-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">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-foreground mb-1\">Start Date *</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-lg border ${\n errors.validFrom ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.validFrom && (\n <p className=\"text-sm text-destructive mt-1\">{errors.validFrom}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border ${\n errors.validUntil ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.validUntil && (\n <p className=\"text-sm text-destructive mt-1\">{errors.validUntil}</p>\n )}\n <p className=\"text-xs text-muted-foreground mt-1\">Leave empty for no expiry</p>\n </div>\n </div>\n </div>\n\n {/* Scope */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Coupon Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-foreground 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-destructive' : 'border-border'\n } bg-background 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-primary/10 text-primary 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-destructive\"\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-foreground placeholder:text-muted-foreground text-sm focus:outline-none\"\n />\n </div>\n {errors.restrictedToUserIds && (\n <p className=\"text-sm text-destructive mt-1\">{errors.restrictedToUserIds}</p>\n )}\n <p className=\"text-xs text-muted-foreground 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 text-foreground hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-6 py-3 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? (\n <>\n <div className=\"size-4 border-2 border-primary-foreground 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 { 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-muted text-muted-foreground hover:text-foreground\"\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 {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Basic Information */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Basic Information</h3>\n\n {/* Code */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Coupon Code *</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-lg border ${\n errors.code ? 'border-destructive' : 'border-border'\n } bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.code && <p className=\"text-sm text-destructive mt-1\">{errors.code}</p>}\n <p className=\"text-xs text-muted-foreground 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-foreground mb-1\">Display Name *</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-lg border ${\n errors.name ? 'border-destructive' : 'border-border'\n } bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.name && <p className=\"text-sm text-destructive mt-1\">{errors.name}</p>}\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n </div>\n\n {/* Discount Configuration */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Discount Configuration</h3>\n\n {/* Currency */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-muted-foreground 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-foreground 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-primary bg-primary/10'\n : 'border-border hover:border-primary/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-foreground 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-muted-foreground\" />\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-lg border ${\n errors.value ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n </>\n ) : (\n <>\n <span className=\"absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground 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-lg border ${\n errors.value ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n </>\n )}\n </div>\n {errors.value && <p className=\"text-sm text-destructive mt-1\">{errors.value}</p>}\n </div>\n\n {/* Min Purchase Amount */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-muted-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n <p className=\"text-xs text-muted-foreground 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-foreground 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-muted-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n <p className=\"text-xs text-muted-foreground 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-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Usage Limits</h3>\n\n {/* Max Total Uses */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground 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-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground 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-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">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-foreground mb-1\">Start Date *</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-lg border ${\n errors.validFrom ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.validFrom && (\n <p className=\"text-sm text-destructive mt-1\">{errors.validFrom}</p>\n )}\n </div>\n\n {/* Valid Until */}\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border ${\n errors.validUntil ? 'border-destructive' : 'border-border'\n } bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.validUntil && (\n <p className=\"text-sm text-destructive mt-1\">{errors.validUntil}</p>\n )}\n <p className=\"text-xs text-muted-foreground mt-1\">Leave empty for no expiry</p>\n </div>\n </div>\n </div>\n\n {/* Scope */}\n <div className=\"bg-card border border-border rounded-lg p-6 space-y-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">Coupon Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-foreground 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-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-foreground 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-destructive' : 'border-border'\n } bg-background 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-primary/10 text-primary 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-destructive\"\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-foreground placeholder:text-muted-foreground text-sm focus:outline-none\"\n />\n </div>\n {errors.restrictedToUserIds && (\n <p className=\"text-sm text-destructive mt-1\">{errors.restrictedToUserIds}</p>\n )}\n <p className=\"text-xs text-muted-foreground 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 text-foreground hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-6 py-3 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? (\n <>\n <div className=\"size-4 border-2 border-primary-foreground 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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"}
@@ -15,7 +15,7 @@ var f = [
15
15
  "fluidgrids",
16
16
  "healthybowl"
17
17
  ], p = () => {
18
- let p = i(), m = n(), [h] = r(), [g, _] = a(!1), [v, y] = a({}), b = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], [x, S] = a({
18
+ let p = i(), m = n(), [h] = r({ refetchQueries: ["ListPromotions"] }), [g, _] = a(!1), [v, y] = a({}), b = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], [x, S] = a({
19
19
  name: "",
20
20
  description: "",
21
21
  startsAt: b,
@@ -54,7 +54,10 @@ var f = [
54
54
  if (e.preventDefault(), T()) {
55
55
  _(!0);
56
56
  try {
57
- let e = x.applicablePlanIds.split(",").map((e) => e.trim()).filter(Boolean);
57
+ let e = x.applicablePlanIds.split(",").flatMap((e) => {
58
+ let t = e.trim();
59
+ return t ? [t] : [];
60
+ });
58
61
  await h({ variables: { input: {
59
62
  name: x.name.trim(),
60
63
  description: x.description.trim() || void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation();\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-lg border ${errors.name ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.name && <p className=\"text-xs text-destructive mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Bonus Months *</label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-lg border ${errors.bonusMonths ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-destructive mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-primary/5 rounded-lg text-sm text-muted-foreground\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-primary\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-foreground\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Start Date *</label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-lg border ${errors.startsAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-lg border ${errors.expiresAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-primary bg-primary/10 text-primary font-medium'\n : 'border-border hover:border-primary/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-lg border border-border text-foreground hover:bg-muted text-sm\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 text-sm disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation({ refetchQueries: ['ListPromotions'] });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds.split(',').flatMap((s) => {\n const trimmed = s.trim();\n return trimmed ? [trimmed] : [];\n });\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-lg border ${errors.name ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.name && <p className=\"text-xs text-destructive mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Bonus Months *</label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-lg border ${errors.bonusMonths ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-destructive mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-primary/5 rounded-lg text-sm text-muted-foreground\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-primary\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-foreground\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Start Date *</label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-lg border ${errors.startsAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-lg border ${errors.expiresAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-primary bg-primary/10 text-primary font-medium'\n : 'border-border hover:border-primary/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-lg border border-border text-foreground hover:bg-muted text-sm\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 text-sm disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}