@burdenoff/microfe-billing 2026.624.1 → 2026.624.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +125 -150
  2. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  3. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +146 -153
  4. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  5. package/dist/billing/modules/billing/pages/RefundsPage.js +143 -149
  6. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  7. package/dist/billing/modules/billing/pages/TransactionsPage.js +232 -252
  8. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  9. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +164 -179
  10. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  11. package/dist/billing/modules/coupons/pages/CouponsListPage.js +206 -244
  12. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  13. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +1 -1
  14. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  15. package/dist/billing/modules/earnings/pages/EarningsPage.js +229 -291
  16. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  17. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +69 -74
  18. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  19. package/dist/billing/modules/earnings/pages/PayoutsPage.js +142 -148
  20. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  21. package/dist/billing/modules/settings/pages/SettingsPage.js +125 -117
  22. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  23. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +78 -66
  24. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  25. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +68 -83
  26. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  27. package/dist/billing/shared/ui/MetricCard.js +1 -1
  28. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  29. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CouponsListPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CouponsListPage.tsx"],"sourcesContent":["/**\n * Coupons List Page\n *\n * Admin interface for managing coupon codes:\n * - View all coupons\n * - Filter by status, type, scope\n * - Create new coupons\n * - Edit/deactivate existing coupons\n * - View usage analytics\n */\n\nimport { type FC, useState, useMemo, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport {\n Plus,\n Search,\n Tag,\n TrendingUp,\n CheckCircle,\n AlertCircle,\n Percent,\n DollarSign,\n Package,\n Eye,\n Edit,\n Trash2,\n Download,\n PowerOff,\n Power,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: 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 PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Coupons</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Tag className=\"size-4 text-accent-blue\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Uses</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">{stats.totalUses}</p>\n <p className=\"text-xs text-text-muted mt-1\">All time</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Discount</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <DollarSign className=\"size-4 text-accent-purple\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Active Rate</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\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 </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"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=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"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=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"sticky top-0 z-10 bg-bg-sunken border-b border-border-subtle\">\n <tr>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Code\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Type\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Value\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Status\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Usage\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Valid Until\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {loading ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-text-muted\">\n Loading coupons…\n </td>\n </tr>\n ) : filteredCoupons.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-text-muted\">\n No coupons found\n </td>\n </tr>\n ) : (\n filteredCoupons.map((coupon: Coupon) => (\n <tr key={coupon.id} className=\"hover:bg-bg-sunken/50\">\n <td className=\"p-4\">\n <div>\n <p className=\"font-medium text-text-primary font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-text-muted\">{coupon.name}</p>\n </div>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {coupon.type === 'PERCENTAGE'\n ? `${coupon.value}%`\n : formatCurrency(coupon.value)}\n </span>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"text-sm text-text-primary\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-text-muted\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-bg-sunken rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-action-primary-bg h-1.5 rounded-full\"\n style={{\n width: `${Math.min(\n (coupon.currentUses / coupon.maxTotalUses) * 100,\n 100\n )}%`,\n }}\n />\n </div>\n )}\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm text-text-muted\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-text-muted/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-text-muted\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-primary font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CouponsListPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CouponsListPage.tsx"],"sourcesContent":["/**\n * Coupons List Page\n *\n * Admin interface for managing coupon codes:\n * - View all coupons\n * - Filter by status, type, scope\n * - Create new coupons\n * - Edit/deactivate existing coupons\n * - View usage analytics\n */\n\nimport { type FC, useState, useMemo, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport {\n Plus,\n Search,\n Tag,\n TrendingUp,\n CheckCircle,\n AlertCircle,\n Percent,\n DollarSign,\n Package,\n Eye,\n Edit,\n Trash2,\n Download,\n PowerOff,\n Power,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\n\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: 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 PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n const couponColumns: ResponsiveTableColumn<Coupon>[] = [\n {\n key: 'code',\n header: 'Code',\n priority: 'primary',\n cell: (coupon) => (\n <div>\n <p className=\"font-medium text-text-primary font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-text-muted\">{coupon.name}</p>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cell: (coupon) => (\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n ),\n },\n {\n key: 'value',\n header: 'Value',\n cell: (coupon) => (\n <span className=\"text-sm font-medium text-text-primary tabular-nums\">\n {coupon.type === 'PERCENTAGE' ? `${coupon.value}%` : formatCurrency(coupon.value)}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (coupon) => (\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n ),\n },\n {\n key: 'usage',\n header: 'Usage',\n cell: (coupon) => (\n <>\n <div className=\"text-sm text-text-primary tabular-nums\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-text-muted\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-bg-sunken rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-action-primary-bg h-1.5 rounded-full\"\n style={{\n width: `${Math.min((coupon.currentUses / coupon.maxTotalUses) * 100, 100)}%`,\n }}\n />\n </div>\n )}\n </>\n ),\n },\n {\n key: 'validUntil',\n header: 'Valid Until',\n priority: 'hidden-on-mobile',\n cell: (coupon) => (\n <span className=\"text-sm text-text-muted\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n ),\n },\n ];\n\n const renderCouponActions = (coupon: Coupon) => (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-text-muted/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </>\n );\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Coupons</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Tag className=\"size-4 text-accent-blue\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Uses</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">{stats.totalUses}</p>\n <p className=\"text-xs text-text-muted mt-1\">All time</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Discount</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <DollarSign className=\"size-4 text-accent-purple\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Active Rate</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\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 </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"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=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"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=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n {loading ? (\n <div className=\"p-8 text-center text-text-muted\">Loading coupons…</div>\n ) : (\n <ResponsiveTable<Coupon>\n columns={couponColumns}\n data={filteredCoupons}\n rowKey={(coupon) => coupon.id}\n rowActions={renderCouponActions}\n emptyState={<div className=\"p-8 text-center text-text-muted\">No coupons found</div>}\n />\n )}\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-text-muted\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-primary font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":"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"}
@@ -561,7 +561,7 @@ var v = [
561
561
  },
562
562
  onBlur: () => I(C),
563
563
  placeholder: E.restrictedToUserIds.length === 0 ? "Enter user email or ID, press Enter to add" : "Add another…",
564
- className: "flex-1 min-w-[200px] bg-transparent text-text-primary placeholder:text-text-muted text-sm focus:outline-none"
564
+ 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)]"
565
565
  })]
566
566
  }),
567
567
  O.restrictedToUserIds && /* @__PURE__ */ h("p", {
@@ -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({ 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 {/* 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:outline-none\"\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 { 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 {/* 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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"}