@open-mercato/checkout 0.6.7 → 0.6.8-develop.6874.1.982d6097d8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/checkout/__integration__/TC-CHKT-042.spec.js +39 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-042.spec.js.map +7 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js +62 -0
- package/dist/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.js.map +7 -0
- package/dist/modules/checkout/api/pay/[slug]/submit/route.js +43 -28
- package/dist/modules/checkout/api/pay/[slug]/submit/route.js.map +2 -2
- package/dist/modules/checkout/api/transactions/route.js +23 -6
- package/dist/modules/checkout/api/transactions/route.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/pay-links/page.js +2 -1
- package/dist/modules/checkout/backend/checkout/pay-links/page.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/templates/page.js +2 -1
- package/dist/modules/checkout/backend/checkout/templates/page.js.map +2 -2
- package/dist/modules/checkout/backend/checkout/transactions/page.js +2 -1
- package/dist/modules/checkout/backend/checkout/transactions/page.js.map +2 -2
- package/dist/modules/checkout/commands/links.js +1 -1
- package/dist/modules/checkout/commands/links.js.map +2 -2
- package/dist/modules/checkout/commands/transactions.js +45 -13
- package/dist/modules/checkout/commands/transactions.js.map +2 -2
- package/dist/modules/checkout/components/CustomerFieldsEditor.js +2 -2
- package/dist/modules/checkout/components/CustomerFieldsEditor.js.map +2 -2
- package/dist/modules/checkout/components/GatewaySettingsFields.js +2 -2
- package/dist/modules/checkout/components/GatewaySettingsFields.js.map +2 -2
- package/dist/modules/checkout/components/LinkTemplateForm.js +8 -8
- package/dist/modules/checkout/components/LinkTemplateForm.js.map +2 -2
- package/dist/modules/checkout/components/LogoUploadField.js +1 -1
- package/dist/modules/checkout/components/LogoUploadField.js.map +2 -2
- package/dist/modules/checkout/components/PayPage.js +39 -38
- package/dist/modules/checkout/components/PayPage.js.map +2 -2
- package/dist/modules/checkout/extension-points.js +68 -0
- package/dist/modules/checkout/extension-points.js.map +7 -0
- package/dist/modules/checkout/lib/transaction-status-machine.js +31 -0
- package/dist/modules/checkout/lib/transaction-status-machine.js.map +7 -0
- package/dist/modules/checkout/search.js +44 -6
- package/dist/modules/checkout/search.js.map +2 -2
- package/dist/modules/checkout/setup.js +28 -0
- package/dist/modules/checkout/setup.js.map +2 -2
- package/dist/modules/checkout/subscribers/search-reindex-transaction-created.js +32 -0
- package/dist/modules/checkout/subscribers/search-reindex-transaction-created.js.map +7 -0
- package/dist/modules/checkout/workers/transaction-expiry.worker.js +9 -2
- package/dist/modules/checkout/workers/transaction-expiry.worker.js.map +2 -2
- package/package.json +10 -9
- package/src/modules/checkout/__integration__/TC-CHKT-042.spec.ts +41 -0
- package/src/modules/checkout/__integration__/TC-CHKT-043-concurrent-status-update.spec.ts +112 -0
- package/src/modules/checkout/__tests__/search.test.ts +21 -0
- package/src/modules/checkout/__tests__/setup-schedules.test.ts +99 -0
- package/src/modules/checkout/api/pay/[slug]/submit/__tests__/route.test.ts +77 -0
- package/src/modules/checkout/api/pay/[slug]/submit/route.ts +58 -28
- package/src/modules/checkout/api/transactions/__tests__/route.test.ts +113 -0
- package/src/modules/checkout/api/transactions/route.ts +30 -6
- package/src/modules/checkout/backend/checkout/pay-links/page.tsx +2 -1
- package/src/modules/checkout/backend/checkout/templates/page.tsx +2 -1
- package/src/modules/checkout/backend/checkout/transactions/page.tsx +2 -1
- package/src/modules/checkout/commands/__tests__/update-transaction-status.test.ts +363 -0
- package/src/modules/checkout/commands/links.ts +5 -1
- package/src/modules/checkout/commands/transactions.ts +68 -13
- package/src/modules/checkout/components/CustomerFieldsEditor.tsx +2 -2
- package/src/modules/checkout/components/GatewaySettingsFields.tsx +2 -2
- package/src/modules/checkout/components/LinkTemplateForm.tsx +8 -8
- package/src/modules/checkout/components/LogoUploadField.tsx +1 -1
- package/src/modules/checkout/components/PayPage.tsx +38 -37
- package/src/modules/checkout/extension-points.ts +67 -0
- package/src/modules/checkout/i18n/de.json +1 -0
- package/src/modules/checkout/i18n/en.json +1 -0
- package/src/modules/checkout/i18n/es.json +1 -0
- package/src/modules/checkout/i18n/ko.json +463 -0
- package/src/modules/checkout/i18n/pl.json +1 -0
- package/src/modules/checkout/lib/__tests__/transaction-status-machine.test.ts +152 -0
- package/src/modules/checkout/lib/transaction-status-machine.ts +71 -0
- package/src/modules/checkout/search.ts +44 -6
- package/src/modules/checkout/setup.ts +51 -0
- package/src/modules/checkout/subscribers/__tests__/search-reindex-transaction-created.test.ts +29 -0
- package/src/modules/checkout/subscribers/search-reindex-transaction-created.ts +39 -0
- package/src/modules/checkout/workers/__tests__/transaction-expiry.worker.test.ts +124 -0
- package/src/modules/checkout/workers/transaction-expiry.worker.ts +11 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/commands/transactions.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CheckoutLink, CheckoutTransaction } from '../data/entities'\nimport { transactionCreateSchema, transactionUpdateStatusSchema } from '../data/validators'\nimport { emitCheckoutEvent } from '../events'\nimport {\n applyTerminalTransactionState,\n isTerminalCheckoutStatus,\n mapGatewayStatusToCheckoutStatus,\n parseCheckoutInput,\n toMoneyString,\n} from '../lib/utils'\n\nfunction resolveTransactionScope(input: { tenantId?: string | null; organizationId?: string | null }) {\n if (!input.organizationId || !input.tenantId) {\n throw new CrudHttpError(400, { error: 'Transaction scope is required' })\n }\n return {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n}\n\ntype CheckoutTerminalEventPayload = {\n transactionId: string\n linkId: string\n templateId: string | null\n slug: string\n status: CheckoutTransaction['status']\n paymentStatus: string | null\n amount: number\n currency: string\n gatewayProvider: string | null\n gatewayTransactionId: string | null\n occurredAt: string\n tenantId: string\n organizationId: string\n}\n\nconst createTransactionCommand: CommandHandler<Record<string, unknown>, { id: string }> = {\n id: 'checkout.transaction.create',\n async execute(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, transactionCreateSchema.parse)\n const scope = resolveTransactionScope(parsed)\n const em = ctx.container.resolve('em') as EntityManager\n let lockedLinkId: string | null = null\n let lockedLinkSlug: string | null = null\n let lockedLinkTemplateId: string | null = null\n let lockedLinkGatewayProvider: string | null = null\n let shouldEmitLockedEvent = false\n const transaction = await em.transactional(async (tx) => {\n const currentLink = await tx.findOne(CheckoutLink, {\n id: parsed.linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n if (!currentLink) {\n throw new CrudHttpError(404, { error: 'Payment link not found' })\n }\n if (currentLink.status !== 'active') {\n throw new CrudHttpError(422, { error: 'This payment link is not currently accepting payments' })\n }\n const reserved = await tx.getConnection().execute<Array<{ id: string }>>(\n `\n UPDATE checkout_links\n SET active_reservation_count = active_reservation_count + 1,\n is_locked = true,\n updated_at = now()\n WHERE id = ?\n AND organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND status = 'active'\n AND (\n max_completions IS NULL\n OR completion_count + active_reservation_count < max_completions\n )\n RETURNING id\n `,\n [parsed.linkId, scope.organizationId, scope.tenantId],\n )\n if (!reserved[0]?.id) {\n throw new CrudHttpError(422, { error: 'This payment link is no longer available' })\n }\n lockedLinkId = currentLink.id\n lockedLinkSlug = currentLink.slug\n lockedLinkTemplateId = currentLink.templateId ?? null\n lockedLinkGatewayProvider = currentLink.gatewayProviderKey ?? null\n shouldEmitLockedEvent = !currentLink.isLocked\n const transaction = tx.create(CheckoutTransaction, {\n ...parsed,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n amount: toMoneyString(parsed.amount) ?? '0.00',\n status: 'processing',\n })\n tx.persist(transaction)\n await tx.flush()\n return transaction\n })\n if (shouldEmitLockedEvent && lockedLinkId && lockedLinkSlug) {\n await emitCheckoutEvent('checkout.link.locked', {\n id: lockedLinkId,\n slug: lockedLinkSlug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n await emitCheckoutEvent('checkout.transaction.created', {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n status: transaction.status,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n await emitCheckoutEvent('checkout.transaction.customerDataCaptured', {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n templateId: lockedLinkTemplateId,\n slug: lockedLinkSlug,\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n gatewayProvider: lockedLinkGatewayProvider,\n gatewayTransactionId: transaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n customerDataCaptured: true,\n }).catch(() => undefined)\n return { id: transaction.id }\n },\n}\n\nconst updateTransactionStatusCommand: CommandHandler<Record<string, unknown>, { ok: true }> = {\n id: 'checkout.transaction.updateStatus',\n async execute(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, transactionUpdateStatusSchema.parse)\n const scope = resolveTransactionScope(parsed)\n const em = ctx.container.resolve('em') as EntityManager\n let terminalEventPayload: CheckoutTerminalEventPayload | null = null\n let emitUsageLimitReached = false\n let usageLimitReachedLinkId: string | null = null\n let usageLimitReachedLinkSlug: string | null = null\n await em.transactional(async (tx) => {\n const transaction = await tx.findOne(CheckoutTransaction, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n if (!transaction) throw new CrudHttpError(404, { error: 'Transaction not found' })\n const link = await tx.findOne(CheckoutLink, {\n id: transaction.linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n if (!link) throw new CrudHttpError(404, { error: 'Payment link not found' })\n\n const previousStatus = transaction.status\n const nextStatus = parsed.status\n const previousTerminal = isTerminalCheckoutStatus(previousStatus)\n const nextTerminal = isTerminalCheckoutStatus(nextStatus)\n\n transaction.status = nextStatus\n transaction.paymentStatus = parsed.paymentStatus ?? transaction.paymentStatus ?? null\n transaction.gatewayTransactionId = parsed.gatewayTransactionId ?? transaction.gatewayTransactionId ?? null\n await tx.flush()\n\n if (!previousTerminal && nextTerminal) {\n const { usageLimitReached } = applyTerminalTransactionState(link, nextStatus)\n await tx.flush()\n if (usageLimitReached) {\n emitUsageLimitReached = true\n usageLimitReachedLinkId = link.id\n usageLimitReachedLinkSlug = link.slug\n }\n }\n if (nextTerminal) {\n terminalEventPayload = {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n templateId: link.templateId ?? null,\n slug: link.slug,\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n gatewayProvider: link.gatewayProviderKey ?? null,\n gatewayTransactionId: transaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n }\n })\n if (parsed.status === 'completed') {\n await emitCheckoutEvent('checkout.transaction.completed', terminalEventPayload ?? {}).catch(() => undefined)\n } else if (parsed.status === 'failed') {\n await emitCheckoutEvent('checkout.transaction.failed', terminalEventPayload ?? {}).catch(() => undefined)\n } else if (parsed.status === 'cancelled') {\n await emitCheckoutEvent('checkout.transaction.cancelled', terminalEventPayload ?? {}).catch(() => undefined)\n } else if (parsed.status === 'expired') {\n await emitCheckoutEvent('checkout.transaction.expired', terminalEventPayload ?? {}).catch(() => undefined)\n }\n if (emitUsageLimitReached && usageLimitReachedLinkId && usageLimitReachedLinkSlug) {\n await emitCheckoutEvent('checkout.link.usageLimitReached', {\n id: usageLimitReachedLinkId,\n slug: usageLimitReachedLinkSlug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { ok: true }\n },\n}\n\nregisterCommand(createTransactionCommand)\nregisterCommand(updateTransactionStatusCommand)\n\nexport function mapGatewayStatusForCommand(status: string | null | undefined) {\n return mapGatewayStatusToCheckoutStatus(status)\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,cAAc,2BAA2B;AAClD,SAAS,yBAAyB,qCAAqC;AACvE,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CheckoutLink, CheckoutTransaction } from '../data/entities'\nimport { transactionCreateSchema, transactionUpdateStatusSchema } from '../data/validators'\nimport { emitCheckoutEvent } from '../events'\nimport {\n applyTerminalTransactionState,\n isTerminalCheckoutStatus,\n mapGatewayStatusToCheckoutStatus,\n parseCheckoutInput,\n toMoneyString,\n} from '../lib/utils'\nimport { assertValidCheckoutStatusTransition } from '../lib/transaction-status-machine'\n\nfunction resolveTransactionScope(input: { tenantId?: string | null; organizationId?: string | null }) {\n if (!input.organizationId || !input.tenantId) {\n throw new CrudHttpError(400, { error: 'Transaction scope is required' })\n }\n return {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n}\n\ntype CheckoutTerminalEventPayload = {\n transactionId: string\n linkId: string\n templateId: string | null\n slug: string\n status: CheckoutTransaction['status']\n paymentStatus: string | null\n amount: number\n currency: string\n gatewayProvider: string | null\n gatewayTransactionId: string | null\n occurredAt: string\n tenantId: string\n organizationId: string\n}\n\nconst createTransactionCommand: CommandHandler<Record<string, unknown>, { id: string }> = {\n id: 'checkout.transaction.create',\n async execute(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, transactionCreateSchema.parse)\n const scope = resolveTransactionScope(parsed)\n const em = ctx.container.resolve('em') as EntityManager\n let lockedLinkId: string | null = null\n let lockedLinkSlug: string | null = null\n let lockedLinkTemplateId: string | null = null\n let lockedLinkGatewayProvider: string | null = null\n let shouldEmitLockedEvent = false\n const transaction = await em.transactional(async (tx) => {\n const currentLink = await tx.findOne(CheckoutLink, {\n id: parsed.linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n if (!currentLink) {\n throw new CrudHttpError(404, { error: 'Payment link not found' })\n }\n if (currentLink.status !== 'active') {\n throw new CrudHttpError(422, { error: 'This payment link is not currently accepting payments' })\n }\n const reserved = await tx.getConnection().execute<Array<{ id: string }>>(\n `\n UPDATE checkout_links\n SET active_reservation_count = active_reservation_count + 1,\n is_locked = true,\n updated_at = now()\n WHERE id = ?\n AND organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND status = 'active'\n AND (\n max_completions IS NULL\n OR completion_count + active_reservation_count < max_completions\n )\n RETURNING id\n `,\n [parsed.linkId, scope.organizationId, scope.tenantId],\n )\n if (!reserved[0]?.id) {\n throw new CrudHttpError(422, { error: 'This payment link is no longer available' })\n }\n lockedLinkId = currentLink.id\n lockedLinkSlug = currentLink.slug\n lockedLinkTemplateId = currentLink.templateId ?? null\n lockedLinkGatewayProvider = currentLink.gatewayProviderKey ?? null\n shouldEmitLockedEvent = !currentLink.isLocked\n const transaction = tx.create(CheckoutTransaction, {\n ...parsed,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n amount: toMoneyString(parsed.amount) ?? '0.00',\n status: 'processing',\n })\n tx.persist(transaction)\n await tx.flush()\n return transaction\n })\n if (shouldEmitLockedEvent && lockedLinkId && lockedLinkSlug) {\n await emitCheckoutEvent('checkout.link.locked', {\n id: lockedLinkId,\n slug: lockedLinkSlug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n await emitCheckoutEvent('checkout.transaction.created', {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n status: transaction.status,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n await emitCheckoutEvent('checkout.transaction.customerDataCaptured', {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n templateId: lockedLinkTemplateId,\n slug: lockedLinkSlug,\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n gatewayProvider: lockedLinkGatewayProvider,\n gatewayTransactionId: transaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n customerDataCaptured: true,\n }).catch(() => undefined)\n return { id: transaction.id }\n },\n}\n\nconst updateTransactionStatusCommand: CommandHandler<Record<string, unknown>, { ok: true }> = {\n id: 'checkout.transaction.updateStatus',\n async execute(rawInput, ctx) {\n const { parsed } = parseCheckoutInput(rawInput, transactionUpdateStatusSchema.parse)\n const scope = resolveTransactionScope(parsed)\n const em = ctx.container.resolve('em') as EntityManager\n let terminalEventPayload: CheckoutTerminalEventPayload | null = null\n let emitUsageLimitReached = false\n let usageLimitReachedLinkId: string | null = null\n let usageLimitReachedLinkSlug: string | null = null\n await em.transactional(async (tx) => {\n const transaction = await tx.findOne(CheckoutTransaction, {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n if (!transaction) throw new CrudHttpError(404, { error: 'Transaction not found' })\n const link = await tx.findOne(CheckoutLink, {\n id: transaction.linkId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n })\n if (!link) throw new CrudHttpError(404, { error: 'Payment link not found' })\n\n const previousStatus = transaction.status\n const nextStatus = parsed.status\n const previousTerminal = isTerminalCheckoutStatus(previousStatus)\n const nextTerminal = isTerminalCheckoutStatus(nextStatus)\n\n // State-machine guard: reject any transition not permitted by the\n // VALID_CHECKOUT_TRANSITIONS map (e.g. completed \u2192 processing).\n // This runs inside the DB transaction so the check and the write share\n // the same serialisable snapshot.\n assertValidCheckoutStatusTransition(previousStatus, nextStatus)\n\n // Resolve the new field values before the write.\n const newPaymentStatus = parsed.paymentStatus ?? transaction.paymentStatus ?? null\n const newGatewayTransactionId = parsed.gatewayTransactionId ?? transaction.gatewayTransactionId ?? null\n\n // Atomic compare-and-swap: the WHERE clause pins the current status so\n // a concurrent writer that already advanced the status to a terminal\n // state will cause this update to match 0 rows \u2014 eliminating the TOCTOU\n // window between the findOne above and this write.\n const affected = await tx.nativeUpdate(\n CheckoutTransaction,\n {\n id: parsed.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n status: previousStatus,\n },\n {\n status: nextStatus,\n paymentStatus: newPaymentStatus,\n gatewayTransactionId: newGatewayTransactionId,\n updatedAt: new Date(),\n },\n )\n\n if (affected === 0) {\n // A concurrent writer already advanced the status. Force a DB round-trip\n // (refresh: true bypasses the MikroORM identity map) so the reported\n // currentStatus reflects the winning writer's value, not the stale snapshot.\n const actualTx = await tx.findOne(\n CheckoutTransaction,\n { id: parsed.id, organizationId: scope.organizationId, tenantId: scope.tenantId },\n { fields: ['status'], refresh: true },\n )\n throw new CrudHttpError(409, {\n error: `[internal] Transaction status was already updated by a concurrent process (expected \"${previousStatus}\", actual \"${actualTx?.status ?? 'unknown'}\")`,\n code: 'concurrent_status_update',\n expectedStatus: previousStatus,\n currentStatus: actualTx?.status ?? 'unknown',\n requestedStatus: nextStatus,\n })\n }\n\n // Sync the entity state in the unit of work by refreshing it.\n // This loads the updated values (status, paymentStatus, gatewayTransactionId)\n // from the DB and marks the entity as clean, avoiding a redundant second write\n // during the subsequent tx.flush().\n await tx.refresh(transaction)\n\n // Only apply terminal link state and emit the terminal event when the\n // status actually changes \u2014 prevents double-notification on idempotent\n // redeliveries (e.g. authorized \u2192 captured, both mapping to 'completed').\n if (!previousTerminal && nextTerminal) {\n const { usageLimitReached } = applyTerminalTransactionState(link, nextStatus)\n await tx.flush()\n if (usageLimitReached) {\n emitUsageLimitReached = true\n usageLimitReachedLinkId = link.id\n usageLimitReachedLinkSlug = link.slug\n }\n }\n if (nextTerminal && previousStatus !== nextStatus) {\n terminalEventPayload = {\n transactionId: transaction.id,\n linkId: transaction.linkId,\n templateId: link.templateId ?? null,\n slug: link.slug,\n status: transaction.status,\n paymentStatus: transaction.paymentStatus ?? null,\n amount: Number(transaction.amount),\n currency: transaction.currencyCode,\n gatewayProvider: link.gatewayProviderKey ?? null,\n gatewayTransactionId: transaction.gatewayTransactionId ?? null,\n occurredAt: new Date().toISOString(),\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n }\n })\n if (terminalEventPayload !== null) {\n if (parsed.status === 'completed') {\n await emitCheckoutEvent('checkout.transaction.completed', terminalEventPayload).catch(() => undefined)\n } else if (parsed.status === 'failed') {\n await emitCheckoutEvent('checkout.transaction.failed', terminalEventPayload).catch(() => undefined)\n } else if (parsed.status === 'cancelled') {\n await emitCheckoutEvent('checkout.transaction.cancelled', terminalEventPayload).catch(() => undefined)\n } else if (parsed.status === 'expired') {\n await emitCheckoutEvent('checkout.transaction.expired', terminalEventPayload).catch(() => undefined)\n }\n }\n if (emitUsageLimitReached && usageLimitReachedLinkId && usageLimitReachedLinkSlug) {\n await emitCheckoutEvent('checkout.link.usageLimitReached', {\n id: usageLimitReachedLinkId,\n slug: usageLimitReachedLinkSlug,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }).catch(() => undefined)\n }\n return { ok: true }\n },\n}\n\nregisterCommand(createTransactionCommand)\nregisterCommand(updateTransactionStatusCommand)\n\nexport function mapGatewayStatusForCommand(status: string | null | undefined) {\n return mapGatewayStatusToCheckoutStatus(status)\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,cAAc,2BAA2B;AAClD,SAAS,yBAAyB,qCAAqC;AACvE,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2CAA2C;AAEpD,SAAS,wBAAwB,OAAqE;AACpG,MAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,UAAU;AAC5C,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,gCAAgC,CAAC;AAAA,EACzE;AACA,SAAO;AAAA,IACL,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,EAClB;AACF;AAkBA,MAAM,2BAAoF;AAAA,EACxF,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,OAAO,IAAI,mBAAmB,UAAU,wBAAwB,KAAK;AAC7E,UAAM,QAAQ,wBAAwB,MAAM;AAC5C,UAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,QAAI,eAA8B;AAClC,QAAI,iBAAgC;AACpC,QAAI,uBAAsC;AAC1C,QAAI,4BAA2C;AAC/C,QAAI,wBAAwB;AAC5B,UAAM,cAAc,MAAM,GAAG,cAAc,OAAO,OAAO;AACvD,YAAM,cAAc,MAAM,GAAG,QAAQ,cAAc;AAAA,QACjD,IAAI,OAAO;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb,CAAC;AACD,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,yBAAyB,CAAC;AAAA,MAClE;AACA,UAAI,YAAY,WAAW,UAAU;AACnC,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wDAAwD,CAAC;AAAA,MACjG;AACA,YAAM,WAAW,MAAM,GAAG,cAAc,EAAE;AAAA,QACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAgBA,CAAC,OAAO,QAAQ,MAAM,gBAAgB,MAAM,QAAQ;AAAA,MACtD;AACA,UAAI,CAAC,SAAS,CAAC,GAAG,IAAI;AACpB,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,2CAA2C,CAAC;AAAA,MACpF;AACA,qBAAe,YAAY;AAC3B,uBAAiB,YAAY;AAC7B,6BAAuB,YAAY,cAAc;AACjD,kCAA4B,YAAY,sBAAsB;AAC9D,8BAAwB,CAAC,YAAY;AACrC,YAAMA,eAAc,GAAG,OAAO,qBAAqB;AAAA,QACjD,GAAG;AAAA,QACH,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,QAAQ,cAAc,OAAO,MAAM,KAAK;AAAA,QACxC,QAAQ;AAAA,MACV,CAAC;AACD,SAAG,QAAQA,YAAW;AACtB,YAAM,GAAG,MAAM;AACf,aAAOA;AAAA,IACT,CAAC;AACD,QAAI,yBAAyB,gBAAgB,gBAAgB;AAC3D,YAAM,kBAAkB,wBAAwB;AAAA,QAC9C,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,MACxB,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1B;AACA,UAAM,kBAAkB,gCAAgC;AAAA,MACtD,eAAe,YAAY;AAAA,MAC3B,QAAQ,YAAY;AAAA,MACpB,QAAQ,YAAY;AAAA,MACpB,QAAQ,OAAO,YAAY,MAAM;AAAA,MACjC,UAAU,YAAY;AAAA,MACtB,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,IACxB,CAAC,EAAE,MAAM,MAAM,MAAS;AACxB,UAAM,kBAAkB,6CAA6C;AAAA,MACnE,eAAe,YAAY;AAAA,MAC3B,QAAQ,YAAY;AAAA,MACpB,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,QAAQ,YAAY;AAAA,MACpB,eAAe,YAAY,iBAAiB;AAAA,MAC5C,QAAQ,OAAO,YAAY,MAAM;AAAA,MACjC,UAAU,YAAY;AAAA,MACtB,iBAAiB;AAAA,MACjB,sBAAsB,YAAY,wBAAwB;AAAA,MAC1D,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,sBAAsB;AAAA,IACxB,CAAC,EAAE,MAAM,MAAM,MAAS;AACxB,WAAO,EAAE,IAAI,YAAY,GAAG;AAAA,EAC9B;AACF;AAEA,MAAM,iCAAwF;AAAA,EAC5F,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,OAAO,IAAI,mBAAmB,UAAU,8BAA8B,KAAK;AACnF,UAAM,QAAQ,wBAAwB,MAAM;AAC5C,UAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,QAAI,uBAA4D;AAChE,QAAI,wBAAwB;AAC5B,QAAI,0BAAyC;AAC7C,QAAI,4BAA2C;AAC/C,UAAM,GAAG,cAAc,OAAO,OAAO;AACnC,YAAM,cAAc,MAAM,GAAG,QAAQ,qBAAqB;AAAA,QACxD,IAAI,OAAO;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AACD,UAAI,CAAC,YAAa,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wBAAwB,CAAC;AACjF,YAAM,OAAO,MAAM,GAAG,QAAQ,cAAc;AAAA,QAC1C,IAAI,YAAY;AAAA,QAChB,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb,CAAC;AACD,UAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,yBAAyB,CAAC;AAE3E,YAAM,iBAAiB,YAAY;AACnC,YAAM,aAAa,OAAO;AAC1B,YAAM,mBAAmB,yBAAyB,cAAc;AAChE,YAAM,eAAe,yBAAyB,UAAU;AAMxD,0CAAoC,gBAAgB,UAAU;AAG9D,YAAM,mBAAmB,OAAO,iBAAiB,YAAY,iBAAiB;AAC9E,YAAM,0BAA0B,OAAO,wBAAwB,YAAY,wBAAwB;AAMnG,YAAM,WAAW,MAAM,GAAG;AAAA,QACxB;AAAA,QACA;AAAA,UACE,IAAI,OAAO;AAAA,UACX,gBAAgB,MAAM;AAAA,UACtB,UAAU,MAAM;AAAA,UAChB,QAAQ;AAAA,QACV;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,eAAe;AAAA,UACf,sBAAsB;AAAA,UACtB,WAAW,oBAAI,KAAK;AAAA,QACtB;AAAA,MACF;AAEA,UAAI,aAAa,GAAG;AAIlB,cAAM,WAAW,MAAM,GAAG;AAAA,UACxB;AAAA,UACA,EAAE,IAAI,OAAO,IAAI,gBAAgB,MAAM,gBAAgB,UAAU,MAAM,SAAS;AAAA,UAChF,EAAE,QAAQ,CAAC,QAAQ,GAAG,SAAS,KAAK;AAAA,QACtC;AACA,cAAM,IAAI,cAAc,KAAK;AAAA,UAC3B,OAAO,wFAAwF,cAAc,cAAc,UAAU,UAAU,SAAS;AAAA,UACxJ,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,eAAe,UAAU,UAAU;AAAA,UACnC,iBAAiB;AAAA,QACnB,CAAC;AAAA,MACH;AAMA,YAAM,GAAG,QAAQ,WAAW;AAK5B,UAAI,CAAC,oBAAoB,cAAc;AACrC,cAAM,EAAE,kBAAkB,IAAI,8BAA8B,MAAM,UAAU;AAC5E,cAAM,GAAG,MAAM;AACf,YAAI,mBAAmB;AACrB,kCAAwB;AACxB,oCAA0B,KAAK;AAC/B,sCAA4B,KAAK;AAAA,QACnC;AAAA,MACF;AACA,UAAI,gBAAgB,mBAAmB,YAAY;AACjD,+BAAuB;AAAA,UACrB,eAAe,YAAY;AAAA,UAC3B,QAAQ,YAAY;AAAA,UACpB,YAAY,KAAK,cAAc;AAAA,UAC/B,MAAM,KAAK;AAAA,UACX,QAAQ,YAAY;AAAA,UACpB,eAAe,YAAY,iBAAiB;AAAA,UAC5C,QAAQ,OAAO,YAAY,MAAM;AAAA,UACjC,UAAU,YAAY;AAAA,UACtB,iBAAiB,KAAK,sBAAsB;AAAA,UAC5C,sBAAsB,YAAY,wBAAwB;AAAA,UAC1D,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,UACnC,UAAU,MAAM;AAAA,UAChB,gBAAgB,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,CAAC;AACD,QAAI,yBAAyB,MAAM;AACjC,UAAI,OAAO,WAAW,aAAa;AACjC,cAAM,kBAAkB,kCAAkC,oBAAoB,EAAE,MAAM,MAAM,MAAS;AAAA,MACvG,WAAW,OAAO,WAAW,UAAU;AACrC,cAAM,kBAAkB,+BAA+B,oBAAoB,EAAE,MAAM,MAAM,MAAS;AAAA,MACpG,WAAW,OAAO,WAAW,aAAa;AACxC,cAAM,kBAAkB,kCAAkC,oBAAoB,EAAE,MAAM,MAAM,MAAS;AAAA,MACvG,WAAW,OAAO,WAAW,WAAW;AACtC,cAAM,kBAAkB,gCAAgC,oBAAoB,EAAE,MAAM,MAAM,MAAS;AAAA,MACrG;AAAA,IACF;AACA,QAAI,yBAAyB,2BAA2B,2BAA2B;AACjF,YAAM,kBAAkB,mCAAmC;AAAA,QACzD,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,UAAU,MAAM;AAAA,QAChB,gBAAgB,MAAM;AAAA,MACxB,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1B;AACA,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACF;AAEA,gBAAgB,wBAAwB;AACxC,gBAAgB,8BAA8B;AAEvC,SAAS,2BAA2B,QAAmC;AAC5E,SAAO,iCAAiC,MAAM;AAChD;",
|
|
6
6
|
"names": ["transaction"]
|
|
7
7
|
}
|
|
@@ -110,7 +110,7 @@ function CustomerFieldsEditor({ value, onChange, errors }) {
|
|
|
110
110
|
[]
|
|
111
111
|
);
|
|
112
112
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
113
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
113
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.customerFieldsEditor.notices.defaultFields") }) }),
|
|
114
114
|
/* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-xl border border-border/70 bg-background", children: [
|
|
115
115
|
/* @__PURE__ */ jsxs("div", { className: "hidden grid-cols-[1fr_1fr_1fr_180px_90px_110px] gap-3 border-b bg-muted/30 px-4 py-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground md:grid", children: [
|
|
116
116
|
/* @__PURE__ */ jsx("div", { children: t("checkout.customerFieldsEditor.columns.fieldKey") }),
|
|
@@ -280,7 +280,7 @@ function CustomerFieldsEditor({ value, onChange, errors }) {
|
|
|
280
280
|
] })
|
|
281
281
|
] }) : null
|
|
282
282
|
] }, index);
|
|
283
|
-
}) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-8", children: /* @__PURE__ */ jsx(Alert, {
|
|
283
|
+
}) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-8", children: /* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.customerFieldsEditor.notices.empty") }) }) })
|
|
284
284
|
] }),
|
|
285
285
|
/* @__PURE__ */ jsxs(
|
|
286
286
|
Button,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/components/CustomerFieldsEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Settings2, Trash2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport type { CustomerFieldDefinitionInput } from '../data/validators'\nimport { getLocalizedDefaultCheckoutCustomerFields } from '../lib/defaults'\n\ntype Props = {\n value: CustomerFieldDefinitionInput[]\n onChange: (next: CustomerFieldDefinitionInput[]) => void\n errors?: Record<string, string>\n}\n\nfunction withSortOrder(next: CustomerFieldDefinitionInput[]): CustomerFieldDefinitionInput[] {\n return next.map((field, index) => ({\n ...field,\n sortOrder: index,\n }))\n}\n\nfunction createField(index: number, t: ReturnType<typeof useT>): CustomerFieldDefinitionInput {\n return {\n key: `customField${index + 1}`,\n label: t('checkout.customerFieldsEditor.newFieldLabel'),\n kind: 'text',\n required: false,\n fixed: false,\n placeholder: t('checkout.customerFieldsEditor.newFieldPlaceholder'),\n sortOrder: index,\n options: [],\n }\n}\n\nexport function CustomerFieldsEditor({ value, onChange, errors }: Props) {\n const t = useT()\n const fields = React.useMemo<CustomerFieldDefinitionInput[]>(\n () => (\n Array.isArray(value)\n ? value\n : getLocalizedDefaultCheckoutCustomerFields(t).map((field) => ({ ...field, options: [] }))\n ),\n [t, value],\n )\n\n const updateField = React.useCallback(\n (index: number, patch: Partial<CustomerFieldDefinitionInput>) => {\n onChange(\n withSortOrder(\n fields.map((field, currentIndex) => (currentIndex === index ? { ...field, ...patch } : field)),\n ),\n )\n },\n [fields, onChange],\n )\n\n const removeField = React.useCallback(\n (index: number) => {\n onChange(withSortOrder(fields.filter((_, currentIndex) => currentIndex !== index)))\n },\n [fields, onChange],\n )\n\n const updateOption = React.useCallback(\n (fieldIndex: number, optionIndex: number, patch: { value?: string; label?: string }) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: options.map((option: { value: string; label: string }, currentIndex: number) => (\n currentIndex === optionIndex ? { ...option, ...patch } : option\n )),\n })\n },\n [fields, updateField],\n )\n\n const addOption = React.useCallback(\n (fieldIndex: number) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: [\n ...options,\n {\n value: `option_${options.length + 1}`,\n label: t('checkout.customerFieldsEditor.options.defaultLabel', 'Option {index}', { index: options.length + 1 }),\n },\n ],\n })\n },\n [fields, t, updateField],\n )\n\n const removeOption = React.useCallback(\n (fieldIndex: number, optionIndex: number) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: options.filter((_: { value: string; label: string }, currentIndex: number) => currentIndex !== optionIndex),\n })\n },\n [fields, updateField],\n )\n\n const readFieldError = React.useCallback(\n (fieldIndex: number, key: string) => errors?.[`customerFieldsSchema.${fieldIndex}.${key}`],\n [errors],\n )\n\n const readOptionError = React.useCallback(\n (fieldIndex: number, optionIndex: number, key: string) => (\n errors?.[`customerFieldsSchema.${fieldIndex}.options.${optionIndex}.${key}`]\n ),\n [errors],\n )\n\n const readNestedError = React.useCallback(\n (path: string) => {\n if (!errors) return undefined\n if (errors[path]) return errors[path]\n const prefix = `${path}.`\n const nestedEntry = Object.entries(errors).find(([key]) => key.startsWith(prefix))\n return nestedEntry?.[1]\n },\n [errors],\n )\n\n const inputClassName = React.useCallback(\n (error?: string) => (error ? 'border-destructive aria-invalid:ring-destructive' : undefined),\n [],\n )\n\n return (\n <div className=\"space-y-4\">\n <Alert variant=\"info\">\n <AlertDescription>\n {t('checkout.customerFieldsEditor.notices.defaultFields')}\n </AlertDescription>\n </Alert>\n\n <div className=\"overflow-hidden rounded-xl border border-border/70 bg-background\">\n <div className=\"hidden grid-cols-[1fr_1fr_1fr_180px_90px_110px] gap-3 border-b bg-muted/30 px-4 py-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground md:grid\">\n <div>{t('checkout.customerFieldsEditor.columns.fieldKey')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.label')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.placeholder')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.type')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.required')}</div>\n <div className=\"text-right\">{t('checkout.customerFieldsEditor.columns.actions')}</div>\n </div>\n\n {fields.length > 0 ? (\n <div className=\"divide-y divide-border/70\">\n {fields.map((field, index) => {\n const keyError = readFieldError(index, 'key')\n const labelError = readFieldError(index, 'label')\n const placeholderError = readFieldError(index, 'placeholder')\n const kindError = readFieldError(index, 'kind')\n const optionsError = readNestedError(`customerFieldsSchema.${index}.options`)\n\n return (\n <div key={index} className=\"space-y-3 px-4 py-4\">\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.fieldKey')}</Label>\n <Input\n value={field.key}\n onChange={(event) => updateField(index, { key: event.target.value })}\n disabled={field.fixed}\n placeholder={t('checkout.customerFieldsEditor.placeholders.fieldKey')}\n className={inputClassName(keyError)}\n aria-invalid={Boolean(keyError)}\n />\n {keyError ? <p className=\"text-xs text-destructive\">{keyError}</p> : null}\n </div>\n\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.label')}</Label>\n <Input\n value={field.label}\n onChange={(event) => updateField(index, { label: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.placeholders.label')}\n className={inputClassName(labelError)}\n aria-invalid={Boolean(labelError)}\n />\n {labelError ? <p className=\"text-xs text-destructive\">{labelError}</p> : null}\n </div>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-[1.2fr_180px_100px_110px] md:items-end\">\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.placeholder')}</Label>\n <Input\n value={field.placeholder ?? ''}\n onChange={(event) => updateField(index, { placeholder: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.placeholders.placeholder')}\n className={inputClassName(placeholderError)}\n aria-invalid={Boolean(placeholderError)}\n />\n {placeholderError ? <p className=\"text-xs text-destructive\">{placeholderError}</p> : null}\n </div>\n\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.type')}</Label>\n <select\n className={kindError\n ? 'w-full rounded-md border border-destructive bg-background px-3 py-2 text-sm aria-invalid:ring-destructive'\n : 'w-full rounded-md border bg-background px-3 py-2 text-sm'}\n value={field.kind}\n onChange={(event) => updateField(index, { kind: event.target.value as CustomerFieldDefinitionInput['kind'] })}\n disabled={field.fixed}\n aria-invalid={Boolean(kindError)}\n >\n <option value=\"text\">{t('checkout.customerFieldsEditor.types.text')}</option>\n <option value=\"multiline\">{t('checkout.customerFieldsEditor.types.multiline')}</option>\n <option value=\"boolean\">{t('checkout.customerFieldsEditor.types.boolean')}</option>\n <option value=\"select\">{t('checkout.customerFieldsEditor.types.select')}</option>\n <option value=\"radio\">{t('checkout.customerFieldsEditor.types.radio')}</option>\n </select>\n {kindError ? <p className=\"text-xs text-destructive\">{kindError}</p> : null}\n </div>\n\n <label className=\"flex h-9 items-center gap-2 rounded-md border border-border/70 bg-muted/30 px-3 text-sm\">\n <input\n type=\"checkbox\"\n checked={field.required}\n onChange={(event) => updateField(index, { required: event.target.checked })}\n />\n {t('checkout.customerFieldsEditor.columns.required')}\n </label>\n\n <div className=\"flex justify-end gap-2\">\n {!field.fixed ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => removeField(index)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n {t('checkout.common.actions.remove')}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled>\n {t('checkout.customerFieldsEditor.locked')}\n </Button>\n )}\n </div>\n </div>\n\n {(field.kind === 'select' || field.kind === 'radio') ? (\n <div className=\"space-y-2 rounded-lg border border-dashed border-border/70 bg-muted/30 p-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n <Settings2 className=\"h-4 w-4 text-muted-foreground\" />\n {t('checkout.customerFieldsEditor.options.title')}\n </div>\n {optionsError ? <p className=\"text-xs text-destructive\">{optionsError}</p> : null}\n <div className=\"overflow-hidden rounded-lg border border-border/70 bg-background\">\n <table className=\"w-full table-fixed\">\n <thead className=\"border-b bg-muted/30\">\n <tr className=\"text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-3 py-2 w-[40%]\">{t('checkout.customerFieldsEditor.options.value')}</th>\n <th className=\"px-3 py-2\">{t('checkout.customerFieldsEditor.options.label')}</th>\n <th className=\"px-3 py-2 w-[96px] text-right\">{t('checkout.customerFieldsEditor.columns.actions')}</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/70\">\n {(field.options ?? []).map((option, optionIndex) => {\n const optionValueError = readOptionError(index, optionIndex, 'value')\n const optionLabelError = readOptionError(index, optionIndex, 'label')\n\n return (\n <tr key={optionIndex}>\n <td className=\"px-3 py-2\">\n <Input\n value={option.value}\n onChange={(event) => updateOption(index, optionIndex, { value: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.options.placeholders.value')}\n className={`h-8 ${inputClassName(optionValueError) ?? ''}`.trim()}\n aria-invalid={Boolean(optionValueError)}\n />\n {optionValueError ? (\n <p className=\"mt-1 text-xs text-destructive\">{optionValueError}</p>\n ) : null}\n </td>\n <td className=\"px-3 py-2\">\n <Input\n value={option.label}\n onChange={(event) => updateOption(index, optionIndex, { label: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.options.placeholders.label')}\n className={`h-8 ${inputClassName(optionLabelError) ?? ''}`.trim()}\n aria-invalid={Boolean(optionLabelError)}\n />\n {optionLabelError ? (\n <p className=\"mt-1 text-xs text-destructive\">{optionLabelError}</p>\n ) : null}\n </td>\n <td className=\"px-3 py-2 text-right\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => removeOption(index, optionIndex)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addOption(index)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('checkout.customerFieldsEditor.options.add')}\n </Button>\n </div>\n ) : null}\n </div>\n )})}\n </div>\n ) : (\n <div className=\"px-4 py-8\">\n <Alert variant=\"info\">\n <AlertDescription>\n {t('checkout.customerFieldsEditor.notices.empty')}\n </AlertDescription>\n </Alert>\n </div>\n )}\n </div>\n\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onChange(withSortOrder([...fields, createField(fields.length, t)]))}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('checkout.customerFieldsEditor.addField')}\n </Button>\n </div>\n )\n}\n\nexport default CustomerFieldsEditor\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus, Settings2, Trash2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport type { CustomerFieldDefinitionInput } from '../data/validators'\nimport { getLocalizedDefaultCheckoutCustomerFields } from '../lib/defaults'\n\ntype Props = {\n value: CustomerFieldDefinitionInput[]\n onChange: (next: CustomerFieldDefinitionInput[]) => void\n errors?: Record<string, string>\n}\n\nfunction withSortOrder(next: CustomerFieldDefinitionInput[]): CustomerFieldDefinitionInput[] {\n return next.map((field, index) => ({\n ...field,\n sortOrder: index,\n }))\n}\n\nfunction createField(index: number, t: ReturnType<typeof useT>): CustomerFieldDefinitionInput {\n return {\n key: `customField${index + 1}`,\n label: t('checkout.customerFieldsEditor.newFieldLabel'),\n kind: 'text',\n required: false,\n fixed: false,\n placeholder: t('checkout.customerFieldsEditor.newFieldPlaceholder'),\n sortOrder: index,\n options: [],\n }\n}\n\nexport function CustomerFieldsEditor({ value, onChange, errors }: Props) {\n const t = useT()\n const fields = React.useMemo<CustomerFieldDefinitionInput[]>(\n () => (\n Array.isArray(value)\n ? value\n : getLocalizedDefaultCheckoutCustomerFields(t).map((field) => ({ ...field, options: [] }))\n ),\n [t, value],\n )\n\n const updateField = React.useCallback(\n (index: number, patch: Partial<CustomerFieldDefinitionInput>) => {\n onChange(\n withSortOrder(\n fields.map((field, currentIndex) => (currentIndex === index ? { ...field, ...patch } : field)),\n ),\n )\n },\n [fields, onChange],\n )\n\n const removeField = React.useCallback(\n (index: number) => {\n onChange(withSortOrder(fields.filter((_, currentIndex) => currentIndex !== index)))\n },\n [fields, onChange],\n )\n\n const updateOption = React.useCallback(\n (fieldIndex: number, optionIndex: number, patch: { value?: string; label?: string }) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: options.map((option: { value: string; label: string }, currentIndex: number) => (\n currentIndex === optionIndex ? { ...option, ...patch } : option\n )),\n })\n },\n [fields, updateField],\n )\n\n const addOption = React.useCallback(\n (fieldIndex: number) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: [\n ...options,\n {\n value: `option_${options.length + 1}`,\n label: t('checkout.customerFieldsEditor.options.defaultLabel', 'Option {index}', { index: options.length + 1 }),\n },\n ],\n })\n },\n [fields, t, updateField],\n )\n\n const removeOption = React.useCallback(\n (fieldIndex: number, optionIndex: number) => {\n const field = fields[fieldIndex]\n if (!field) return\n const options = Array.isArray(field.options) ? field.options : []\n updateField(fieldIndex, {\n options: options.filter((_: { value: string; label: string }, currentIndex: number) => currentIndex !== optionIndex),\n })\n },\n [fields, updateField],\n )\n\n const readFieldError = React.useCallback(\n (fieldIndex: number, key: string) => errors?.[`customerFieldsSchema.${fieldIndex}.${key}`],\n [errors],\n )\n\n const readOptionError = React.useCallback(\n (fieldIndex: number, optionIndex: number, key: string) => (\n errors?.[`customerFieldsSchema.${fieldIndex}.options.${optionIndex}.${key}`]\n ),\n [errors],\n )\n\n const readNestedError = React.useCallback(\n (path: string) => {\n if (!errors) return undefined\n if (errors[path]) return errors[path]\n const prefix = `${path}.`\n const nestedEntry = Object.entries(errors).find(([key]) => key.startsWith(prefix))\n return nestedEntry?.[1]\n },\n [errors],\n )\n\n const inputClassName = React.useCallback(\n (error?: string) => (error ? 'border-destructive aria-invalid:ring-destructive' : undefined),\n [],\n )\n\n return (\n <div className=\"space-y-4\">\n <Alert status=\"information\">\n <AlertDescription>\n {t('checkout.customerFieldsEditor.notices.defaultFields')}\n </AlertDescription>\n </Alert>\n\n <div className=\"overflow-hidden rounded-xl border border-border/70 bg-background\">\n <div className=\"hidden grid-cols-[1fr_1fr_1fr_180px_90px_110px] gap-3 border-b bg-muted/30 px-4 py-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground md:grid\">\n <div>{t('checkout.customerFieldsEditor.columns.fieldKey')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.label')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.placeholder')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.type')}</div>\n <div>{t('checkout.customerFieldsEditor.columns.required')}</div>\n <div className=\"text-right\">{t('checkout.customerFieldsEditor.columns.actions')}</div>\n </div>\n\n {fields.length > 0 ? (\n <div className=\"divide-y divide-border/70\">\n {fields.map((field, index) => {\n const keyError = readFieldError(index, 'key')\n const labelError = readFieldError(index, 'label')\n const placeholderError = readFieldError(index, 'placeholder')\n const kindError = readFieldError(index, 'kind')\n const optionsError = readNestedError(`customerFieldsSchema.${index}.options`)\n\n return (\n <div key={index} className=\"space-y-3 px-4 py-4\">\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.fieldKey')}</Label>\n <Input\n value={field.key}\n onChange={(event) => updateField(index, { key: event.target.value })}\n disabled={field.fixed}\n placeholder={t('checkout.customerFieldsEditor.placeholders.fieldKey')}\n className={inputClassName(keyError)}\n aria-invalid={Boolean(keyError)}\n />\n {keyError ? <p className=\"text-xs text-destructive\">{keyError}</p> : null}\n </div>\n\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.label')}</Label>\n <Input\n value={field.label}\n onChange={(event) => updateField(index, { label: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.placeholders.label')}\n className={inputClassName(labelError)}\n aria-invalid={Boolean(labelError)}\n />\n {labelError ? <p className=\"text-xs text-destructive\">{labelError}</p> : null}\n </div>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-[1.2fr_180px_100px_110px] md:items-end\">\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.placeholder')}</Label>\n <Input\n value={field.placeholder ?? ''}\n onChange={(event) => updateField(index, { placeholder: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.placeholders.placeholder')}\n className={inputClassName(placeholderError)}\n aria-invalid={Boolean(placeholderError)}\n />\n {placeholderError ? <p className=\"text-xs text-destructive\">{placeholderError}</p> : null}\n </div>\n\n <div className=\"space-y-1\">\n <Label>{t('checkout.customerFieldsEditor.columns.type')}</Label>\n <select\n className={kindError\n ? 'w-full rounded-md border border-destructive bg-background px-3 py-2 text-sm aria-invalid:ring-destructive'\n : 'w-full rounded-md border bg-background px-3 py-2 text-sm'}\n value={field.kind}\n onChange={(event) => updateField(index, { kind: event.target.value as CustomerFieldDefinitionInput['kind'] })}\n disabled={field.fixed}\n aria-invalid={Boolean(kindError)}\n >\n <option value=\"text\">{t('checkout.customerFieldsEditor.types.text')}</option>\n <option value=\"multiline\">{t('checkout.customerFieldsEditor.types.multiline')}</option>\n <option value=\"boolean\">{t('checkout.customerFieldsEditor.types.boolean')}</option>\n <option value=\"select\">{t('checkout.customerFieldsEditor.types.select')}</option>\n <option value=\"radio\">{t('checkout.customerFieldsEditor.types.radio')}</option>\n </select>\n {kindError ? <p className=\"text-xs text-destructive\">{kindError}</p> : null}\n </div>\n\n <label className=\"flex h-9 items-center gap-2 rounded-md border border-border/70 bg-muted/30 px-3 text-sm\">\n <input\n type=\"checkbox\"\n checked={field.required}\n onChange={(event) => updateField(index, { required: event.target.checked })}\n />\n {t('checkout.customerFieldsEditor.columns.required')}\n </label>\n\n <div className=\"flex justify-end gap-2\">\n {!field.fixed ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => removeField(index)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n {t('checkout.common.actions.remove')}\n </Button>\n ) : (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled>\n {t('checkout.customerFieldsEditor.locked')}\n </Button>\n )}\n </div>\n </div>\n\n {(field.kind === 'select' || field.kind === 'radio') ? (\n <div className=\"space-y-2 rounded-lg border border-dashed border-border/70 bg-muted/30 p-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n <Settings2 className=\"h-4 w-4 text-muted-foreground\" />\n {t('checkout.customerFieldsEditor.options.title')}\n </div>\n {optionsError ? <p className=\"text-xs text-destructive\">{optionsError}</p> : null}\n <div className=\"overflow-hidden rounded-lg border border-border/70 bg-background\">\n <table className=\"w-full table-fixed\">\n <thead className=\"border-b bg-muted/30\">\n <tr className=\"text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <th className=\"px-3 py-2 w-[40%]\">{t('checkout.customerFieldsEditor.options.value')}</th>\n <th className=\"px-3 py-2\">{t('checkout.customerFieldsEditor.options.label')}</th>\n <th className=\"px-3 py-2 w-[96px] text-right\">{t('checkout.customerFieldsEditor.columns.actions')}</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/70\">\n {(field.options ?? []).map((option, optionIndex) => {\n const optionValueError = readOptionError(index, optionIndex, 'value')\n const optionLabelError = readOptionError(index, optionIndex, 'label')\n\n return (\n <tr key={optionIndex}>\n <td className=\"px-3 py-2\">\n <Input\n value={option.value}\n onChange={(event) => updateOption(index, optionIndex, { value: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.options.placeholders.value')}\n className={`h-8 ${inputClassName(optionValueError) ?? ''}`.trim()}\n aria-invalid={Boolean(optionValueError)}\n />\n {optionValueError ? (\n <p className=\"mt-1 text-xs text-destructive\">{optionValueError}</p>\n ) : null}\n </td>\n <td className=\"px-3 py-2\">\n <Input\n value={option.label}\n onChange={(event) => updateOption(index, optionIndex, { label: event.target.value })}\n placeholder={t('checkout.customerFieldsEditor.options.placeholders.label')}\n className={`h-8 ${inputClassName(optionLabelError) ?? ''}`.trim()}\n aria-invalid={Boolean(optionLabelError)}\n />\n {optionLabelError ? (\n <p className=\"mt-1 text-xs text-destructive\">{optionLabelError}</p>\n ) : null}\n </td>\n <td className=\"px-3 py-2 text-right\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => removeOption(index, optionIndex)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addOption(index)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('checkout.customerFieldsEditor.options.add')}\n </Button>\n </div>\n ) : null}\n </div>\n )})}\n </div>\n ) : (\n <div className=\"px-4 py-8\">\n <Alert status=\"information\">\n <AlertDescription>\n {t('checkout.customerFieldsEditor.notices.empty')}\n </AlertDescription>\n </Alert>\n </div>\n )}\n </div>\n\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onChange(withSortOrder([...fields, createField(fields.length, t)]))}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('checkout.customerFieldsEditor.addField')}\n </Button>\n </div>\n )\n}\n\nexport default CustomerFieldsEditor\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -57,10 +57,10 @@ function GatewaySettingsFields({ providerKey, value, onChange }) {
|
|
|
57
57
|
}, [providerKey]);
|
|
58
58
|
const fields = descriptor?.sessionConfig?.fields ?? [];
|
|
59
59
|
if (!providerKey) {
|
|
60
|
-
return /* @__PURE__ */ jsx(Alert, {
|
|
60
|
+
return /* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.gatewaySettings.notices.chooseProvider") }) });
|
|
61
61
|
}
|
|
62
62
|
if (!fields.length) {
|
|
63
|
-
return /* @__PURE__ */ jsx(Alert, {
|
|
63
|
+
return /* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.gatewaySettings.notices.noSettings") }) });
|
|
64
64
|
}
|
|
65
65
|
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: fields.map((field) => {
|
|
66
66
|
const currentValue = value?.[field.key];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/checkout/components/GatewaySettingsFields.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\ntype Descriptor = {\n providerKey: string\n label: string\n sessionConfig?: {\n fields?: Array<{\n key: string\n label: string\n type: 'text' | 'number' | 'select' | 'multiselect' | 'boolean' | 'textarea' | 'secret' | 'url'\n description?: string\n options?: Array<{ value: string; label: string }>\n }>\n }\n}\n\ntype Props = {\n providerKey: string | null | undefined\n value: Record<string, unknown> | null | undefined\n onChange: (next: Record<string, unknown>) => void\n}\n\nexport function GatewaySettingsFields({ providerKey, value, onChange }: Props) {\n const t = useT()\n const [descriptor, setDescriptor] = React.useState<Descriptor | null>(null)\n\n const patchSetting = React.useCallback(\n (fieldKey: string, nextValue: unknown) => {\n const next = { ...(value ?? {}) }\n if (nextValue === undefined || nextValue === null || nextValue === '') {\n delete next[fieldKey]\n } else {\n next[fieldKey] = nextValue\n }\n onChange(next)\n },\n [onChange, value],\n )\n\n const toggleMultiselectValue = React.useCallback(\n (fieldKey: string, optionValue: string) => {\n const current = Array.isArray(value?.[fieldKey])\n ? value?.[fieldKey].filter((entry): entry is string => typeof entry === 'string')\n : []\n const next = current.includes(optionValue)\n ? current.filter((entry) => entry !== optionValue)\n : [...current, optionValue]\n patchSetting(fieldKey, next.length ? next : undefined)\n },\n [patchSetting, value],\n )\n\n React.useEffect(() => {\n let active = true\n if (!providerKey) {\n setDescriptor(null)\n return () => { active = false }\n }\n void readApiResultOrThrow<Descriptor>(`/api/payment_gateways/providers/${encodeURIComponent(providerKey)}`)\n .then((result) => {\n if (active) setDescriptor(result)\n })\n .catch(() => {\n if (active) setDescriptor(null)\n })\n return () => { active = false }\n }, [providerKey])\n\n const fields = descriptor?.sessionConfig?.fields ?? []\n if (!providerKey) {\n return (\n <Alert
|
|
5
|
-
"mappings": ";AAuFQ,cAwBM,YAxBN;AAtFR,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,4BAA4B;AACrC,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,OAAO,wBAAwB;AACxC,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAsBA,SAAS,sBAAsB,EAAE,aAAa,OAAO,SAAS,GAAU;AAC7E,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAA4B,IAAI;AAE1E,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,UAAkB,cAAuB;AACxC,YAAM,OAAO,EAAE,GAAI,SAAS,CAAC,EAAG;AAChC,UAAI,cAAc,UAAa,cAAc,QAAQ,cAAc,IAAI;AACrE,eAAO,KAAK,QAAQ;AAAA,MACtB,OAAO;AACL,aAAK,QAAQ,IAAI;AAAA,MACnB;AACA,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,UAAU,KAAK;AAAA,EAClB;AAEA,QAAM,yBAAyB,MAAM;AAAA,IACnC,CAAC,UAAkB,gBAAwB;AACzC,YAAM,UAAU,MAAM,QAAQ,QAAQ,QAAQ,CAAC,IAC3C,QAAQ,QAAQ,EAAE,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ,IAC9E,CAAC;AACL,YAAM,OAAO,QAAQ,SAAS,WAAW,IACrC,QAAQ,OAAO,CAAC,UAAU,UAAU,WAAW,IAC/C,CAAC,GAAG,SAAS,WAAW;AAC5B,mBAAa,UAAU,KAAK,SAAS,OAAO,MAAS;AAAA,IACvD;AAAA,IACA,CAAC,cAAc,KAAK;AAAA,EACtB;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,SAAS;AACb,QAAI,CAAC,aAAa;AAChB,oBAAc,IAAI;AAClB,aAAO,MAAM;AAAE,iBAAS;AAAA,MAAM;AAAA,IAChC;AACA,SAAK,qBAAiC,mCAAmC,mBAAmB,WAAW,CAAC,EAAE,EACvG,KAAK,CAAC,WAAW;AAChB,UAAI,OAAQ,eAAc,MAAM;AAAA,IAClC,CAAC,EACA,MAAM,MAAM;AACX,UAAI,OAAQ,eAAc,IAAI;AAAA,IAChC,CAAC;AACH,WAAO,MAAM;AAAE,eAAS;AAAA,IAAM;AAAA,EAChC,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,SAAS,YAAY,eAAe,UAAU,CAAC;AACrD,MAAI,CAAC,aAAa;AAChB,WACE,oBAAC,SAAM,
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\ntype Descriptor = {\n providerKey: string\n label: string\n sessionConfig?: {\n fields?: Array<{\n key: string\n label: string\n type: 'text' | 'number' | 'select' | 'multiselect' | 'boolean' | 'textarea' | 'secret' | 'url'\n description?: string\n options?: Array<{ value: string; label: string }>\n }>\n }\n}\n\ntype Props = {\n providerKey: string | null | undefined\n value: Record<string, unknown> | null | undefined\n onChange: (next: Record<string, unknown>) => void\n}\n\nexport function GatewaySettingsFields({ providerKey, value, onChange }: Props) {\n const t = useT()\n const [descriptor, setDescriptor] = React.useState<Descriptor | null>(null)\n\n const patchSetting = React.useCallback(\n (fieldKey: string, nextValue: unknown) => {\n const next = { ...(value ?? {}) }\n if (nextValue === undefined || nextValue === null || nextValue === '') {\n delete next[fieldKey]\n } else {\n next[fieldKey] = nextValue\n }\n onChange(next)\n },\n [onChange, value],\n )\n\n const toggleMultiselectValue = React.useCallback(\n (fieldKey: string, optionValue: string) => {\n const current = Array.isArray(value?.[fieldKey])\n ? value?.[fieldKey].filter((entry): entry is string => typeof entry === 'string')\n : []\n const next = current.includes(optionValue)\n ? current.filter((entry) => entry !== optionValue)\n : [...current, optionValue]\n patchSetting(fieldKey, next.length ? next : undefined)\n },\n [patchSetting, value],\n )\n\n React.useEffect(() => {\n let active = true\n if (!providerKey) {\n setDescriptor(null)\n return () => { active = false }\n }\n void readApiResultOrThrow<Descriptor>(`/api/payment_gateways/providers/${encodeURIComponent(providerKey)}`)\n .then((result) => {\n if (active) setDescriptor(result)\n })\n .catch(() => {\n if (active) setDescriptor(null)\n })\n return () => { active = false }\n }, [providerKey])\n\n const fields = descriptor?.sessionConfig?.fields ?? []\n if (!providerKey) {\n return (\n <Alert status=\"information\">\n <AlertDescription>\n {t('checkout.gatewaySettings.notices.chooseProvider')}\n </AlertDescription>\n </Alert>\n )\n }\n if (!fields.length) {\n return (\n <Alert status=\"information\">\n <AlertDescription>\n {t('checkout.gatewaySettings.notices.noSettings')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {fields.map((field) => {\n const currentValue = value?.[field.key]\n return (\n <div key={field.key} className=\"space-y-2 rounded-lg border border-border/70 bg-muted/30 p-3\">\n <Label>{field.label}</Label>\n {field.type === 'select' ? (\n <Select\n value={typeof currentValue === 'string' ? currentValue : ''}\n onValueChange={(next) => patchSetting(field.key, next)}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('checkout.gatewaySettings.selectPlaceholder')} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map((option) => (\n <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'multiselect' ? (\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {(field.options ?? []).map((option) => {\n const selectedValues = Array.isArray(currentValue)\n ? currentValue.filter((entry): entry is string => typeof entry === 'string')\n : []\n const checked = selectedValues.includes(option.value)\n return (\n <label\n key={option.value}\n className=\"flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm\"\n >\n <Checkbox\n checked={checked}\n onCheckedChange={() => toggleMultiselectValue(field.key, option.value)}\n />\n <span>{option.label}</span>\n </label>\n )\n })}\n </div>\n ) : field.type === 'boolean' ? (\n <label className=\"flex items-center gap-2 text-sm\">\n <Checkbox\n checked={currentValue === true}\n onCheckedChange={(next) => patchSetting(field.key, next === true)}\n />\n {t('checkout.common.enabled')}\n </label>\n ) : field.type === 'textarea' ? (\n <Textarea\n value={typeof currentValue === 'string' ? currentValue : ''}\n onChange={(event) => patchSetting(field.key, event.target.value)}\n />\n ) : (\n <Input\n type={field.type === 'number' ? 'number' : field.type === 'secret' ? 'password' : 'text'}\n value={typeof currentValue === 'string' || typeof currentValue === 'number' ? `${currentValue}` : ''}\n onChange={(event) => patchSetting(field.key, event.target.value)}\n />\n )}\n {field.description ? <p className=\"text-xs text-muted-foreground\">{field.description}</p> : null}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default GatewaySettingsFields\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -324,7 +324,7 @@ function PriceListEditor({
|
|
|
324
324
|
]);
|
|
325
325
|
}, [items, onChange]);
|
|
326
326
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
327
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
327
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.priceList.notices.singleCurrency") }) }),
|
|
328
328
|
error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null,
|
|
329
329
|
/* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-border/70 bg-background", children: items.length > 0 ? /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[760px] table-fixed", children: [
|
|
330
330
|
/* @__PURE__ */ jsx("thead", { className: "border-b bg-muted/30", children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
|
|
@@ -390,7 +390,7 @@ function PriceListEditor({
|
|
|
390
390
|
}
|
|
391
391
|
) })
|
|
392
392
|
] }, `${item.id}:${index}`)) })
|
|
393
|
-
] }) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-8", children: /* @__PURE__ */ jsx(Alert, {
|
|
393
|
+
] }) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-8", children: /* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.priceList.notices.empty") }) }) }) }),
|
|
394
394
|
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addItem, children: [
|
|
395
395
|
/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
|
|
396
396
|
t("checkout.linkTemplateForm.priceList.actions.addItem")
|
|
@@ -823,7 +823,7 @@ function CustomerDetailsSection({ values, setValue, errors }) {
|
|
|
823
823
|
] }),
|
|
824
824
|
collectCustomerDetailsError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: collectCustomerDetailsError }) : null,
|
|
825
825
|
collectCustomerDetails ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
826
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
826
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.customerDetails.notices.simpleLink") }) }),
|
|
827
827
|
customerFieldsError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: customerFieldsError }) : null,
|
|
828
828
|
/* @__PURE__ */ jsx(
|
|
829
829
|
CustomerFieldsEditor,
|
|
@@ -833,7 +833,7 @@ function CustomerDetailsSection({ values, setValue, errors }) {
|
|
|
833
833
|
errors
|
|
834
834
|
}
|
|
835
835
|
)
|
|
836
|
-
] }) : /* @__PURE__ */ jsx(Alert, {
|
|
836
|
+
] }) : /* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.customerDetails.notices.disabled") }) })
|
|
837
837
|
] });
|
|
838
838
|
}
|
|
839
839
|
function LegalSection({ values, setValue, errors }) {
|
|
@@ -857,7 +857,7 @@ function LegalSection({ values, setValue, errors }) {
|
|
|
857
857
|
[legalDocuments, setValue]
|
|
858
858
|
);
|
|
859
859
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
860
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
860
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.legal.notice") }) }),
|
|
861
861
|
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (next) => setTab(next), children: [
|
|
862
862
|
/* @__PURE__ */ jsxs(TabsList, { className: SETTINGS_TABS_LIST_CLASS, children: [
|
|
863
863
|
/* @__PURE__ */ jsxs(TabsTrigger, { value: "terms", className: SETTINGS_TABS_TRIGGER_CLASS, children: [
|
|
@@ -965,7 +965,7 @@ function MessagesSection({ values, setValue, errors }) {
|
|
|
965
965
|
}
|
|
966
966
|
};
|
|
967
967
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
968
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
968
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.messages.notice") }) }),
|
|
969
969
|
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (next) => setTab(next), children: [
|
|
970
970
|
/* @__PURE__ */ jsx(TabsList, { className: SETTINGS_TABS_LIST_CLASS, children: ["success", "cancel", "error"].map((item) => /* @__PURE__ */ jsxs(TabsTrigger, { value: item, className: SETTINGS_TABS_TRIGGER_CLASS, children: [
|
|
971
971
|
/* @__PURE__ */ jsx(MessageSquare, { className: "mr-2 h-4 w-4" }),
|
|
@@ -1044,7 +1044,7 @@ function EmailsSection({ values, setValue, errors }) {
|
|
|
1044
1044
|
}
|
|
1045
1045
|
};
|
|
1046
1046
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1047
|
-
/* @__PURE__ */ jsx(Alert, {
|
|
1047
|
+
/* @__PURE__ */ jsx(Alert, { status: "information", children: /* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.emails.notice") }) }),
|
|
1048
1048
|
/* @__PURE__ */ jsx(VariableHint, {}),
|
|
1049
1049
|
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (next) => setTab(next), children: [
|
|
1050
1050
|
/* @__PURE__ */ jsx(TabsList, { className: SETTINGS_TABS_LIST_CLASS, children: ["start", "success", "error"].map((item) => {
|
|
@@ -1357,7 +1357,7 @@ function LinkTemplateForm({ mode, recordId }) {
|
|
|
1357
1357
|
() => `checkout-${mode}-editor-${recordId ?? "new"}`,
|
|
1358
1358
|
[mode, recordId]
|
|
1359
1359
|
);
|
|
1360
|
-
const lockedNotice = isLocked ? /* @__PURE__ */ jsxs(Alert, {
|
|
1360
|
+
const lockedNotice = isLocked ? /* @__PURE__ */ jsxs(Alert, { status: "warning", children: [
|
|
1361
1361
|
/* @__PURE__ */ jsx(AlertTitle, { children: t("checkout.linkTemplateForm.locked.title") }),
|
|
1362
1362
|
/* @__PURE__ */ jsx(AlertDescription, { children: t("checkout.linkTemplateForm.locked.description") })
|
|
1363
1363
|
] }) : void 0;
|