@burdenoff/microfe-billing 2026.526.1 → 2026.528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingAdminRoutes.js +1 -1
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingUserRoutes.js +1 -1
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +15 -8
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +13 -6
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +8 -4
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -2
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +44 -23
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +60 -31
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +9 -9
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +8 -8
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +16 -10
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +24 -16
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +5 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +46 -29
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +34 -22
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +10 -10
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +3 -3
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js +7 -0
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +13 -13
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +5 -5
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +4 -4
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +11 -11
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +3 -3
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +8 -8
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +61 -52
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +22 -15
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +30 -21
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +9 -8
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +11 -8
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +22 -17
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +15 -8
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +17 -11
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +15 -11
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +21 -17
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +14 -11
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +32 -26
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +14 -10
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +18 -11
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +44 -32
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +35 -26
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +15 -15
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +11 -11
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +5 -5
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +9 -4
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +13 -8
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -2
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +14 -7
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +23 -9
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +11 -7
- package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +6 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +5 -4
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +26 -12
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +356 -349
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +157 -123
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +13 -8
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +15 -7
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +30 -20
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +25 -16
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +50 -34
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/AccessDenied.js +2 -2
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +67 -0
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -0
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +6 -2
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
- package/dist/billing/shared/components/EmptyState.js +7 -21
- package/dist/billing/shared/components/EmptyState.js.map +1 -1
- package/dist/billing/shared/components/PageHeader.js +7 -19
- package/dist/billing/shared/components/PageHeader.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +5 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/index.js +1 -0
- package/dist/billing/shared/hooks/index.js +1 -0
- package/dist/billing/shared/hooks/useActorProfiles.js +113 -0
- package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -0
- package/package.json +2 -2
|
@@ -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=\"h-4 w-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"h-4 w-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"h-4 w-4\" />;\n default:\n return <Tag className=\"h-4 w-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=\"h-6 w-6\" />}\n />\n\n <button\n onClick={() => navigateTo('/coupons/create')}\n className=\"px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2\"\n >\n <Plus className=\"h-4 w-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=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Coupons</span>\n <Tag className=\"h-4 w-4 text-accent-blue\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalCoupons}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Uses</span>\n <TrendingUp className=\"h-4 w-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalUses}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">All time</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Discount</span>\n <DollarSign className=\"h-4 w-4 text-accent-purple\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Active Rate</span>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-muted-foreground 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 h-4 w-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 h-4 w-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-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-lg bg-destructive px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-destructive/90 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-card border border-border 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 h-4 w-4 text-muted-foreground\" />\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-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 {/* 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-card border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"bg-muted/50 border-b border-border\">\n <tr>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Code</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Type</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Value</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Status</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Usage</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Valid Until</th>\n <th className=\"text-right p-4 text-sm font-medium text-foreground\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {loading ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n Loading coupons...\n </td>\n </tr>\n ) : filteredCoupons.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n No coupons found\n </td>\n </tr>\n ) : (\n filteredCoupons.map((coupon: Coupon) => (\n <tr key={coupon.id} className=\"hover:bg-muted/50\">\n <td className=\"p-4\">\n <div>\n <p className=\"font-medium text-foreground font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-muted-foreground\">{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-foreground 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-foreground\">\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-foreground\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-muted-foreground\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-muted rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-primary 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-muted-foreground\">\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 onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground\"\n title=\"View details\"\n >\n <Eye className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground\"\n title=\"Edit coupon\"\n >\n <Edit className=\"h-4 w-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"h-4 w-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <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-muted-foreground hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"h-4 w-4\" />\n </button>\n ) : (\n <button\n disabled\n className=\"p-2 rounded-lg text-muted-foreground/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"h-4 w-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-muted-foreground\">\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 onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </button>\n <span className=\"text-sm text-foreground font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted flex items-center gap-2\"\n >\n <Download className=\"h-4 w-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 { 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=\"px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2\"\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=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Coupons</span>\n <Tag className=\"size-4 text-accent-blue\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalCoupons}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Uses</span>\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalUses}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">All time</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Discount</span>\n <DollarSign className=\"size-4 text-accent-purple\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Active Rate</span>\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-muted-foreground 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-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-lg bg-destructive px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-destructive/90 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-card border border-border 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-muted-foreground\" />\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-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 {/* 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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\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-card border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"bg-muted/50 border-b border-border\">\n <tr>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Code</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Type</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Value</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Status</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Usage</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Valid Until</th>\n <th className=\"text-right p-4 text-sm font-medium text-foreground\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {loading ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n Loading coupons…\n </td>\n </tr>\n ) : filteredCoupons.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n No coupons found\n </td>\n </tr>\n ) : (\n filteredCoupons.map((coupon: Coupon) => (\n <tr key={coupon.id} className=\"hover:bg-muted/50\">\n <td className=\"p-4\">\n <div>\n <p className=\"font-medium text-foreground font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-muted-foreground\">{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-foreground 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-foreground\">\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-foreground\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-muted-foreground\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-muted rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-primary 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-muted-foreground\">\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-muted text-muted-foreground hover:text-foreground\"\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-muted text-muted-foreground hover:text-foreground\"\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-destructive/10 text-muted-foreground hover:text-destructive 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-muted-foreground 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-muted-foreground/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-muted-foreground\">\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 hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-foreground 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 hover:bg-muted 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 text-foreground hover:bg-muted 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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"}
|
|
@@ -50,13 +50,14 @@ var g = () => {
|
|
|
50
50
|
children: [/* @__PURE__ */ m("div", {
|
|
51
51
|
className: "flex items-center gap-4",
|
|
52
52
|
children: [/* @__PURE__ */ p("button", {
|
|
53
|
+
type: "button",
|
|
53
54
|
onClick: () => v("/coupons"),
|
|
54
55
|
className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground",
|
|
55
|
-
children: /* @__PURE__ */ p(o, { className: "
|
|
56
|
+
children: /* @__PURE__ */ p(o, { className: "size-5" })
|
|
56
57
|
}), /* @__PURE__ */ p(t, {
|
|
57
58
|
title: _("billing.coupons.createTitle", "Create Coupon"),
|
|
58
59
|
description: "Create a new discount code or promotional offer",
|
|
59
|
-
icon: /* @__PURE__ */ p(d, { className: "
|
|
60
|
+
icon: /* @__PURE__ */ p(d, { className: "size-6" })
|
|
60
61
|
})]
|
|
61
62
|
}), /* @__PURE__ */ m("form", {
|
|
62
63
|
onSubmit: async (e) => {
|
|
@@ -183,7 +184,7 @@ var g = () => {
|
|
|
183
184
|
type: "button",
|
|
184
185
|
onClick: () => D("type", e),
|
|
185
186
|
className: `p-4 rounded-lg border-2 transition-colors ${b.type === e ? "border-primary bg-primary/10" : "border-border hover:border-primary/50"}`,
|
|
186
|
-
children: [/* @__PURE__ */ p(n, { className: "
|
|
187
|
+
children: [/* @__PURE__ */ p(n, { className: "size-5 mx-auto mb-2" }), /* @__PURE__ */ p("p", {
|
|
187
188
|
className: "text-sm font-medium",
|
|
188
189
|
children: t
|
|
189
190
|
})]
|
|
@@ -196,7 +197,7 @@ var g = () => {
|
|
|
196
197
|
}),
|
|
197
198
|
/* @__PURE__ */ p("div", {
|
|
198
199
|
className: "relative",
|
|
199
|
-
children: b.type === "PERCENTAGE" ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(l, { className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
200
|
+
children: b.type === "PERCENTAGE" ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(l, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" }), /* @__PURE__ */ p("input", {
|
|
200
201
|
type: "number",
|
|
201
202
|
value: b.value,
|
|
202
203
|
onChange: (e) => D("value", parseFloat(e.target.value)),
|
|
@@ -204,7 +205,7 @@ var g = () => {
|
|
|
204
205
|
max: "100",
|
|
205
206
|
step: "0.01",
|
|
206
207
|
className: `w-full pl-10 pr-4 py-2 rounded-lg border ${S.value ? "border-destructive" : "border-border"} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`
|
|
207
|
-
})] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
208
|
+
})] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" }), /* @__PURE__ */ p("input", {
|
|
208
209
|
type: "number",
|
|
209
210
|
value: b.value,
|
|
210
211
|
onChange: (e) => D("value", parseFloat(e.target.value)),
|
|
@@ -225,7 +226,7 @@ var g = () => {
|
|
|
225
226
|
}),
|
|
226
227
|
/* @__PURE__ */ m("div", {
|
|
227
228
|
className: "relative",
|
|
228
|
-
children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
229
|
+
children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" }), /* @__PURE__ */ p("input", {
|
|
229
230
|
type: "number",
|
|
230
231
|
value: b.minPurchaseAmount || "",
|
|
231
232
|
onChange: (e) => D("minPurchaseAmount", e.target.value ? parseFloat(e.target.value) : null),
|
|
@@ -247,7 +248,7 @@ var g = () => {
|
|
|
247
248
|
}),
|
|
248
249
|
/* @__PURE__ */ m("div", {
|
|
249
250
|
className: "relative",
|
|
250
|
-
children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
251
|
+
children: [/* @__PURE__ */ p(s, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" }), /* @__PURE__ */ p("input", {
|
|
251
252
|
type: "number",
|
|
252
253
|
value: b.maxDiscountAmount || "",
|
|
253
254
|
onChange: (e) => D("maxDiscountAmount", e.target.value ? parseFloat(e.target.value) : null),
|
|
@@ -408,7 +409,7 @@ var g = () => {
|
|
|
408
409
|
type: "submit",
|
|
409
410
|
disabled: w,
|
|
410
411
|
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",
|
|
411
|
-
children: w ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", { className: "
|
|
412
|
+
children: w ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", { className: "size-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin" }), "Creating…"] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Create Coupon"] })
|
|
412
413
|
})]
|
|
413
414
|
})
|
|
414
415
|
]
|
|
@@ -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 } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, Tag, Percent, DollarSign, Package } from 'lucide-react';\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 [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 // 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 // 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 // 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 onClick={() => navigateTo('/coupons')}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground\"\n >\n <ArrowLeft className=\"h-5 w-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=\"h-6 w-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 {/* 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 Amount', icon: DollarSign },\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 className=\"h-5 w-5 mx-auto mb-2\" />\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 h-4 w-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 <DollarSign className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-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 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\n </label>\n <div className=\"relative\">\n <DollarSign className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\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 to use coupon\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\n </label>\n <div className=\"relative\">\n <DollarSign className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\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 amount for percentage-based coupons\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 </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=\"h-4 w-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin\" />\n Creating...\n </>\n ) : (\n <>\n <Save className=\"h-4 w-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 } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ArrowLeft, Save, Tag, Percent, DollarSign, Package } from 'lucide-react';\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 [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 // 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 // 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 // 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 {/* 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 Amount', icon: DollarSign },\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 className=\"size-5 mx-auto mb-2\" />\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 <DollarSign 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 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\n </label>\n <div className=\"relative\">\n <DollarSign className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\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 to use coupon\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\n </label>\n <div className=\"relative\">\n <DollarSign className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\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 amount for percentage-based coupons\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 </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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"}
|
|
@@ -73,15 +73,16 @@ var g = () => {
|
|
|
73
73
|
title: _("billing.coupons.editTitle", "Edit Coupon"),
|
|
74
74
|
description: "Loading coupon data...",
|
|
75
75
|
actions: /* @__PURE__ */ m("button", {
|
|
76
|
+
type: "button",
|
|
76
77
|
onClick: () => v("/coupons"),
|
|
77
78
|
className: "px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted flex items-center gap-2",
|
|
78
|
-
children: [/* @__PURE__ */ p(u, { className: "
|
|
79
|
+
children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Back"]
|
|
79
80
|
})
|
|
80
81
|
}), /* @__PURE__ */ p("div", {
|
|
81
82
|
className: "flex items-center justify-center h-64",
|
|
82
83
|
children: /* @__PURE__ */ p("div", {
|
|
83
84
|
className: "text-muted-foreground",
|
|
84
|
-
children: "Loading coupon
|
|
85
|
+
children: "Loading coupon…"
|
|
85
86
|
})
|
|
86
87
|
})]
|
|
87
88
|
});
|
|
@@ -91,9 +92,10 @@ var g = () => {
|
|
|
91
92
|
title: "Edit Coupon",
|
|
92
93
|
description: "Coupon not found",
|
|
93
94
|
actions: /* @__PURE__ */ m("button", {
|
|
95
|
+
type: "button",
|
|
94
96
|
onClick: () => v("/coupons"),
|
|
95
97
|
className: "px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted flex items-center gap-2",
|
|
96
|
-
children: [/* @__PURE__ */ p(u, { className: "
|
|
98
|
+
children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Back"]
|
|
97
99
|
})
|
|
98
100
|
}), /* @__PURE__ */ p("div", {
|
|
99
101
|
className: "flex items-center justify-center h-64",
|
|
@@ -110,9 +112,10 @@ var g = () => {
|
|
|
110
112
|
title: "Edit Coupon",
|
|
111
113
|
description: `Editing: ${R.code}`,
|
|
112
114
|
actions: /* @__PURE__ */ m("button", {
|
|
115
|
+
type: "button",
|
|
113
116
|
onClick: () => v("/coupons"),
|
|
114
117
|
className: "px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted flex items-center gap-2",
|
|
115
|
-
children: [/* @__PURE__ */ p(u, { className: "
|
|
118
|
+
children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Back to Coupons"]
|
|
116
119
|
})
|
|
117
120
|
}), /* @__PURE__ */ p("div", {
|
|
118
121
|
className: "max-w-3xl mx-auto mt-8",
|
|
@@ -330,7 +333,7 @@ var g = () => {
|
|
|
330
333
|
}),
|
|
331
334
|
j && /* @__PURE__ */ m("div", {
|
|
332
335
|
className: "flex items-start gap-3 p-4 rounded-lg bg-destructive/10 border border-destructive/20",
|
|
333
|
-
children: [/* @__PURE__ */ p(l, { className: "
|
|
336
|
+
children: [/* @__PURE__ */ p(l, { className: "size-5 text-destructive mt-0.5" }), /* @__PURE__ */ m("div", {
|
|
334
337
|
className: "flex-1",
|
|
335
338
|
children: [/* @__PURE__ */ p("p", {
|
|
336
339
|
className: "text-sm font-medium text-destructive",
|
|
@@ -343,14 +346,14 @@ var g = () => {
|
|
|
343
346
|
}),
|
|
344
347
|
N && /* @__PURE__ */ m("div", {
|
|
345
348
|
className: "flex items-start gap-3 p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-bg-subtle",
|
|
346
|
-
children: [/* @__PURE__ */ p(d, { className: "
|
|
349
|
+
children: [/* @__PURE__ */ p(d, { className: "size-5 text-status-success-text mt-0.5" }), /* @__PURE__ */ m("div", {
|
|
347
350
|
className: "flex-1",
|
|
348
351
|
children: [/* @__PURE__ */ p("p", {
|
|
349
352
|
className: "text-sm font-medium text-status-success-text",
|
|
350
353
|
children: "Success"
|
|
351
354
|
}), /* @__PURE__ */ p("p", {
|
|
352
355
|
className: "text-sm text-status-success-text/90 mt-1",
|
|
353
|
-
children: "Coupon updated successfully! Redirecting
|
|
356
|
+
children: "Coupon updated successfully! Redirecting…"
|
|
354
357
|
})]
|
|
355
358
|
})]
|
|
356
359
|
}),
|
|
@@ -360,7 +363,7 @@ var g = () => {
|
|
|
360
363
|
type: "submit",
|
|
361
364
|
disabled: E || N,
|
|
362
365
|
className: "px-6 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
363
|
-
children: [/* @__PURE__ */ p(f, { className: "
|
|
366
|
+
children: [/* @__PURE__ */ p(f, { className: "size-4" }), E ? "Updating..." : "Update Coupon"]
|
|
364
367
|
}), /* @__PURE__ */ p("button", {
|
|
365
368
|
type: "button",
|
|
366
369
|
onClick: () => v("/coupons"),
|