@burdenoff/microfe-billing 2026.1013.3 → 2026.1013.4
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/modules/addons/pages/AddonCreatePage.js +4 -4
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -3
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js +3 -3
- package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js +2 -2
- package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +2 -2
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/StoreShippingSection.js +1 -1
- package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +2 -2
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +2 -2
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +1 -1
- 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/CreditWithdrawalPage.js +2 -2
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +3 -3
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +3 -3
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -3
- package/dist/billing/modules/plans/pages/PlanEditPage.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/QuotaTemplateEditPage.js +2 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompleteBillingAccountModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CompleteBillingAccountModal.tsx"],"sourcesContent":["/**\n * Modal shown at checkout when the selected billing account is missing\n * a billing address or contact number — fields that can't be auto-filled\n * during account creation but are required by payment providers.\n */\n\nimport { type FC, useState } from 'react';\nimport { MapPin, Phone, Loader2, X } from 'lucide-react';\nimport {\n useSetBillingAddressMutation,\n useUpdateBillingAccountMutation,\n} from '../../../../generated/global-operations';\n\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\ninterface FormState {\n contactNumber: string;\n line1: string;\n line2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n contactNumber?: string;\n line1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\ninterface Props {\n billingAccountId: string;\n /** Whether contactNumber is already filled on the account */\n hasPhone: boolean;\n /** Whether a billing address already exists (with line1 + postalCode) */\n hasAddress: boolean;\n onCompleted: () => void;\n onCancel: () => void;\n}\n\n/** Returns true when a billing account has everything required for checkout. */\nexport function isBillingAccountComplete(account: {\n contactNumber?: string | null;\n billingAddresses?: Array<{\n line1?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n }> | null;\n}): boolean {\n const hasPhone = Boolean(account.contactNumber?.trim());\n const primaryAddress = account.billingAddresses?.[0];\n const hasAddress = Boolean(\n primaryAddress?.line1?.trim() &&\n primaryAddress?.city?.trim() &&\n primaryAddress?.postalCode?.trim() &&\n primaryAddress?.country?.trim()\n );\n return hasPhone && hasAddress;\n}\n\nexport const CompleteBillingAccountModal: FC<Props> = ({\n billingAccountId,\n hasPhone,\n hasAddress,\n onCompleted,\n onCancel,\n}) => {\n const [form, setForm] = useState<FormState>({\n contactNumber: '',\n line1: '',\n line2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [submitting, setSubmitting] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n const [setBillingAddress] = useSetBillingAddressMutation();\n const [updateBillingAccount] = useUpdateBillingAccountMutation();\n\n const set = (field: keyof FormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validate = (): boolean => {\n const e: FormErrors = {};\n if (!hasPhone && !form.contactNumber.trim()) {\n e.contactNumber = 'Phone number is required for payment processing';\n }\n if (!hasAddress) {\n if (!form.line1.trim()) e.line1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n }\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async () => {\n if (!validate()) return;\n setSubmitting(true);\n setServerError(null);\n\n try {\n const ops: Promise<unknown>[] = [];\n\n if (!hasPhone && form.contactNumber.trim()) {\n ops.push(\n updateBillingAccount({\n variables: {\n input: {\n id: billingAccountId,\n contactNumber: form.contactNumber.trim(),\n },\n },\n })\n );\n }\n\n if (!hasAddress) {\n ops.push(\n setBillingAddress({\n variables: {\n input: {\n billingAccountId,\n line1: form.line1.trim(),\n ...(form.line2.trim() ? { line2: form.line2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n },\n },\n })\n );\n }\n\n await Promise.all(ops);\n onCompleted();\n } catch {\n setServerError('Failed to save billing details. Please try again.');\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-start justify-between p-6 border-b border-border-subtle\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Complete billing details</h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n We need a few more details before processing your payment.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-2 text-text-secondary hover:text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"p-6 space-y-6\">\n {serverError && (\n <div className=\"p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n {serverError}\n </div>\n )}\n\n {/* Phone */}\n {!hasPhone && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Phone className=\"size-4 text-text-secondary\" />\n Contact number\n </div>\n <div>\n <input\n type=\"tel\"\n value={form.contactNumber}\n onChange={(e) => set('contactNumber', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.contactNumber ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.contactNumber && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.contactNumber}</p>\n )}\n </div>\n </section>\n )}\n\n {/* Address */}\n {!hasAddress && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Billing address\n </div>\n\n <div className=\"space-y-3\">\n {/* Country */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => set('country', e.target.value)}\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.country ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Line 1 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.line1}\n onChange={(e) => set('line1', e.target.value)}\n placeholder=\"123 Main St\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.line1 ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.line1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.line1}</p>\n )}\n </div>\n\n {/* Line 2 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.line2}\n onChange={(e) => set('line2', e.target.value)}\n placeholder=\"Apt 4B\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City + State */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => set('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.city ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => set('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Postal code */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => set('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.postalCode ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.postalCode}</p>\n )}\n </div>\n </div>\n </section>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 p-6 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={submitting}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={submitting}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {submitting && <Loader2 className=\"size-4 animate-spin\" />}\n Save and continue\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompleteBillingAccountModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CompleteBillingAccountModal.tsx"],"sourcesContent":["/**\n * Modal shown at checkout when the selected billing account is missing\n * a billing address or contact number — fields that can't be auto-filled\n * during account creation but are required by payment providers.\n */\n\nimport { type FC, useState } from 'react';\nimport { MapPin, Phone, Loader2, X } from 'lucide-react';\nimport {\n useSetBillingAddressMutation,\n useUpdateBillingAccountMutation,\n} from '../../../../generated/global-operations';\n\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\ninterface FormState {\n contactNumber: string;\n line1: string;\n line2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n contactNumber?: string;\n line1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\ninterface Props {\n billingAccountId: string;\n /** Whether contactNumber is already filled on the account */\n hasPhone: boolean;\n /** Whether a billing address already exists (with line1 + postalCode) */\n hasAddress: boolean;\n onCompleted: () => void;\n onCancel: () => void;\n}\n\n/** Returns true when a billing account has everything required for checkout. */\nexport function isBillingAccountComplete(account: {\n contactNumber?: string | null;\n billingAddresses?: Array<{\n line1?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n }> | null;\n}): boolean {\n const hasPhone = Boolean(account.contactNumber?.trim());\n const primaryAddress = account.billingAddresses?.[0];\n const hasAddress = Boolean(\n primaryAddress?.line1?.trim() &&\n primaryAddress?.city?.trim() &&\n primaryAddress?.postalCode?.trim() &&\n primaryAddress?.country?.trim()\n );\n return hasPhone && hasAddress;\n}\n\nexport const CompleteBillingAccountModal: FC<Props> = ({\n billingAccountId,\n hasPhone,\n hasAddress,\n onCompleted,\n onCancel,\n}) => {\n const [form, setForm] = useState<FormState>({\n contactNumber: '',\n line1: '',\n line2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [submitting, setSubmitting] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n const [setBillingAddress] = useSetBillingAddressMutation();\n const [updateBillingAccount] = useUpdateBillingAccountMutation();\n\n const set = (field: keyof FormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validate = (): boolean => {\n const e: FormErrors = {};\n if (!hasPhone && !form.contactNumber.trim()) {\n e.contactNumber = 'Phone number is required for payment processing';\n }\n if (!hasAddress) {\n if (!form.line1.trim()) e.line1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n }\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async () => {\n if (!validate()) return;\n setSubmitting(true);\n setServerError(null);\n\n try {\n const ops: Promise<unknown>[] = [];\n\n if (!hasPhone && form.contactNumber.trim()) {\n ops.push(\n updateBillingAccount({\n variables: {\n input: {\n id: billingAccountId,\n contactNumber: form.contactNumber.trim(),\n },\n },\n })\n );\n }\n\n if (!hasAddress) {\n ops.push(\n setBillingAddress({\n variables: {\n input: {\n billingAccountId,\n line1: form.line1.trim(),\n ...(form.line2.trim() ? { line2: form.line2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n },\n },\n })\n );\n }\n\n await Promise.all(ops);\n onCompleted();\n } catch {\n setServerError('Failed to save billing details. Please try again.');\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-start justify-between p-6 border-b border-border-subtle\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Complete billing details</h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n We need a few more details before processing your payment.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-2 text-text-secondary hover:text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"p-6 space-y-6\">\n {serverError && (\n <div className=\"p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n {serverError}\n </div>\n )}\n\n {/* Phone */}\n {!hasPhone && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Phone className=\"size-4 text-text-secondary\" />\n Contact number\n </div>\n <div>\n <input\n type=\"tel\"\n value={form.contactNumber}\n onChange={(e) => set('contactNumber', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.contactNumber ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.contactNumber && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.contactNumber}</p>\n )}\n </div>\n </section>\n )}\n\n {/* Address */}\n {!hasAddress && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Billing address\n </div>\n\n <div className=\"space-y-3\">\n {/* Country */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => set('country', e.target.value)}\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.country ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Line 1 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.line1}\n onChange={(e) => set('line1', e.target.value)}\n placeholder=\"123 Main St\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.line1 ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.line1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.line1}</p>\n )}\n </div>\n\n {/* Line 2 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.line2}\n onChange={(e) => set('line2', e.target.value)}\n placeholder=\"Apt 4B\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City + State */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => set('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.city ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => set('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Postal code */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => set('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.postalCode ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.postalCode}</p>\n )}\n </div>\n </div>\n </section>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 p-6 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={submitting}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={submitting}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {submitting && <Loader2 className=\"size-4 animate-spin\" />}\n Save and continue\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -278,7 +278,7 @@ var x = ({ storeOrderId: x, currency: S, onChange: C }) => {
|
|
|
278
278
|
}), /* @__PURE__ */ g("select", {
|
|
279
279
|
value: V.country,
|
|
280
280
|
onChange: (e) => Q("country", e.target.value),
|
|
281
|
-
className: $(!!U.country)
|
|
281
|
+
className: `${$(!!U.country)} scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary`,
|
|
282
282
|
children: ee.map((e) => /* @__PURE__ */ g("option", {
|
|
283
283
|
value: e.value,
|
|
284
284
|
children: e.label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoreShippingSection.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/StoreShippingSection.tsx"],"sourcesContent":["/**\n * StoreShippingSection\n * ============================================================================\n * Physical-store-order-only shipping picker rendered inside the checkout\n * left column. Lets the buyer pick (or add) a delivery address and a shipping\n * method, then fetches a DISPLAY-ONLY cost estimate. The authoritative cost is\n * recomputed and persisted server-side via `setStoreOrderShipping` at\n * proceed-to-payment time (see CheckoutPage.handleProceedToPayment).\n *\n * This component is only ever mounted for store orders that contain a PHYSICAL\n * line item, so nothing here runs on the digital/plan/addon/credits checkout\n * path.\n *\n * Field set / validation / country list mirror\n * `components/CompleteBillingAccountModal.tsx` for visual + behavioural\n * consistency (shipping adds a required recipient `fullName`).\n */\n\nimport { type FC, useEffect, useMemo, useRef, useState } from 'react';\nimport { MapPin, Truck, Plus, Loader2, Check, X, AlertTriangle } from 'lucide-react';\nimport {\n useMyShippingAddresses,\n useCreateShippingAddress,\n useShippingMethods,\n useEstimateOrderShipping,\n type ShippingAddress,\n} from '../hooks/useStoreShipping';\n\n// Mirror of CompleteBillingAccountModal COUNTRY_OPTIONS.\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\nexport interface StoreShippingSelection {\n shippingAddressId: string | null;\n shippingMethodId: string | null;\n /** DISPLAY-ONLY estimate in the order currency; backend recomputes on submit. */\n shippingCost: number;\n destinationCountry: string | null;\n}\n\ninterface Props {\n storeOrderId: string;\n /** Order/checkout currency, used only for formatting the estimate. */\n currency: string;\n onChange: (selection: StoreShippingSelection) => void;\n}\n\ninterface AddressFormState {\n fullName: string;\n phone: string;\n addressLine1: string;\n addressLine2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface AddressFormErrors {\n fullName?: string;\n addressLine1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\nconst EMPTY_FORM: AddressFormState = {\n fullName: '',\n phone: '',\n addressLine1: '',\n addressLine2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n};\n\nfunction formatCost(amount: number, currency: string): string {\n try {\n return amount.toLocaleString('en', { style: 'currency', currency: currency || 'USD' });\n } catch {\n return `${currency || 'USD'} ${amount.toFixed(2)}`;\n }\n}\n\nfunction formatEta(minDays?: number | null, maxDays?: number | null): string | null {\n if (minDays == null && maxDays == null) return null;\n if (minDays != null && maxDays != null) {\n return minDays === maxDays ? `${minDays} days` : `${minDays}–${maxDays} days`;\n }\n const single = minDays ?? maxDays;\n return single != null ? `${single} days` : null;\n}\n\nexport const StoreShippingSection: FC<Props> = ({ storeOrderId, currency, onChange }) => {\n const {\n addresses,\n loading: addressesLoading,\n refetch: refetchAddresses,\n } = useMyShippingAddresses();\n const { createShippingAddress, loading: creatingAddress } = useCreateShippingAddress();\n const { methods, loading: methodsLoading } = useShippingMethods();\n const [runEstimate, { data: estData, loading: estimateLoading, error: estimateError }] =\n useEstimateOrderShipping();\n\n const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);\n const [selectedMethodId, setSelectedMethodId] = useState<string | null>(null);\n\n const [showAddForm, setShowAddForm] = useState(false);\n const [form, setForm] = useState<AddressFormState>(EMPTY_FORM);\n const [formErrors, setFormErrors] = useState<AddressFormErrors>({});\n const [formServerError, setFormServerError] = useState<string | null>(null);\n\n // Keep onChange in a ref so the emit effect never depends on its identity.\n const onChangeRef = useRef(onChange);\n useEffect(() => {\n onChangeRef.current = onChange;\n });\n\n const selectedAddress = useMemo<ShippingAddress | undefined>(\n () => addresses.find((a) => a.id === selectedAddressId),\n [addresses, selectedAddressId]\n );\n\n // Methods available for the selected address' destination country.\n // Empty availableCountries is treated as \"available everywhere\".\n const availableMethods = useMemo(() => {\n if (!selectedAddress) return [];\n const country = selectedAddress.country;\n return methods\n .filter(\n (m) =>\n m.isActive &&\n (m.availableCountries.length === 0 || m.availableCountries.includes(country)) &&\n !m.excludedCountries.includes(country)\n )\n .sort((a, b) => a.displayOrder - b.displayOrder);\n }, [methods, selectedAddress]);\n\n // Default the address selection to the user's default (or first) address.\n useEffect(() => {\n if (!selectedAddressId && addresses.length > 0) {\n const preferred = addresses.find((a) => a.isDefault) ?? addresses[0];\n setSelectedAddressId(preferred.id);\n }\n }, [addresses, selectedAddressId]);\n\n // Drop a selected method that is no longer valid for the chosen destination.\n useEffect(() => {\n if (selectedMethodId && !availableMethods.some((m) => m.id === selectedMethodId)) {\n setSelectedMethodId(null);\n }\n }, [availableMethods, selectedMethodId]);\n\n // Default the method selection when there's exactly one option — nothing to\n // actually choose between, so don't make the buyer click it.\n useEffect(() => {\n if (!selectedMethodId && availableMethods.length === 1) {\n setSelectedMethodId(availableMethods[0].id);\n }\n }, [availableMethods, selectedMethodId]);\n\n // Fetch a fresh estimate whenever address + method are both chosen.\n useEffect(() => {\n if (selectedAddress && selectedMethodId) {\n void runEstimate({\n variables: {\n input: {\n orderId: storeOrderId,\n shippingMethodId: selectedMethodId,\n destinationCountry: selectedAddress.country,\n destinationPostalCode: selectedAddress.postalCode,\n },\n },\n });\n }\n }, [\n selectedAddress?.id,\n selectedAddress?.country,\n selectedAddress?.postalCode,\n selectedMethodId,\n storeOrderId,\n runEstimate,\n selectedAddress,\n ]);\n\n const estimate = estData?.estimateOrderShipping;\n const estimateReady = Boolean(\n selectedAddressId &&\n selectedMethodId &&\n !estimateLoading &&\n estimate &&\n estimate.shippingMethodId === selectedMethodId\n );\n\n // Emit the current selection upward. Method id + cost are only surfaced once a\n // matching estimate has resolved, so the parent's proceed gate stays disabled\n // while an estimate is in flight (free shipping resolves with cost 0, which is\n // still \"ready\").\n useEffect(() => {\n onChangeRef.current({\n shippingAddressId: selectedAddressId,\n shippingMethodId: estimateReady ? selectedMethodId : null,\n shippingCost: estimateReady && estimate ? estimate.cost : 0,\n destinationCountry: selectedAddress?.country ?? null,\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- emit derived selection; estimate object identity excluded intentionally\n }, [\n selectedAddressId,\n selectedMethodId,\n estimateReady,\n estimate?.cost,\n selectedAddress?.country,\n ]);\n\n const setField = (field: keyof AddressFormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validateForm = (): boolean => {\n const e: AddressFormErrors = {};\n if (!form.fullName.trim()) e.fullName = 'Recipient name is required';\n if (!form.addressLine1.trim()) e.addressLine1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n setFormErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleCreateAddress = async () => {\n if (!validateForm()) return;\n setFormServerError(null);\n try {\n const created = await createShippingAddress({\n fullName: form.fullName.trim(),\n ...(form.phone.trim() ? { phone: form.phone.trim() } : {}),\n addressLine1: form.addressLine1.trim(),\n ...(form.addressLine2.trim() ? { addressLine2: form.addressLine2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n isDefault: addresses.length === 0,\n });\n if (!created) {\n setFormServerError('Failed to save address. Please try again.');\n return;\n }\n await refetchAddresses();\n setSelectedAddressId(created.id);\n setSelectedMethodId(null);\n setForm(EMPTY_FORM);\n setFormErrors({});\n setShowAddForm(false);\n } catch {\n setFormServerError('Failed to save address. Please try again.');\n }\n };\n\n const inputClass = (hasError: boolean) =>\n `w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n hasError ? 'border-status-error-border' : 'border-border-subtle'\n }`;\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Truck className=\"size-4 text-text-secondary\" />\n Shipping\n </div>\n\n {/* Delivery address */}\n <section className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Delivery address\n </div>\n {!showAddForm && (\n <button\n type=\"button\"\n onClick={() => {\n setShowAddForm(true);\n setFormServerError(null);\n }}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-text-link hover:underline\"\n >\n <Plus className=\"size-3.5\" />\n Add address\n </button>\n )}\n </div>\n\n {addressesLoading && addresses.length === 0 ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n Loading addresses…\n </div>\n ) : (\n <>\n {addresses.length === 0 && !showAddForm && (\n <p className=\"text-sm text-text-secondary\">\n No saved delivery addresses yet. Add one to continue.\n </p>\n )}\n {addresses.length > 0 && (\n <div className=\"space-y-2\">\n {addresses.map((addr) => {\n const selected = addr.id === selectedAddressId;\n return (\n <button\n key={addr.id}\n type=\"button\"\n onClick={() => {\n setSelectedAddressId(addr.id);\n setSelectedMethodId(null);\n }}\n className={`w-full text-left p-3 rounded-input border transition-colors ${\n selected\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">\n {addr.fullName}\n {addr.label ? (\n <span className=\"ml-2 text-xs text-text-muted\">({addr.label})</span>\n ) : null}\n </p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {[\n addr.addressLine1,\n addr.addressLine2,\n addr.city,\n addr.state,\n addr.postalCode,\n addr.country,\n ]\n .filter(Boolean)\n .join(', ')}\n </p>\n {addr.phone && (\n <p className=\"text-text-muted text-xs mt-0.5\">{addr.phone}</p>\n )}\n </div>\n {selected && (\n <Check className=\"size-4 text-action-primary-bg shrink-0 mt-0.5\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n )}\n </>\n )}\n\n {/* Inline add-address form */}\n {showAddForm && (\n <div className=\"rounded-input border border-border-subtle p-3 space-y-3 bg-bg-sunken\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">New delivery address</span>\n <button\n type=\"button\"\n onClick={() => {\n setShowAddForm(false);\n setFormErrors({});\n setFormServerError(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary rounded-button\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {formServerError && (\n <div className=\"p-2 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-xs text-status-error-text\">\n {formServerError}\n </div>\n )}\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Recipient name</label>\n <input\n type=\"text\"\n value={form.fullName}\n onChange={(e) => setField('fullName', e.target.value)}\n placeholder=\"Jane Doe\"\n className={inputClass(Boolean(formErrors.fullName))}\n />\n {formErrors.fullName && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.fullName}</p>\n )}\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Phone <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"tel\"\n value={form.phone}\n onChange={(e) => setField('phone', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={inputClass(false)}\n />\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => setField('country', e.target.value)}\n className={inputClass(Boolean(formErrors.country))}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.addressLine1}\n onChange={(e) => setField('addressLine1', e.target.value)}\n placeholder=\"123 Main St\"\n className={inputClass(Boolean(formErrors.addressLine1))}\n />\n {formErrors.addressLine1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.addressLine1}</p>\n )}\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.addressLine2}\n onChange={(e) => setField('addressLine2', e.target.value)}\n placeholder=\"Apt 4B\"\n className={inputClass(false)}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => setField('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={inputClass(Boolean(formErrors.city))}\n />\n {formErrors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => setField('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className={inputClass(false)}\n />\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => setField('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={inputClass(Boolean(formErrors.postalCode))}\n />\n {formErrors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.postalCode}</p>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleCreateAddress}\n disabled={creatingAddress}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {creatingAddress && <Loader2 className=\"size-4 animate-spin\" />}\n Save address\n </button>\n </div>\n </div>\n )}\n </section>\n\n {/* Shipping method */}\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Truck className=\"size-4 text-text-secondary\" />\n Shipping method\n </div>\n\n {!selectedAddress ? (\n <p className=\"text-sm text-text-secondary\">Select a delivery address to see options.</p>\n ) : methodsLoading && methods.length === 0 ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n Loading options…\n </div>\n ) : availableMethods.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No shipping methods available for {selectedAddress.country}. Try a different address.\n </p>\n ) : availableMethods.length === 1 ? (\n (() => {\n const method = availableMethods[0];\n const eta = formatEta(method.estimatedMinDays, method.estimatedMaxDays);\n const showEstimate = estimateReady && estimate;\n const unavailable = Boolean(estimateError) && !estimateLoading;\n return (\n <div\n className={`w-full p-3 rounded-input border bg-bg-sunken ${\n unavailable ? 'border-status-error-border' : 'border-action-primary-bg'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">{method.name}</p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {method.carrier ? `${method.carrier} · ` : ''}\n {eta ?? 'Delivery time varies'}\n </p>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {estimateLoading ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : unavailable ? (\n <>\n <span className=\"text-sm font-semibold text-status-error-text\">\n Not available\n </span>\n <AlertTriangle className=\"size-4 text-status-error-text\" />\n </>\n ) : (\n <>\n <span className=\"text-sm font-semibold text-text-primary\">\n {showEstimate\n ? formatCost(estimate.cost, estimate.currency || currency)\n : formatCost(method.baseCost, method.currency || currency)}\n </span>\n <Check className=\"size-4 text-action-primary-bg\" />\n </>\n )}\n </div>\n </div>\n </div>\n );\n })()\n ) : (\n <div className=\"space-y-2\">\n {availableMethods.map((method) => {\n const selected = method.id === selectedMethodId;\n const eta = formatEta(method.estimatedMinDays, method.estimatedMaxDays);\n const showEstimate = selected && estimateReady && estimate;\n const unavailable = selected && Boolean(estimateError) && !estimateLoading;\n return (\n <button\n key={method.id}\n type=\"button\"\n onClick={() => setSelectedMethodId(method.id)}\n className={`w-full text-left p-3 rounded-input border transition-colors ${\n unavailable\n ? 'border-status-error-border bg-bg-sunken'\n : selected\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">{method.name}</p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {method.carrier ? `${method.carrier} · ` : ''}\n {eta ?? 'Delivery time varies'}\n </p>\n </div>\n <div className=\"text-right shrink-0\">\n {selected && estimateLoading ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : unavailable ? (\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-status-error-text\">\n <AlertTriangle className=\"size-4\" />\n Not available\n </span>\n ) : (\n <span className=\"text-sm font-semibold text-text-primary\">\n {showEstimate\n ? formatCost(estimate.cost, estimate.currency || currency)\n : formatCost(method.baseCost, method.currency || currency)}\n </span>\n )}\n </div>\n </div>\n </button>\n );\n })}\n </div>\n )}\n\n {estimateReady && estimate && (\n <p className=\"text-xs text-text-secondary\">\n Estimated shipping: {formatCost(estimate.cost, estimate.currency || currency)}\n {estimate.estimatedDeliveryDate\n ? ` · arrives around ${new Date(estimate.estimatedDeliveryDate).toLocaleDateString()}`\n : ''}\n </p>\n )}\n\n {estimateError && !estimateLoading && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" aria-hidden=\"true\" />\n <span>\n {estimateError.message ||\n \"Sorry, this item can't be delivered to your address right now. Please try a different delivery address or contact support.\"}\n </span>\n </div>\n )}\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StoreShippingSection.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/StoreShippingSection.tsx"],"sourcesContent":["/**\n * StoreShippingSection\n * ============================================================================\n * Physical-store-order-only shipping picker rendered inside the checkout\n * left column. Lets the buyer pick (or add) a delivery address and a shipping\n * method, then fetches a DISPLAY-ONLY cost estimate. The authoritative cost is\n * recomputed and persisted server-side via `setStoreOrderShipping` at\n * proceed-to-payment time (see CheckoutPage.handleProceedToPayment).\n *\n * This component is only ever mounted for store orders that contain a PHYSICAL\n * line item, so nothing here runs on the digital/plan/addon/credits checkout\n * path.\n *\n * Field set / validation / country list mirror\n * `components/CompleteBillingAccountModal.tsx` for visual + behavioural\n * consistency (shipping adds a required recipient `fullName`).\n */\n\nimport { type FC, useEffect, useMemo, useRef, useState } from 'react';\nimport { MapPin, Truck, Plus, Loader2, Check, X, AlertTriangle } from 'lucide-react';\nimport {\n useMyShippingAddresses,\n useCreateShippingAddress,\n useShippingMethods,\n useEstimateOrderShipping,\n type ShippingAddress,\n} from '../hooks/useStoreShipping';\n\n// Mirror of CompleteBillingAccountModal COUNTRY_OPTIONS.\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\nexport interface StoreShippingSelection {\n shippingAddressId: string | null;\n shippingMethodId: string | null;\n /** DISPLAY-ONLY estimate in the order currency; backend recomputes on submit. */\n shippingCost: number;\n destinationCountry: string | null;\n}\n\ninterface Props {\n storeOrderId: string;\n /** Order/checkout currency, used only for formatting the estimate. */\n currency: string;\n onChange: (selection: StoreShippingSelection) => void;\n}\n\ninterface AddressFormState {\n fullName: string;\n phone: string;\n addressLine1: string;\n addressLine2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface AddressFormErrors {\n fullName?: string;\n addressLine1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\nconst EMPTY_FORM: AddressFormState = {\n fullName: '',\n phone: '',\n addressLine1: '',\n addressLine2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n};\n\nfunction formatCost(amount: number, currency: string): string {\n try {\n return amount.toLocaleString('en', { style: 'currency', currency: currency || 'USD' });\n } catch {\n return `${currency || 'USD'} ${amount.toFixed(2)}`;\n }\n}\n\nfunction formatEta(minDays?: number | null, maxDays?: number | null): string | null {\n if (minDays == null && maxDays == null) return null;\n if (minDays != null && maxDays != null) {\n return minDays === maxDays ? `${minDays} days` : `${minDays}–${maxDays} days`;\n }\n const single = minDays ?? maxDays;\n return single != null ? `${single} days` : null;\n}\n\nexport const StoreShippingSection: FC<Props> = ({ storeOrderId, currency, onChange }) => {\n const {\n addresses,\n loading: addressesLoading,\n refetch: refetchAddresses,\n } = useMyShippingAddresses();\n const { createShippingAddress, loading: creatingAddress } = useCreateShippingAddress();\n const { methods, loading: methodsLoading } = useShippingMethods();\n const [runEstimate, { data: estData, loading: estimateLoading, error: estimateError }] =\n useEstimateOrderShipping();\n\n const [selectedAddressId, setSelectedAddressId] = useState<string | null>(null);\n const [selectedMethodId, setSelectedMethodId] = useState<string | null>(null);\n\n const [showAddForm, setShowAddForm] = useState(false);\n const [form, setForm] = useState<AddressFormState>(EMPTY_FORM);\n const [formErrors, setFormErrors] = useState<AddressFormErrors>({});\n const [formServerError, setFormServerError] = useState<string | null>(null);\n\n // Keep onChange in a ref so the emit effect never depends on its identity.\n const onChangeRef = useRef(onChange);\n useEffect(() => {\n onChangeRef.current = onChange;\n });\n\n const selectedAddress = useMemo<ShippingAddress | undefined>(\n () => addresses.find((a) => a.id === selectedAddressId),\n [addresses, selectedAddressId]\n );\n\n // Methods available for the selected address' destination country.\n // Empty availableCountries is treated as \"available everywhere\".\n const availableMethods = useMemo(() => {\n if (!selectedAddress) return [];\n const country = selectedAddress.country;\n return methods\n .filter(\n (m) =>\n m.isActive &&\n (m.availableCountries.length === 0 || m.availableCountries.includes(country)) &&\n !m.excludedCountries.includes(country)\n )\n .sort((a, b) => a.displayOrder - b.displayOrder);\n }, [methods, selectedAddress]);\n\n // Default the address selection to the user's default (or first) address.\n useEffect(() => {\n if (!selectedAddressId && addresses.length > 0) {\n const preferred = addresses.find((a) => a.isDefault) ?? addresses[0];\n setSelectedAddressId(preferred.id);\n }\n }, [addresses, selectedAddressId]);\n\n // Drop a selected method that is no longer valid for the chosen destination.\n useEffect(() => {\n if (selectedMethodId && !availableMethods.some((m) => m.id === selectedMethodId)) {\n setSelectedMethodId(null);\n }\n }, [availableMethods, selectedMethodId]);\n\n // Default the method selection when there's exactly one option — nothing to\n // actually choose between, so don't make the buyer click it.\n useEffect(() => {\n if (!selectedMethodId && availableMethods.length === 1) {\n setSelectedMethodId(availableMethods[0].id);\n }\n }, [availableMethods, selectedMethodId]);\n\n // Fetch a fresh estimate whenever address + method are both chosen.\n useEffect(() => {\n if (selectedAddress && selectedMethodId) {\n void runEstimate({\n variables: {\n input: {\n orderId: storeOrderId,\n shippingMethodId: selectedMethodId,\n destinationCountry: selectedAddress.country,\n destinationPostalCode: selectedAddress.postalCode,\n },\n },\n });\n }\n }, [\n selectedAddress?.id,\n selectedAddress?.country,\n selectedAddress?.postalCode,\n selectedMethodId,\n storeOrderId,\n runEstimate,\n selectedAddress,\n ]);\n\n const estimate = estData?.estimateOrderShipping;\n const estimateReady = Boolean(\n selectedAddressId &&\n selectedMethodId &&\n !estimateLoading &&\n estimate &&\n estimate.shippingMethodId === selectedMethodId\n );\n\n // Emit the current selection upward. Method id + cost are only surfaced once a\n // matching estimate has resolved, so the parent's proceed gate stays disabled\n // while an estimate is in flight (free shipping resolves with cost 0, which is\n // still \"ready\").\n useEffect(() => {\n onChangeRef.current({\n shippingAddressId: selectedAddressId,\n shippingMethodId: estimateReady ? selectedMethodId : null,\n shippingCost: estimateReady && estimate ? estimate.cost : 0,\n destinationCountry: selectedAddress?.country ?? null,\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps -- emit derived selection; estimate object identity excluded intentionally\n }, [\n selectedAddressId,\n selectedMethodId,\n estimateReady,\n estimate?.cost,\n selectedAddress?.country,\n ]);\n\n const setField = (field: keyof AddressFormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validateForm = (): boolean => {\n const e: AddressFormErrors = {};\n if (!form.fullName.trim()) e.fullName = 'Recipient name is required';\n if (!form.addressLine1.trim()) e.addressLine1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n setFormErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleCreateAddress = async () => {\n if (!validateForm()) return;\n setFormServerError(null);\n try {\n const created = await createShippingAddress({\n fullName: form.fullName.trim(),\n ...(form.phone.trim() ? { phone: form.phone.trim() } : {}),\n addressLine1: form.addressLine1.trim(),\n ...(form.addressLine2.trim() ? { addressLine2: form.addressLine2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n isDefault: addresses.length === 0,\n });\n if (!created) {\n setFormServerError('Failed to save address. Please try again.');\n return;\n }\n await refetchAddresses();\n setSelectedAddressId(created.id);\n setSelectedMethodId(null);\n setForm(EMPTY_FORM);\n setFormErrors({});\n setShowAddForm(false);\n } catch {\n setFormServerError('Failed to save address. Please try again.');\n }\n };\n\n const inputClass = (hasError: boolean) =>\n `w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n hasError ? 'border-status-error-border' : 'border-border-subtle'\n }`;\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Truck className=\"size-4 text-text-secondary\" />\n Shipping\n </div>\n\n {/* Delivery address */}\n <section className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Delivery address\n </div>\n {!showAddForm && (\n <button\n type=\"button\"\n onClick={() => {\n setShowAddForm(true);\n setFormServerError(null);\n }}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-text-link hover:underline\"\n >\n <Plus className=\"size-3.5\" />\n Add address\n </button>\n )}\n </div>\n\n {addressesLoading && addresses.length === 0 ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n Loading addresses…\n </div>\n ) : (\n <>\n {addresses.length === 0 && !showAddForm && (\n <p className=\"text-sm text-text-secondary\">\n No saved delivery addresses yet. Add one to continue.\n </p>\n )}\n {addresses.length > 0 && (\n <div className=\"space-y-2\">\n {addresses.map((addr) => {\n const selected = addr.id === selectedAddressId;\n return (\n <button\n key={addr.id}\n type=\"button\"\n onClick={() => {\n setSelectedAddressId(addr.id);\n setSelectedMethodId(null);\n }}\n className={`w-full text-left p-3 rounded-input border transition-colors ${\n selected\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">\n {addr.fullName}\n {addr.label ? (\n <span className=\"ml-2 text-xs text-text-muted\">({addr.label})</span>\n ) : null}\n </p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {[\n addr.addressLine1,\n addr.addressLine2,\n addr.city,\n addr.state,\n addr.postalCode,\n addr.country,\n ]\n .filter(Boolean)\n .join(', ')}\n </p>\n {addr.phone && (\n <p className=\"text-text-muted text-xs mt-0.5\">{addr.phone}</p>\n )}\n </div>\n {selected && (\n <Check className=\"size-4 text-action-primary-bg shrink-0 mt-0.5\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n )}\n </>\n )}\n\n {/* Inline add-address form */}\n {showAddForm && (\n <div className=\"rounded-input border border-border-subtle p-3 space-y-3 bg-bg-sunken\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">New delivery address</span>\n <button\n type=\"button\"\n onClick={() => {\n setShowAddForm(false);\n setFormErrors({});\n setFormServerError(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary rounded-button\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {formServerError && (\n <div className=\"p-2 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-xs text-status-error-text\">\n {formServerError}\n </div>\n )}\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Recipient name</label>\n <input\n type=\"text\"\n value={form.fullName}\n onChange={(e) => setField('fullName', e.target.value)}\n placeholder=\"Jane Doe\"\n className={inputClass(Boolean(formErrors.fullName))}\n />\n {formErrors.fullName && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.fullName}</p>\n )}\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Phone <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"tel\"\n value={form.phone}\n onChange={(e) => setField('phone', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={inputClass(false)}\n />\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => setField('country', e.target.value)}\n className={`${inputClass(Boolean(formErrors.country))} scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary`}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.addressLine1}\n onChange={(e) => setField('addressLine1', e.target.value)}\n placeholder=\"123 Main St\"\n className={inputClass(Boolean(formErrors.addressLine1))}\n />\n {formErrors.addressLine1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.addressLine1}</p>\n )}\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.addressLine2}\n onChange={(e) => setField('addressLine2', e.target.value)}\n placeholder=\"Apt 4B\"\n className={inputClass(false)}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => setField('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={inputClass(Boolean(formErrors.city))}\n />\n {formErrors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => setField('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className={inputClass(false)}\n />\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => setField('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={inputClass(Boolean(formErrors.postalCode))}\n />\n {formErrors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{formErrors.postalCode}</p>\n )}\n </div>\n\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleCreateAddress}\n disabled={creatingAddress}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {creatingAddress && <Loader2 className=\"size-4 animate-spin\" />}\n Save address\n </button>\n </div>\n </div>\n )}\n </section>\n\n {/* Shipping method */}\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Truck className=\"size-4 text-text-secondary\" />\n Shipping method\n </div>\n\n {!selectedAddress ? (\n <p className=\"text-sm text-text-secondary\">Select a delivery address to see options.</p>\n ) : methodsLoading && methods.length === 0 ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n Loading options…\n </div>\n ) : availableMethods.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No shipping methods available for {selectedAddress.country}. Try a different address.\n </p>\n ) : availableMethods.length === 1 ? (\n (() => {\n const method = availableMethods[0];\n const eta = formatEta(method.estimatedMinDays, method.estimatedMaxDays);\n const showEstimate = estimateReady && estimate;\n const unavailable = Boolean(estimateError) && !estimateLoading;\n return (\n <div\n className={`w-full p-3 rounded-input border bg-bg-sunken ${\n unavailable ? 'border-status-error-border' : 'border-action-primary-bg'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">{method.name}</p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {method.carrier ? `${method.carrier} · ` : ''}\n {eta ?? 'Delivery time varies'}\n </p>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {estimateLoading ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : unavailable ? (\n <>\n <span className=\"text-sm font-semibold text-status-error-text\">\n Not available\n </span>\n <AlertTriangle className=\"size-4 text-status-error-text\" />\n </>\n ) : (\n <>\n <span className=\"text-sm font-semibold text-text-primary\">\n {showEstimate\n ? formatCost(estimate.cost, estimate.currency || currency)\n : formatCost(method.baseCost, method.currency || currency)}\n </span>\n <Check className=\"size-4 text-action-primary-bg\" />\n </>\n )}\n </div>\n </div>\n </div>\n );\n })()\n ) : (\n <div className=\"space-y-2\">\n {availableMethods.map((method) => {\n const selected = method.id === selectedMethodId;\n const eta = formatEta(method.estimatedMinDays, method.estimatedMaxDays);\n const showEstimate = selected && estimateReady && estimate;\n const unavailable = selected && Boolean(estimateError) && !estimateLoading;\n return (\n <button\n key={method.id}\n type=\"button\"\n onClick={() => setSelectedMethodId(method.id)}\n className={`w-full text-left p-3 rounded-input border transition-colors ${\n unavailable\n ? 'border-status-error-border bg-bg-sunken'\n : selected\n ? 'border-action-primary-bg bg-bg-sunken'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"text-sm\">\n <p className=\"font-medium text-text-primary\">{method.name}</p>\n <p className=\"text-text-secondary text-xs mt-0.5\">\n {method.carrier ? `${method.carrier} · ` : ''}\n {eta ?? 'Delivery time varies'}\n </p>\n </div>\n <div className=\"text-right shrink-0\">\n {selected && estimateLoading ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : unavailable ? (\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-status-error-text\">\n <AlertTriangle className=\"size-4\" />\n Not available\n </span>\n ) : (\n <span className=\"text-sm font-semibold text-text-primary\">\n {showEstimate\n ? formatCost(estimate.cost, estimate.currency || currency)\n : formatCost(method.baseCost, method.currency || currency)}\n </span>\n )}\n </div>\n </div>\n </button>\n );\n })}\n </div>\n )}\n\n {estimateReady && estimate && (\n <p className=\"text-xs text-text-secondary\">\n Estimated shipping: {formatCost(estimate.cost, estimate.currency || currency)}\n {estimate.estimatedDeliveryDate\n ? ` · arrives around ${new Date(estimate.estimatedDeliveryDate).toLocaleDateString()}`\n : ''}\n </p>\n )}\n\n {estimateError && !estimateLoading && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\"\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" aria-hidden=\"true\" />\n <span>\n {estimateError.message ||\n \"Sorry, this item can't be delivered to your address right now. Please try a different delivery address or contact support.\"}\n </span>\n </div>\n )}\n </section>\n </div>\n );\n};\n"],"mappings":";;;;;AA6BA,IAAM,KAAkB;CACtB;EAAE,OAAO;EAAM,OAAO;CAAQ;CAC9B;EAAE,OAAO;EAAM,OAAO;CAAgB;CACtC;EAAE,OAAO;EAAM,OAAO;CAAiB;CACvC;EAAE,OAAO;EAAM,OAAO;CAAY;CAClC;EAAE,OAAO;EAAM,OAAO;CAAS;CAC/B;EAAE,OAAO;EAAM,OAAO;CAAY;CAClC;EAAE,OAAO;EAAM,OAAO;CAAU;CAChC;EAAE,OAAO;EAAM,OAAO;CAAS;CAC/B;EAAE,OAAO;EAAM,OAAO;CAAM;AAC9B,GAoCM,IAA+B;CACnC,UAAU;CACV,OAAO;CACP,cAAc;CACd,cAAc;CACd,MAAM;CACN,OAAO;CACP,YAAY;CACZ,SAAS;AACX;AAEA,SAAS,EAAW,GAAgB,GAA0B;CAC5D,IAAI;EACF,OAAO,EAAO,eAAe,MAAM;GAAE,OAAO;GAAY,UAAU,KAAY;EAAM,CAAC;CACvF,QAAQ;EACN,OAAO,GAAG,KAAY,MAAM,GAAG,EAAO,QAAQ,CAAC;CACjD;AACF;AAEA,SAAS,EAAU,GAAyB,GAAwC;CAClF,IAAI,KAAW,QAAQ,KAAW,MAAM,OAAO;CAC/C,IAAI,KAAW,QAAQ,KAAW,MAChC,OAAO,MAAY,IAAU,GAAG,EAAQ,SAAS,GAAG,EAAQ,GAAG,EAAQ;CAEzE,IAAM,IAAS,KAAW;CAC1B,OAAO,KAAU,OAA0B,OAAnB,GAAG,EAAO;AACpC;AAEA,IAAa,KAAmC,EAAE,iBAAc,aAAU,kBAAe;CACvF,IAAM,EACJ,cACA,SAAS,GACT,SAAS,MACP,EAAuB,GACrB,EAAE,0BAAuB,SAAS,MAAoB,EAAyB,GAC/E,EAAE,YAAS,SAAS,MAAmB,EAAmB,GAC1D,CAAC,GAAa,EAAE,MAAM,GAAS,SAAS,GAAiB,OAAO,OACpE,EAAyB,GAErB,CAAC,GAAmB,KAAwB,EAAwB,IAAI,GACxE,CAAC,GAAkB,KAAuB,EAAwB,IAAI,GAEtE,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAM,KAAW,EAA2B,CAAU,GACvD,CAAC,GAAY,KAAiB,EAA4B,CAAC,CAAC,GAC5D,CAAC,GAAiB,KAAsB,EAAwB,IAAI,GAGpE,IAAc,EAAO,CAAQ;CACnC,QAAgB;EACd,EAAY,UAAU;CACxB,CAAC;CAED,IAAM,IAAkB,QAChB,EAAU,MAAM,MAAM,EAAE,OAAO,CAAiB,GACtD,CAAC,GAAW,CAAiB,CAC/B,GAIM,IAAmB,QAAc;EACrC,IAAI,CAAC,GAAiB,OAAO,CAAC;EAC9B,IAAM,IAAU,EAAgB;EAChC,OAAO,EACJ,QACE,MACC,EAAE,aACD,EAAE,mBAAmB,WAAW,KAAK,EAAE,mBAAmB,SAAS,CAAO,MAC3E,CAAC,EAAE,kBAAkB,SAAS,CAAO,CACzC,EACC,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,YAAY;CACnD,GAAG,CAAC,GAAS,CAAe,CAAC;CA0B7B,AAvBA,QAAgB;EACd,AAAI,CAAC,KAAqB,EAAU,SAAS,KAE3C,GADkB,EAAU,MAAM,MAAM,EAAE,SAAS,KAAK,EAAU,IACnC,EAAE;CAErC,GAAG,CAAC,GAAW,CAAiB,CAAC,GAGjC,QAAgB;EACd,AAAI,KAAoB,CAAC,EAAiB,MAAM,MAAM,EAAE,OAAO,CAAgB,KAC7E,EAAoB,IAAI;CAE5B,GAAG,CAAC,GAAkB,CAAgB,CAAC,GAIvC,QAAgB;EACd,AAAI,CAAC,KAAoB,EAAiB,WAAW,KACnD,EAAoB,EAAiB,GAAG,EAAE;CAE9C,GAAG,CAAC,GAAkB,CAAgB,CAAC,GAGvC,QAAgB;EACd,AAAI,KAAmB,KACrB,EAAiB,EACf,WAAW,EACT,OAAO;GACL,SAAS;GACT,kBAAkB;GAClB,oBAAoB,EAAgB;GACpC,uBAAuB,EAAgB;EACzC,EACF,EACF,CAAC;CAEL,GAAG;EACD,GAAiB;EACjB,GAAiB;EACjB,GAAiB;EACjB;EACA;EACA;EACA;CACF,CAAC;CAED,IAAM,IAAW,GAAS,uBACpB,IAAgB,GACpB,KACA,KACA,CAAC,KACD,KACA,EAAS,qBAAqB;CAOhC,QAAgB;EACd,EAAY,QAAQ;GAClB,mBAAmB;GACnB,kBAAkB,IAAgB,IAAmB;GACrD,cAAc,KAAiB,IAAW,EAAS,OAAO;GAC1D,oBAAoB,GAAiB,WAAW;EAClD,CAAC;CAEH,GAAG;EACD;EACA;EACA;EACA,GAAU;EACV,GAAiB;CACnB,CAAC;CAED,IAAM,KAAY,GAA+B,MAC/C,GAAS,OAAU;EAAE,GAAG;GAAO,IAAQ;CAAM,EAAE,GAE3C,WAA8B;EAClC,IAAM,IAAuB,CAAC;EAO9B,OANK,EAAK,SAAS,KAAK,MAAG,EAAE,WAAW,+BACnC,EAAK,aAAa,KAAK,MAAG,EAAE,eAAe,+BAC3C,EAAK,KAAK,KAAK,MAAG,EAAE,OAAO,qBAC3B,EAAK,WAAW,KAAK,MAAG,EAAE,aAAa,4BACvC,EAAK,QAAQ,KAAK,MAAG,EAAE,UAAU,wBACtC,EAAc,CAAC,GACR,OAAO,KAAK,CAAC,EAAE,WAAW;CACnC,GAEM,KAAsB,YAAY;EACjC,OAAa,GAClB;KAAmB,IAAI;GACvB,IAAI;IACF,IAAM,IAAU,MAAM,EAAsB;KAC1C,UAAU,EAAK,SAAS,KAAK;KAC7B,GAAI,EAAK,MAAM,KAAK,IAAI,EAAE,OAAO,EAAK,MAAM,KAAK,EAAE,IAAI,CAAC;KACxD,cAAc,EAAK,aAAa,KAAK;KACrC,GAAI,EAAK,aAAa,KAAK,IAAI,EAAE,cAAc,EAAK,aAAa,KAAK,EAAE,IAAI,CAAC;KAC7E,MAAM,EAAK,KAAK,KAAK;KACrB,GAAI,EAAK,MAAM,KAAK,IAAI,EAAE,OAAO,EAAK,MAAM,KAAK,EAAE,IAAI,CAAC;KACxD,YAAY,EAAK,WAAW,KAAK;KACjC,SAAS,EAAK;KACd,WAAW,EAAU,WAAW;IAClC,CAAC;IACD,IAAI,CAAC,GAAS;KACZ,EAAmB,2CAA2C;KAC9D;IACF;IAMA,AALA,MAAM,EAAiB,GACvB,EAAqB,EAAQ,EAAE,GAC/B,EAAoB,IAAI,GACxB,EAAQ,CAAU,GAClB,EAAc,CAAC,CAAC,GAChB,EAAe,EAAK;GACtB,QAAQ;IACN,EAAmB,2CAA2C;GAChE;EAzBuB;CA0BzB,GAEM,KAAc,MAClB,wLACE,IAAW,+BAA+B;CAG9C,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAO,WAAU,6BAA8B,CAAA,GAAC,UAE7C;;GAGL,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,6BAA8B,CAAA,GAAC,kBAE9C;UACJ,CAAC,KACA,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;QAEb,AADA,EAAe,EAAI,GACnB,EAAmB,IAAI;OACzB;OACA,WAAU;iBANZ,CAQE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,GAAC,aAEvB;QAEP;;KAEJ,KAAoB,EAAU,WAAW,IACxC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,sBAAuB,CAAA,GAAC,oBAExC;UAEL,kBAAA,GAAA,EAAA,UAAA,CACG,EAAU,WAAW,KAAK,CAAC,KAC1B,kBAAC,KAAD;MAAG,WAAU;gBAA8B;KAExC,CAAA,GAEJ,EAAU,SAAS,KAClB,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAU,KAAK,MAAS;OACvB,IAAM,IAAW,EAAK,OAAO;OAC7B,OACE,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe;SAEb,AADA,EAAqB,EAAK,EAAE,GAC5B,EAAoB,IAAI;QAC1B;QACA,WAAW,+DACT,IACI,0CACA;kBAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,KAAD;YAAG,WAAU;sBAAb,CACG,EAAK,UACL,EAAK,QACJ,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cAA+C;cAAE,EAAK;cAAM;aAAO;iBACjE,IACH;;WACH,kBAAC,KAAD;YAAG,WAAU;sBACV;aACC,EAAK;aACL,EAAK;aACL,EAAK;aACL,EAAK;aACL,EAAK;aACL,EAAK;YACP,EACG,OAAO,OAAO,EACd,KAAK,IAAI;WACX,CAAA;WACF,EAAK,SACJ,kBAAC,KAAD;YAAG,WAAU;sBAAkC,EAAK;WAAS,CAAA;UAE5D;aACJ,KACC,kBAAC,GAAD,EAAO,WAAU,gDAAiD,CAAA,CAEjE;;OACC,GAxCD,EAAK,EAwCJ;MAEZ,CAAC;KACE,CAAA,CAEP,EAAA,CAAA;KAIH,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAwC;QAA0B,CAAA,GAClF,kBAAC,UAAD;SACE,MAAK;SACL,eAAe;UAGb,AAFA,EAAe,EAAK,GACpB,EAAc,CAAC,CAAC,GAChB,EAAmB,IAAI;SACzB;SACA,WAAU;mBAEV,kBAAC,GAAD,EAAG,WAAU,SAAU,CAAA;QACjB,CAAA,CACL;;OAEJ,KACC,kBAAC,OAAD;QAAK,WAAU;kBACZ;OACE,CAAA;OAGP,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,WAAU;mBAAyC;QAAqB,CAAA;QAC/E,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAK;SACZ,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,KAAK;SACpD,aAAY;SACZ,WAAW,EAAW,EAAQ,EAAW,QAAS;QACnD,CAAA;QACA,EAAW,YACV,kBAAC,KAAD;SAAG,WAAU;mBAAuC,EAAW;QAAY,CAAA;OAE1E,EAAA,CAAA;OAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CAA0D,UAClD,kBAAC,QAAD;SAAM,WAAU;mBAAkB;QAAgB,CAAA,CACnD;WACP,kBAAC,SAAD;QACE,MAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,KAAK;QACjD,aAAY;QACZ,WAAW,EAAW,EAAK;OAC5B,CAAA,CACE,EAAA,CAAA;OAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAyC;OAAc,CAAA,GACxE,kBAAC,UAAD;QACE,OAAO,EAAK;QACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,KAAK;QACnD,WAAW,GAAG,EAAW,EAAQ,EAAW,OAAQ,EAAE;kBAErD,GAAgB,KAAK,MACpB,kBAAC,UAAD;SAAsB,OAAO,EAAE;mBAC5B,EAAE;QACG,GAFK,EAAE,KAEP,CACT;OACK,CAAA,CACL,EAAA,CAAA;OAEL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,WAAU;mBAAyC;QAAqB,CAAA;QAC/E,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAK;SACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,KAAK;SACxD,aAAY;SACZ,WAAW,EAAW,EAAQ,EAAW,YAAa;QACvD,CAAA;QACA,EAAW,gBACV,kBAAC,KAAD;SAAG,WAAU;mBAAuC,EAAW;QAAgB,CAAA;OAE9E,EAAA,CAAA;OAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CAA0D,2BACjC,kBAAC,QAAD;SAAM,WAAU;mBAAkB;QAAgB,CAAA,CACpE;WACP,kBAAC,SAAD;QACE,MAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAS,gBAAgB,EAAE,OAAO,KAAK;QACxD,aAAY;QACZ,WAAW,EAAW,EAAK;OAC5B,CAAA,CACE,EAAA,CAAA;OAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,SAAD;UAAO,WAAU;oBAAyC;SAAW,CAAA;SACrE,kBAAC,SAAD;UACE,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,KAAK;UAChD,aAAY;UACZ,WAAW,EAAW,EAAQ,EAAW,IAAK;SAC/C,CAAA;SACA,EAAW,QACV,kBAAC,KAAD;UAAG,WAAU;oBAAuC,EAAW;SAAQ,CAAA;QAEtE,EAAA,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CAA0D,UAClD,kBAAC,QAAD;UAAM,WAAU;oBAAkB;SAAgB,CAAA,CACnD;YACP,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAK;SACZ,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,KAAK;SACjD,aAAY;SACZ,WAAW,EAAW,EAAK;QAC5B,CAAA,CACE,EAAA,CAAA,CACF;;OAEL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,WAAU;mBAAyC;QAAkB,CAAA;QAC5E,kBAAC,SAAD;SACE,MAAK;SACL,OAAO,EAAK;SACZ,WAAW,MAAM,EAAS,cAAc,EAAE,OAAO,KAAK;SACtD,aAAY;SACZ,WAAW,EAAW,EAAQ,EAAW,UAAW;QACrD,CAAA;QACA,EAAW,cACV,kBAAC,KAAD;SAAG,WAAU;mBAAuC,EAAW;QAAc,CAAA;OAE5E,EAAA,CAAA;OAEL,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,UAAD;SACE,MAAK;SACL,SAAS;SACT,UAAU;SACV,WAAU;mBAJZ,CAMG,KAAmB,kBAAC,GAAD,EAAS,WAAU,sBAAuB,CAAA,GAAE,cAE1D;;OACL,CAAA;MACF;;IAEA;;GAGT,kBAAC,WAAD;IAAS,WAAU;cAAnB;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,6BAA8B,CAAA,GAAC,iBAE7C;;KAEH,IAEE,KAAkB,EAAQ,WAAW,IACvC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,sBAAuB,CAAA,GAAC,kBAExC;UACH,EAAiB,WAAW,IAC9B,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAA2C;OACN,EAAgB;OAAQ;MAC1D;UACD,EAAiB,WAAW,WACvB;MACL,IAAM,IAAS,EAAiB,IAC1B,IAAM,EAAU,EAAO,kBAAkB,EAAO,gBAAgB,GAChE,IAAe,KAAiB,GAChC,IAAc,EAAQ,KAAkB,CAAC;MAC/C,OACE,kBAAC,OAAD;OACE,WAAW,gDACT,IAAc,+BAA+B;iBAG/C,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAiC,EAAO;SAAQ,CAAA,GAC7D,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,EAAO,UAAU,GAAG,EAAO,QAAQ,OAAO,IAC1C,KAAO,sBACP;WACA;YACL,kBAAC,OAAD;SAAK,WAAU;mBACZ,IACC,kBAAC,GAAD,EAAS,WAAU,0CAA2C,CAAA,IAC5D,IACF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;UAAM,WAAU;oBAA+C;SAEzD,CAAA,GACN,kBAAC,GAAD,EAAe,WAAU,gCAAiC,CAAA,CAC1D,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,IACG,EAAW,EAAS,MAAM,EAAS,YAAY,CAAQ,IACvD,EAAW,EAAO,UAAU,EAAO,YAAY,CAAQ;SACvD,CAAA,GACN,kBAAC,GAAD,EAAO,WAAU,gCAAiC,CAAA,CAClD,EAAA,CAAA;QAED,CAAA,CACF;;MACF,CAAA;KAET,GAAG,IAEH,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAiB,KAAK,MAAW;OAChC,IAAM,IAAW,EAAO,OAAO,GACzB,IAAM,EAAU,EAAO,kBAAkB,EAAO,gBAAgB,GAChE,IAAe,KAAY,KAAiB,GAC5C,IAAc,KAAY,EAAQ,KAAkB,CAAC;OAC3D,OACE,kBAAC,UAAD;QAEE,MAAK;QACL,eAAe,EAAoB,EAAO,EAAE;QAC5C,WAAW,+DACT,IACI,4CACA,IACE,0CACA;kBAGR,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAiC,EAAO;UAAQ,CAAA,GAC7D,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAO,UAAU,GAAG,EAAO,QAAQ,OAAO,IAC1C,KAAO,sBACP;YACA;aACL,kBAAC,OAAD;UAAK,WAAU;oBACZ,KAAY,IACX,kBAAC,GAAD,EAAS,WAAU,0CAA2C,CAAA,IAC5D,IACF,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD,EAAe,WAAU,SAAU,CAAA,GAAC,eAEhC;eAEN,kBAAC,QAAD;WAAM,WAAU;qBACb,IACG,EAAW,EAAS,MAAM,EAAS,YAAY,CAAQ,IACvD,EAAW,EAAO,UAAU,EAAO,YAAY,CAAQ;UACvD,CAAA;SAEL,CAAA,CACF;;OACC,GApCD,EAAO,EAoCN;MAEZ,CAAC;KACE,CAAA,IAvGL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;KAA4C,CAAA;KA0GxF,KAAiB,KAChB,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAA2C;OACpB,EAAW,EAAS,MAAM,EAAS,YAAY,CAAQ;OAC3E,EAAS,wBACN,qBAAqB,IAAI,KAAK,EAAS,qBAAqB,EAAE,mBAAmB,MACjF;MACH;;KAGJ,KAAiB,CAAC,KACjB,kBAAC,OAAD;MACE,MAAK;MACL,WAAU;gBAFZ,CAIE,kBAAC,GAAD;OAAe,WAAU;OAAyB,eAAY;MAAQ,CAAA,GACtE,kBAAC,QAAD,EAAA,UACG,EAAc,WACb,6HACE,CAAA,CACH;;IAEA;;EACN;;AAET"}
|
|
@@ -117,7 +117,7 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
117
117
|
id: "ws-selector",
|
|
118
118
|
value: v,
|
|
119
119
|
onChange: (e) => y(e.target.value),
|
|
120
|
-
className: "w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
120
|
+
className: "w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
121
121
|
children: m.map((e) => /* @__PURE__ */ l("option", {
|
|
122
122
|
value: e.id,
|
|
123
123
|
children: e.name
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Quota assigned!</h2>\n <p className=\"text-sm text-text-secondary\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-text-link\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-text-secondary\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-bg-sunken rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-primary\">{q.quotaName}</span>\n <span className=\"text-text-secondary\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-text-primary\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary\">\n Will be assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Quota assigned!</h2>\n <p className=\"text-sm text-text-secondary\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-text-link\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-text-secondary\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-bg-sunken rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-primary\">{q.quotaName}</span>\n <span className=\"text-text-secondary\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-text-primary\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary\">\n Will be assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -242,7 +242,7 @@ var D = () => {
|
|
|
242
242
|
onChange: (e) => {
|
|
243
243
|
N(e.target.value), R(0);
|
|
244
244
|
},
|
|
245
|
-
className: "px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
245
|
+
className: "px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
246
246
|
children: [
|
|
247
247
|
/* @__PURE__ */ S("option", {
|
|
248
248
|
value: "ALL",
|
|
@@ -267,7 +267,7 @@ var D = () => {
|
|
|
267
267
|
onChange: (e) => {
|
|
268
268
|
F(e.target.value), R(0);
|
|
269
269
|
},
|
|
270
|
-
className: "px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
270
|
+
className: "px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
271
271
|
children: [
|
|
272
272
|
/* @__PURE__ */ S("option", {
|
|
273
273
|
value: "ALL",
|
|
@@ -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 {\n ResponsiveTable,\n type ResponsiveTableColumn,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n const couponColumns: ResponsiveTableColumn<Coupon>[] = [\n {\n key: 'code',\n header: 'Code',\n priority: 'primary',\n cell: (coupon) => (\n <div>\n <p className=\"font-medium text-text-primary font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-text-muted\">{coupon.name}</p>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cell: (coupon) => (\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n ),\n },\n {\n key: 'value',\n header: 'Value',\n cell: (coupon) => (\n <span className=\"text-sm font-medium text-text-primary tabular-nums\">\n {coupon.type === 'PERCENTAGE' ? `${coupon.value}%` : formatCurrency(coupon.value)}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (coupon) => (\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n ),\n },\n {\n key: 'usage',\n header: 'Usage',\n cell: (coupon) => (\n <>\n <div className=\"text-sm text-text-primary tabular-nums\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-text-muted\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-bg-sunken rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-action-primary-bg h-1.5 rounded-full\"\n style={{\n width: `${Math.min((coupon.currentUses / coupon.maxTotalUses) * 100, 100)}%`,\n }}\n />\n </div>\n )}\n </>\n ),\n },\n {\n key: 'validUntil',\n header: 'Valid Until',\n priority: 'hidden-on-mobile',\n cell: (coupon) => (\n <span className=\"text-sm text-text-muted\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n ),\n },\n ];\n\n const renderCouponActions = (coupon: Coupon) => (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-text-muted/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </>\n );\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n <PagePurpose>\n {tr(\n 'billing.coupons.purpose',\n 'Create and manage discount codes that customers redeem at checkout — percentage or fixed-amount off, with optional usage limits and expiry. Track how often each coupon has been used and deactivate ones you no longer want to honour.'\n )}\n </PagePurpose>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Coupons</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Tag className=\"size-4 text-accent-blue\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Uses</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">{stats.totalUses}</p>\n <p className=\"text-xs text-text-muted mt-1\">All time</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Discount</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <DollarSign className=\"size-4 text-accent-purple\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Active Rate</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-10 pr-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-bg-surface border border-border-seam rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n {loading ? (\n <div className=\"p-8 text-center text-text-muted\">Loading coupons…</div>\n ) : (\n <ResponsiveTable<Coupon>\n columns={couponColumns}\n data={filteredCoupons}\n rowKey={(coupon) => coupon.id}\n rowActions={renderCouponActions}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No coupons found\"\n description=\"Create a coupon to start offering discounts at checkout.\"\n />\n }\n />\n )}\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-text-muted\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-primary font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;AAyEA,IAAa,UAA4B;CACvC,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GACM,IAAa,GAAmB,GAChC,KAAc,EAAsB,GAKpC,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAc,KAAmB,EAA6B,KAAK,GACpE,CAAC,GAAY,KAAiB,EAA2B,KAAK,GAC9D,CAAC,KAAe,EAA4B,KAAK,GACjD,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAoB,KAAyB,EAAwB,IAAI,GAC1E,CAAC,GAAY,KAAiB,EAAwB,IAAI,GAC1D,CAAC,GAAU,KAAe,EAGtB,IAAI,GAGR,EAAE,SAAM,aAAS,eAAY,EAAoB,EACrD,WAAW;EACT,OAAO;EACP,QAAQ,IAAO;EACf,QAAQ;GACN,QAAQ,MAAiB,QAA4C,KAAA,IAAnC;GAClC,MAAM,MAAe,QAAwC,KAAA,IAA/B;GAC9B,OAAO,MAAgB,QAA0C,KAAA,IAAjC;GAChC,QAAQ,KAAe,KAAA;EACzB;CACF,EACF,CAAC,GAEK,IAAU,QACR,GAAM,aAAa,WAAW,CAAC,GACrC,CAAC,GAAM,aAAa,OAAO,CAC7B,GACM,IAAQ,GAAM,aAAa,SAAS,GACpC,KAAU,GAAM,aAAa,WAAW,IACxC,KAAa,KAAK,KAAK,IAAQ,EAAS,GAGxC,CAAC,GAAkB,EAAE,SAAS,OAAkB,EAA4B,GAC5E,CAAC,MAAgB,EAAwB,GAGzC,IAAkB;CAGxB,QAAgB;EACd,EAAQ,CAAC;CACX,GAAG;EAAC;EAAa;EAAc;EAAY;CAAW,CAAC;CAGvD,IAAM,IAAQ,SAOL;EAAE,cAAc;EAAO,eANR,EAAQ,QAAQ,MAAc,EAAE,WAAW,QAAQ,EAAE;EAM9B,WAL3B,EAAQ,QAAQ,GAAa,MAAc,IAAM,EAAE,aAAa,CAKrC;EAAW,oBAJ7B,EAAQ,QAAQ,GAAa,MAClD,EAAE,SAAS,iBAAuB,IAAM,EAAE,QAAQ,EAAE,cACjD,GACN,CACqD;CAAmB,IAC1E,CAAC,GAAS,CAAK,CAAC;CAGnB,IAAI,CAAC,GAAY,oBACf,OAAO,kBAAC,GAAD,EAAc,SAAQ,+CAAgD,CAAA;CAI/E,IAAM,KAAkB,MACf,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;CACZ,CAAC,EAAE,OAAO,CAAM,GAIZ,MAAc,MACX,IAAI,KAAK,CAAU,EAAE,mBAAmB,SAAS;EACtD,MAAM;EACN,OAAO;EACP,KAAK;CACP,CAAC,GAIG,MAAkB,MAAmB;EACzC,QAAQ,GAAR;GACE,KAAK,UACH,OAAO;GACT,KAAK,YACH,OAAO;GACT,KAAK,WACH,OAAO;GACT,SACE,OAAO;EACX;CACF,GAGM,MAAe,MAAiB;EACpC,QAAQ,GAAR;GACE,KAAK,cACH,OAAO,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA;GACtC,KAAK,gBACH,OAAO,kBAAC,GAAD,EAAY,WAAU,SAAU,CAAA;GACzC,KAAK,iBACH,OAAO,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA;GACtC,SACE,OAAO,kBAAC,GAAD,EAAK,WAAU,SAAU,CAAA;EACpC;CACF;CAuIA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,OAAO,EAAG,yBAAyB,SAAS;KAC5C,aAAY;KACZ,MAAM,kBAAC,GAAD,EAAK,WAAU,SAAU,CAAA;IAChC,CAAA,GAED,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,iBAAiB;KAC3C,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GAAC,eAErB;MACL;;GAEL,kBAAC,GAAD,EAAA,UACG,EACC,2BACA,yOACF,EACW,CAAA;GAGb,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA8B;QAAmB,CAAA,GACjE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAK,WAAU,0BAA2B,CAAA;QACtC,CAAA,CACH;;OACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM;OACN,CAAA;OACH,kBAAC,KAAD;QAAG,WAAU;kBAAb,CAA6C,EAAM,eAAc,SAAU;;MACxE;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA8B;QAAgB,CAAA,GAC9D,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,IAAD,EAAY,WAAU,kCAAmC,CAAA;QACrD,CAAA,CACH;;OACL,kBAAC,KAAD;QAAG,WAAU;kBAAyD,EAAM;OAAa,CAAA;OACzF,kBAAC,KAAD;QAAG,WAAU;kBAA+B;OAAW,CAAA;MACpD;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA8B;QAAoB,CAAA,GAClE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAY,WAAU,4BAA6B,CAAA;QAC/C,CAAA,CACH;;OACL,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAe,EAAM,kBAAkB;OACvC,CAAA;OACH,kBAAC,KAAD;QAAG,WAAU;kBAA+B;OAAoB,CAAA;MAC7D;;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAA8B;QAAiB,CAAA,GAC/D,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAa,WAAU,kCAAmC,CAAA;QACtD,CAAA,CACH;;OACL,kBAAC,KAAD;QAAG,WAAU;kBAAb,CACG,EAAM,eAAe,IAClB,KAAK,MAAO,EAAM,gBAAgB,EAAM,eAAgB,GAAG,IAC3D,GAAE,GAEL;;OACH,kBAAC,KAAD;QAAG,WAAU;kBAA+B;OAAiB,CAAA;MAC1D;;IACF;;GAEJ,KACC,kBAAC,OAAD;IACE,WACE,EAAS,SAAS,YACd,mIACA;IAEN,MAAK;IACL,aAAU;cAPZ,CASG,EAAS,SAAS,YACjB,kBAAC,GAAD,EAAa,WAAU,yBAA0B,CAAA,IAEjD,kBAAC,IAAD,EAAa,WAAU,yBAA0B,CAAA,GAEnD,kBAAC,KAAD;KAAG,WAAU;eAAuB,EAAS;IAAW,CAAA,CACrD;;GAGN,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAkD;OAC9B,kBAAC,QAAD;QAAM,WAAU;kBAAa,EAAmB;OAAW,CAAA;OAAC;MAC7E;SACH,kBAAC,KAAD;MAAG,WAAU;gBAAsC;KAEhD,CAAA,CACA,EAAA,CAAA,GACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAsB,IAAI;OACzC,WAAU;iBACX;MAEO,CAAA,GACR,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAiB,EAAmB,EAAE;OACrD,UAAU;OACV,WAAU;iBAET,IAAe,kBAAkB;MAC5B,CAAA,CACL;OACF;;GACF,CAAA;GAIP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,kEAAmE,CAAA,GACrF,kBAAC,SAAD;SACE,MAAK;SACL,aAAY;SACZ,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;SAC9C,WAAU;QACX,CAAA,CACE;;MACF,CAAA;MAGL,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM;QAEf,AADA,EAAgB,EAAE,OAAO,KAA2B,GACpD,EAAQ,CAAC;OACX;OACA,WAAU;iBANZ;QAQE,kBAAC,UAAD;SAAQ,OAAM;mBAAM;QAAkB,CAAA;QACtC,kBAAC,UAAD;SAAQ,OAAM;mBAAS;QAAc,CAAA;QACrC,kBAAC,UAAD;SAAQ,OAAM;mBAAW;QAAgB,CAAA;QACzC,kBAAC,UAAD;SAAQ,OAAM;mBAAU;QAAe,CAAA;OACjC;;MAGR,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM;QAEf,AADA,EAAc,EAAE,OAAO,KAAyB,GAChD,EAAQ,CAAC;OACX;OACA,WAAU;iBANZ;QAQE,kBAAC,UAAD;SAAQ,OAAM;mBAAM;QAAiB,CAAA;QACrC,kBAAC,UAAD;SAAQ,OAAM;mBAAa;QAAkB,CAAA;QAC7C,kBAAC,UAAD;SAAQ,OAAM;mBAAe;QAAoB,CAAA;QACjD,kBAAC,UAAD;SAAQ,OAAM;mBAAgB;QAAqB,CAAA;OAC7C;;KACL;;GACF,CAAA;GAGL,kBAAC,OAAD;IAAK,WAAU;cACZ,KACC,kBAAC,OAAD;KAAK,WAAU;eAAkC;IAAqB,CAAA,IAEtE,kBAAC,GAAD;KACE,SAAS;MAjUjB;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAA2C,EAAO;OAAQ,CAAA,GACvE,kBAAC,KAAD;QAAG,WAAU;kBAA2B,EAAO;OAAQ,CAAA,CACpD,EAAA,CAAA;MAET;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO,MACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,GAAY,EAAO,IAAI,GACxB,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAO,KAAK,QAAQ,KAAK,GAAG,EAAE,YAAY;QACvC,CAAA,CACH;;MAET;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO,MACL,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAO,SAAS,eAAe,GAAG,EAAO,MAAM,KAAK,EAAe,EAAO,KAAK;OAC5E,CAAA;MAEV;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO,MACL,kBAAC,QAAD;QACE,WAAW,0DAA0D,GACnE,EAAO,MACT;kBAEC,EAAO;OACJ,CAAA;MAEV;MACA;OACE,KAAK;OACL,QAAQ;OACR,OAAO,MACL,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAe,EAAO;QAAkB,CAAA,GACvD,EAAO,gBACN,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAAkC,OAAI,EAAO,YAAmB;UAE/D;WACJ,EAAO,gBACN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SACE,WAAU;SACV,OAAO,EACL,OAAO,GAAG,KAAK,IAAK,EAAO,cAAc,EAAO,eAAgB,KAAK,GAAG,EAAE,GAC5E;QACD,CAAA;OACE,CAAA,CAEP,EAAA,CAAA;MAEN;MACA;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MACL,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAO,aAAa,GAAW,EAAO,UAAU,IAAI;OACjD,CAAA;MAEV;KAmPiB;KACT,MAAM;KACN,SAAS,MAAW,EAAO;KAC3B,aAnPmB,MAC3B,kBAAA,GAAA,EAAA,UAAA;MACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,YAAY,EAAO,IAAI;OACjD,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAK,WAAU,SAAU,CAAA;MACnB,CAAA;MACR,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,YAAY,EAAO,GAAG,MAAM;OACtD,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA;MACpB,CAAA;MACP,EAAO,WAAW,WACjB,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAsB,CAAM;OAC3C,UAAU,KAAgB,MAAe,EAAO;OAChD,WAAU;OACV,OAAM;iBAEN,kBAAC,IAAD,EAAU,WAAU,SAAU,CAAA;MACxB,CAAA,IACN,EAAO,WAAW,aACpB,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAmB,EAAO,IAAI,EAAa,MAAM;OAChE,UAAU,MAAe,EAAO;OAChC,WAAU;OACV,OAAM;iBAEN,kBAAC,IAAD,EAAO,WAAU,SAAU,CAAA;MACrB,CAAA,IAER,kBAAC,UAAD;OACE,MAAK;OACL,UAAA;OACA,WAAU;OACV,OAAO,yBAAyB,EAAO,OAAO,YAAY,EAAE;iBAE5D,kBAAC,IAAD,EAAQ,WAAU,SAAU,CAAA;MACtB,CAAA;KAEV,EAAA,CAAA;KAoMM,YACE,kBAAC,IAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;KACb,CAAA;IAEJ,CAAA;GAEA,CAAA;GAGJ,IAAQ,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MAAuC;MAC5B,IAAO,KAAY;MAAE;MAAE,KAAK,KAAK,IAAO,KAAK,IAAW,CAAK;MAAE;MAAK;MAAO;MAAI;KAEvF;QACH,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;OAChD,UAAU,MAAS;OACnB,WAAU;iBAEV,kBAAC,GAAD,EAAa,WAAU,SAAU,CAAA;MAC3B,CAAA;MACR,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG,IAAO;QAAE;QAAI;OACV;;MACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,IAAI,CAAC;OACnC,UAAU,CAAC;OACX,WAAU;iBAEV,kBAAC,GAAD,EAAc,WAAU,SAAU,CAAA;MAC5B,CAAA;KACL;MACF;;GAIP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAU,WAAU,SAAU,CAAA,GAAC,gBAEzB;;GACL,CAAA;EACF;;CAGP,eAAe,GAAiB,GAAkB;EAChD,IAAI;GAIF,AAHA,MAAM,EAAiB,EAAE,WAAW,EAAE,IAAI,EAAS,EAAE,CAAC,GACtD,MAAM,EAAQ,GACd,EAAsB,IAAI,GAC1B,EAAY;IAAE,MAAM;IAAW,SAAS;GAAmC,CAAC;EAC9E,SAAS,GAAO;GAEd,AADA,QAAQ,MAAM,gCAAgC,CAAK,GACnD,EAAY;IACV,MAAM;IACN,SAAS,gCAAgC,aAAiB,QAAQ,EAAM,UAAU;GACpF,CAAC;EACH;CACF;CAEA,eAAe,GAAmB,GAAkB,GAAyB;EAC3E,EAAc,CAAQ;EACtB,IAAI;GAGF,AAFA,MAAM,GAAa,EAAE,WAAW;IAAE,IAAI;IAAU,OAAO,EAAE,QAAQ,EAAU;GAAE,EAAE,CAAC,GAChF,MAAM,EAAQ,GACd,EAAY;IACV,MAAM;IACN,SAAS,UAAU,MAAc,EAAa,SAAS,cAAc,cAAc;GACrF,CAAC;EACH,SAAS,GAAO;GAEd,AADA,QAAQ,MAAM,mCAAmC,CAAK,GACtD,EAAY;IACV,MAAM;IACN,SAAS,4BAA4B,aAAiB,QAAQ,EAAM,UAAU;GAChF,CAAC;EACH,UAAU;GACR,EAAc,IAAI;EACpB;CACF;CAEA,SAAS,KAAe;EACtB,IAAM,IAA0B,EAAgB,KAAK,OAAoB;GACvE,MAAM,EAAO;GACb,MAAM,EAAO;GACb,aAAa,EAAO,eAAe;GACnC,QAAQ,EAAO;GACf,MAAM,EAAO;GACb,OAAO,EAAO;GACd,aAAa,EAAO;GACpB,cAAc,EAAO,gBAAgB;GACrC,YAAY,EAAO,cAAc;EACnC,EAAE;EAEF,IAAI,EAAK,WAAW,GAAG;GACrB,EAAY;IACV,MAAM;IACN,SAAS;GACX,CAAC;GACD;EACF;EAEA,IAAM,IAAY,OAAO,KAAK,EAAK,EAAE,EAAE,KAAK,GAAG,GACzC,IAAU,EACb,KAAK,MACJ,OAAO,OAAO,CAAG,EACd,KAAK,MAAU,IAAI,OAAO,CAAK,EAAE,WAAW,MAAK,MAAI,EAAE,EAAE,EACzD,KAAK,GAAG,CACb,EACC,KAAK,IAAI,GAEN,IAAO,IAAI,KAAK,CAAC,GAAG,EAAU,IAAI,GAAS,GAAG,EAAE,MAAM,yBAAyB,CAAC,GAChF,IAAc,IAAI,gBAAgB,CAAI,GACtC,IAAO,SAAS,cAAc,GAAG;EAMvC,AALA,EAAK,OAAO,GACZ,EAAK,WAAW,uBAChB,EAAK,MAAM,GACX,IAAI,gBAAgB,CAAW,GAE/B,EAAY;GACV,MAAM;GACN,SAAS,YAAY,EAAK,OAAO,SAAS,EAAK,WAAW,IAAI,KAAK,IAAI;EACzE,CAAC;CACH;AACF"}
|
|
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 {\n ResponsiveTable,\n type ResponsiveTableColumn,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n const couponColumns: ResponsiveTableColumn<Coupon>[] = [\n {\n key: 'code',\n header: 'Code',\n priority: 'primary',\n cell: (coupon) => (\n <div>\n <p className=\"font-medium text-text-primary font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-text-muted\">{coupon.name}</p>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cell: (coupon) => (\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n ),\n },\n {\n key: 'value',\n header: 'Value',\n cell: (coupon) => (\n <span className=\"text-sm font-medium text-text-primary tabular-nums\">\n {coupon.type === 'PERCENTAGE' ? `${coupon.value}%` : formatCurrency(coupon.value)}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (coupon) => (\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n ),\n },\n {\n key: 'usage',\n header: 'Usage',\n cell: (coupon) => (\n <>\n <div className=\"text-sm text-text-primary tabular-nums\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-text-muted\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-bg-sunken rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-action-primary-bg h-1.5 rounded-full\"\n style={{\n width: `${Math.min((coupon.currentUses / coupon.maxTotalUses) * 100, 100)}%`,\n }}\n />\n </div>\n )}\n </>\n ),\n },\n {\n key: 'validUntil',\n header: 'Valid Until',\n priority: 'hidden-on-mobile',\n cell: (coupon) => (\n <span className=\"text-sm text-text-muted\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n ),\n },\n ];\n\n const renderCouponActions = (coupon: Coupon) => (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-text-muted/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </>\n );\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n <PagePurpose>\n {tr(\n 'billing.coupons.purpose',\n 'Create and manage discount codes that customers redeem at checkout — percentage or fixed-amount off, with optional usage limits and expiry. Track how often each coupon has been used and deactivate ones you no longer want to honour.'\n )}\n </PagePurpose>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Coupons</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Tag className=\"size-4 text-accent-blue\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Uses</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">{stats.totalUses}</p>\n <p className=\"text-xs text-text-muted mt-1\">All time</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Discount</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <DollarSign className=\"size-4 text-accent-purple\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Active Rate</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-10 pr-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-bg-surface border border-border-seam rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n {loading ? (\n <div className=\"p-8 text-center text-text-muted\">Loading coupons…</div>\n ) : (\n <ResponsiveTable<Coupon>\n columns={couponColumns}\n data={filteredCoupons}\n rowKey={(coupon) => coupon.id}\n rowActions={renderCouponActions}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No coupons found\"\n description=\"Create a coupon to start offering discounts at checkout.\"\n />\n }\n />\n )}\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-text-muted\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-primary font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":"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"}
|
|
@@ -273,7 +273,7 @@ var y = [
|
|
|
273
273
|
/* @__PURE__ */ h("select", {
|
|
274
274
|
value: k.currency,
|
|
275
275
|
onChange: (e) => L("currency", e.target.value),
|
|
276
|
-
className: "w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
276
|
+
className: "w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
277
277
|
children: y.map(({ code: e, symbol: t, name: n }) => /* @__PURE__ */ g("option", {
|
|
278
278
|
value: e,
|
|
279
279
|
children: [
|
|
@@ -572,7 +572,7 @@ var y = [
|
|
|
572
572
|
}), /* @__PURE__ */ g("select", {
|
|
573
573
|
value: k.scope,
|
|
574
574
|
onChange: (e) => L("scope", e.target.value),
|
|
575
|
-
className: "w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
575
|
+
className: "w-full px-4 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
576
576
|
children: [
|
|
577
577
|
/* @__PURE__ */ h("option", {
|
|
578
578
|
value: "GENERIC",
|