@withpotter/components 0.13.0-next.124 → 0.13.0-next.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/billing/overview-tab.js +3 -4
  2. package/dist/billing/overview-tab.js.map +1 -1
  3. package/dist/billing/plans-tab.js +12 -6
  4. package/dist/billing/plans-tab.js.map +1 -1
  5. package/dist/charts/index.d.ts +1 -1
  6. package/dist/charts/index.js +1 -3
  7. package/dist/charts/index.js.map +1 -1
  8. package/dist/dashboard/activity-feed.d.ts +0 -2
  9. package/dist/dashboard/activity-feed.js +1 -4
  10. package/dist/dashboard/activity-feed.js.map +1 -1
  11. package/dist/dashboard/home/ask-controls.js +0 -1
  12. package/dist/dashboard/home/ask-controls.js.map +1 -1
  13. package/dist/dashboard/home/ask-mode-chip.js +4 -4
  14. package/dist/dashboard/home/ask-mode-chip.js.map +1 -1
  15. package/dist/dashboard/home/home-ask.js +3 -3
  16. package/dist/dashboard/home/home-ask.js.map +1 -1
  17. package/dist/dashboard/home/home-quick-actions.js +1 -9
  18. package/dist/dashboard/home/home-quick-actions.js.map +1 -1
  19. package/dist/dashboard/home/widgets/activity-widget.js +43 -56
  20. package/dist/dashboard/home/widgets/activity-widget.js.map +1 -1
  21. package/dist/dashboard/home/widgets/home-widgets.js +2 -3
  22. package/dist/dashboard/home/widgets/home-widgets.js.map +1 -1
  23. package/dist/dashboard/home/widgets/money-widgets.js +4 -9
  24. package/dist/dashboard/home/widgets/money-widgets.js.map +1 -1
  25. package/dist/dashboard/home/widgets/registry.d.ts +3 -6
  26. package/dist/dashboard/home/widgets/registry.js +2 -12
  27. package/dist/dashboard/home/widgets/registry.js.map +1 -1
  28. package/dist/dashboard/home/widgets/team-widget.js +3 -15
  29. package/dist/dashboard/home/widgets/team-widget.js.map +1 -1
  30. package/dist/dashboard/home/widgets/types.d.ts +1 -13
  31. package/dist/dashboard/home/widgets/types.js.map +1 -1
  32. package/dist/dashboard/home/widgets/use-home-layout.js +4 -9
  33. package/dist/dashboard/home/widgets/use-home-layout.js.map +1 -1
  34. package/dist/dashboard/home/widgets/website-widget.js +1 -9
  35. package/dist/dashboard/home/widgets/website-widget.js.map +1 -1
  36. package/dist/dashboard/page-transition.d.ts +0 -11
  37. package/dist/dashboard/page-transition.js +2 -9
  38. package/dist/dashboard/page-transition.js.map +1 -1
  39. package/dist/features/gift-card-artwork.js +1 -1
  40. package/dist/features/gift-card-artwork.js.map +1 -1
  41. package/dist/features/gift-cards/gift-cards-feature.js +518 -656
  42. package/dist/features/gift-cards/gift-cards-feature.js.map +1 -1
  43. package/dist/features/product-form.js +1 -1
  44. package/dist/features/product-form.js.map +1 -1
  45. package/dist/mobile/screens/billing-flows.js +6 -7
  46. package/dist/mobile/screens/billing-flows.js.map +1 -1
  47. package/dist/mobile/screens/billing.js +2 -2
  48. package/dist/mobile/screens/billing.js.map +1 -1
  49. package/dist/notifications-dropdown.js +1 -11
  50. package/dist/notifications-dropdown.js.map +1 -1
  51. package/dist/shell/notification-kinds.d.ts +2 -11
  52. package/dist/shell/notification-kinds.js +1 -16
  53. package/dist/shell/notification-kinds.js.map +1 -1
  54. package/dist/shell/notification-row.d.ts +2 -6
  55. package/dist/shell/notification-row.js +33 -59
  56. package/dist/shell/notification-row.js.map +1 -1
  57. package/dist/shell/product-switcher.js +0 -3
  58. package/dist/shell/product-switcher.js.map +1 -1
  59. package/dist/shell/settings-modal-context.d.ts +0 -2
  60. package/dist/shell/settings-modal-context.js +2 -4
  61. package/dist/shell/settings-modal-context.js.map +1 -1
  62. package/dist/shell/settings-panels.js +9 -29
  63. package/dist/shell/settings-panels.js.map +1 -1
  64. package/dist/shell/settings-rows.d.ts +2 -7
  65. package/dist/shell/settings-rows.js +36 -93
  66. package/dist/shell/settings-rows.js.map +1 -1
  67. package/dist/shell/shell.css +0 -192
  68. package/dist/shell/sidebar-footer.js +2 -6
  69. package/dist/shell/sidebar-footer.js.map +1 -1
  70. package/dist/shell/unified-settings-modal.d.ts +2 -3
  71. package/dist/shell/unified-settings-modal.js +74 -151
  72. package/dist/shell/unified-settings-modal.js.map +1 -1
  73. package/dist/sidebar/config.js +3 -19
  74. package/dist/sidebar/config.js.map +1 -1
  75. package/dist/sidebar/more-group.js +1 -1
  76. package/dist/sidebar/more-group.js.map +1 -1
  77. package/dist/sidebar/nav-renderers.js +0 -2
  78. package/dist/sidebar/nav-renderers.js.map +1 -1
  79. package/dist/sidebar/nav-section.js +3 -5
  80. package/dist/sidebar/nav-section.js.map +1 -1
  81. package/dist/sidebar/setup-progress.d.ts +1 -3
  82. package/dist/sidebar/setup-progress.js +2 -2
  83. package/dist/sidebar/setup-progress.js.map +1 -1
  84. package/dist/sidebar/spine-groups.js +1 -7
  85. package/dist/sidebar/spine-groups.js.map +1 -1
  86. package/dist/sidebar.js +5 -5
  87. package/dist/sidebar.js.map +1 -1
  88. package/dist/templates/template-live-thumb.js +5 -16
  89. package/dist/templates/template-live-thumb.js.map +1 -1
  90. package/package.json +5 -5
  91. package/dist/dashboard/activity-paid.d.ts +0 -10
  92. package/dist/dashboard/activity-paid.js +0 -7
  93. package/dist/dashboard/activity-paid.js.map +0 -1
  94. package/dist/dashboard/home/host-quick-actions.d.ts +0 -19
  95. package/dist/dashboard/home/host-quick-actions.js +0 -12
  96. package/dist/dashboard/home/host-quick-actions.js.map +0 -1
  97. package/dist/dashboard/home/widgets/host-widgets.d.ts +0 -6
  98. package/dist/dashboard/home/widgets/host-widgets.js +0 -16
  99. package/dist/dashboard/home/widgets/host-widgets.js.map +0 -1
  100. package/dist/dashboard/home/widgets/member-label.d.ts +0 -11
  101. package/dist/dashboard/home/widgets/member-label.js +0 -10
  102. package/dist/dashboard/home/widgets/member-label.js.map +0 -1
  103. package/dist/dashboard/home/widgets/middle-ellipsis.d.ts +0 -5
  104. package/dist/dashboard/home/widgets/middle-ellipsis.js +0 -11
  105. package/dist/dashboard/home/widgets/middle-ellipsis.js.map +0 -1
  106. package/dist/dashboard/home/widgets/revenue-series.d.ts +0 -12
  107. package/dist/dashboard/home/widgets/revenue-series.js +0 -26
  108. package/dist/dashboard/home/widgets/revenue-series.js.map +0 -1
  109. package/dist/dashboard/page-transition-key.d.ts +0 -7
  110. package/dist/dashboard/page-transition-key.js +0 -11
  111. package/dist/dashboard/page-transition-key.js.map +0 -1
  112. package/dist/features/gift-cards/gift-card-logic.d.ts +0 -41
  113. package/dist/features/gift-cards/gift-card-logic.js +0 -65
  114. package/dist/features/gift-cards/gift-card-logic.js.map +0 -1
  115. package/dist/features/gift-cards/hint.d.ts +0 -9
  116. package/dist/features/gift-cards/hint.js +0 -25
  117. package/dist/features/gift-cards/hint.js.map +0 -1
  118. package/dist/shell/notification-actions.d.ts +0 -12
  119. package/dist/shell/notification-actions.js +0 -18
  120. package/dist/shell/notification-actions.js.map +0 -1
  121. package/dist/shell/settings-groups.d.ts +0 -23
  122. package/dist/shell/settings-groups.js +0 -22
  123. package/dist/shell/settings-groups.js.map +0 -1
  124. package/dist/shell/settings-host.d.ts +0 -39
  125. package/dist/shell/settings-host.js +0 -37
  126. package/dist/shell/settings-host.js.map +0 -1
  127. package/dist/shell/sidebar-cards.d.ts +0 -32
  128. package/dist/shell/sidebar-cards.js +0 -37
  129. package/dist/shell/sidebar-cards.js.map +0 -1
  130. package/dist/templates/template-tile-source.d.ts +0 -24
  131. package/dist/templates/template-tile-source.js +0 -18
  132. package/dist/templates/template-tile-source.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/features/gift-cards/gift-cards-feature.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { Copy, Download, Gift, History, Mail, Plus, Search } from 'lucide-react';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { normalizeCurrencyCode } from '@withpotter/lib/utils';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { Field } from '@withpotter/ui-kit/field';\nimport { FormDialog } from '@withpotter/ui-kit/form-dialog';\nimport { InlineDeleteButton } from '@withpotter/ui-kit/inline-delete-button';\nimport { Input } from '@withpotter/ui-kit/input';\nimport { ListPage } from '@withpotter/ui-kit/list-page';\nimport { Textarea } from '@withpotter/ui-kit/textarea';\nimport { Tooltip } from '@withpotter/ui-kit/tooltip';\nimport {\n giftCardsApi,\n giftcardsApi,\n communicationsApi,\n type CreateGiftCardData,\n type GiftCard,\n type GiftCardStats,\n type GiftCardStatus,\n} from '@withpotter/lib/api';\nimport { useFormatCurrency } from '@withpotter/hooks/use-format-currency';\nimport { useQuietPoll } from '@withpotter/hooks/use-quiet-poll';\nimport { InlineListSkeleton } from '@withpotter/ui-kit/page-skeletons';\nimport { RefreshButton } from '@withpotter/ui-kit/refresh-button';\nimport { TableEmptyState } from '@withpotter/ui-kit/table-empty-state';\nimport { TablePagination } from '@withpotter/ui-kit/table-pagination';\nimport { useToast } from '@withpotter/ui-kit/use-toast';\nimport { MobilePageHeader } from '../../mobile/mobile-page-header';\nimport { useRegisterRefresh } from '../../mobile/pull-to-refresh-context';\nimport { MetricRow, MetricTile } from '@withpotter/ui-kit/metric-row';\nimport {\n createGiftCardEmailHtml,\n downloadGiftCardArtwork,\n GiftCardArtwork,\n GIFT_CARD_PRESETS,\n} from '../gift-card-artwork';\nimport {\n createInFlightDeduper,\n currencySymbol,\n isFirstRun,\n PAGE_HINT_LINES,\n STATUS_FILTERS,\n STATUS_LABEL,\n} from './gift-card-logic';\nimport { Hint } from './hint';\n\nconst PAGE_SIZE = 20;\n\n/**\n * `NEXT_PUBLIC_BOX_GIFT_CARDS_LIVE=true`: the list keeps itself current, re-read\n * every two seconds while the tab is visible, and the Reload button goes.\n * Unset, the page reads once and keeps its Reload button.\n */\nconst LIVE = process.env.NEXT_PUBLIC_BOX_GIFT_CARDS_LIVE === 'true';\nconst LIVE_REFRESH_MS = 2_000;\n\nfunction minimumExpirationDate(): string {\n const tomorrow = new Date();\n tomorrow.setDate(tomorrow.getDate() + 1);\n return tomorrow.toISOString().slice(0, 10);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Something went wrong';\n}\n\nfunction dateLabel(value: string | null): string {\n if (!value) return 'Never';\n return new Date(value).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nfunction statusClass(status: GiftCardStatus): string {\n switch (status) {\n case 'active':\n return 'bg-[var(--success-bg)] text-[var(--success)]';\n case 'used':\n return 'bg-[var(--info-bg)] text-[var(--info)]';\n case 'expired':\n return 'bg-[var(--error-bg)] text-[var(--error)]';\n case 'cancelled':\n return 'bg-[var(--bg-muted)] text-[var(--text-tertiary)]';\n }\n}\n\ninterface IssueGiftCardModalProps {\n currency: string;\n merchantName: string;\n onClose: () => void;\n onCreated: (giftCard: GiftCard) => void;\n}\n\nfunction IssueGiftCardModal({\n currency,\n merchantName,\n onClose,\n onCreated,\n}: IssueGiftCardModalProps) {\n const { tenant } = useAuth();\n const formId = useId();\n const ids = {\n amount: `${formId}-amount`,\n recipientName: `${formId}-recipient-name`,\n recipientEmail: `${formId}-recipient-email`,\n purchaserName: `${formId}-purchaser-name`,\n purchaserEmail: `${formId}-purchaser-email`,\n message: `${formId}-message`,\n expiresAt: `${formId}-expires-at`,\n };\n const [form, setForm] = useState<CreateGiftCardData>({\n originalAmount: 0,\n currency,\n design: GIFT_CARD_PRESETS[0],\n });\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const setField = <Key extends keyof CreateGiftCardData>(\n key: Key,\n value: CreateGiftCardData[Key],\n ) => setForm((current) => ({ ...current, [key]: value }));\n\n const submit = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug || form.originalAmount <= 0) return;\n setSaving(true);\n setError(null);\n try {\n const payload = Object.fromEntries(\n Object.entries(form).filter(([, value]) => value !== ''),\n ) as unknown as CreateGiftCardData;\n const giftCard = await giftCardsApi.create(tenant.slug, payload);\n onCreated(giftCard);\n } catch (submitError) {\n setError(errorMessage(submitError));\n } finally {\n setSaving(false);\n }\n };\n\n const symbol = currencySymbol(form.currency || currency);\n\n return (\n <FormDialog\n title=\"Issue gift card\"\n description=\"We create the code for you. Fields marked * are required.\"\n size=\"lg\"\n className=\"max-w-4xl\"\n onClose={onClose}\n footer={\n <div className=\"flex justify-end gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={formId}\n disabled={saving || form.originalAmount <= 0}\n >\n {saving ? 'Issuing...' : 'Issue gift card'}\n </Button>\n </div>\n }\n >\n <form\n id={formId}\n onSubmit={submit}\n className=\"grid gap-6 md:grid-cols-[17rem_minmax(0,1fr)]\"\n >\n <div className=\"space-y-4 md:self-start\">\n <GiftCardArtwork\n design={form.design ?? GIFT_CARD_PRESETS[0]}\n merchantName={merchantName}\n amount={\n form.originalAmount > 0\n ? new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: form.currency || currency,\n }).format(form.originalAmount)\n : undefined\n }\n recipientName={form.recipientName}\n senderName={form.purchaserName}\n />\n <fieldset>\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Design\n </legend>\n <div className=\"mt-2 grid grid-cols-4 gap-2 md:grid-cols-2\">\n {GIFT_CARD_PRESETS.map((design) => {\n const selected = form.design?.designId === design.designId;\n return (\n <button\n key={design.designId}\n type=\"button\"\n onClick={() => setField('design', design)}\n className={`flex min-w-0 items-center gap-2 rounded-md border p-2 text-left text-xs font-medium ${\n selected\n ? 'border-brand-500 bg-brand-500/10 text-[var(--text-primary)]'\n : 'border-[var(--border-default)] text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]'\n }`}\n aria-label={design.name}\n aria-pressed={selected}\n >\n <span\n className=\"h-5 w-5 shrink-0 rounded-sm border border-black/10\"\n style={{ backgroundColor: design.palette.background }}\n />\n <span className=\"hidden truncate md:block\">{design.name}</span>\n </button>\n );\n })}\n </div>\n </fieldset>\n </div>\n\n <div className=\"space-y-4\">\n <Field\n label=\"Amount\"\n htmlFor={ids.amount}\n required\n hint={`In ${form.currency || currency}, your business currency. It cannot be changed here.`}\n >\n <div className=\"relative\">\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 left-3 flex items-center text-sm text-[var(--text-tertiary)]\"\n >\n {symbol}\n </span>\n <Input\n id={ids.amount}\n type=\"number\"\n min=\"0.01\"\n step=\"0.01\"\n required\n aria-required\n className={symbol.length > 2 ? 'pl-14' : 'pl-8'}\n value={form.originalAmount || ''}\n onChange={(event) =>\n setField('originalAmount', Number(event.target.value))\n }\n />\n </div>\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field\n label=\"Recipient name\"\n htmlFor={ids.recipientName}\n hint=\"Optional. Who receives and spends the card.\"\n >\n <Input\n id={ids.recipientName}\n value={form.recipientName ?? ''}\n onChange={(event) => setField('recipientName', event.target.value)}\n />\n </Field>\n <Field\n label=\"Recipient email\"\n htmlFor={ids.recipientEmail}\n hint=\"Optional. Nothing is emailed when you issue. Send it from the card's details.\"\n >\n <Input\n id={ids.recipientEmail}\n type=\"email\"\n value={form.recipientEmail ?? ''}\n onChange={(event) =>\n setField('recipientEmail', event.target.value)\n }\n />\n </Field>\n <Field\n label=\"Purchaser name\"\n htmlFor={ids.purchaserName}\n hint=\"Optional. Who is paying for the card.\"\n >\n <Input\n id={ids.purchaserName}\n value={form.purchaserName ?? ''}\n onChange={(event) => setField('purchaserName', event.target.value)}\n />\n </Field>\n <Field\n label=\"Purchaser email\"\n htmlFor={ids.purchaserEmail}\n hint=\"Optional.\"\n >\n <Input\n id={ids.purchaserEmail}\n type=\"email\"\n value={form.purchaserEmail ?? ''}\n onChange={(event) =>\n setField('purchaserEmail', event.target.value)\n }\n />\n </Field>\n </div>\n <Field\n label=\"Personal message\"\n htmlFor={ids.message}\n hint=\"Optional. Up to 500 characters.\"\n >\n <Textarea\n id={ids.message}\n rows={3}\n maxLength={500}\n value={form.personalMessage ?? ''}\n onChange={(event) => setField('personalMessage', event.target.value)}\n />\n </Field>\n <Field\n label=\"Expiration date\"\n htmlFor={ids.expiresAt}\n hint=\"Optional. Leave it empty and the card has no expiry date.\"\n >\n <Input\n id={ids.expiresAt}\n type=\"date\"\n min={minimumExpirationDate()}\n value={form.expiresAt ?? ''}\n onChange={(event) => setField('expiresAt', event.target.value)}\n />\n </Field>\n {error && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]\"\n >\n {error}\n </p>\n )}\n </div>\n </form>\n </FormDialog>\n );\n}\n\ninterface BalanceModalProps {\n onClose: () => void;\n initialCode?: string;\n}\n\nfunction BalanceModal({ onClose, initialCode = '' }: BalanceModalProps) {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const codeId = useId();\n const [code, setCode] = useState(initialCode);\n const [checking, setChecking] = useState(false);\n const [result, setResult] = useState<{\n valid: boolean;\n balance?: number;\n error?: string;\n } | null>(null);\n\n const submit = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug || !code.trim()) return;\n setChecking(true);\n try {\n setResult(\n await giftCardsApi.checkBalance(tenant.slug, code.trim().toUpperCase()),\n );\n } catch (checkError) {\n setResult({ valid: false, error: errorMessage(checkError) });\n } finally {\n setChecking(false);\n }\n };\n\n return (\n <FormDialog title=\"Check gift card balance\" size=\"sm\" onClose={onClose}>\n <form onSubmit={submit} className=\"space-y-4\">\n <Field label=\"Gift card code\" htmlFor={codeId} required>\n <Input\n id={codeId}\n required\n aria-required\n autoComplete=\"off\"\n spellCheck={false}\n className=\"font-mono uppercase\"\n value={code}\n onChange={(event) => {\n setCode(event.target.value.toUpperCase());\n setResult(null);\n }}\n />\n </Field>\n <div role=\"status\" aria-live=\"polite\">\n {result && (\n <div\n className={`rounded-md p-4 ${\n result.valid\n ? 'bg-[var(--success-bg)] text-[var(--success)]'\n : 'bg-[var(--error-bg)] text-[var(--error)]'\n }`}\n >\n {result.valid\n ? `Available balance: ${formatCurrency(result.balance ?? 0)}`\n : result.error || 'This gift card is not valid.'}\n </div>\n )}\n </div>\n <Button\n type=\"submit\"\n className=\"w-full\"\n disabled={checking || !code.trim()}\n >\n {checking ? 'Checking...' : 'Check balance'}\n </Button>\n </form>\n </FormDialog>\n );\n}\n\ninterface GiftCardDetailModalProps {\n giftCard: GiftCard;\n onClose: () => void;\n onChanged: () => void;\n}\n\nfunction GiftCardDetailModal({\n giftCard,\n onClose,\n onChanged,\n}: GiftCardDetailModalProps) {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const { toast } = useToast();\n const [amount, setAmount] = useState('');\n const [referenceId, setReferenceId] = useState('');\n const [note, setNote] = useState('');\n const [saving, setSaving] = useState(false);\n const [sharing, setSharing] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const copyCode = async () => {\n await navigator.clipboard.writeText(giftCard.code);\n toast({ title: 'Gift card code copied', description: giftCard.code });\n };\n\n const artworkData = {\n design: giftCard.design ?? GIFT_CARD_PRESETS[0],\n merchantName: tenant?.businessName || 'Gift card',\n amount: formatCurrency(Number(giftCard.originalAmount), giftCard.currency),\n recipientName: giftCard.recipientName ?? undefined,\n senderName: giftCard.purchaserName ?? undefined,\n code: giftCard.code,\n };\n\n const sendByEmail = async () => {\n if (!tenant?.slug || !giftCard.recipientEmail) return;\n setSharing(true);\n setError(null);\n try {\n const result = await communicationsApi.sendCustomEmail(tenant.slug, {\n recipientEmail: giftCard.recipientEmail,\n recipientName: giftCard.recipientName ?? undefined,\n subject: `${giftCard.purchaserName || tenant.businessName} sent you a gift card`,\n htmlContent: createGiftCardEmailHtml(artworkData),\n metadata: { giftCardId: giftCard.id, kind: 'gift_card_delivery' },\n });\n if (!result.success) throw new Error(result.error || 'Email delivery failed');\n toast({ title: 'Gift card email sent', description: giftCard.recipientEmail });\n } catch (sendError) {\n setError(errorMessage(sendError));\n } finally {\n setSharing(false);\n }\n };\n\n const redeem = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug) return;\n setSaving(true);\n setError(null);\n try {\n const result = await giftCardsApi.redeem(tenant.slug, giftCard.code, {\n amount: Number(amount),\n ...(referenceId ? { referenceId } : {}),\n ...(note ? { note } : {}),\n });\n if (!result.success) throw new Error(result.error || 'Redemption failed');\n toast({ title: 'Gift card redeemed' });\n onChanged();\n onClose();\n } catch (redeemError) {\n setError(errorMessage(redeemError));\n } finally {\n setSaving(false);\n }\n };\n\n // Voids the card and keeps its record and history. The older DELETE\n // endpoint destroyed the row, and with it the balance a customer had paid for.\n const remove = async () => {\n if (!tenant?.slug) return;\n setSaving(true);\n try {\n await giftcardsApi.cancel(tenant.slug, giftCard.id);\n toast({ title: 'Gift card cancelled' });\n onChanged();\n onClose();\n } catch (deleteError) {\n setError(errorMessage(deleteError));\n setSaving(false);\n }\n };\n\n return (\n <FormDialog title=\"Gift card details\" onClose={onClose}>\n <div className=\"space-y-5\">\n <GiftCardArtwork {...artworkData} />\n <div className=\"grid grid-cols-2 gap-2\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n icon={<Download />}\n onClick={() => downloadGiftCardArtwork(artworkData)}\n >\n Download\n </Button>\n <Tooltip\n label={\n giftCard.recipientEmail\n ? ''\n : 'Add a recipient email to send this card'\n }\n >\n <span className=\"block\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n icon={<Mail />}\n className=\"w-full\"\n onClick={sendByEmail}\n disabled={sharing || !giftCard.recipientEmail}\n >\n {sharing ? 'Sending...' : 'Send by email'}\n </Button>\n </span>\n </Tooltip>\n </div>\n <div className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-subtle)] p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <p className=\"text-xs font-medium text-[var(--text-muted)]\">Code</p>\n <p className=\"mt-1 font-mono text-xl font-semibold text-[var(--text-primary)]\">\n {giftCard.code}\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"icon-sm\"\n aria-label=\"Copy code\"\n onClick={copyCode}\n >\n <Copy />\n </Button>\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Balance left</p>\n <p className=\"font-semibold text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Original value</p>\n <p className=\"font-semibold text-[var(--text-primary)]\">\n {formatCurrency(\n Number(giftCard.originalAmount),\n giftCard.currency,\n )}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Recipient</p>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {giftCard.recipientName || giftCard.recipientEmail || 'Walk-in'}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Expires</p>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {dateLabel(giftCard.expiresAt)}\n </p>\n </div>\n </div>\n </div>\n\n {giftCard.status === 'active' && Number(giftCard.balance) > 0 && (\n <form onSubmit={redeem} className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n Redeem value\n </h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <Input\n label=\"Amount\"\n type=\"number\"\n min=\"0.01\"\n max={Number(giftCard.balance)}\n step=\"0.01\"\n required\n aria-required\n value={amount}\n onChange={(event) => setAmount(event.target.value)}\n />\n <Input\n label=\"Order or reference ID (optional)\"\n value={referenceId}\n onChange={(event) => setReferenceId(event.target.value)}\n />\n </div>\n <Input\n label=\"Note (optional)\"\n value={note}\n onChange={(event) => setNote(event.target.value)}\n />\n <Button\n type=\"submit\"\n disabled={saving || !amount || Number(amount) <= 0}\n >\n {saving ? 'Redeeming...' : 'Redeem gift card'}\n </Button>\n </form>\n )}\n\n <div>\n <div className=\"mb-2 flex items-center gap-2\">\n <History className=\"h-4 w-4 text-[var(--text-tertiary)]\" />\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n Redemption history\n </h3>\n </div>\n {giftCard.redemptions.length === 0 ? (\n <p className=\"text-sm text-[var(--text-muted)]\">No redemptions yet.</p>\n ) : (\n <div className=\"divide-y divide-[var(--border-default)] rounded-md border border-[var(--border-default)]\">\n {giftCard.redemptions.map((redemption) => (\n <div\n key={redemption.id}\n className=\"flex items-center justify-between gap-3 px-3 py-2.5\"\n >\n <div>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {redemption.orderId || 'Manual redemption'}\n </p>\n <p className=\"text-xs text-[var(--text-muted)]\">\n {dateLabel(redemption.redeemedAt)}\n </p>\n </div>\n <span className=\"text-sm font-medium text-[var(--text-primary)]\">\n -{formatCurrency(Number(redemption.amount), giftCard.currency)}\n </span>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {error && (\n <p\n role=\"alert\"\n className=\"rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]\"\n >\n {error}\n </p>\n )}\n {giftCard.status !== 'cancelled' && (\n <div className=\"flex justify-end border-t border-[var(--border-default)] pt-4\">\n <InlineDeleteButton\n onDelete={remove}\n disabled={saving}\n title=\"Cancel gift card\"\n confirmText=\"Cancel this gift card?\"\n className=\"text-[var(--error)]\"\n />\n </div>\n )}\n </div>\n </FormDialog>\n );\n}\n\nexport function GiftCardsFeature() {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const { toast } = useToast();\n const slug = tenant?.slug;\n const currency = normalizeCurrencyCode(\n (tenant?.settings as Record<string, unknown> | undefined)?.currency,\n );\n const [giftCards, setGiftCards] = useState<GiftCard[]>([]);\n const [stats, setStats] = useState<GiftCardStats | null>(null);\n const [loading, setLoading] = useState(true);\n const [loaded, setLoaded] = useState(false);\n const [search, setSearch] = useState('');\n const [status, setStatus] = useState<GiftCardStatus | ''>('');\n const [page, setPage] = useState(1);\n const [count, setCount] = useState(0);\n const [showIssue, setShowIssue] = useState(false);\n const [showBalance, setShowBalance] = useState(false);\n const [selected, setSelected] = useState<GiftCard | null>(null);\n\n // Two effects, an unstable callback and a pull-to-refresh registration all\n // used to start the same reads on load, so every request went out twice.\n // Identical reads in flight together now share one request.\n const [dedupe] = useState(createInFlightDeduper);\n const toastRef = useRef(toast);\n useEffect(() => {\n toastRef.current = toast;\n });\n\n // Only the newest read may land: a quiet re-read started before the filters\n // changed must not put the old filter's rows back on screen.\n const latestRead = useRef(0);\n const readGiftCards = useCallback(\n async (quiet: boolean) => {\n if (!slug) return;\n // A page's own read moves the marker; a quiet re-read only checks it.\n const read = quiet ? latestRead.current : ++latestRead.current;\n if (!quiet) setLoading(true);\n const query = {\n ...(status ? { status } : {}),\n ...(search.trim() ? { search: search.trim() } : {}),\n page,\n limit: PAGE_SIZE,\n };\n try {\n const list = await dedupe.run(\n `list:${slug}:${JSON.stringify(query)}`,\n () => giftCardsApi.list(slug, query),\n );\n if (read !== latestRead.current) return;\n setGiftCards(list.rows);\n setCount(list.count);\n } catch (loadError) {\n // A quiet re-read keeps what is on screen and says nothing; the poll\n // counts the failure and slows down.\n if (quiet) throw loadError;\n toastRef.current({\n title: 'Unable to load gift cards',\n description: errorMessage(loadError),\n });\n } finally {\n if (!quiet && read === latestRead.current) {\n setLoading(false);\n setLoaded(true);\n }\n }\n },\n [dedupe, page, search, slug, status],\n );\n\n // The totals do not depend on the filters, so they are read on their own and\n // not again each time the search box or a filter changes.\n const readStats = useCallback(\n async (quiet: boolean) => {\n if (!slug) return;\n try {\n setStats(await dedupe.run(`stats:${slug}`, () => giftCardsApi.stats(slug)));\n } catch (statsError) {\n if (quiet) throw statsError;\n }\n },\n [dedupe, slug],\n );\n\n const reloadAll = useCallback(\n () => Promise.all([readGiftCards(false), readStats(false)]),\n [readGiftCards, readStats],\n );\n\n useEffect(() => {\n readGiftCards(false);\n }, [readGiftCards]);\n\n useEffect(() => {\n readStats(false);\n }, [readStats]);\n\n useQuietPoll(\n useCallback(\n () => Promise.all([readGiftCards(true), readStats(true)]),\n [readGiftCards, readStats],\n ),\n LIVE_REFRESH_MS,\n LIVE,\n );\n\n useRegisterRefresh(reloadAll);\n\n const copyCode = async (event: React.MouseEvent, code: string) => {\n event.stopPropagation();\n await navigator.clipboard.writeText(code);\n toast({ title: 'Gift card code copied', description: code });\n };\n\n const totalPages = Math.max(1, Math.ceil(count / PAGE_SIZE));\n const hasFilters = Boolean(search.trim() || status);\n const firstRun =\n loaded &&\n isFirstRun({\n loading,\n search,\n status,\n count,\n totalIssued: stats?.totalIssued,\n });\n const hasCards = loaded && !firstRun;\n const showStats = hasCards && stats !== null;\n\n const openIssue = () => setShowIssue(true);\n const openBalance = () => setShowBalance(true);\n\n return (\n <ListPage\n largeTitle\n title=\"Gift cards\"\n mobileHeader={\n <MobilePageHeader\n title=\"Gift cards\"\n action={\n hasCards ? { label: 'Issue gift card', onClick: openIssue } : undefined\n }\n />\n }\n actions={\n <>\n <Hint lines={PAGE_HINT_LINES} topic=\"gift cards\" />\n {!LIVE && <RefreshButton onClick={reloadAll} refreshing={loading} />}\n {hasCards && (\n <>\n <Button\n type=\"button\"\n variant=\"secondary\"\n icon={<Search />}\n onClick={openBalance}\n >\n Check balance\n </Button>\n <Button type=\"button\" icon={<Plus />} onClick={openIssue}>\n Issue gift card\n </Button>\n </>\n )}\n </>\n }\n >\n {showStats && (\n <>\n <div className=\"hidden lg:block\">\n <MetricRow>\n <MetricTile\n label=\"Total sold\"\n value={formatCurrency(stats?.totalIssued ?? 0, currency)}\n />\n <MetricTile\n label=\"Still to spend\"\n value={formatCurrency(stats?.outstanding ?? 0, currency)}\n />\n <MetricTile\n label=\"Spent\"\n value={formatCurrency(stats?.redeemed ?? 0, currency)}\n />\n <MetricTile label=\"Cards in use\" value={stats?.activeCards ?? 0} />\n </MetricRow>\n </div>\n\n <div className=\"grid grid-cols-2 gap-px overflow-hidden rounded-md bg-[var(--border-default)] lg:hidden\">\n <div className=\"bg-[var(--bg-subtle)] p-4\">\n <p className=\"text-xs text-[var(--text-muted)]\">Still to spend</p>\n <p className=\"mt-1 text-lg font-semibold text-[var(--text-primary)]\">\n {formatCurrency(stats?.outstanding ?? 0, currency)}\n </p>\n </div>\n <div className=\"bg-[var(--bg-subtle)] p-4\">\n <p className=\"text-xs text-[var(--text-muted)]\">Cards in use</p>\n <p className=\"mt-1 text-lg font-semibold text-[var(--text-primary)]\">\n {stats?.activeCards ?? 0}\n </p>\n </div>\n </div>\n </>\n )}\n\n {hasCards && (\n <div className=\"flex gap-2 overflow-x-auto lg:hidden\">\n <Button\n type=\"button\"\n variant=\"secondary\"\n shape=\"pill\"\n icon={<Search />}\n onClick={openBalance}\n >\n Check balance\n </Button>\n </div>\n )}\n\n {!firstRun && (\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center\">\n <div className=\"relative flex-1\">\n <Search\n aria-hidden\n className=\"pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]\"\n />\n <Input\n type=\"search\"\n aria-label=\"Search gift cards\"\n placeholder=\"Search code, recipient, or email\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n className=\"pl-10\"\n />\n </div>\n <div\n role=\"group\"\n aria-label=\"Filter by status\"\n className=\"flex gap-1 overflow-x-auto\"\n >\n {STATUS_FILTERS.map((filter) => {\n const pressed = status === filter.value;\n return (\n <Tooltip key={filter.value} label={filter.hint}>\n <Button\n type=\"button\"\n size=\"sm\"\n variant={pressed ? 'secondary' : 'ghost'}\n aria-pressed={pressed}\n className=\"shrink-0\"\n onClick={() => {\n setStatus(filter.value);\n setPage(1);\n }}\n >\n {filter.label}\n </Button>\n </Tooltip>\n );\n })}\n </div>\n </div>\n )}\n\n {/* The page's second heading level, so the empty state's own title below\n follows the h1 in order. */}\n <h2 className=\"sr-only\">Issued gift cards</h2>\n\n {!loaded && <InlineListSkeleton />}\n\n {loading && loaded && giftCards.length === 0 && <InlineListSkeleton />}\n\n {firstRun && (\n <div>\n <TableEmptyState\n icon={Gift}\n title=\"Issue your first gift card\"\n description=\"Create a stored-value card for a customer or walk-in purchase\"\n action={{ label: 'Issue gift card', onClick: openIssue }}\n />\n <div className=\"flex justify-center\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n icon={<Search />}\n onClick={openBalance}\n >\n Check balance\n </Button>\n </div>\n </div>\n )}\n\n {loaded && !loading && !firstRun && giftCards.length === 0 && (\n <TableEmptyState\n icon={Gift}\n title=\"No gift cards found\"\n description=\"Try changing the search or status filter\"\n action={\n hasFilters\n ? {\n label: 'Clear search and filters',\n onClick: () => {\n setSearch('');\n setStatus('');\n setPage(1);\n },\n }\n : undefined\n }\n />\n )}\n\n {giftCards.length > 0 && (\n <>\n <div className=\"space-y-2 lg:hidden\">\n {giftCards.map((giftCard) => (\n <button\n key={giftCard.id}\n type=\"button\"\n onClick={() => setSelected(giftCard)}\n className=\"flex w-full items-center justify-between gap-3 rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] p-4 text-left\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-mono text-sm font-semibold text-[var(--text-primary)]\">\n {giftCard.code}\n </p>\n <p className=\"truncate text-sm text-[var(--text-tertiary)]\">\n {giftCard.recipientName || giftCard.recipientEmail || 'Walk-in'}\n </p>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-semibold text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </p>\n <span className={`text-xs ${statusClass(giftCard.status)}`}>\n {STATUS_LABEL[giftCard.status]}\n </span>\n </div>\n </button>\n ))}\n </div>\n\n <div className=\"hidden overflow-x-auto rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] lg:block\">\n <table className=\"w-full min-w-[760px]\">\n <thead className=\"border-b border-[var(--border-default)] bg-[var(--bg-subtle)]\">\n <tr>\n {['Code', 'Recipient', 'Issued', 'Balance', 'Expires', 'Status'].map(\n (heading) => (\n <th\n key={heading}\n scope=\"col\"\n className=\"px-4 py-3 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]\"\n >\n {heading}\n </th>\n ),\n )}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-[var(--border-default)]\">\n {giftCards.map((giftCard) => (\n <tr\n key={giftCard.id}\n tabIndex={0}\n aria-label={`Open gift card ${giftCard.code}`}\n onClick={() => setSelected(giftCard)}\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setSelected(giftCard);\n }\n }}\n className=\"cursor-pointer hover:bg-[var(--interactive-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--focus-ring)]\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-mono text-sm font-medium text-[var(--text-primary)]\">\n {giftCard.code}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Copy code ${giftCard.code}`}\n onClick={(event) => copyCode(event, giftCard.code)}\n >\n <Copy />\n </Button>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <p className=\"text-sm text-[var(--text-primary)]\">\n {giftCard.recipientName || 'Walk-in'}\n </p>\n <p className=\"text-xs text-[var(--text-muted)]\">\n {giftCard.recipientEmail}\n </p>\n </td>\n <td className=\"px-4 py-3 text-sm text-[var(--text-secondary)]\">\n {formatCurrency(\n Number(giftCard.originalAmount),\n giftCard.currency,\n )}\n </td>\n <td className=\"px-4 py-3 text-sm font-medium text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </td>\n <td className=\"px-4 py-3 text-sm text-[var(--text-secondary)]\">\n {dateLabel(giftCard.expiresAt)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${statusClass(giftCard.status)}`}\n >\n {STATUS_LABEL[giftCard.status]}\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <TablePagination\n page={page}\n totalPages={totalPages}\n pageSize={PAGE_SIZE}\n totalItems={count}\n entityName=\"gift cards\"\n onPageChange={setPage}\n />\n </>\n )}\n\n {showIssue && (\n <IssueGiftCardModal\n currency={currency}\n merchantName={tenant?.businessName || 'Gift card'}\n onClose={() => setShowIssue(false)}\n onCreated={(giftCard) => {\n setShowIssue(false);\n setSelected(giftCard);\n toast({\n title: 'Gift card issued',\n description: giftCard.code,\n });\n reloadAll();\n }}\n />\n )}\n {showBalance && <BalanceModal onClose={() => setShowBalance(false)} />}\n {selected && (\n <GiftCardDetailModal\n giftCard={selected}\n onClose={() => setSelected(null)}\n onChanged={reloadAll}\n />\n )}\n </ListPage>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/features/gift-cards/gift-cards-feature.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n Copy,\n Download,\n Gift,\n History,\n Mail,\n Plus,\n Search,\n X,\n} from 'lucide-react';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { normalizeCurrencyCode } from '@withpotter/lib/utils';\nimport { InlineDeleteButton } from '@withpotter/ui-kit/inline-delete-button';\nimport {\n giftCardsApi,\n communicationsApi,\n type CreateGiftCardData,\n type GiftCard,\n type GiftCardStats,\n type GiftCardStatus,\n} from '@withpotter/lib/api';\nimport { useFormatCurrency } from '@withpotter/hooks/use-format-currency';\nimport { useQuietPoll } from '@withpotter/hooks/use-quiet-poll';\nimport { InlineListSkeleton } from '@withpotter/ui-kit/page-skeletons';\nimport { RefreshButton } from '@withpotter/ui-kit/refresh-button';\nimport { TableEmptyState } from '@withpotter/ui-kit/table-empty-state';\nimport { TablePagination } from '@withpotter/ui-kit/table-pagination';\nimport { useToast } from '@withpotter/ui-kit/use-toast';\nimport { MobilePageHeader } from '../../mobile/mobile-page-header';\nimport { useRegisterRefresh } from '../../mobile/pull-to-refresh-context';\nimport { MetricRow, MetricTile } from '@withpotter/ui-kit/metric-row';\nimport {\n createGiftCardEmailHtml,\n downloadGiftCardArtwork,\n GiftCardArtwork,\n GIFT_CARD_PRESETS,\n} from '../gift-card-artwork';\n\nconst PAGE_SIZE = 20;\n\n/**\n * `NEXT_PUBLIC_BOX_GIFT_CARDS_LIVE=true`: the list keeps itself current, re-read\n * every two seconds while the tab is visible, and the Reload button goes.\n * Unset, the page reads once and keeps its Reload button.\n */\nconst LIVE = process.env.NEXT_PUBLIC_BOX_GIFT_CARDS_LIVE === 'true';\nconst LIVE_REFRESH_MS = 2_000;\n\nfunction minimumExpirationDate(): string {\n const tomorrow = new Date();\n tomorrow.setDate(tomorrow.getDate() + 1);\n return tomorrow.toISOString().slice(0, 10);\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Something went wrong';\n}\n\nfunction dateLabel(value: string | null): string {\n if (!value) return 'Never';\n return new Date(value).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nfunction statusClass(status: GiftCardStatus): string {\n switch (status) {\n case 'active':\n return 'bg-[var(--success-bg)] text-[var(--success)]';\n case 'used':\n return 'bg-[var(--info-bg)] text-[var(--info)]';\n case 'expired':\n return 'bg-[var(--error-bg)] text-[var(--error)]';\n case 'cancelled':\n return 'bg-[var(--bg-muted)] text-[var(--text-tertiary)]';\n }\n}\n\ninterface ModalProps {\n title: string;\n onClose: () => void;\n children: React.ReactNode;\n}\n\nfunction Modal({ title, onClose, children }: ModalProps) {\n return (\n <div className=\"fixed inset-0 z-50 flex items-end justify-center bg-black/40 sm:items-center sm:p-4\">\n <div className=\"max-h-[92vh] w-full overflow-y-auto bg-[var(--bg-base)] sm:max-w-xl sm:rounded-md sm:border sm:border-[var(--border-default)]\">\n <div className=\"sticky top-0 z-10 flex items-center justify-between border-b border-[var(--border-default)] bg-[var(--bg-base)] px-4 py-3\">\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n {title}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md p-2 text-[var(--text-tertiary)] hover:bg-[var(--interactive-hover)]\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n {children}\n </div>\n </div>\n );\n}\n\ninterface IssueGiftCardModalProps {\n currency: string;\n merchantName: string;\n onClose: () => void;\n onCreated: (giftCard: GiftCard) => void;\n}\n\nfunction IssueGiftCardModal({\n currency,\n merchantName,\n onClose,\n onCreated,\n}: IssueGiftCardModalProps) {\n const { tenant } = useAuth();\n const [form, setForm] = useState<CreateGiftCardData>({\n originalAmount: 0,\n currency,\n design: GIFT_CARD_PRESETS[0],\n });\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const setField = <Key extends keyof CreateGiftCardData>(\n key: Key,\n value: CreateGiftCardData[Key],\n ) => setForm((current) => ({ ...current, [key]: value }));\n\n const submit = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug || form.originalAmount <= 0) return;\n setSaving(true);\n setError(null);\n try {\n const payload = Object.fromEntries(\n Object.entries(form).filter(([, value]) => value !== ''),\n ) as unknown as CreateGiftCardData;\n const giftCard = await giftCardsApi.create(tenant.slug, payload);\n onCreated(giftCard);\n } catch (submitError) {\n setError(errorMessage(submitError));\n } finally {\n setSaving(false);\n }\n };\n\n const inputClass =\n 'w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm text-[var(--text-primary)] focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20';\n\n return (\n <Modal title=\"Issue gift card\" onClose={onClose}>\n <form onSubmit={submit} className=\"space-y-5 p-4\">\n <div className=\"grid gap-4 sm:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)] sm:items-start\">\n <GiftCardArtwork\n design={form.design ?? GIFT_CARD_PRESETS[0]}\n merchantName={merchantName}\n amount={\n form.originalAmount > 0\n ? new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: form.currency || currency,\n }).format(form.originalAmount)\n : undefined\n }\n recipientName={form.recipientName}\n senderName={form.purchaserName}\n />\n <fieldset>\n <legend className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Design\n </legend>\n <div className=\"mt-2 grid grid-cols-4 gap-2 sm:grid-cols-1\">\n {GIFT_CARD_PRESETS.map((design) => {\n const selected = form.design?.designId === design.designId;\n return (\n <button\n key={design.designId}\n type=\"button\"\n onClick={() => setField('design', design)}\n className={`flex min-w-0 items-center gap-2 rounded-md border p-2 text-left text-xs font-medium ${\n selected\n ? 'border-brand-500 bg-brand-500/10 text-[var(--text-primary)]'\n : 'border-[var(--border-default)] text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]'\n }`}\n aria-label={design.name}\n aria-pressed={selected}\n >\n <span\n className=\"h-5 w-5 shrink-0 rounded-sm border border-black/10\"\n style={{ backgroundColor: design.palette.background }}\n />\n <span className=\"hidden truncate sm:block\">{design.name}</span>\n </button>\n );\n })}\n </div>\n </fieldset>\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Amount\n </span>\n <input\n className={inputClass}\n type=\"number\"\n min=\"0.01\"\n step=\"0.01\"\n required\n value={form.originalAmount || ''}\n onChange={(event) =>\n setField('originalAmount', Number(event.target.value))\n }\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Currency\n </span>\n <input\n className={inputClass}\n readOnly\n required\n value={form.currency}\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Recipient name\n </span>\n <input\n className={inputClass}\n value={form.recipientName ?? ''}\n onChange={(event) => setField('recipientName', event.target.value)}\n />\n <span className=\"block text-xs text-[var(--text-muted)]\">\n The person who receives the gift card and spends it.\n </span>\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Recipient email\n </span>\n <input\n className={inputClass}\n type=\"email\"\n value={form.recipientEmail ?? ''}\n onChange={(event) =>\n setField('recipientEmail', event.target.value)\n }\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Purchaser name\n </span>\n <input\n className={inputClass}\n value={form.purchaserName ?? ''}\n onChange={(event) => setField('purchaserName', event.target.value)}\n />\n <span className=\"block text-xs text-[var(--text-muted)]\">\n The person paying for the gift card.\n </span>\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Purchaser email\n </span>\n <input\n className={inputClass}\n type=\"email\"\n value={form.purchaserEmail ?? ''}\n onChange={(event) =>\n setField('purchaserEmail', event.target.value)\n }\n />\n </label>\n </div>\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Personal message\n </span>\n <textarea\n className={`${inputClass} min-h-24 resize-y`}\n maxLength={500}\n value={form.personalMessage ?? ''}\n onChange={(event) => setField('personalMessage', event.target.value)}\n />\n </label>\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Expiration date\n </span>\n <input\n className={inputClass}\n type=\"date\"\n min={minimumExpirationDate()}\n value={form.expiresAt ?? ''}\n onChange={(event) => setField('expiresAt', event.target.value)}\n />\n </label>\n {error && (\n <p className=\"rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]\">\n {error}\n </p>\n )}\n <div className=\"flex justify-end gap-2 border-t border-[var(--border-default)] pt-4\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md border border-[var(--border-default)] px-4 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={saving || form.originalAmount <= 0}\n className=\"rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:opacity-50\"\n >\n {saving ? 'Issuing...' : 'Issue gift card'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\ninterface BalanceModalProps {\n onClose: () => void;\n initialCode?: string;\n}\n\nfunction BalanceModal({ onClose, initialCode = '' }: BalanceModalProps) {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const [code, setCode] = useState(initialCode);\n const [checking, setChecking] = useState(false);\n const [result, setResult] = useState<{\n valid: boolean;\n balance?: number;\n error?: string;\n } | null>(null);\n\n const submit = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug || !code.trim()) return;\n setChecking(true);\n try {\n setResult(\n await giftCardsApi.checkBalance(tenant.slug, code.trim().toUpperCase()),\n );\n } catch (checkError) {\n setResult({ valid: false, error: errorMessage(checkError) });\n } finally {\n setChecking(false);\n }\n };\n\n return (\n <Modal title=\"Check gift card balance\" onClose={onClose}>\n <form onSubmit={submit} className=\"space-y-4 p-4\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-[var(--text-secondary)]\">\n Gift card code\n </span>\n <input\n autoFocus\n className=\"w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 font-mono text-sm uppercase text-[var(--text-primary)] focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20\"\n value={code}\n onChange={(event) => {\n setCode(event.target.value.toUpperCase());\n setResult(null);\n }}\n />\n </label>\n {result && (\n <div\n className={`rounded-md p-4 ${\n result.valid\n ? 'bg-[var(--success-bg)] text-[var(--success)]'\n : 'bg-[var(--error-bg)] text-[var(--error)]'\n }`}\n >\n {result.valid\n ? `Available balance: ${formatCurrency(result.balance ?? 0)}`\n : result.error || 'This gift card is not valid.'}\n </div>\n )}\n <button\n type=\"submit\"\n disabled={checking || !code.trim()}\n className=\"w-full rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:opacity-50\"\n >\n {checking ? 'Checking...' : 'Check balance'}\n </button>\n </form>\n </Modal>\n );\n}\n\ninterface GiftCardDetailModalProps {\n giftCard: GiftCard;\n onClose: () => void;\n onChanged: () => void;\n}\n\nfunction GiftCardDetailModal({\n giftCard,\n onClose,\n onChanged,\n}: GiftCardDetailModalProps) {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const { toast } = useToast();\n const [amount, setAmount] = useState('');\n const [referenceId, setReferenceId] = useState('');\n const [note, setNote] = useState('');\n const [saving, setSaving] = useState(false);\n const [sharing, setSharing] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const copyCode = async () => {\n await navigator.clipboard.writeText(giftCard.code);\n toast({ title: 'Gift card code copied', description: giftCard.code });\n };\n\n const artworkData = {\n design: giftCard.design ?? GIFT_CARD_PRESETS[0],\n merchantName: tenant?.businessName || 'Gift card',\n amount: formatCurrency(Number(giftCard.originalAmount), giftCard.currency),\n recipientName: giftCard.recipientName ?? undefined,\n senderName: giftCard.purchaserName ?? undefined,\n code: giftCard.code,\n };\n\n const sendByEmail = async () => {\n if (!tenant?.slug || !giftCard.recipientEmail) return;\n setSharing(true);\n setError(null);\n try {\n const result = await communicationsApi.sendCustomEmail(tenant.slug, {\n recipientEmail: giftCard.recipientEmail,\n recipientName: giftCard.recipientName ?? undefined,\n subject: `${giftCard.purchaserName || tenant.businessName} sent you a gift card`,\n htmlContent: createGiftCardEmailHtml(artworkData),\n metadata: { giftCardId: giftCard.id, kind: 'gift_card_delivery' },\n });\n if (!result.success) throw new Error(result.error || 'Email delivery failed');\n toast({ title: 'Gift card email sent', description: giftCard.recipientEmail });\n } catch (sendError) {\n setError(errorMessage(sendError));\n } finally {\n setSharing(false);\n }\n };\n\n const redeem = async (event: React.FormEvent) => {\n event.preventDefault();\n if (!tenant?.slug) return;\n setSaving(true);\n setError(null);\n try {\n const result = await giftCardsApi.redeem(tenant.slug, giftCard.code, {\n amount: Number(amount),\n ...(referenceId ? { referenceId } : {}),\n ...(note ? { note } : {}),\n });\n if (!result.success) throw new Error(result.error || 'Redemption failed');\n toast({ title: 'Gift card redeemed' });\n onChanged();\n onClose();\n } catch (redeemError) {\n setError(errorMessage(redeemError));\n } finally {\n setSaving(false);\n }\n };\n\n const remove = async () => {\n if (!tenant?.slug) return;\n setSaving(true);\n try {\n await giftCardsApi.delete(tenant.slug, giftCard.id);\n toast({ title: 'Gift card cancelled' });\n onChanged();\n onClose();\n } catch (deleteError) {\n setError(errorMessage(deleteError));\n setSaving(false);\n }\n };\n\n return (\n <Modal title=\"Gift card details\" onClose={onClose}>\n <div className=\"space-y-5 p-4\">\n <GiftCardArtwork {...artworkData} />\n <div className=\"grid grid-cols-2 gap-2\">\n <button\n type=\"button\"\n onClick={() => downloadGiftCardArtwork(artworkData)}\n className=\"flex items-center justify-center gap-2 rounded-md border border-[var(--border-default)] px-3 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]\"\n >\n <Download className=\"h-4 w-4\" />\n Download\n </button>\n <button\n type=\"button\"\n onClick={sendByEmail}\n disabled={sharing || !giftCard.recipientEmail}\n title={giftCard.recipientEmail ? undefined : 'Add a recipient email to send this card'}\n className=\"flex items-center justify-center gap-2 rounded-md bg-brand-500 px-3 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Mail className=\"h-4 w-4\" />\n {sharing ? 'Sending...' : 'Send by email'}\n </button>\n </div>\n <div className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-subtle)] p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <p className=\"text-xs font-medium uppercase text-[var(--text-muted)]\">\n Code\n </p>\n <p className=\"mt-1 font-mono text-xl font-semibold text-[var(--text-primary)]\">\n {giftCard.code}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={copyCode}\n className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] p-2 text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]\"\n title=\"Copy code\"\n >\n <Copy className=\"h-4 w-4\" />\n </button>\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Balance</p>\n <p className=\"font-semibold text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Issued value</p>\n <p className=\"font-semibold text-[var(--text-primary)]\">\n {formatCurrency(\n Number(giftCard.originalAmount),\n giftCard.currency,\n )}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Recipient</p>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {giftCard.recipientName || giftCard.recipientEmail || 'Walk-in'}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-[var(--text-muted)]\">Expires</p>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {dateLabel(giftCard.expiresAt)}\n </p>\n </div>\n </div>\n </div>\n\n {giftCard.status === 'active' && Number(giftCard.balance) > 0 && (\n <form onSubmit={redeem} className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n Redeem value\n </h3>\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <input\n type=\"number\"\n min=\"0.01\"\n max={Number(giftCard.balance)}\n step=\"0.01\"\n required\n placeholder=\"Amount\"\n value={amount}\n onChange={(event) => setAmount(event.target.value)}\n className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none\"\n />\n <input\n placeholder=\"Order or reference ID\"\n value={referenceId}\n onChange={(event) => setReferenceId(event.target.value)}\n className=\"rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none\"\n />\n </div>\n <input\n placeholder=\"Note (optional)\"\n value={note}\n onChange={(event) => setNote(event.target.value)}\n className=\"w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none\"\n />\n <button\n type=\"submit\"\n disabled={saving || !amount || Number(amount) <= 0}\n className=\"rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:opacity-50\"\n >\n {saving ? 'Redeeming...' : 'Redeem gift card'}\n </button>\n </form>\n )}\n\n <div>\n <div className=\"mb-2 flex items-center gap-2\">\n <History className=\"h-4 w-4 text-[var(--text-tertiary)]\" />\n <h3 className=\"text-sm font-semibold text-[var(--text-primary)]\">\n Redemption history\n </h3>\n </div>\n {giftCard.redemptions.length === 0 ? (\n <p className=\"text-sm text-[var(--text-muted)]\">No redemptions yet.</p>\n ) : (\n <div className=\"divide-y divide-[var(--border-default)] rounded-md border border-[var(--border-default)]\">\n {giftCard.redemptions.map((redemption) => (\n <div\n key={redemption.id}\n className=\"flex items-center justify-between gap-3 px-3 py-2.5\"\n >\n <div>\n <p className=\"text-sm text-[var(--text-primary)]\">\n {redemption.orderId || 'Manual redemption'}\n </p>\n <p className=\"text-xs text-[var(--text-muted)]\">\n {dateLabel(redemption.redeemedAt)}\n </p>\n </div>\n <span className=\"text-sm font-medium text-[var(--text-primary)]\">\n -{formatCurrency(Number(redemption.amount), giftCard.currency)}\n </span>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {error && (\n <p className=\"rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]\">\n {error}\n </p>\n )}\n <div className=\"flex justify-end border-t border-[var(--border-default)] pt-4\">\n <InlineDeleteButton\n onDelete={remove}\n disabled={saving}\n title=\"Cancel gift card\"\n confirmText=\"Cancel this gift card?\"\n className=\"text-[var(--error)]\"\n />\n </div>\n </div>\n </Modal>\n );\n}\n\nexport function GiftCardsFeature() {\n const { tenant } = useAuth();\n const formatCurrency = useFormatCurrency();\n const { toast } = useToast();\n const currency = normalizeCurrencyCode(\n (tenant?.settings as Record<string, unknown> | undefined)?.currency,\n );\n const [giftCards, setGiftCards] = useState<GiftCard[]>([]);\n const [stats, setStats] = useState<GiftCardStats | null>(null);\n const [loading, setLoading] = useState(true);\n const [search, setSearch] = useState('');\n const [status, setStatus] = useState<GiftCardStatus | ''>('');\n const [page, setPage] = useState(1);\n const [count, setCount] = useState(0);\n const [showIssue, setShowIssue] = useState(false);\n const [showBalance, setShowBalance] = useState(false);\n const [selected, setSelected] = useState<GiftCard | null>(null);\n\n // Only the newest read may land: a quiet re-read started before the filters\n // changed must not put the old filter's rows back on screen.\n const latestRead = useRef(0);\n const readGiftCards = useCallback(\n async (quiet: boolean) => {\n if (!tenant?.slug) return;\n // A page's own read moves the marker; a quiet re-read only checks it.\n const read = quiet ? latestRead.current : ++latestRead.current;\n if (!quiet) setLoading(true);\n try {\n const [list, giftCardStats] = await Promise.all([\n giftCardsApi.list(tenant.slug, {\n ...(status ? { status } : {}),\n ...(search.trim() ? { search: search.trim() } : {}),\n page,\n limit: PAGE_SIZE,\n }),\n giftCardsApi.stats(tenant.slug),\n ]);\n if (read !== latestRead.current) return;\n setGiftCards(list.rows);\n setCount(list.count);\n setStats(giftCardStats);\n } catch (loadError) {\n // A quiet re-read keeps what is on screen and says nothing; the poll\n // counts the failure and slows down.\n if (quiet) throw loadError;\n toast({\n title: 'Unable to load gift cards',\n description: errorMessage(loadError),\n });\n } finally {\n if (!quiet) setLoading(false);\n }\n },\n [page, search, status, tenant?.slug, toast],\n );\n const loadGiftCards = useCallback(() => readGiftCards(false), [readGiftCards]);\n\n useEffect(() => {\n loadGiftCards();\n }, [loadGiftCards]);\n\n useQuietPoll(\n useCallback(() => readGiftCards(true), [readGiftCards]),\n LIVE_REFRESH_MS,\n LIVE,\n );\n\n useRegisterRefresh(loadGiftCards);\n\n const copyCode = async (event: React.MouseEvent, code: string) => {\n event.stopPropagation();\n await navigator.clipboard.writeText(code);\n toast({ title: 'Gift card code copied', description: code });\n };\n\n const totalPages = Math.max(1, Math.ceil(count / PAGE_SIZE));\n const filters: Array<{ value: GiftCardStatus | ''; label: string }> = [\n { value: '', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'used', label: 'Used' },\n { value: 'expired', label: 'Expired' },\n { value: 'cancelled', label: 'Cancelled' },\n ];\n\n return (\n <div className=\"mx-auto max-w-7xl space-y-5 lg:space-y-6\">\n <MobilePageHeader\n title=\"Gift cards\"\n action={{ label: 'Issue gift card', onClick: () => setShowIssue(true) }}\n />\n <div className=\"hidden items-center justify-between gap-3 lg:flex\">\n <div>\n <h1 className=\"text-2xl font-semibold text-[var(--text-primary)]\">\n Gift cards\n </h1>\n <p className=\"mt-1 text-sm text-[var(--text-tertiary)]\">\n Credit a customer pays for now and spends later. Discounts and\n loyalty points cost the customer nothing.\n </p>\n </div>\n <div className=\"flex gap-2\">\n {!LIVE && (\n <RefreshButton onClick={loadGiftCards} refreshing={loading} />\n )}\n <button\n type=\"button\"\n onClick={() => setShowBalance(true)}\n className=\"flex items-center gap-2 rounded-md border border-[var(--border-default)] px-3 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]\"\n >\n <Search className=\"h-4 w-4\" />\n Check balance\n </button>\n <button\n type=\"button\"\n onClick={() => setShowIssue(true)}\n className=\"flex items-center gap-2 rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600\"\n >\n <Plus className=\"h-4 w-4\" />\n Issue gift card\n </button>\n </div>\n </div>\n\n <div className=\"hidden lg:block\">\n <MetricRow>\n <MetricTile\n label=\"Issued value\"\n value={formatCurrency(stats?.totalIssued ?? 0, currency)}\n />\n <MetricTile\n label=\"Outstanding\"\n value={formatCurrency(stats?.outstanding ?? 0, currency)}\n />\n <MetricTile\n label=\"Redeemed\"\n value={formatCurrency(stats?.redeemed ?? 0, currency)}\n />\n <MetricTile label=\"Active cards\" value={stats?.activeCards ?? 0} />\n </MetricRow>\n </div>\n\n <div className=\"grid grid-cols-2 gap-px overflow-hidden rounded-md bg-[var(--border-default)] lg:hidden\">\n <div className=\"bg-[var(--bg-subtle)] p-4\">\n <p className=\"text-xs text-[var(--text-muted)]\">Outstanding</p>\n <p className=\"mt-1 text-lg font-semibold text-[var(--text-primary)]\">\n {formatCurrency(stats?.outstanding ?? 0, currency)}\n </p>\n </div>\n <div className=\"bg-[var(--bg-subtle)] p-4\">\n <p className=\"text-xs text-[var(--text-muted)]\">Active cards</p>\n <p className=\"mt-1 text-lg font-semibold text-[var(--text-primary)]\">\n {stats?.activeCards ?? 0}\n </p>\n </div>\n </div>\n\n <div className=\"flex gap-2 overflow-x-auto lg:hidden\">\n <button\n type=\"button\"\n onClick={() => setShowIssue(true)}\n className=\"flex shrink-0 items-center gap-1.5 rounded-full bg-brand-500 px-3.5 py-2 text-sm font-medium text-[var(--text-inverse)]\"\n >\n <Plus className=\"h-4 w-4\" />\n Issue gift card\n </button>\n <button\n type=\"button\"\n onClick={() => setShowBalance(true)}\n className=\"flex shrink-0 items-center gap-1.5 rounded-full bg-[var(--bg-subtle)] px-3.5 py-2 text-sm font-medium text-[var(--text-secondary)]\"\n >\n <Search className=\"h-4 w-4\" />\n Check balance\n </button>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]\" />\n <input\n type=\"search\"\n placeholder=\"Search code, recipient, or email\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n className=\"w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] py-2 pl-10 pr-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20\"\n />\n </div>\n <div className=\"flex gap-2 overflow-x-auto\">\n {filters.map((filter) => (\n <button\n key={filter.value}\n type=\"button\"\n onClick={() => {\n setStatus(filter.value);\n setPage(1);\n }}\n className={`shrink-0 rounded-md px-3 py-2 text-sm font-medium ${\n status === filter.value\n ? 'bg-brand-500 text-[var(--text-inverse)]'\n : 'border border-[var(--border-default)] bg-[var(--bg-base)] text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]'\n }`}\n >\n {filter.label}\n </button>\n ))}\n </div>\n </div>\n\n {loading && giftCards.length === 0 && <InlineListSkeleton />}\n\n {!loading && giftCards.length === 0 && (\n <TableEmptyState\n icon={Gift}\n title={search || status ? 'No gift cards found' : 'Issue your first gift card'}\n description={\n search || status\n ? 'Try changing the search or status filter'\n : 'Create a stored-value card for a customer or walk-in purchase'\n }\n />\n )}\n\n {giftCards.length > 0 && (\n <>\n <div className=\"space-y-2 lg:hidden\">\n {giftCards.map((giftCard) => (\n <button\n key={giftCard.id}\n type=\"button\"\n onClick={() => setSelected(giftCard)}\n className=\"flex w-full items-center justify-between gap-3 rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] p-4 text-left\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-mono text-sm font-semibold text-[var(--text-primary)]\">\n {giftCard.code}\n </p>\n <p className=\"truncate text-sm text-[var(--text-tertiary)]\">\n {giftCard.recipientName || giftCard.recipientEmail || 'Walk-in'}\n </p>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-sm font-semibold text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </p>\n <span className={`text-xs capitalize ${statusClass(giftCard.status)}`}>\n {giftCard.status}\n </span>\n </div>\n </button>\n ))}\n </div>\n\n <div className=\"hidden overflow-x-auto rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] lg:block\">\n <table className=\"w-full min-w-[760px]\">\n <thead className=\"border-b border-[var(--border-default)] bg-[var(--bg-subtle)]\">\n <tr>\n {['Code', 'Recipient', 'Issued', 'Balance', 'Expires', 'Status'].map(\n (heading) => (\n <th\n key={heading}\n className=\"px-4 py-3 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]\"\n >\n {heading}\n </th>\n ),\n )}\n </tr>\n </thead>\n <tbody className=\"divide-y divide-[var(--border-default)]\">\n {giftCards.map((giftCard) => (\n <tr\n key={giftCard.id}\n onClick={() => setSelected(giftCard)}\n className=\"cursor-pointer hover:bg-[var(--interactive-hover)]\"\n >\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-mono text-sm font-medium text-[var(--text-primary)]\">\n {giftCard.code}\n </span>\n <button\n type=\"button\"\n onClick={(event) => copyCode(event, giftCard.code)}\n className=\"rounded p-1 text-[var(--text-muted)] hover:text-[var(--text-primary)]\"\n title=\"Copy code\"\n >\n <Copy className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n <p className=\"text-sm text-[var(--text-primary)]\">\n {giftCard.recipientName || 'Walk-in'}\n </p>\n <p className=\"text-xs text-[var(--text-muted)]\">\n {giftCard.recipientEmail}\n </p>\n </td>\n <td className=\"px-4 py-3 text-sm text-[var(--text-secondary)]\">\n {formatCurrency(\n Number(giftCard.originalAmount),\n giftCard.currency,\n )}\n </td>\n <td className=\"px-4 py-3 text-sm font-medium text-[var(--text-primary)]\">\n {formatCurrency(Number(giftCard.balance), giftCard.currency)}\n </td>\n <td className=\"px-4 py-3 text-sm text-[var(--text-secondary)]\">\n {dateLabel(giftCard.expiresAt)}\n </td>\n <td className=\"px-4 py-3\">\n <span\n className={`inline-flex rounded-full px-2.5 py-1 text-xs font-medium capitalize ${statusClass(giftCard.status)}`}\n >\n {giftCard.status}\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <TablePagination\n page={page}\n totalPages={totalPages}\n pageSize={PAGE_SIZE}\n totalItems={count}\n entityName=\"gift cards\"\n onPageChange={setPage}\n />\n </>\n )}\n\n {showIssue && (\n <IssueGiftCardModal\n currency={currency}\n merchantName={tenant?.businessName || 'Gift card'}\n onClose={() => setShowIssue(false)}\n onCreated={(giftCard) => {\n setShowIssue(false);\n setSelected(giftCard);\n toast({\n title: 'Gift card issued',\n description: giftCard.code,\n });\n loadGiftCards();\n }}\n />\n )}\n {showBalance && <BalanceModal onClose={() => setShowBalance(false)} />}\n {selected && (\n <GiftCardDetailModal\n giftCard={selected}\n onClose={() => setSelected(null)}\n onChanged={loadGiftCards}\n />\n )}\n </div>\n );\n}\n"],"mappings":";AA6FQ,SAkyBA,UAjyBE,KADF;AA3FR,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AACzD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OAKK;AACP,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,WAAW,kBAAkB;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,YAAY;AAOlB,MAAM,OAAO,QAAQ,IAAI,oCAAoC;AAC7D,MAAM,kBAAkB;AAExB,SAAS,wBAAgC;AACvC,QAAM,WAAW,oBAAI,KAAK;AAC1B,WAAS,QAAQ,SAAS,QAAQ,IAAI,CAAC;AACvC,SAAO,SAAS,YAAY,EAAE,MAAM,GAAG,EAAE;AAC3C;AAEA,SAAS,aAAa,OAAwB;AAC5C,SAAO,iBAAiB,QAAQ,MAAM,UAAU;AAClD;AAEA,SAAS,UAAU,OAA8B;AAC/C,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,IAAI,KAAK,KAAK,EAAE,mBAAmB,QAAW;AAAA,IACnD,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,EACR,CAAC;AACH;AAEA,SAAS,YAAY,QAAgC;AACnD,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAQA,SAAS,MAAM,EAAE,OAAO,SAAS,SAAS,GAAe;AACvD,SACE,oBAAC,SAAI,WAAU,uFACb,+BAAC,SAAI,WAAU,iIACb;AAAA,yBAAC,SAAI,WAAU,6HACb;AAAA,0BAAC,QAAG,WAAU,sDACX,iBACH;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,8BAAC,KAAE,WAAU,WAAU;AAAA;AAAA,MACzB;AAAA,OACF;AAAA,IACC;AAAA,KACH,GACF;AAEJ;AASA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,QAAM,CAAC,MAAM,OAAO,IAAI,SAA6B;AAAA,IACnD,gBAAgB;AAAA,IAChB;AAAA,IACA,QAAQ,kBAAkB,CAAC;AAAA,EAC7B,CAAC;AACD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,WAAW,CACf,KACA,UACG,QAAQ,CAAC,aAAa,EAAE,GAAG,SAAS,CAAC,GAAG,GAAG,MAAM,EAAE;AAExD,QAAM,SAAS,OAAO,UAA2B;AAC/C,UAAM,eAAe;AACrB,QAAI,CAAC,QAAQ,QAAQ,KAAK,kBAAkB,EAAG;AAC/C,cAAU,IAAI;AACd,aAAS,IAAI;AACb,QAAI;AACF,YAAM,UAAU,OAAO;AAAA,QACrB,OAAO,QAAQ,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,EAAE;AAAA,MACzD;AACA,YAAM,WAAW,MAAM,aAAa,OAAO,OAAO,MAAM,OAAO;AAC/D,gBAAU,QAAQ;AAAA,IACpB,SAAS,aAAa;AACpB,eAAS,aAAa,WAAW,CAAC;AAAA,IACpC,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,aACJ;AAEF,SACE,oBAAC,SAAM,OAAM,mBAAkB,SAC7B,+BAAC,UAAK,UAAU,QAAQ,WAAU,iBAChC;AAAA,yBAAC,SAAI,WAAU,4EACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,KAAK,UAAU,kBAAkB,CAAC;AAAA,UAC1C;AAAA,UACA,QACE,KAAK,iBAAiB,IAClB,IAAI,KAAK,aAAa,QAAW;AAAA,YAC/B,OAAO;AAAA,YACP,UAAU,KAAK,YAAY;AAAA,UAC7B,CAAC,EAAE,OAAO,KAAK,cAAc,IAC7B;AAAA,UAEN,eAAe,KAAK;AAAA,UACpB,YAAY,KAAK;AAAA;AAAA,MACnB;AAAA,MACA,qBAAC,cACC;AAAA,4BAAC,YAAO,WAAU,oDAAmD,oBAErE;AAAA,QACA,oBAAC,SAAI,WAAU,8CACZ,4BAAkB,IAAI,CAAC,WAAW;AACjC,gBAAM,WAAW,KAAK,QAAQ,aAAa,OAAO;AAClD,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,SAAS,MAAM,SAAS,UAAU,MAAM;AAAA,cACxC,WAAW,uFACT,WACI,gEACA,iGACN;AAAA,cACA,cAAY,OAAO;AAAA,cACnB,gBAAc;AAAA,cAEd;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBACV,OAAO,EAAE,iBAAiB,OAAO,QAAQ,WAAW;AAAA;AAAA,gBACtD;AAAA,gBACA,oBAAC,UAAK,WAAU,4BAA4B,iBAAO,MAAK;AAAA;AAAA;AAAA,YAfnD,OAAO;AAAA,UAgBd;AAAA,QAEJ,CAAC,GACH;AAAA,SACF;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,oBAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,MAAK;AAAA,YACL,KAAI;AAAA,YACJ,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,OAAO,KAAK,kBAAkB;AAAA,YAC9B,UAAU,CAAC,UACT,SAAS,kBAAkB,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA;AAAA,QAEzD;AAAA,SACF;AAAA,MACA,qBAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,sBAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,UAAQ;AAAA,YACR,UAAQ;AAAA,YACR,OAAO,KAAK;AAAA;AAAA,QACd;AAAA,SACF;AAAA,MACA,qBAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,4BAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,OAAO,KAAK,iBAAiB;AAAA,YAC7B,UAAU,CAAC,UAAU,SAAS,iBAAiB,MAAM,OAAO,KAAK;AAAA;AAAA,QACnE;AAAA,QACA,oBAAC,UAAK,WAAU,0CAAyC,kEAEzD;AAAA,SACF;AAAA,MACA,qBAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,6BAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,MAAK;AAAA,YACL,OAAO,KAAK,kBAAkB;AAAA,YAC9B,UAAU,CAAC,UACT,SAAS,kBAAkB,MAAM,OAAO,KAAK;AAAA;AAAA,QAEjD;AAAA,SACF;AAAA,MACA,qBAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,4BAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,OAAO,KAAK,iBAAiB;AAAA,YAC7B,UAAU,CAAC,UAAU,SAAS,iBAAiB,MAAM,OAAO,KAAK;AAAA;AAAA,QACnE;AAAA,QACA,oBAAC,UAAK,WAAU,0CAAyC,kDAEzD;AAAA,SACF;AAAA,MACA,qBAAC,WAAM,WAAU,eACf;AAAA,4BAAC,UAAK,WAAU,oDAAmD,6BAEnE;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,YACX,MAAK;AAAA,YACL,OAAO,KAAK,kBAAkB;AAAA,YAC9B,UAAU,CAAC,UACT,SAAS,kBAAkB,MAAM,OAAO,KAAK;AAAA;AAAA,QAEjD;AAAA,SACF;AAAA,OACF;AAAA,IACA,qBAAC,WAAM,WAAU,qBACf;AAAA,0BAAC,UAAK,WAAU,oDAAmD,8BAEnE;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,UAAU;AAAA,UACxB,WAAW;AAAA,UACX,OAAO,KAAK,mBAAmB;AAAA,UAC/B,UAAU,CAAC,UAAU,SAAS,mBAAmB,MAAM,OAAO,KAAK;AAAA;AAAA,MACrE;AAAA,OACF;AAAA,IACA,qBAAC,WAAM,WAAU,qBACf;AAAA,0BAAC,UAAK,WAAU,oDAAmD,6BAEnE;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,MAAK;AAAA,UACL,KAAK,sBAAsB;AAAA,UAC3B,OAAO,KAAK,aAAa;AAAA,UACzB,UAAU,CAAC,UAAU,SAAS,aAAa,MAAM,OAAO,KAAK;AAAA;AAAA,MAC/D;AAAA,OACF;AAAA,IACC,SACC,oBAAC,OAAE,WAAU,yEACV,iBACH;AAAA,IAEF,qBAAC,SAAI,WAAU,uEACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU,UAAU,KAAK,kBAAkB;AAAA,UAC3C,WAAU;AAAA,UAET,mBAAS,eAAe;AAAA;AAAA,MAC3B;AAAA,OACF;AAAA,KACF,GACF;AAEJ;AAOA,SAAS,aAAa,EAAE,SAAS,cAAc,GAAG,GAAsB;AACtE,QAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,WAAW;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAIlB,IAAI;AAEd,QAAM,SAAS,OAAO,UAA2B;AAC/C,UAAM,eAAe;AACrB,QAAI,CAAC,QAAQ,QAAQ,CAAC,KAAK,KAAK,EAAG;AACnC,gBAAY,IAAI;AAChB,QAAI;AACF;AAAA,QACE,MAAM,aAAa,aAAa,OAAO,MAAM,KAAK,KAAK,EAAE,YAAY,CAAC;AAAA,MACxE;AAAA,IACF,SAAS,YAAY;AACnB,gBAAU,EAAE,OAAO,OAAO,OAAO,aAAa,UAAU,EAAE,CAAC;AAAA,IAC7D,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,2BAA0B,SACrC,+BAAC,UAAK,UAAU,QAAQ,WAAU,iBAChC;AAAA,yBAAC,WAAM,WAAU,qBACf;AAAA,0BAAC,UAAK,WAAU,oDAAmD,4BAEnE;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAS;AAAA,UACT,WAAU;AAAA,UACV,OAAO;AAAA,UACP,UAAU,CAAC,UAAU;AACnB,oBAAQ,MAAM,OAAO,MAAM,YAAY,CAAC;AACxC,sBAAU,IAAI;AAAA,UAChB;AAAA;AAAA,MACF;AAAA,OACF;AAAA,IACC,UACC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,kBACT,OAAO,QACH,iDACA,0CACN;AAAA,QAEC,iBAAO,QACJ,sBAAsB,eAAe,OAAO,WAAW,CAAC,CAAC,KACzD,OAAO,SAAS;AAAA;AAAA,IACtB;AAAA,IAEF;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU,YAAY,CAAC,KAAK,KAAK;AAAA,QACjC,WAAU;AAAA,QAET,qBAAW,gBAAgB;AAAA;AAAA,IAC9B;AAAA,KACF,GACF;AAEJ;AAQA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE;AACvC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,EAAE;AACjD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,WAAW,YAAY;AAC3B,UAAM,UAAU,UAAU,UAAU,SAAS,IAAI;AACjD,UAAM,EAAE,OAAO,yBAAyB,aAAa,SAAS,KAAK,CAAC;AAAA,EACtE;AAEA,QAAM,cAAc;AAAA,IAClB,QAAQ,SAAS,UAAU,kBAAkB,CAAC;AAAA,IAC9C,cAAc,QAAQ,gBAAgB;AAAA,IACtC,QAAQ,eAAe,OAAO,SAAS,cAAc,GAAG,SAAS,QAAQ;AAAA,IACzE,eAAe,SAAS,iBAAiB;AAAA,IACzC,YAAY,SAAS,iBAAiB;AAAA,IACtC,MAAM,SAAS;AAAA,EACjB;AAEA,QAAM,cAAc,YAAY;AAC9B,QAAI,CAAC,QAAQ,QAAQ,CAAC,SAAS,eAAgB;AAC/C,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,kBAAkB,gBAAgB,OAAO,MAAM;AAAA,QAClE,gBAAgB,SAAS;AAAA,QACzB,eAAe,SAAS,iBAAiB;AAAA,QACzC,SAAS,GAAG,SAAS,iBAAiB,OAAO,YAAY;AAAA,QACzD,aAAa,wBAAwB,WAAW;AAAA,QAChD,UAAU,EAAE,YAAY,SAAS,IAAI,MAAM,qBAAqB;AAAA,MAClE,CAAC;AACD,UAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,OAAO,SAAS,uBAAuB;AAC5E,YAAM,EAAE,OAAO,wBAAwB,aAAa,SAAS,eAAe,CAAC;AAAA,IAC/E,SAAS,WAAW;AAClB,eAAS,aAAa,SAAS,CAAC;AAAA,IAClC,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,SAAS,OAAO,UAA2B;AAC/C,UAAM,eAAe;AACrB,QAAI,CAAC,QAAQ,KAAM;AACnB,cAAU,IAAI;AACd,aAAS,IAAI;AACb,QAAI;AACF,YAAM,SAAS,MAAM,aAAa,OAAO,OAAO,MAAM,SAAS,MAAM;AAAA,QACnE,QAAQ,OAAO,MAAM;AAAA,QACrB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,QACrC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACzB,CAAC;AACD,UAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,OAAO,SAAS,mBAAmB;AACxE,YAAM,EAAE,OAAO,qBAAqB,CAAC;AACrC,gBAAU;AACV,cAAQ;AAAA,IACV,SAAS,aAAa;AACpB,eAAS,aAAa,WAAW,CAAC;AAAA,IACpC,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,SAAS,YAAY;AACzB,QAAI,CAAC,QAAQ,KAAM;AACnB,cAAU,IAAI;AACd,QAAI;AACF,YAAM,aAAa,OAAO,OAAO,MAAM,SAAS,EAAE;AAClD,YAAM,EAAE,OAAO,sBAAsB,CAAC;AACtC,gBAAU;AACV,cAAQ;AAAA,IACV,SAAS,aAAa;AACpB,eAAS,aAAa,WAAW,CAAC;AAClC,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,SACE,oBAAC,SAAM,OAAM,qBAAoB,SAC/B,+BAAC,SAAI,WAAU,iBACb;AAAA,wBAAC,mBAAiB,GAAG,aAAa;AAAA,IAClC,qBAAC,SAAI,WAAU,0BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,wBAAwB,WAAW;AAAA,UAClD,WAAU;AAAA,UAEV;AAAA,gCAAC,YAAS,WAAU,WAAU;AAAA,YAAE;AAAA;AAAA;AAAA,MAElC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,WAAW,CAAC,SAAS;AAAA,UAC/B,OAAO,SAAS,iBAAiB,SAAY;AAAA,UAC7C,WAAU;AAAA,UAEV;AAAA,gCAAC,QAAK,WAAU,WAAU;AAAA,YACzB,UAAU,eAAe;AAAA;AAAA;AAAA,MAC5B;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,8EACb;AAAA,2BAAC,SAAI,WAAU,0CACb;AAAA,6BAAC,SACC;AAAA,8BAAC,OAAE,WAAU,0DAAyD,kBAEtE;AAAA,UACA,oBAAC,OAAE,WAAU,mEACV,mBAAS,MACZ;AAAA,WACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,8BAAC,QAAK,WAAU,WAAU;AAAA;AAAA,QAC5B;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,+BACb;AAAA,6BAAC,SACC;AAAA,8BAAC,OAAE,WAAU,oCAAmC,qBAAO;AAAA,UACvD,oBAAC,OAAE,WAAU,4CACV,yBAAe,OAAO,SAAS,OAAO,GAAG,SAAS,QAAQ,GAC7D;AAAA,WACF;AAAA,QACA,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,oCAAmC,0BAAY;AAAA,UAC5D,oBAAC,OAAE,WAAU,4CACV;AAAA,YACC,OAAO,SAAS,cAAc;AAAA,YAC9B,SAAS;AAAA,UACX,GACF;AAAA,WACF;AAAA,QACA,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,oCAAmC,uBAAS;AAAA,UACzD,oBAAC,OAAE,WAAU,sCACV,mBAAS,iBAAiB,SAAS,kBAAkB,WACxD;AAAA,WACF;AAAA,QACA,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,oCAAmC,qBAAO;AAAA,UACvD,oBAAC,OAAE,WAAU,sCACV,oBAAU,SAAS,SAAS,GAC/B;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEC,SAAS,WAAW,YAAY,OAAO,SAAS,OAAO,IAAI,KAC1D,qBAAC,UAAK,UAAU,QAAQ,WAAU,aAChC;AAAA,0BAAC,QAAG,WAAU,oDAAmD,0BAEjE;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,KAAI;AAAA,YACJ,KAAK,OAAO,SAAS,OAAO;AAAA,YAC5B,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,UAAU,UAAU,MAAM,OAAO,KAAK;AAAA,YACjD,WAAU;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,UAAU,eAAe,MAAM,OAAO,KAAK;AAAA,YACtD,WAAU;AAAA;AAAA,QACZ;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU,CAAC,UAAU,QAAQ,MAAM,OAAO,KAAK;AAAA,UAC/C,WAAU;AAAA;AAAA,MACZ;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU,UAAU,CAAC,UAAU,OAAO,MAAM,KAAK;AAAA,UACjD,WAAU;AAAA,UAET,mBAAS,iBAAiB;AAAA;AAAA,MAC7B;AAAA,OACF;AAAA,IAGF,qBAAC,SACC;AAAA,2BAAC,SAAI,WAAU,gCACb;AAAA,4BAAC,WAAQ,WAAU,uCAAsC;AAAA,QACzD,oBAAC,QAAG,WAAU,oDAAmD,gCAEjE;AAAA,SACF;AAAA,MACC,SAAS,YAAY,WAAW,IAC/B,oBAAC,OAAE,WAAU,oCAAmC,iCAAmB,IAEnE,oBAAC,SAAI,WAAU,4FACZ,mBAAS,YAAY,IAAI,CAAC,eACzB;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,iCAAC,SACC;AAAA,kCAAC,OAAE,WAAU,sCACV,qBAAW,WAAW,qBACzB;AAAA,cACA,oBAAC,OAAE,WAAU,oCACV,oBAAU,WAAW,UAAU,GAClC;AAAA,eACF;AAAA,YACA,qBAAC,UAAK,WAAU,kDAAiD;AAAA;AAAA,cAC7D,eAAe,OAAO,WAAW,MAAM,GAAG,SAAS,QAAQ;AAAA,eAC/D;AAAA;AAAA;AAAA,QAbK,WAAW;AAAA,MAclB,CACD,GACH;AAAA,OAEJ;AAAA,IAEC,SACC,oBAAC,OAAE,WAAU,yEACV,iBACH;AAAA,IAEF,oBAAC,SAAI,WAAU,iEACb;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV,UAAU;AAAA,QACV,OAAM;AAAA,QACN,aAAY;AAAA,QACZ,WAAU;AAAA;AAAA,IACZ,GACF;AAAA,KACF,GACF;AAEJ;AAEO,SAAS,mBAAmB;AACjC,QAAM,EAAE,OAAO,IAAI,QAAQ;AAC3B,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,WAAW;AAAA,IACd,QAAQ,UAAkD;AAAA,EAC7D;AACA,QAAM,CAAC,WAAW,YAAY,IAAI,SAAqB,CAAC,CAAC;AACzD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA+B,IAAI;AAC7D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,EAAE;AACvC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA8B,EAAE;AAC5D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,CAAC;AAClC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,CAAC;AACpC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,IAAI;AAI9D,QAAM,aAAa,OAAO,CAAC;AAC3B,QAAM,gBAAgB;AAAA,IACpB,OAAO,UAAmB;AACxB,UAAI,CAAC,QAAQ,KAAM;AAEnB,YAAM,OAAO,QAAQ,WAAW,UAAU,EAAE,WAAW;AACvD,UAAI,CAAC,MAAO,YAAW,IAAI;AAC3B,UAAI;AACF,cAAM,CAAC,MAAM,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC9C,aAAa,KAAK,OAAO,MAAM;AAAA,YAC7B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,YAC3B,GAAI,OAAO,KAAK,IAAI,EAAE,QAAQ,OAAO,KAAK,EAAE,IAAI,CAAC;AAAA,YACjD;AAAA,YACA,OAAO;AAAA,UACT,CAAC;AAAA,UACD,aAAa,MAAM,OAAO,IAAI;AAAA,QAChC,CAAC;AACD,YAAI,SAAS,WAAW,QAAS;AACjC,qBAAa,KAAK,IAAI;AACtB,iBAAS,KAAK,KAAK;AACnB,iBAAS,aAAa;AAAA,MACxB,SAAS,WAAW;AAGlB,YAAI,MAAO,OAAM;AACjB,cAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa,aAAa,SAAS;AAAA,QACrC,CAAC;AAAA,MACH,UAAE;AACA,YAAI,CAAC,MAAO,YAAW,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,CAAC,MAAM,QAAQ,QAAQ,QAAQ,MAAM,KAAK;AAAA,EAC5C;AACA,QAAM,gBAAgB,YAAY,MAAM,cAAc,KAAK,GAAG,CAAC,aAAa,CAAC;AAE7E,YAAU,MAAM;AACd,kBAAc;AAAA,EAChB,GAAG,CAAC,aAAa,CAAC;AAElB;AAAA,IACE,YAAY,MAAM,cAAc,IAAI,GAAG,CAAC,aAAa,CAAC;AAAA,IACtD;AAAA,IACA;AAAA,EACF;AAEA,qBAAmB,aAAa;AAEhC,QAAM,WAAW,OAAO,OAAyB,SAAiB;AAChE,UAAM,gBAAgB;AACtB,UAAM,UAAU,UAAU,UAAU,IAAI;AACxC,UAAM,EAAE,OAAO,yBAAyB,aAAa,KAAK,CAAC;AAAA,EAC7D;AAEA,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,SAAS,CAAC;AAC3D,QAAM,UAAgE;AAAA,IACpE,EAAE,OAAO,IAAI,OAAO,MAAM;AAAA,IAC1B,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,IACnC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,IAC/B,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,IACrC,EAAE,OAAO,aAAa,OAAO,YAAY;AAAA,EAC3C;AAEA,SACE,qBAAC,SAAI,WAAU,4CACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,EAAE,OAAO,mBAAmB,SAAS,MAAM,aAAa,IAAI,EAAE;AAAA;AAAA,IACxE;AAAA,IACA,qBAAC,SAAI,WAAU,qDACb;AAAA,2BAAC,SACC;AAAA,4BAAC,QAAG,WAAU,qDAAoD,wBAElE;AAAA,QACA,oBAAC,OAAE,WAAU,4CAA2C,sHAGxD;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,cACZ;AAAA,SAAC,QACA,oBAAC,iBAAc,SAAS,eAAe,YAAY,SAAS;AAAA,QAE9D;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,eAAe,IAAI;AAAA,YAClC,WAAU;AAAA,YAEV;AAAA,kCAAC,UAAO,WAAU,WAAU;AAAA,cAAE;AAAA;AAAA;AAAA,QAEhC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,aAAa,IAAI;AAAA,YAChC,WAAU;AAAA,YAEV;AAAA,kCAAC,QAAK,WAAU,WAAU;AAAA,cAAE;AAAA;AAAA;AAAA,QAE9B;AAAA,SACF;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,mBACb,+BAAC,aACC;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,eAAe,OAAO,eAAe,GAAG,QAAQ;AAAA;AAAA,MACzD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,eAAe,OAAO,eAAe,GAAG,QAAQ;AAAA;AAAA,MACzD;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,eAAe,OAAO,YAAY,GAAG,QAAQ;AAAA;AAAA,MACtD;AAAA,MACA,oBAAC,cAAW,OAAM,gBAAe,OAAO,OAAO,eAAe,GAAG;AAAA,OACnE,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,2FACb;AAAA,2BAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,OAAE,WAAU,oCAAmC,yBAAW;AAAA,QAC3D,oBAAC,OAAE,WAAU,yDACV,yBAAe,OAAO,eAAe,GAAG,QAAQ,GACnD;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,OAAE,WAAU,oCAAmC,0BAAY;AAAA,QAC5D,oBAAC,OAAE,WAAU,yDACV,iBAAO,eAAe,GACzB;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,wCACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,aAAa,IAAI;AAAA,UAChC,WAAU;AAAA,UAEV;AAAA,gCAAC,QAAK,WAAU,WAAU;AAAA,YAAE;AAAA;AAAA;AAAA,MAE9B;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,eAAe,IAAI;AAAA,UAClC,WAAU;AAAA,UAEV;AAAA,gCAAC,UAAO,WAAU,WAAU;AAAA,YAAE;AAAA;AAAA;AAAA,MAEhC;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,mDACb;AAAA,2BAAC,SAAI,WAAU,mBACb;AAAA,4BAAC,UAAO,WAAU,6EAA4E;AAAA,QAC9F;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,UAAU;AACnB,wBAAU,MAAM,OAAO,KAAK;AAC5B,sBAAQ,CAAC;AAAA,YACX;AAAA,YACA,WAAU;AAAA;AAAA,QACZ;AAAA,SACF;AAAA,MACA,oBAAC,SAAI,WAAU,8BACZ,kBAAQ,IAAI,CAAC,WACZ;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,sBAAU,OAAO,KAAK;AACtB,oBAAQ,CAAC;AAAA,UACX;AAAA,UACA,WAAW,qDACT,WAAW,OAAO,QACd,4CACA,4HACN;AAAA,UAEC,iBAAO;AAAA;AAAA,QAZH,OAAO;AAAA,MAad,CACD,GACH;AAAA,OACF;AAAA,IAEC,WAAW,UAAU,WAAW,KAAK,oBAAC,sBAAmB;AAAA,IAEzD,CAAC,WAAW,UAAU,WAAW,KAChC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAO,UAAU,SAAS,wBAAwB;AAAA,QAClD,aACE,UAAU,SACN,6CACA;AAAA;AAAA,IAER;AAAA,IAGD,UAAU,SAAS,KAClB,iCACE;AAAA,0BAAC,SAAI,WAAU,uBACZ,oBAAU,IAAI,CAAC,aACd;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,YAAY,QAAQ;AAAA,UACnC,WAAU;AAAA,UAEV;AAAA,iCAAC,SAAI,WAAU,WACb;AAAA,kCAAC,OAAE,WAAU,uEACV,mBAAS,MACZ;AAAA,cACA,oBAAC,OAAE,WAAU,gDACV,mBAAS,iBAAiB,SAAS,kBAAkB,WACxD;AAAA,eACF;AAAA,YACA,qBAAC,SAAI,WAAU,uBACb;AAAA,kCAAC,OAAE,WAAU,oDACV,yBAAe,OAAO,SAAS,OAAO,GAAG,SAAS,QAAQ,GAC7D;AAAA,cACA,oBAAC,UAAK,WAAW,sBAAsB,YAAY,SAAS,MAAM,CAAC,IAChE,mBAAS,QACZ;AAAA,eACF;AAAA;AAAA;AAAA,QApBK,SAAS;AAAA,MAqBhB,CACD,GACH;AAAA,MAEA,oBAAC,SAAI,WAAU,wGACb,+BAAC,WAAM,WAAU,wBACf;AAAA,4BAAC,WAAM,WAAU,iEACf,8BAAC,QACE,WAAC,QAAQ,aAAa,UAAU,WAAW,WAAW,QAAQ,EAAE;AAAA,UAC/D,CAAC,YACC;AAAA,YAAC;AAAA;AAAA,cAEC,WAAU;AAAA,cAET;AAAA;AAAA,YAHI;AAAA,UAIP;AAAA,QAEJ,GACF,GACF;AAAA,QACA,oBAAC,WAAM,WAAU,2CACd,oBAAU,IAAI,CAAC,aACd;AAAA,UAAC;AAAA;AAAA,YAEC,SAAS,MAAM,YAAY,QAAQ;AAAA,YACnC,WAAU;AAAA,YAEV;AAAA,kCAAC,QAAG,WAAU,aACZ,+BAAC,SAAI,WAAU,2BACb;AAAA,oCAAC,UAAK,WAAU,4DACb,mBAAS,MACZ;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS,CAAC,UAAU,SAAS,OAAO,SAAS,IAAI;AAAA,oBACjD,WAAU;AAAA,oBACV,OAAM;AAAA,oBAEN,8BAAC,QAAK,WAAU,eAAc;AAAA;AAAA,gBAChC;AAAA,iBACF,GACF;AAAA,cACA,qBAAC,QAAG,WAAU,aACZ;AAAA,oCAAC,OAAE,WAAU,sCACV,mBAAS,iBAAiB,WAC7B;AAAA,gBACA,oBAAC,OAAE,WAAU,oCACV,mBAAS,gBACZ;AAAA,iBACF;AAAA,cACA,oBAAC,QAAG,WAAU,kDACX;AAAA,gBACC,OAAO,SAAS,cAAc;AAAA,gBAC9B,SAAS;AAAA,cACX,GACF;AAAA,cACA,oBAAC,QAAG,WAAU,4DACX,yBAAe,OAAO,SAAS,OAAO,GAAG,SAAS,QAAQ,GAC7D;AAAA,cACA,oBAAC,QAAG,WAAU,kDACX,oBAAU,SAAS,SAAS,GAC/B;AAAA,cACA,oBAAC,QAAG,WAAU,aACZ;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,uEAAuE,YAAY,SAAS,MAAM,CAAC;AAAA,kBAE7G,mBAAS;AAAA;AAAA,cACZ,GACF;AAAA;AAAA;AAAA,UA7CK,SAAS;AAAA,QA8ChB,CACD,GACH;AAAA,SACF,GACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,YAAW;AAAA,UACX,cAAc;AAAA;AAAA,MAChB;AAAA,OACF;AAAA,IAGD,aACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,cAAc,QAAQ,gBAAgB;AAAA,QACtC,SAAS,MAAM,aAAa,KAAK;AAAA,QACjC,WAAW,CAAC,aAAa;AACvB,uBAAa,KAAK;AAClB,sBAAY,QAAQ;AACpB,gBAAM;AAAA,YACJ,OAAO;AAAA,YACP,aAAa,SAAS;AAAA,UACxB,CAAC;AACD,wBAAc;AAAA,QAChB;AAAA;AAAA,IACF;AAAA,IAED,eAAe,oBAAC,gBAAa,SAAS,MAAM,eAAe,KAAK,GAAG;AAAA,IACnE,YACC;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV,SAAS,MAAM,YAAY,IAAI;AAAA,QAC/B,WAAW;AAAA;AAAA,IACb;AAAA,KAEJ;AAEJ;","names":[]}
@@ -154,7 +154,7 @@ function ProductForm({ product, onSaved, onClose }) {
154
154
  const isEditing = !!product;
155
155
  const tenantCurrency = useMemo(() => {
156
156
  const settings = tenant?.settings;
157
- return settings?.currency || "NGN";
157
+ return settings?.currency || "USD";
158
158
  }, [tenant?.settings]);
159
159
  const tenantCategories = useMemo(() => {
160
160
  const saved = tenant?.settings?.categories || {};
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/features/product-form.tsx"],"sourcesContent":["// @ts-nocheck — Legacy product form, no longer imported. Kept for reference only.\n'use client';\n\n/**\n * @deprecated Use ContentTypeStepperPanel + useProductFormState + useApiDispatch instead.\n *\n * This component is retained only for the product detail edit pages\n * (inventory/[productId]/page.tsx). New creation flows should use\n * ContentTypeStepperPanel with productMode props.\n */\n\nimport { useState, useEffect, useMemo, useCallback } from 'react';\nimport {\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from '@dnd-kit/core';\nimport { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { useCurrentTemplate } from '@withpotter/lib/template-context';\nimport { getCurrencySymbol } from '@withpotter/components/dynamic-form';\nimport { contentApi } from '@withpotter/lib/api/content';\nimport { inventoryApi } from '@withpotter/lib/api/inventory';\nimport { merchantSubscriptionsApi } from '@withpotter/lib/api/merchant-subscriptions';\nimport { resourceCategoriesApi } from '@withpotter/lib/api/resource-categories';\nimport type { MerchantPlan } from '@withpotter/lib/api/types';\nimport { ContentItem } from '@withpotter/lib/api/types';\nimport {\n ProductType,\n generateSlug,\n type CreateProductPayload,\n type Product,\n type ProductMedia,\n type ProductBehaviors,\n type BookableConfig,\n type ShippableConfig,\n type SubscribableConfig,\n type DownloadableConfig,\n type RentableConfig,\n type TicketedConfig,\n isBehaviorEnabled,\n} from '@withpotter/lib/product-types';\nimport type { ContentResourceRequirementItem } from '@withpotter/lib/resource-types';\nimport {\n FormStepperPanel,\n type FormStep,\n} from '@withpotter/ui-kit/form-stepper-panel';\nimport {\n type BehaviorKey,\n type BehaviorLabelOverrides,\n} from '@withpotter/components/behaviors/behavior-config-section';\nimport { createBasicsStep } from './product-form/basics-step';\nimport {\n createBehaviorConfigPanels,\n createBehaviorsStep,\n} from './product-form/behaviors-step';\nimport { createDetailsStep } from './product-form/details-step';\nimport { createOrganizeStep } from './product-form/organize-step';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ProductFormProps {\n /** Existing product to edit, or null for create mode */\n product?: Product | null;\n /** Called after successful save with the saved product */\n onSaved: () => void;\n /** Called when the form is closed/cancelled */\n onClose: () => void;\n}\n\n// Platform field names that are extracted from formValues and sent as top-level payload fields\n// Fields that are rendered manually in the platform section below.\n// All other template fields pass through to DynamicForm (e.g. category,\n// tags, gallery, shortDescription, allergens, dietary, etc.).\nconst PLATFORM_FIELDS = new Set([\n 'title',\n 'slug',\n 'description',\n 'featured',\n 'isActive',\n 'image',\n 'gallery',\n 'price',\n 'compareAtPrice',\n 'currency',\n 'trackInventory',\n 'quantity',\n 'lowStockThreshold',\n 'type',\n]);\n\nfunction normalizeMediaItem(input: unknown): ProductMedia | null {\n if (!input) return null;\n if (typeof input === 'string') {\n return { url: input, type: 'image' };\n }\n\n if (typeof input !== 'object') return null;\n const item = input as Record<string, unknown>;\n if (typeof item.url !== 'string' || item.url.trim().length === 0) return null;\n\n return {\n url: item.url,\n type: item.type === 'video' ? 'video' : 'image',\n alt: typeof item.alt === 'string' ? item.alt : undefined,\n width: typeof item.width === 'number' ? item.width : undefined,\n height: typeof item.height === 'number' ? item.height : undefined,\n thumbnail: typeof item.thumbnail === 'string' ? item.thumbnail : undefined,\n duration: typeof item.duration === 'number' ? item.duration : undefined,\n };\n}\n\nfunction normalizeMediaList(input: unknown): ProductMedia[] {\n if (!Array.isArray(input)) return [];\n return input\n .map((item) => normalizeMediaItem(item))\n .filter((item): item is ProductMedia => item !== null);\n}\n\ninterface ProductGalleryItem extends ProductMedia {\n id: string;\n}\n\nfunction createGalleryItemId(url: string): string {\n return `${url}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction toGalleryItems(media: ProductMedia[]): ProductGalleryItem[] {\n return media.map((item) => ({\n ...item,\n id: createGalleryItemId(item.url),\n }));\n}\n\nfunction stripGalleryItemIds(items: ProductGalleryItem[]): ProductMedia[] {\n return items.map(({ id, ...rest }) => rest);\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function ProductForm({ product, onSaved, onClose }: ProductFormProps) {\n const { tenant } = useAuth();\n const { template } = useCurrentTemplate(tenant?.templateId);\n const sellableContentType = null as any;\n\n // Form state\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n\n // Platform field values\n const [productType, setProductType] = useState<ProductType>(\n (product?.type as ProductType) || ProductType.PHYSICAL,\n );\n const [title, setTitle] = useState(product?.title || '');\n const [slug, setSlug] = useState(product?.slug || '');\n const [description, setDescription] = useState(product?.description || '');\n const [price, setPrice] = useState<number | undefined>(\n product?.price != null ? Number(product.price) : undefined,\n );\n const [compareAtPrice, setCompareAtPrice] = useState<number | undefined>(\n product?.compareAtPrice != null\n ? Number(product.compareAtPrice)\n : undefined,\n );\n const [trackInventory, setTrackInventory] = useState(\n product?.trackInventory ?? true,\n );\n const [quantity, setQuantity] = useState<number | ''>(product?.quantity ?? 0);\n const [lowStockThreshold, setLowStockThreshold] = useState<number | ''>(\n product?.lowStockThreshold ?? 5,\n );\n const [featured, setFeatured] = useState(product?.featured ?? false);\n const [isActive, setIsActive] = useState(product?.isActive ?? true);\n const [heroImage, setHeroImage] = useState<ProductMedia | null>(\n normalizeMediaItem(product?.image) || null,\n );\n const [galleryItems, setGalleryItems] = useState<ProductGalleryItem[]>(() =>\n toGalleryItems(normalizeMediaList(product?.gallery)),\n );\n const [showHeroImageModal, setShowHeroImageModal] = useState(false);\n const [showGalleryMediaModal, setShowGalleryMediaModal] = useState(false);\n const [editingGalleryIndex, setEditingGalleryIndex] = useState<number | null>(\n null,\n );\n\n const gallerySensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: 4 },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n );\n\n const handleGalleryDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const oldIndex = galleryItems.findIndex(\n (item) => item.id === String(active.id),\n );\n const newIndex = galleryItems.findIndex(\n (item) => item.id === String(over.id),\n );\n if (oldIndex < 0 || newIndex < 0) return;\n setGalleryItems((prev) => arrayMove(prev, oldIndex, newIndex));\n },\n [galleryItems],\n );\n\n // Collection assignment state\n const [allCollections, setAllCollections] = useState<ContentItem[]>([]);\n const [selectedCollectionIds, setSelectedCollectionIds] = useState<\n Set<string>\n >(new Set());\n const [collectionsLoading, setCollectionsLoading] = useState(false);\n\n // Resource categories & requirements state (for bookable behavior)\n const [resourceCategories, setResourceCategories] = useState<\n Array<{ id: string; label: string; labelPlural: string; kind: string }>\n >([]);\n const [resourceRequirements, setResourceRequirements] = useState<\n ContentResourceRequirementItem[]\n >([]);\n const [activePlans, setActivePlans] = useState<MerchantPlan[]>([]);\n\n // Template field values (stored in `data`)\n const [templateValues, setTemplateValues] = useState<Record<string, unknown>>(\n product?.data || {},\n );\n\n const isEditing = !!product;\n\n // Get tenant currency\n const tenantCurrency = useMemo(() => {\n const settings = tenant?.settings as Record<string, unknown> | undefined;\n return (settings?.currency as string) || 'NGN';\n }, [tenant?.settings]);\n\n // Get tenant categories from settings, falling back to template defaults\n const tenantCategories = useMemo(() => {\n const saved =\n ((tenant?.settings as Record<string, unknown>)?.categories as Record<\n string,\n { value: string; label: string; sortOrder?: number }[]\n >) || {};\n\n // If tenant already has saved categories, use them\n if (Object.keys(saved).length > 0) return saved;\n\n // Fall back to defaults from template's categoryManager siteSettings\n if (!template?.siteSettings) return saved;\n const defaults: Record<\n string,\n { value: string; label: string; sortOrder?: number }[]\n > = {};\n for (const [, section] of Object.entries(template.siteSettings)) {\n const s = section as unknown as {\n type?: string;\n contentTypeKey?: string;\n defaults?: { value: string; label: string; sortOrder?: number }[];\n };\n if (s?.type === 'categoryManager' && s.contentTypeKey && s.defaults) {\n defaults[s.contentTypeKey] = [...s.defaults];\n }\n }\n return Object.keys(defaults).length > 0 ? defaults : saved;\n }, [tenant?.settings, template?.siteSettings]);\n\n // Currency symbol helper (moved up — needed by behavior config panels)\n const currencySymbol = useMemo(\n () => getCurrencySymbol(tenantCurrency),\n [tenantCurrency],\n );\n\n // Industry-defined fields from the content type\n const mergedFields = useMemo(() => {\n if (!sellableContentType) return [];\n return sellableContentType.fields;\n }, [sellableContentType]);\n\n // Filter out platform fields that we render manually — only show template-specific fields\n const templateOnlyFields = useMemo(() => {\n return mergedFields.filter((f) => !PLATFORM_FIELDS.has(f.name));\n }, [mergedFields]);\n\n // ── Suggested detail sections (opt-in template fields) ──\n // Build a list of toggleable sections from template field groups + ungrouped fields\n const detailSections = useMemo(() => {\n const groups = sellableContentType?.fieldGroups || [];\n const groupedFieldNames = new Set(groups.flatMap((g) => g.fields));\n const ungrouped = templateOnlyFields.filter(\n (f) => !groupedFieldNames.has(f.name),\n );\n\n const sections: Array<{\n key: string;\n label: string;\n description?: string;\n fieldNames: string[];\n }> = [];\n\n // Each field group becomes a section\n for (const g of groups) {\n const hasFields = g.fields.some((fn) =>\n templateOnlyFields.some((f) => f.name === fn),\n );\n if (hasFields) {\n sections.push({\n key: `group:${g.label}`,\n label: g.label,\n description: g.description,\n fieldNames: g.fields,\n });\n }\n }\n\n // Each ungrouped field becomes its own section\n for (const f of ungrouped) {\n sections.push({\n key: `field:${f.name}`,\n label: f.label || f.name,\n fieldNames: [f.name],\n });\n }\n\n return sections;\n }, [templateOnlyFields, sellableContentType?.fieldGroups]);\n\n // Track which sections the user has opted into\n const [enabledDetailSections, setEnabledDetailSections] = useState<\n Set<string>\n >(new Set());\n\n // On edit, auto-enable sections that already have data\n useEffect(() => {\n if (!isEditing || !product?.data || detailSections.length === 0) return;\n const data = product.data as Record<string, unknown>;\n const enabled = new Set<string>();\n for (const section of detailSections) {\n const hasData = section.fieldNames.some((fn) => {\n const val = data[fn];\n if (val === undefined || val === null || val === '') return false;\n if (Array.isArray(val) && val.length === 0) return false;\n return true;\n });\n if (hasData) enabled.add(section.key);\n }\n if (enabled.size > 0) setEnabledDetailSections(enabled);\n }, [isEditing, product?.data, detailSections]);\n\n const toggleDetailSection = useCallback((key: string) => {\n setEnabledDetailSections((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }, []);\n\n // Filter template fields + groups to only enabled ones for DynamicForm\n const activeTemplateFields = useMemo(() => {\n const enabledFieldNames = new Set<string>();\n for (const section of detailSections) {\n if (enabledDetailSections.has(section.key)) {\n for (const fn of section.fieldNames) enabledFieldNames.add(fn);\n }\n }\n return templateOnlyFields.filter((f) => enabledFieldNames.has(f.name));\n }, [templateOnlyFields, detailSections, enabledDetailSections]);\n\n const activeFieldGroups = useMemo(() => {\n if (!sellableContentType?.fieldGroups) return undefined;\n return sellableContentType.fieldGroups.filter((g) =>\n enabledDetailSections.has(`group:${g.label}`),\n );\n }, [sellableContentType?.fieldGroups, enabledDetailSections]);\n\n // Auto-generate slug from title (on create only)\n useEffect(() => {\n if (!isEditing && !slugManuallyEdited && title) {\n setSlug(generateSlug(title));\n }\n }, [title, isEditing, slugManuallyEdited]);\n\n // Pre-populate template values from product.data on edit\n useEffect(() => {\n setTemplateValues(product?.data || {});\n setHeroImage(normalizeMediaItem(product?.image) || null);\n setGalleryItems(toGalleryItems(normalizeMediaList(product?.gallery)));\n }, [product?.data, product?.image, product?.gallery]);\n\n // Fetch collections and pre-select those containing this product\n useEffect(() => {\n if (!tenant?.slug) return;\n let cancelled = false;\n const fetchCollections = async () => {\n setCollectionsLoading(true);\n try {\n const result = await contentApi.list(tenant.slug, 'collections', {\n limit: 200,\n });\n if (cancelled) return;\n const items = result.data || [];\n setAllCollections(items);\n\n // On edit, find which collections already contain this product\n if (product?.id) {\n const matched = new Set<string>();\n for (const c of items) {\n const ids = c.data?.productIds;\n if (Array.isArray(ids) && ids.includes(product.id)) {\n matched.add(c.id);\n }\n }\n setSelectedCollectionIds(matched);\n }\n } catch {\n // silently handle — collections picker just won't appear\n } finally {\n if (!cancelled) setCollectionsLoading(false);\n }\n };\n fetchCollections();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug, product?.id]);\n\n // Fetch resource categories (for bookable resource requirements)\n useEffect(() => {\n if (!tenant?.slug) return;\n let cancelled = false;\n const fetchCategories = async () => {\n try {\n const cats = await resourceCategoriesApi.list(tenant.slug);\n if (cancelled) return;\n setResourceCategories(\n (cats || []).map((c) => ({\n id: c.id,\n label: c.label || (c as any).name || '',\n labelPlural: c.labelPlural || '',\n kind: c.kind || 'person',\n })),\n );\n } catch {\n // silently handle — resource requirements section won't appear\n }\n };\n fetchCategories();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug]);\n\n // Fetch active MerchantPlans (for subscribable linked plan selector)\n useEffect(() => {\n if (!tenant?.slug) return;\n merchantSubscriptionsApi\n .getActivePlans(tenant.slug)\n .then((plans) => setActivePlans(plans || []))\n .catch(() => {\n // silently handle — plan selector will be hidden when plans is empty\n });\n }, [tenant?.slug]);\n\n // Fetch existing resource requirements when editing a product\n useEffect(() => {\n if (!tenant?.slug || !product?.id) return;\n let cancelled = false;\n const fetchRequirements = async () => {\n try {\n const reqs = await resourceCategoriesApi.getContentRequirements(\n tenant.slug,\n product.id,\n );\n if (cancelled) return;\n setResourceRequirements(\n (reqs || []).map((r) => ({\n categoryId: r.categoryId,\n isRequired: r.isRequired ?? true,\n assignmentStrategy: r.assignmentStrategy as\n ContentResourceRequirementItem['assignmentStrategy'] | undefined,\n requiredSkills: r.requiredSkills || [],\n eligibleResourceIds: r.eligibleResourceIds || null,\n })),\n );\n } catch {\n // silently handle — no existing requirements\n }\n };\n fetchRequirements();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug, product?.id]);\n\n // ── Behavior config state (Phase 4: config objects instead of booleans) ──\n const [behaviorConfigs, setBehaviorConfigs] = useState<ProductBehaviors>(\n () => {\n // On edit, load existing behaviors from the product\n if (product?.behaviors) {\n return product.behaviors as ProductBehaviors;\n }\n // On create, initialize from the template's content type behaviors\n if (sellableContentType?.behaviors) {\n const b: ProductBehaviors = {};\n for (const behavior of sellableContentType.behaviors) {\n (b as any)[behavior] = { enabled: true };\n }\n return b;\n }\n return { sellable: { enabled: true } };\n },\n );\n\n // Re-initialize behavior configs when the template changes (create mode)\n useEffect(() => {\n if (!isEditing && sellableContentType?.behaviors) {\n setBehaviorConfigs((prev) => {\n // Only set defaults for behaviors that aren't already configured\n const updated = { ...prev };\n for (const behavior of sellableContentType.behaviors) {\n if ((updated as any)[behavior] === undefined) {\n (updated as any)[behavior] = { enabled: true };\n }\n }\n return updated;\n });\n }\n }, [sellableContentType, isEditing]);\n\n // ── Content gating state ──\n const [gatedBy, setGatedBy] = useState<string[]>(() => {\n if (\n product?.behaviors?.gatedBy &&\n Array.isArray(product.behaviors.gatedBy)\n ) {\n return product.behaviors.gatedBy;\n }\n return [];\n });\n const [gatedByPlanIds, setGatedByPlanIds] = useState<string[]>(() => {\n if (\n product?.behaviors?.gatedByPlanIds &&\n Array.isArray(product.behaviors.gatedByPlanIds)\n ) {\n return product.behaviors.gatedByPlanIds;\n }\n return [];\n });\n\n // Derive which MerchantPlan is linked to the subscribable behavior\n const linkedPlanId = useMemo(() => {\n const config = getBehaviorConfig<SubscribableConfig>(\n behaviorConfigs.subscribable,\n );\n return config?.linkedPlanId;\n }, [behaviorConfigs.subscribable]);\n\n const linkedPlanName = useMemo(() => {\n if (!linkedPlanId || !activePlans) return undefined;\n return activePlans.find((p) => p.id === linkedPlanId)?.name;\n }, [linkedPlanId, activePlans]);\n\n // Derive which behaviors are available for the product form toggle list.\n // Priority: tenant's enabledBehaviors > template content type behaviors > fallback to all behaviors\n const availableBehaviors = useMemo<BehaviorKey[]>(() => {\n const tenantSettings = tenant?.settings as\n Record<string, unknown> | undefined;\n const tenantBehaviors = tenantSettings?.enabledBehaviors;\n\n // 1. Tenant has explicit preferences — use them\n if (Array.isArray(tenantBehaviors) && tenantBehaviors.length > 0) {\n return tenantBehaviors.filter(\n (b): b is BehaviorKey => b !== 'giftable' && b !== 'leadCapture',\n );\n }\n\n // 2. Template content type defines behaviors\n if (\n sellableContentType?.behaviors &&\n sellableContentType.behaviors.length > 0\n ) {\n const filtered = sellableContentType.behaviors.filter(\n (b): b is BehaviorKey => b !== 'giftable' && b !== 'leadCapture',\n );\n if (filtered.length > 0) return filtered;\n }\n\n // 3. Fallback — offer all behaviors by platform default\n return [\n 'sellable',\n 'bookable',\n 'shippable',\n 'subscribable',\n 'downloadable',\n 'rentable',\n 'ticketed',\n ] as BehaviorKey[];\n }, [tenant?.settings, sellableContentType]);\n\n // Template-provided behavior label overrides\n const behaviorLabelOverrides = useMemo<\n BehaviorLabelOverrides | undefined\n >(() => {\n // Templates can provide behaviorLabels in their schema\n const ct = sellableContentType as unknown as {\n behaviorLabels?: BehaviorLabelOverrides;\n };\n return ct?.behaviorLabels;\n }, [sellableContentType]);\n\n // Build flat behaviors for backward-compatible save (also used by behavior field merging)\n const behaviors = useMemo(() => {\n const b: Record<string, boolean> = {};\n for (const [key, value] of Object.entries(behaviorConfigs)) {\n if (key === 'gatedBy' || key === 'gatedByPlanIds' || key === 'reservable')\n continue;\n b[key] = isBehaviorEnabled(value);\n }\n return b;\n }, [behaviorConfigs]);\n\n // ── Behavior config update handlers ──\n const updateBookableConfig = useCallback((config: BookableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, bookable: config }));\n }, []);\n\n const updateShippableConfig = useCallback((config: ShippableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, shippable: config }));\n }, []);\n\n const updateSubscribableConfig = useCallback((config: SubscribableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, subscribable: config }));\n }, []);\n\n const updateDownloadableConfig = useCallback((config: DownloadableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, downloadable: config }));\n }, []);\n\n const updateRentableConfig = useCallback((config: RentableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, rentable: config }));\n }, []);\n\n const updateTicketedConfig = useCallback((config: TicketedConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, ticketed: config }));\n }, []);\n\n // Build config panel map for the BehaviorConfigSection\n const behaviorConfigPanels = useMemo(\n () =>\n createBehaviorConfigPanels({\n behaviorConfigs,\n updateBookableConfig,\n updateShippableConfig,\n updateSubscribableConfig,\n updateDownloadableConfig,\n updateRentableConfig,\n updateTicketedConfig,\n behaviorLabelOverrides,\n currencySymbol,\n resourceCategories,\n resourceRequirements,\n setResourceRequirements,\n activePlans,\n }),\n [\n behaviorConfigs,\n updateBookableConfig,\n updateShippableConfig,\n updateSubscribableConfig,\n updateDownloadableConfig,\n updateRentableConfig,\n updateTicketedConfig,\n behaviorLabelOverrides,\n currencySymbol,\n resourceCategories,\n resourceRequirements,\n ],\n );\n\n // Handle save\n const handleSave = useCallback(async () => {\n if (!tenant?.slug) return;\n\n // Basic validation\n if (!title.trim()) {\n setError('Title is required');\n return;\n }\n if (!slug.trim()) {\n setError('Slug is required');\n return;\n }\n\n setSaving(true);\n setError(null);\n\n try {\n const payload: CreateProductPayload = {\n type: productType,\n title: title.trim(),\n slug: slug.trim(),\n description: description || undefined,\n price: price !== undefined ? Number(price) || 0 : undefined,\n compareAtPrice:\n compareAtPrice !== undefined\n ? Number(compareAtPrice) || 0\n : undefined,\n currency: tenantCurrency,\n trackInventory,\n quantity: typeof quantity === 'number' ? quantity : 0,\n lowStockThreshold:\n typeof lowStockThreshold === 'number' ? lowStockThreshold : 5,\n featured,\n isActive,\n image: heroImage\n ? ({\n ...heroImage,\n type: 'image',\n } as ProductMedia)\n : undefined,\n // An empty list on an edit is sent as [], not left out: the API reads\n // an omitted gallery as \"unchanged\", so removing the last item would\n // save successfully and bring it straight back.\n gallery:\n galleryItems.length > 0\n ? stripGalleryItemIds(galleryItems)\n : isEditing\n ? []\n : undefined,\n behaviors: {\n ...behaviorConfigs,\n ...(gatedBy.length > 0 && { gatedBy }),\n ...(gatedByPlanIds.length > 0 && { gatedByPlanIds }),\n },\n data: templateValues,\n };\n\n let productId: string | undefined;\n\n if (isEditing && product) {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { type, ...updatePayload } = payload;\n await inventoryApi.update(\n tenant.slug,\n product.id,\n updatePayload as Record<string, unknown>,\n );\n productId = product.id;\n } else {\n const created = await inventoryApi.create(\n tenant.slug,\n payload as unknown as Record<string, unknown>,\n );\n productId = created.id;\n }\n\n // Sync collection assignments — add/remove this product from collections\n if (productId && allCollections.length > 0) {\n const updates: Promise<unknown>[] = [];\n for (const collection of allCollections) {\n const currentIds: string[] = Array.isArray(\n collection.data?.productIds,\n )\n ? (collection.data.productIds as string[])\n : [];\n const hasProduct = currentIds.includes(productId);\n const shouldHave = selectedCollectionIds.has(collection.id);\n\n if (shouldHave && !hasProduct) {\n // Add product to this collection\n updates.push(\n contentApi.update(tenant.slug, 'collections', collection.id, {\n data: {\n ...collection.data,\n productIds: [...currentIds, productId],\n },\n }),\n );\n } else if (!shouldHave && hasProduct) {\n // Remove product from this collection\n updates.push(\n contentApi.update(tenant.slug, 'collections', collection.id, {\n data: {\n ...collection.data,\n productIds: currentIds.filter((id) => id !== productId),\n },\n }),\n );\n }\n }\n if (updates.length > 0) {\n await Promise.all(updates);\n }\n }\n\n // Save resource requirements if bookable behavior is active\n if (\n productId &&\n isBehaviorEnabled(behaviorConfigs.bookable) &&\n resourceRequirements.length > 0\n ) {\n try {\n await resourceCategoriesApi.setContentRequirements(\n tenant.slug,\n productId,\n { requirements: resourceRequirements },\n );\n } catch {\n // Non-blocking — product was saved, requirements just didn't persist\n console.warn('Failed to save resource requirements');\n }\n }\n\n onSaved();\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : 'Failed to save product';\n setError(msg);\n } finally {\n setSaving(false);\n }\n }, [\n tenant?.slug,\n productType,\n title,\n slug,\n description,\n price,\n compareAtPrice,\n tenantCurrency,\n trackInventory,\n quantity,\n lowStockThreshold,\n featured,\n isActive,\n heroImage,\n galleryItems,\n behaviorConfigs,\n gatedBy,\n gatedByPlanIds,\n templateValues,\n isEditing,\n product,\n onSaved,\n allCollections,\n selectedCollectionIds,\n resourceRequirements,\n ]);\n\n // Currency formatting helpers\n const formatCurrency = (num: number | undefined): string => {\n if (num === undefined || num === null || isNaN(num)) return '';\n return new Intl.NumberFormat('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(num);\n };\n\n const parseCurrency = (str: string): number | undefined => {\n const cleaned = str.replace(/[^0-9.]/g, '');\n if (!cleaned) return undefined;\n const num = parseFloat(cleaned);\n return isNaN(num) ? undefined : num;\n };\n\n // Display state for currency fields\n const [priceDisplay, setPriceDisplay] = useState(() =>\n formatCurrency(product?.price != null ? Number(product.price) : undefined),\n );\n const [priceFocused, setPriceFocused] = useState(false);\n const [compareDisplay, setCompareDisplay] = useState(() =>\n formatCurrency(\n product?.compareAtPrice != null\n ? Number(product.compareAtPrice)\n : undefined,\n ),\n );\n const [compareFocused, setCompareFocused] = useState(false);\n\n // Build steps for the stepper\n const steps = useMemo<FormStep[]>(() => {\n const result: FormStep[] = [];\n\n // Step 1: Title, Image & Pricing\n result.push(\n createBasicsStep({\n title,\n setTitle,\n heroImage,\n setHeroImage,\n showHeroImageModal,\n setShowHeroImageModal,\n galleryItems,\n setGalleryItems,\n gallerySensors,\n handleGalleryDragEnd,\n showGalleryMediaModal,\n setShowGalleryMediaModal,\n editingGalleryIndex,\n setEditingGalleryIndex,\n description,\n setDescription,\n currencySymbol,\n priceFocused,\n setPriceFocused,\n priceDisplay,\n setPriceDisplay,\n price,\n setPrice,\n compareFocused,\n setCompareFocused,\n compareDisplay,\n setCompareDisplay,\n compareAtPrice,\n setCompareAtPrice,\n formatCurrency,\n parseCurrency,\n }),\n );\n\n // Step 2: Behaviors — What can customers do with this product?\n result.push(\n createBehaviorsStep({\n behaviorConfigs,\n setBehaviorConfigs,\n behaviorLabelOverrides,\n availableBehaviors,\n behaviorConfigPanels,\n gatedBy,\n setGatedBy,\n gatedByPlanIds,\n setGatedByPlanIds,\n activePlans,\n linkedPlanName,\n }),\n );\n\n // Step 3: Product Details & Inventory\n result.push(\n createDetailsStep({\n behaviorConfigs,\n detailSections,\n enabledDetailSections,\n toggleDetailSection,\n activeTemplateFields,\n activeFieldGroups,\n templateValues,\n setTemplateValues,\n tenantCurrency,\n tenantCategories,\n tenantSlug: tenant?.slug,\n trackInventory,\n setTrackInventory,\n quantity,\n setQuantity,\n lowStockThreshold,\n setLowStockThreshold,\n }),\n );\n\n // Step 4: Collections & Organization\n result.push(\n createOrganizeStep({\n collectionsLoading,\n allCollections,\n selectedCollectionIds,\n setSelectedCollectionIds,\n isActive,\n setIsActive,\n isEditing,\n productType,\n setProductType,\n slug,\n setSlug,\n setSlugManuallyEdited,\n product,\n featured,\n setFeatured,\n }),\n );\n\n return result;\n }, [\n title,\n heroImage,\n galleryItems,\n showHeroImageModal,\n showGalleryMediaModal,\n editingGalleryIndex,\n gallerySensors,\n handleGalleryDragEnd,\n description,\n currencySymbol,\n priceFocused,\n priceDisplay,\n price,\n compareFocused,\n compareDisplay,\n compareAtPrice,\n templateOnlyFields,\n sellableContentType,\n templateValues,\n tenantCurrency,\n tenantCategories,\n tenant?.slug,\n trackInventory,\n quantity,\n lowStockThreshold,\n collectionsLoading,\n allCollections,\n selectedCollectionIds,\n isEditing,\n productType,\n slug,\n product?.slug,\n isActive,\n featured,\n behaviorConfigs,\n behaviorLabelOverrides,\n availableBehaviors,\n behaviorConfigPanels,\n detailSections,\n enabledDetailSections,\n toggleDetailSection,\n activeTemplateFields,\n activeFieldGroups,\n gatedBy,\n gatedByPlanIds,\n linkedPlanId,\n linkedPlanName,\n ]);\n\n return (\n <FormStepperPanel\n title={isEditing ? 'Edit Product' : 'Add Product'}\n steps={steps}\n onSave={handleSave}\n onClose={onClose}\n saving={saving}\n saveLabel={isEditing ? 'Update Product' : 'Create Product'}\n error={error}\n onClearError={() => setError(null)}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/features/product-form.tsx"],"sourcesContent":["// @ts-nocheck — Legacy product form, no longer imported. Kept for reference only.\n'use client';\n\n/**\n * @deprecated Use ContentTypeStepperPanel + useProductFormState + useApiDispatch instead.\n *\n * This component is retained only for the product detail edit pages\n * (inventory/[productId]/page.tsx). New creation flows should use\n * ContentTypeStepperPanel with productMode props.\n */\n\nimport { useState, useEffect, useMemo, useCallback } from 'react';\nimport {\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from '@dnd-kit/core';\nimport { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { useCurrentTemplate } from '@withpotter/lib/template-context';\nimport { getCurrencySymbol } from '@withpotter/components/dynamic-form';\nimport { contentApi } from '@withpotter/lib/api/content';\nimport { inventoryApi } from '@withpotter/lib/api/inventory';\nimport { merchantSubscriptionsApi } from '@withpotter/lib/api/merchant-subscriptions';\nimport { resourceCategoriesApi } from '@withpotter/lib/api/resource-categories';\nimport type { MerchantPlan } from '@withpotter/lib/api/types';\nimport { ContentItem } from '@withpotter/lib/api/types';\nimport {\n ProductType,\n generateSlug,\n type CreateProductPayload,\n type Product,\n type ProductMedia,\n type ProductBehaviors,\n type BookableConfig,\n type ShippableConfig,\n type SubscribableConfig,\n type DownloadableConfig,\n type RentableConfig,\n type TicketedConfig,\n isBehaviorEnabled,\n} from '@withpotter/lib/product-types';\nimport type { ContentResourceRequirementItem } from '@withpotter/lib/resource-types';\nimport {\n FormStepperPanel,\n type FormStep,\n} from '@withpotter/ui-kit/form-stepper-panel';\nimport {\n type BehaviorKey,\n type BehaviorLabelOverrides,\n} from '@withpotter/components/behaviors/behavior-config-section';\nimport { createBasicsStep } from './product-form/basics-step';\nimport {\n createBehaviorConfigPanels,\n createBehaviorsStep,\n} from './product-form/behaviors-step';\nimport { createDetailsStep } from './product-form/details-step';\nimport { createOrganizeStep } from './product-form/organize-step';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ProductFormProps {\n /** Existing product to edit, or null for create mode */\n product?: Product | null;\n /** Called after successful save with the saved product */\n onSaved: () => void;\n /** Called when the form is closed/cancelled */\n onClose: () => void;\n}\n\n// Platform field names that are extracted from formValues and sent as top-level payload fields\n// Fields that are rendered manually in the platform section below.\n// All other template fields pass through to DynamicForm (e.g. category,\n// tags, gallery, shortDescription, allergens, dietary, etc.).\nconst PLATFORM_FIELDS = new Set([\n 'title',\n 'slug',\n 'description',\n 'featured',\n 'isActive',\n 'image',\n 'gallery',\n 'price',\n 'compareAtPrice',\n 'currency',\n 'trackInventory',\n 'quantity',\n 'lowStockThreshold',\n 'type',\n]);\n\nfunction normalizeMediaItem(input: unknown): ProductMedia | null {\n if (!input) return null;\n if (typeof input === 'string') {\n return { url: input, type: 'image' };\n }\n\n if (typeof input !== 'object') return null;\n const item = input as Record<string, unknown>;\n if (typeof item.url !== 'string' || item.url.trim().length === 0) return null;\n\n return {\n url: item.url,\n type: item.type === 'video' ? 'video' : 'image',\n alt: typeof item.alt === 'string' ? item.alt : undefined,\n width: typeof item.width === 'number' ? item.width : undefined,\n height: typeof item.height === 'number' ? item.height : undefined,\n thumbnail: typeof item.thumbnail === 'string' ? item.thumbnail : undefined,\n duration: typeof item.duration === 'number' ? item.duration : undefined,\n };\n}\n\nfunction normalizeMediaList(input: unknown): ProductMedia[] {\n if (!Array.isArray(input)) return [];\n return input\n .map((item) => normalizeMediaItem(item))\n .filter((item): item is ProductMedia => item !== null);\n}\n\ninterface ProductGalleryItem extends ProductMedia {\n id: string;\n}\n\nfunction createGalleryItemId(url: string): string {\n return `${url}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction toGalleryItems(media: ProductMedia[]): ProductGalleryItem[] {\n return media.map((item) => ({\n ...item,\n id: createGalleryItemId(item.url),\n }));\n}\n\nfunction stripGalleryItemIds(items: ProductGalleryItem[]): ProductMedia[] {\n return items.map(({ id, ...rest }) => rest);\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function ProductForm({ product, onSaved, onClose }: ProductFormProps) {\n const { tenant } = useAuth();\n const { template } = useCurrentTemplate(tenant?.templateId);\n const sellableContentType = null as any;\n\n // Form state\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n\n // Platform field values\n const [productType, setProductType] = useState<ProductType>(\n (product?.type as ProductType) || ProductType.PHYSICAL,\n );\n const [title, setTitle] = useState(product?.title || '');\n const [slug, setSlug] = useState(product?.slug || '');\n const [description, setDescription] = useState(product?.description || '');\n const [price, setPrice] = useState<number | undefined>(\n product?.price != null ? Number(product.price) : undefined,\n );\n const [compareAtPrice, setCompareAtPrice] = useState<number | undefined>(\n product?.compareAtPrice != null\n ? Number(product.compareAtPrice)\n : undefined,\n );\n const [trackInventory, setTrackInventory] = useState(\n product?.trackInventory ?? true,\n );\n const [quantity, setQuantity] = useState<number | ''>(product?.quantity ?? 0);\n const [lowStockThreshold, setLowStockThreshold] = useState<number | ''>(\n product?.lowStockThreshold ?? 5,\n );\n const [featured, setFeatured] = useState(product?.featured ?? false);\n const [isActive, setIsActive] = useState(product?.isActive ?? true);\n const [heroImage, setHeroImage] = useState<ProductMedia | null>(\n normalizeMediaItem(product?.image) || null,\n );\n const [galleryItems, setGalleryItems] = useState<ProductGalleryItem[]>(() =>\n toGalleryItems(normalizeMediaList(product?.gallery)),\n );\n const [showHeroImageModal, setShowHeroImageModal] = useState(false);\n const [showGalleryMediaModal, setShowGalleryMediaModal] = useState(false);\n const [editingGalleryIndex, setEditingGalleryIndex] = useState<number | null>(\n null,\n );\n\n const gallerySensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: 4 },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n );\n\n const handleGalleryDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const oldIndex = galleryItems.findIndex(\n (item) => item.id === String(active.id),\n );\n const newIndex = galleryItems.findIndex(\n (item) => item.id === String(over.id),\n );\n if (oldIndex < 0 || newIndex < 0) return;\n setGalleryItems((prev) => arrayMove(prev, oldIndex, newIndex));\n },\n [galleryItems],\n );\n\n // Collection assignment state\n const [allCollections, setAllCollections] = useState<ContentItem[]>([]);\n const [selectedCollectionIds, setSelectedCollectionIds] = useState<\n Set<string>\n >(new Set());\n const [collectionsLoading, setCollectionsLoading] = useState(false);\n\n // Resource categories & requirements state (for bookable behavior)\n const [resourceCategories, setResourceCategories] = useState<\n Array<{ id: string; label: string; labelPlural: string; kind: string }>\n >([]);\n const [resourceRequirements, setResourceRequirements] = useState<\n ContentResourceRequirementItem[]\n >([]);\n const [activePlans, setActivePlans] = useState<MerchantPlan[]>([]);\n\n // Template field values (stored in `data`)\n const [templateValues, setTemplateValues] = useState<Record<string, unknown>>(\n product?.data || {},\n );\n\n const isEditing = !!product;\n\n // Get tenant currency\n const tenantCurrency = useMemo(() => {\n const settings = tenant?.settings as Record<string, unknown> | undefined;\n return (settings?.currency as string) || 'USD';\n }, [tenant?.settings]);\n\n // Get tenant categories from settings, falling back to template defaults\n const tenantCategories = useMemo(() => {\n const saved =\n ((tenant?.settings as Record<string, unknown>)?.categories as Record<\n string,\n { value: string; label: string; sortOrder?: number }[]\n >) || {};\n\n // If tenant already has saved categories, use them\n if (Object.keys(saved).length > 0) return saved;\n\n // Fall back to defaults from template's categoryManager siteSettings\n if (!template?.siteSettings) return saved;\n const defaults: Record<\n string,\n { value: string; label: string; sortOrder?: number }[]\n > = {};\n for (const [, section] of Object.entries(template.siteSettings)) {\n const s = section as unknown as {\n type?: string;\n contentTypeKey?: string;\n defaults?: { value: string; label: string; sortOrder?: number }[];\n };\n if (s?.type === 'categoryManager' && s.contentTypeKey && s.defaults) {\n defaults[s.contentTypeKey] = [...s.defaults];\n }\n }\n return Object.keys(defaults).length > 0 ? defaults : saved;\n }, [tenant?.settings, template?.siteSettings]);\n\n // Currency symbol helper (moved up — needed by behavior config panels)\n const currencySymbol = useMemo(\n () => getCurrencySymbol(tenantCurrency),\n [tenantCurrency],\n );\n\n // Industry-defined fields from the content type\n const mergedFields = useMemo(() => {\n if (!sellableContentType) return [];\n return sellableContentType.fields;\n }, [sellableContentType]);\n\n // Filter out platform fields that we render manually — only show template-specific fields\n const templateOnlyFields = useMemo(() => {\n return mergedFields.filter((f) => !PLATFORM_FIELDS.has(f.name));\n }, [mergedFields]);\n\n // ── Suggested detail sections (opt-in template fields) ──\n // Build a list of toggleable sections from template field groups + ungrouped fields\n const detailSections = useMemo(() => {\n const groups = sellableContentType?.fieldGroups || [];\n const groupedFieldNames = new Set(groups.flatMap((g) => g.fields));\n const ungrouped = templateOnlyFields.filter(\n (f) => !groupedFieldNames.has(f.name),\n );\n\n const sections: Array<{\n key: string;\n label: string;\n description?: string;\n fieldNames: string[];\n }> = [];\n\n // Each field group becomes a section\n for (const g of groups) {\n const hasFields = g.fields.some((fn) =>\n templateOnlyFields.some((f) => f.name === fn),\n );\n if (hasFields) {\n sections.push({\n key: `group:${g.label}`,\n label: g.label,\n description: g.description,\n fieldNames: g.fields,\n });\n }\n }\n\n // Each ungrouped field becomes its own section\n for (const f of ungrouped) {\n sections.push({\n key: `field:${f.name}`,\n label: f.label || f.name,\n fieldNames: [f.name],\n });\n }\n\n return sections;\n }, [templateOnlyFields, sellableContentType?.fieldGroups]);\n\n // Track which sections the user has opted into\n const [enabledDetailSections, setEnabledDetailSections] = useState<\n Set<string>\n >(new Set());\n\n // On edit, auto-enable sections that already have data\n useEffect(() => {\n if (!isEditing || !product?.data || detailSections.length === 0) return;\n const data = product.data as Record<string, unknown>;\n const enabled = new Set<string>();\n for (const section of detailSections) {\n const hasData = section.fieldNames.some((fn) => {\n const val = data[fn];\n if (val === undefined || val === null || val === '') return false;\n if (Array.isArray(val) && val.length === 0) return false;\n return true;\n });\n if (hasData) enabled.add(section.key);\n }\n if (enabled.size > 0) setEnabledDetailSections(enabled);\n }, [isEditing, product?.data, detailSections]);\n\n const toggleDetailSection = useCallback((key: string) => {\n setEnabledDetailSections((prev) => {\n const next = new Set(prev);\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.add(key);\n }\n return next;\n });\n }, []);\n\n // Filter template fields + groups to only enabled ones for DynamicForm\n const activeTemplateFields = useMemo(() => {\n const enabledFieldNames = new Set<string>();\n for (const section of detailSections) {\n if (enabledDetailSections.has(section.key)) {\n for (const fn of section.fieldNames) enabledFieldNames.add(fn);\n }\n }\n return templateOnlyFields.filter((f) => enabledFieldNames.has(f.name));\n }, [templateOnlyFields, detailSections, enabledDetailSections]);\n\n const activeFieldGroups = useMemo(() => {\n if (!sellableContentType?.fieldGroups) return undefined;\n return sellableContentType.fieldGroups.filter((g) =>\n enabledDetailSections.has(`group:${g.label}`),\n );\n }, [sellableContentType?.fieldGroups, enabledDetailSections]);\n\n // Auto-generate slug from title (on create only)\n useEffect(() => {\n if (!isEditing && !slugManuallyEdited && title) {\n setSlug(generateSlug(title));\n }\n }, [title, isEditing, slugManuallyEdited]);\n\n // Pre-populate template values from product.data on edit\n useEffect(() => {\n setTemplateValues(product?.data || {});\n setHeroImage(normalizeMediaItem(product?.image) || null);\n setGalleryItems(toGalleryItems(normalizeMediaList(product?.gallery)));\n }, [product?.data, product?.image, product?.gallery]);\n\n // Fetch collections and pre-select those containing this product\n useEffect(() => {\n if (!tenant?.slug) return;\n let cancelled = false;\n const fetchCollections = async () => {\n setCollectionsLoading(true);\n try {\n const result = await contentApi.list(tenant.slug, 'collections', {\n limit: 200,\n });\n if (cancelled) return;\n const items = result.data || [];\n setAllCollections(items);\n\n // On edit, find which collections already contain this product\n if (product?.id) {\n const matched = new Set<string>();\n for (const c of items) {\n const ids = c.data?.productIds;\n if (Array.isArray(ids) && ids.includes(product.id)) {\n matched.add(c.id);\n }\n }\n setSelectedCollectionIds(matched);\n }\n } catch {\n // silently handle — collections picker just won't appear\n } finally {\n if (!cancelled) setCollectionsLoading(false);\n }\n };\n fetchCollections();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug, product?.id]);\n\n // Fetch resource categories (for bookable resource requirements)\n useEffect(() => {\n if (!tenant?.slug) return;\n let cancelled = false;\n const fetchCategories = async () => {\n try {\n const cats = await resourceCategoriesApi.list(tenant.slug);\n if (cancelled) return;\n setResourceCategories(\n (cats || []).map((c) => ({\n id: c.id,\n label: c.label || (c as any).name || '',\n labelPlural: c.labelPlural || '',\n kind: c.kind || 'person',\n })),\n );\n } catch {\n // silently handle — resource requirements section won't appear\n }\n };\n fetchCategories();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug]);\n\n // Fetch active MerchantPlans (for subscribable linked plan selector)\n useEffect(() => {\n if (!tenant?.slug) return;\n merchantSubscriptionsApi\n .getActivePlans(tenant.slug)\n .then((plans) => setActivePlans(plans || []))\n .catch(() => {\n // silently handle — plan selector will be hidden when plans is empty\n });\n }, [tenant?.slug]);\n\n // Fetch existing resource requirements when editing a product\n useEffect(() => {\n if (!tenant?.slug || !product?.id) return;\n let cancelled = false;\n const fetchRequirements = async () => {\n try {\n const reqs = await resourceCategoriesApi.getContentRequirements(\n tenant.slug,\n product.id,\n );\n if (cancelled) return;\n setResourceRequirements(\n (reqs || []).map((r) => ({\n categoryId: r.categoryId,\n isRequired: r.isRequired ?? true,\n assignmentStrategy: r.assignmentStrategy as\n ContentResourceRequirementItem['assignmentStrategy'] | undefined,\n requiredSkills: r.requiredSkills || [],\n eligibleResourceIds: r.eligibleResourceIds || null,\n })),\n );\n } catch {\n // silently handle — no existing requirements\n }\n };\n fetchRequirements();\n return () => {\n cancelled = true;\n };\n }, [tenant?.slug, product?.id]);\n\n // ── Behavior config state (Phase 4: config objects instead of booleans) ──\n const [behaviorConfigs, setBehaviorConfigs] = useState<ProductBehaviors>(\n () => {\n // On edit, load existing behaviors from the product\n if (product?.behaviors) {\n return product.behaviors as ProductBehaviors;\n }\n // On create, initialize from the template's content type behaviors\n if (sellableContentType?.behaviors) {\n const b: ProductBehaviors = {};\n for (const behavior of sellableContentType.behaviors) {\n (b as any)[behavior] = { enabled: true };\n }\n return b;\n }\n return { sellable: { enabled: true } };\n },\n );\n\n // Re-initialize behavior configs when the template changes (create mode)\n useEffect(() => {\n if (!isEditing && sellableContentType?.behaviors) {\n setBehaviorConfigs((prev) => {\n // Only set defaults for behaviors that aren't already configured\n const updated = { ...prev };\n for (const behavior of sellableContentType.behaviors) {\n if ((updated as any)[behavior] === undefined) {\n (updated as any)[behavior] = { enabled: true };\n }\n }\n return updated;\n });\n }\n }, [sellableContentType, isEditing]);\n\n // ── Content gating state ──\n const [gatedBy, setGatedBy] = useState<string[]>(() => {\n if (\n product?.behaviors?.gatedBy &&\n Array.isArray(product.behaviors.gatedBy)\n ) {\n return product.behaviors.gatedBy;\n }\n return [];\n });\n const [gatedByPlanIds, setGatedByPlanIds] = useState<string[]>(() => {\n if (\n product?.behaviors?.gatedByPlanIds &&\n Array.isArray(product.behaviors.gatedByPlanIds)\n ) {\n return product.behaviors.gatedByPlanIds;\n }\n return [];\n });\n\n // Derive which MerchantPlan is linked to the subscribable behavior\n const linkedPlanId = useMemo(() => {\n const config = getBehaviorConfig<SubscribableConfig>(\n behaviorConfigs.subscribable,\n );\n return config?.linkedPlanId;\n }, [behaviorConfigs.subscribable]);\n\n const linkedPlanName = useMemo(() => {\n if (!linkedPlanId || !activePlans) return undefined;\n return activePlans.find((p) => p.id === linkedPlanId)?.name;\n }, [linkedPlanId, activePlans]);\n\n // Derive which behaviors are available for the product form toggle list.\n // Priority: tenant's enabledBehaviors > template content type behaviors > fallback to all behaviors\n const availableBehaviors = useMemo<BehaviorKey[]>(() => {\n const tenantSettings = tenant?.settings as\n Record<string, unknown> | undefined;\n const tenantBehaviors = tenantSettings?.enabledBehaviors;\n\n // 1. Tenant has explicit preferences — use them\n if (Array.isArray(tenantBehaviors) && tenantBehaviors.length > 0) {\n return tenantBehaviors.filter(\n (b): b is BehaviorKey => b !== 'giftable' && b !== 'leadCapture',\n );\n }\n\n // 2. Template content type defines behaviors\n if (\n sellableContentType?.behaviors &&\n sellableContentType.behaviors.length > 0\n ) {\n const filtered = sellableContentType.behaviors.filter(\n (b): b is BehaviorKey => b !== 'giftable' && b !== 'leadCapture',\n );\n if (filtered.length > 0) return filtered;\n }\n\n // 3. Fallback — offer all behaviors by platform default\n return [\n 'sellable',\n 'bookable',\n 'shippable',\n 'subscribable',\n 'downloadable',\n 'rentable',\n 'ticketed',\n ] as BehaviorKey[];\n }, [tenant?.settings, sellableContentType]);\n\n // Template-provided behavior label overrides\n const behaviorLabelOverrides = useMemo<\n BehaviorLabelOverrides | undefined\n >(() => {\n // Templates can provide behaviorLabels in their schema\n const ct = sellableContentType as unknown as {\n behaviorLabels?: BehaviorLabelOverrides;\n };\n return ct?.behaviorLabels;\n }, [sellableContentType]);\n\n // Build flat behaviors for backward-compatible save (also used by behavior field merging)\n const behaviors = useMemo(() => {\n const b: Record<string, boolean> = {};\n for (const [key, value] of Object.entries(behaviorConfigs)) {\n if (key === 'gatedBy' || key === 'gatedByPlanIds' || key === 'reservable')\n continue;\n b[key] = isBehaviorEnabled(value);\n }\n return b;\n }, [behaviorConfigs]);\n\n // ── Behavior config update handlers ──\n const updateBookableConfig = useCallback((config: BookableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, bookable: config }));\n }, []);\n\n const updateShippableConfig = useCallback((config: ShippableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, shippable: config }));\n }, []);\n\n const updateSubscribableConfig = useCallback((config: SubscribableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, subscribable: config }));\n }, []);\n\n const updateDownloadableConfig = useCallback((config: DownloadableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, downloadable: config }));\n }, []);\n\n const updateRentableConfig = useCallback((config: RentableConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, rentable: config }));\n }, []);\n\n const updateTicketedConfig = useCallback((config: TicketedConfig) => {\n setBehaviorConfigs((prev) => ({ ...prev, ticketed: config }));\n }, []);\n\n // Build config panel map for the BehaviorConfigSection\n const behaviorConfigPanels = useMemo(\n () =>\n createBehaviorConfigPanels({\n behaviorConfigs,\n updateBookableConfig,\n updateShippableConfig,\n updateSubscribableConfig,\n updateDownloadableConfig,\n updateRentableConfig,\n updateTicketedConfig,\n behaviorLabelOverrides,\n currencySymbol,\n resourceCategories,\n resourceRequirements,\n setResourceRequirements,\n activePlans,\n }),\n [\n behaviorConfigs,\n updateBookableConfig,\n updateShippableConfig,\n updateSubscribableConfig,\n updateDownloadableConfig,\n updateRentableConfig,\n updateTicketedConfig,\n behaviorLabelOverrides,\n currencySymbol,\n resourceCategories,\n resourceRequirements,\n ],\n );\n\n // Handle save\n const handleSave = useCallback(async () => {\n if (!tenant?.slug) return;\n\n // Basic validation\n if (!title.trim()) {\n setError('Title is required');\n return;\n }\n if (!slug.trim()) {\n setError('Slug is required');\n return;\n }\n\n setSaving(true);\n setError(null);\n\n try {\n const payload: CreateProductPayload = {\n type: productType,\n title: title.trim(),\n slug: slug.trim(),\n description: description || undefined,\n price: price !== undefined ? Number(price) || 0 : undefined,\n compareAtPrice:\n compareAtPrice !== undefined\n ? Number(compareAtPrice) || 0\n : undefined,\n currency: tenantCurrency,\n trackInventory,\n quantity: typeof quantity === 'number' ? quantity : 0,\n lowStockThreshold:\n typeof lowStockThreshold === 'number' ? lowStockThreshold : 5,\n featured,\n isActive,\n image: heroImage\n ? ({\n ...heroImage,\n type: 'image',\n } as ProductMedia)\n : undefined,\n // An empty list on an edit is sent as [], not left out: the API reads\n // an omitted gallery as \"unchanged\", so removing the last item would\n // save successfully and bring it straight back.\n gallery:\n galleryItems.length > 0\n ? stripGalleryItemIds(galleryItems)\n : isEditing\n ? []\n : undefined,\n behaviors: {\n ...behaviorConfigs,\n ...(gatedBy.length > 0 && { gatedBy }),\n ...(gatedByPlanIds.length > 0 && { gatedByPlanIds }),\n },\n data: templateValues,\n };\n\n let productId: string | undefined;\n\n if (isEditing && product) {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { type, ...updatePayload } = payload;\n await inventoryApi.update(\n tenant.slug,\n product.id,\n updatePayload as Record<string, unknown>,\n );\n productId = product.id;\n } else {\n const created = await inventoryApi.create(\n tenant.slug,\n payload as unknown as Record<string, unknown>,\n );\n productId = created.id;\n }\n\n // Sync collection assignments — add/remove this product from collections\n if (productId && allCollections.length > 0) {\n const updates: Promise<unknown>[] = [];\n for (const collection of allCollections) {\n const currentIds: string[] = Array.isArray(\n collection.data?.productIds,\n )\n ? (collection.data.productIds as string[])\n : [];\n const hasProduct = currentIds.includes(productId);\n const shouldHave = selectedCollectionIds.has(collection.id);\n\n if (shouldHave && !hasProduct) {\n // Add product to this collection\n updates.push(\n contentApi.update(tenant.slug, 'collections', collection.id, {\n data: {\n ...collection.data,\n productIds: [...currentIds, productId],\n },\n }),\n );\n } else if (!shouldHave && hasProduct) {\n // Remove product from this collection\n updates.push(\n contentApi.update(tenant.slug, 'collections', collection.id, {\n data: {\n ...collection.data,\n productIds: currentIds.filter((id) => id !== productId),\n },\n }),\n );\n }\n }\n if (updates.length > 0) {\n await Promise.all(updates);\n }\n }\n\n // Save resource requirements if bookable behavior is active\n if (\n productId &&\n isBehaviorEnabled(behaviorConfigs.bookable) &&\n resourceRequirements.length > 0\n ) {\n try {\n await resourceCategoriesApi.setContentRequirements(\n tenant.slug,\n productId,\n { requirements: resourceRequirements },\n );\n } catch {\n // Non-blocking — product was saved, requirements just didn't persist\n console.warn('Failed to save resource requirements');\n }\n }\n\n onSaved();\n } catch (e: unknown) {\n const msg = e instanceof Error ? e.message : 'Failed to save product';\n setError(msg);\n } finally {\n setSaving(false);\n }\n }, [\n tenant?.slug,\n productType,\n title,\n slug,\n description,\n price,\n compareAtPrice,\n tenantCurrency,\n trackInventory,\n quantity,\n lowStockThreshold,\n featured,\n isActive,\n heroImage,\n galleryItems,\n behaviorConfigs,\n gatedBy,\n gatedByPlanIds,\n templateValues,\n isEditing,\n product,\n onSaved,\n allCollections,\n selectedCollectionIds,\n resourceRequirements,\n ]);\n\n // Currency formatting helpers\n const formatCurrency = (num: number | undefined): string => {\n if (num === undefined || num === null || isNaN(num)) return '';\n return new Intl.NumberFormat('en-US', {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(num);\n };\n\n const parseCurrency = (str: string): number | undefined => {\n const cleaned = str.replace(/[^0-9.]/g, '');\n if (!cleaned) return undefined;\n const num = parseFloat(cleaned);\n return isNaN(num) ? undefined : num;\n };\n\n // Display state for currency fields\n const [priceDisplay, setPriceDisplay] = useState(() =>\n formatCurrency(product?.price != null ? Number(product.price) : undefined),\n );\n const [priceFocused, setPriceFocused] = useState(false);\n const [compareDisplay, setCompareDisplay] = useState(() =>\n formatCurrency(\n product?.compareAtPrice != null\n ? Number(product.compareAtPrice)\n : undefined,\n ),\n );\n const [compareFocused, setCompareFocused] = useState(false);\n\n // Build steps for the stepper\n const steps = useMemo<FormStep[]>(() => {\n const result: FormStep[] = [];\n\n // Step 1: Title, Image & Pricing\n result.push(\n createBasicsStep({\n title,\n setTitle,\n heroImage,\n setHeroImage,\n showHeroImageModal,\n setShowHeroImageModal,\n galleryItems,\n setGalleryItems,\n gallerySensors,\n handleGalleryDragEnd,\n showGalleryMediaModal,\n setShowGalleryMediaModal,\n editingGalleryIndex,\n setEditingGalleryIndex,\n description,\n setDescription,\n currencySymbol,\n priceFocused,\n setPriceFocused,\n priceDisplay,\n setPriceDisplay,\n price,\n setPrice,\n compareFocused,\n setCompareFocused,\n compareDisplay,\n setCompareDisplay,\n compareAtPrice,\n setCompareAtPrice,\n formatCurrency,\n parseCurrency,\n }),\n );\n\n // Step 2: Behaviors — What can customers do with this product?\n result.push(\n createBehaviorsStep({\n behaviorConfigs,\n setBehaviorConfigs,\n behaviorLabelOverrides,\n availableBehaviors,\n behaviorConfigPanels,\n gatedBy,\n setGatedBy,\n gatedByPlanIds,\n setGatedByPlanIds,\n activePlans,\n linkedPlanName,\n }),\n );\n\n // Step 3: Product Details & Inventory\n result.push(\n createDetailsStep({\n behaviorConfigs,\n detailSections,\n enabledDetailSections,\n toggleDetailSection,\n activeTemplateFields,\n activeFieldGroups,\n templateValues,\n setTemplateValues,\n tenantCurrency,\n tenantCategories,\n tenantSlug: tenant?.slug,\n trackInventory,\n setTrackInventory,\n quantity,\n setQuantity,\n lowStockThreshold,\n setLowStockThreshold,\n }),\n );\n\n // Step 4: Collections & Organization\n result.push(\n createOrganizeStep({\n collectionsLoading,\n allCollections,\n selectedCollectionIds,\n setSelectedCollectionIds,\n isActive,\n setIsActive,\n isEditing,\n productType,\n setProductType,\n slug,\n setSlug,\n setSlugManuallyEdited,\n product,\n featured,\n setFeatured,\n }),\n );\n\n return result;\n }, [\n title,\n heroImage,\n galleryItems,\n showHeroImageModal,\n showGalleryMediaModal,\n editingGalleryIndex,\n gallerySensors,\n handleGalleryDragEnd,\n description,\n currencySymbol,\n priceFocused,\n priceDisplay,\n price,\n compareFocused,\n compareDisplay,\n compareAtPrice,\n templateOnlyFields,\n sellableContentType,\n templateValues,\n tenantCurrency,\n tenantCategories,\n tenant?.slug,\n trackInventory,\n quantity,\n lowStockThreshold,\n collectionsLoading,\n allCollections,\n selectedCollectionIds,\n isEditing,\n productType,\n slug,\n product?.slug,\n isActive,\n featured,\n behaviorConfigs,\n behaviorLabelOverrides,\n availableBehaviors,\n behaviorConfigPanels,\n detailSections,\n enabledDetailSections,\n toggleDetailSection,\n activeTemplateFields,\n activeFieldGroups,\n gatedBy,\n gatedByPlanIds,\n linkedPlanId,\n linkedPlanName,\n ]);\n\n return (\n <FormStepperPanel\n title={isEditing ? 'Edit Product' : 'Add Product'}\n steps={steps}\n onSave={handleSave}\n onClose={onClose}\n saving={saving}\n saveLabel={isEditing ? 'Update Product' : 'Create Product'}\n error={error}\n onClearError={() => setError(null)}\n />\n );\n}\n"],"mappings":"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","names":[]}