@open-mercato/checkout 0.6.6-develop.6184.1.b7e55f8d61 → 0.6.6-develop.6198.1.0822f97cc6

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.
@@ -41,8 +41,8 @@ test.describe("TC-CHKT-035: Terms/privacy links open popup with sanitized markdo
41
41
  expect(await page.evaluate(() => window.__checkoutTermsInjected ?? null)).toBeNull();
42
42
  await page.getByRole("button", { name: /close/i }).click();
43
43
  await expect(page.getByRole("dialog")).toBeHidden();
44
- await page.locator('input[type="checkbox"]').nth(0).check();
45
- await page.locator('input[type="checkbox"]').nth(1).check();
44
+ await page.getByRole("checkbox", { name: /terms of service/i }).check();
45
+ await page.getByRole("checkbox", { name: /privacy policy/i }).check();
46
46
  await page.getByRole("button", { name: /pay now/i }).click();
47
47
  await page.waitForURL(new RegExp(`/pay/${link.slug}/success/`));
48
48
  const transactionId = page.url().split("/").pop();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/checkout/__integration__/TC-CHKT-035.spec.ts"],
4
- "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n createCheckoutClientHeaders,\n createFixedTemplateInput,\n createLinkFixture,\n deleteCheckoutEntityIfExists,\n readCheckoutTransaction,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-035: Terms/privacy links open popup with sanitized markdown content and accepted proof is stored on transaction', () => {\n test('opens the legal-document modal, keeps unsafe markdown inert, and persists consent proof on the transaction', async ({ page, request }) => {\n let token: string | null = null\n let linkId: string | null = null\n\n try {\n token = await getAuthToken(request)\n const link = await createLinkFixture(request, token, {\n ...createFixedTemplateInput({\n status: 'active',\n collectCustomerDetails: false,\n legalDocuments: {\n terms: {\n title: 'Terms of Service',\n markdown: '<script>window.__checkoutTermsInjected = true</script>\\n\\n**Keep this safe**',\n required: true,\n },\n privacyPolicy: {\n title: 'Privacy Policy',\n markdown: 'We only use your data for this checkout.',\n required: true,\n },\n },\n }),\n })\n linkId = link.id\n\n await page.context().setExtraHTTPHeaders(createCheckoutClientHeaders())\n await page.goto(`/pay/${encodeURIComponent(link.slug)}`)\n await page.getByRole('button', { name: /read document/i }).first().click()\n\n await expect(page.getByRole('dialog')).toBeVisible()\n await expect(page.getByRole('heading', { name: 'Terms of Service' })).toBeVisible()\n await expect(page.getByText('Keep this safe')).toBeVisible()\n expect(await page.evaluate(() => (window as Window & { __checkoutTermsInjected?: boolean }).__checkoutTermsInjected ?? null)).toBeNull()\n await page.getByRole('button', { name: /close/i }).click()\n await expect(page.getByRole('dialog')).toBeHidden()\n\n await page.locator('input[type=\"checkbox\"]').nth(0).check()\n await page.locator('input[type=\"checkbox\"]').nth(1).check()\n await page.getByRole('button', { name: /pay now/i }).click()\n await page.waitForURL(new RegExp(`/pay/${link.slug}/success/`))\n\n const transactionId = page.url().split('/').pop()\n expect(transactionId).toBeTruthy()\n\n const transaction = await readCheckoutTransaction(request, token, transactionId!)\n expect(transaction.acceptedLegalConsents).toMatchObject({\n terms: {\n title: 'Terms of Service',\n required: true,\n markdownHash: expect.any(String),\n acceptedAt: expect.any(String),\n },\n privacyPolicy: {\n title: 'Privacy Policy',\n required: true,\n markdownHash: expect.any(String),\n acceptedAt: expect.any(String),\n },\n })\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
5
- "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,2HAA2H,MAAM;AAC7I,OAAK,8GAA8G,OAAO,EAAE,MAAM,QAAQ,MAAM;AAC9I,QAAI,QAAuB;AAC3B,QAAI,SAAwB;AAE5B,QAAI;AACF,cAAQ,MAAM,aAAa,OAAO;AAClC,YAAM,OAAO,MAAM,kBAAkB,SAAS,OAAO;AAAA,QACnD,GAAG,yBAAyB;AAAA,UAC1B,QAAQ;AAAA,UACR,wBAAwB;AAAA,UACxB,gBAAgB;AAAA,YACd,OAAO;AAAA,cACL,OAAO;AAAA,cACP,UAAU;AAAA,cACV,UAAU;AAAA,YACZ;AAAA,YACA,eAAe;AAAA,cACb,OAAO;AAAA,cACP,UAAU;AAAA,cACV,UAAU;AAAA,YACZ;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AACD,eAAS,KAAK;AAEd,YAAM,KAAK,QAAQ,EAAE,oBAAoB,4BAA4B,CAAC;AACtE,YAAM,KAAK,KAAK,QAAQ,mBAAmB,KAAK,IAAI,CAAC,EAAE;AACvD,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM;AAEzE,YAAM,OAAO,KAAK,UAAU,QAAQ,CAAC,EAAE,YAAY;AACnD,YAAM,OAAO,KAAK,UAAU,WAAW,EAAE,MAAM,mBAAmB,CAAC,CAAC,EAAE,YAAY;AAClF,YAAM,OAAO,KAAK,UAAU,gBAAgB,CAAC,EAAE,YAAY;AAC3D,aAAO,MAAM,KAAK,SAAS,MAAO,OAA0D,2BAA2B,IAAI,CAAC,EAAE,SAAS;AACvI,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,EAAE,MAAM;AACzD,YAAM,OAAO,KAAK,UAAU,QAAQ,CAAC,EAAE,WAAW;AAElD,YAAM,KAAK,QAAQ,wBAAwB,EAAE,IAAI,CAAC,EAAE,MAAM;AAC1D,YAAM,KAAK,QAAQ,wBAAwB,EAAE,IAAI,CAAC,EAAE,MAAM;AAC1D,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC,EAAE,MAAM;AAC3D,YAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,IAAI,WAAW,CAAC;AAE9D,YAAM,gBAAgB,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAChD,aAAO,aAAa,EAAE,WAAW;AAEjC,YAAM,cAAc,MAAM,wBAAwB,SAAS,OAAO,aAAc;AAChF,aAAO,YAAY,qBAAqB,EAAE,cAAc;AAAA,QACtD,OAAO;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc,OAAO,IAAI,MAAM;AAAA,UAC/B,YAAY,OAAO,IAAI,MAAM;AAAA,QAC/B;AAAA,QACA,eAAe;AAAA,UACb,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc,OAAO,IAAI,MAAM;AAAA,UAC/B,YAAY,OAAO,IAAI,MAAM;AAAA,QAC/B;AAAA,MACF,CAAC;AAAA,IACH,UAAE;AACA,YAAM,6BAA6B,SAAS,OAAO,SAAS,MAAM;AAAA,IACpE;AAAA,EACF,CAAC;AACH,CAAC;",
4
+ "sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { getAuthToken } from '@open-mercato/core/modules/core/__integration__/helpers/api'\nimport {\n createCheckoutClientHeaders,\n createFixedTemplateInput,\n createLinkFixture,\n deleteCheckoutEntityIfExists,\n readCheckoutTransaction,\n} from './helpers/fixtures'\n\ntest.describe('TC-CHKT-035: Terms/privacy links open popup with sanitized markdown content and accepted proof is stored on transaction', () => {\n test('opens the legal-document modal, keeps unsafe markdown inert, and persists consent proof on the transaction', async ({ page, request }) => {\n let token: string | null = null\n let linkId: string | null = null\n\n try {\n token = await getAuthToken(request)\n const link = await createLinkFixture(request, token, {\n ...createFixedTemplateInput({\n status: 'active',\n collectCustomerDetails: false,\n legalDocuments: {\n terms: {\n title: 'Terms of Service',\n markdown: '<script>window.__checkoutTermsInjected = true</script>\\n\\n**Keep this safe**',\n required: true,\n },\n privacyPolicy: {\n title: 'Privacy Policy',\n markdown: 'We only use your data for this checkout.',\n required: true,\n },\n },\n }),\n })\n linkId = link.id\n\n await page.context().setExtraHTTPHeaders(createCheckoutClientHeaders())\n await page.goto(`/pay/${encodeURIComponent(link.slug)}`)\n await page.getByRole('button', { name: /read document/i }).first().click()\n\n await expect(page.getByRole('dialog')).toBeVisible()\n await expect(page.getByRole('heading', { name: 'Terms of Service' })).toBeVisible()\n await expect(page.getByText('Keep this safe')).toBeVisible()\n expect(await page.evaluate(() => (window as Window & { __checkoutTermsInjected?: boolean }).__checkoutTermsInjected ?? null)).toBeNull()\n await page.getByRole('button', { name: /close/i }).click()\n await expect(page.getByRole('dialog')).toBeHidden()\n\n await page.getByRole('checkbox', { name: /terms of service/i }).check()\n await page.getByRole('checkbox', { name: /privacy policy/i }).check()\n await page.getByRole('button', { name: /pay now/i }).click()\n await page.waitForURL(new RegExp(`/pay/${link.slug}/success/`))\n\n const transactionId = page.url().split('/').pop()\n expect(transactionId).toBeTruthy()\n\n const transaction = await readCheckoutTransaction(request, token, transactionId!)\n expect(transaction.acceptedLegalConsents).toMatchObject({\n terms: {\n title: 'Terms of Service',\n required: true,\n markdownHash: expect.any(String),\n acceptedAt: expect.any(String),\n },\n privacyPolicy: {\n title: 'Privacy Policy',\n required: true,\n markdownHash: expect.any(String),\n acceptedAt: expect.any(String),\n },\n })\n } finally {\n await deleteCheckoutEntityIfExists(request, token, 'links', linkId)\n }\n })\n})\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAY;AAC7B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,KAAK,SAAS,2HAA2H,MAAM;AAC7I,OAAK,8GAA8G,OAAO,EAAE,MAAM,QAAQ,MAAM;AAC9I,QAAI,QAAuB;AAC3B,QAAI,SAAwB;AAE5B,QAAI;AACF,cAAQ,MAAM,aAAa,OAAO;AAClC,YAAM,OAAO,MAAM,kBAAkB,SAAS,OAAO;AAAA,QACnD,GAAG,yBAAyB;AAAA,UAC1B,QAAQ;AAAA,UACR,wBAAwB;AAAA,UACxB,gBAAgB;AAAA,YACd,OAAO;AAAA,cACL,OAAO;AAAA,cACP,UAAU;AAAA,cACV,UAAU;AAAA,YACZ;AAAA,YACA,eAAe;AAAA,cACb,OAAO;AAAA,cACP,UAAU;AAAA,cACV,UAAU;AAAA,YACZ;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AACD,eAAS,KAAK;AAEd,YAAM,KAAK,QAAQ,EAAE,oBAAoB,4BAA4B,CAAC;AACtE,YAAM,KAAK,KAAK,QAAQ,mBAAmB,KAAK,IAAI,CAAC,EAAE;AACvD,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM;AAEzE,YAAM,OAAO,KAAK,UAAU,QAAQ,CAAC,EAAE,YAAY;AACnD,YAAM,OAAO,KAAK,UAAU,WAAW,EAAE,MAAM,mBAAmB,CAAC,CAAC,EAAE,YAAY;AAClF,YAAM,OAAO,KAAK,UAAU,gBAAgB,CAAC,EAAE,YAAY;AAC3D,aAAO,MAAM,KAAK,SAAS,MAAO,OAA0D,2BAA2B,IAAI,CAAC,EAAE,SAAS;AACvI,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,EAAE,MAAM;AACzD,YAAM,OAAO,KAAK,UAAU,QAAQ,CAAC,EAAE,WAAW;AAElD,YAAM,KAAK,UAAU,YAAY,EAAE,MAAM,oBAAoB,CAAC,EAAE,MAAM;AACtE,YAAM,KAAK,UAAU,YAAY,EAAE,MAAM,kBAAkB,CAAC,EAAE,MAAM;AACpE,YAAM,KAAK,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC,EAAE,MAAM;AAC3D,YAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,IAAI,WAAW,CAAC;AAE9D,YAAM,gBAAgB,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAChD,aAAO,aAAa,EAAE,WAAW;AAEjC,YAAM,cAAc,MAAM,wBAAwB,SAAS,OAAO,aAAc;AAChF,aAAO,YAAY,qBAAqB,EAAE,cAAc;AAAA,QACtD,OAAO;AAAA,UACL,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc,OAAO,IAAI,MAAM;AAAA,UAC/B,YAAY,OAAO,IAAI,MAAM;AAAA,QAC/B;AAAA,QACA,eAAe;AAAA,UACb,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc,OAAO,IAAI,MAAM;AAAA,UAC/B,YAAY,OAAO,IAAI,MAAM;AAAA,QAC/B;AAAA,MACF,CAAC;AAAA,IACH,UAAE;AACA,YAAM,6BAA6B,SAAS,OAAO,SAAS,MAAM;AAAA,IACpE;AAAA,EACF,CAAC;AACH,CAAC;",
6
6
  "names": []
7
7
  }
@@ -16,9 +16,18 @@ import { mapCrudServerErrorToFormErrors } from "@open-mercato/ui/backend/utils/s
16
16
  import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
17
17
  import { Button } from "@open-mercato/ui/primitives/button";
18
18
  import { Card, CardContent } from "@open-mercato/ui/primitives/card";
19
+ import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
19
20
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
20
21
  import { Input } from "@open-mercato/ui/primitives/input";
21
22
  import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
23
+ import { Radio, RadioGroup } from "@open-mercato/ui/primitives/radio";
24
+ import {
25
+ Select,
26
+ SelectContent,
27
+ SelectItem,
28
+ SelectTrigger,
29
+ SelectValue
30
+ } from "@open-mercato/ui/primitives/select";
22
31
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
23
32
  import { Textarea } from "@open-mercato/ui/primitives/textarea";
24
33
  import {
@@ -36,6 +45,24 @@ const LEGAL_CONSENT_HANDLE = ComponentReplacementHandles.section("checkout.pay-p
36
45
  const GATEWAY_FORM_HANDLE = ComponentReplacementHandles.section("checkout.pay-page", "gateway-form");
37
46
  const HELP_HANDLE = ComponentReplacementHandles.section("checkout.pay-page", "help");
38
47
  const FOOTER_HANDLE = ComponentReplacementHandles.section("checkout.pay-page", "footer");
48
+ const CHECKOUT_THEME_FALLBACKS = {
49
+ accent: "#1E3A8A",
50
+ accentSecondary: "#F59E0B",
51
+ darkBackground: "#0B1020",
52
+ lightBackground: "#F5EFE6",
53
+ darkText: "#F8FAFC",
54
+ lightText: "#111827",
55
+ darkMutedText: "#CBD5E1",
56
+ lightMutedText: "#6B7280",
57
+ darkSurface: "#111827",
58
+ lightSurface: "#FFFFFF",
59
+ darkShell: "#020617",
60
+ lightShell: "#FFFDF8",
61
+ darkBorder: "#E2E8F0",
62
+ inputBackground: "rgba(255, 255, 255, 0.96)",
63
+ inputText: "#0F172A"
64
+ };
65
+ const PAY_PAGE_EMPTY_SELECT_VALUE = "__om_checkout_empty__";
39
66
  function clamp(value, min, max) {
40
67
  return Math.min(Math.max(value, min), max);
41
68
  }
@@ -135,22 +162,33 @@ function withAlpha(color, alpha) {
135
162
  function getContrastText(color) {
136
163
  const { r, g, b } = hexToRgb(color);
137
164
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
138
- return luminance > 0.62 ? "#0F172A" : "#F8FAFC";
165
+ return luminance > 0.62 ? CHECKOUT_THEME_FALLBACKS.inputText : CHECKOUT_THEME_FALLBACKS.darkText;
139
166
  }
140
167
  function resolveThemeTokens(payload, prefersDark) {
141
- const accent = normalizeHexColor(payload?.primaryColor, "#1E3A8A");
142
- const accentSecondary = normalizeHexColor(payload?.secondaryColor, "#F59E0B");
168
+ const accent = normalizeHexColor(payload?.primaryColor, CHECKOUT_THEME_FALLBACKS.accent);
169
+ const accentSecondary = normalizeHexColor(payload?.secondaryColor, CHECKOUT_THEME_FALLBACKS.accentSecondary);
143
170
  const requestedMode = payload?.themeMode ?? "auto";
144
171
  const mode = requestedMode === "dark" || requestedMode === "auto" && prefersDark ? "dark" : "light";
145
- const background = normalizeHexColor(payload?.backgroundColor, mode === "dark" ? "#0B1020" : "#F5EFE6");
146
- const text = mode === "dark" ? "#F8FAFC" : "#111827";
147
- const mutedText = mode === "dark" ? "#CBD5E1" : "#6B7280";
148
- const surface = mixHexColors(background, mode === "dark" ? "#111827" : "#FFFFFF", mode === "dark" ? 0.62 : 0.76);
172
+ const background = normalizeHexColor(
173
+ payload?.backgroundColor,
174
+ mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkBackground : CHECKOUT_THEME_FALLBACKS.lightBackground
175
+ );
176
+ const text = mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkText : CHECKOUT_THEME_FALLBACKS.lightText;
177
+ const mutedText = mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkMutedText : CHECKOUT_THEME_FALLBACKS.lightMutedText;
178
+ const surface = mixHexColors(
179
+ background,
180
+ mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkSurface : CHECKOUT_THEME_FALLBACKS.lightSurface,
181
+ mode === "dark" ? 0.62 : 0.76
182
+ );
149
183
  const surfaceMuted = mixHexColors(accentSecondary, background, mode === "dark" ? 0.18 : 0.88);
150
184
  const surfaceStrong = mixHexColors(accent, background, mode === "dark" ? 0.22 : 0.9);
151
- const border = withAlpha(mode === "dark" ? "#E2E8F0" : accent, mode === "dark" ? 0.16 : 0.14);
185
+ const border = withAlpha(mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkBorder : accent, mode === "dark" ? 0.16 : 0.14);
152
186
  const borderStrong = withAlpha(accent, mode === "dark" ? 0.34 : 0.22);
153
- const shellBackground = mixHexColors(background, mode === "dark" ? "#020617" : "#FFFDF8", mode === "dark" ? 0.82 : 0.64);
187
+ const shellBackground = mixHexColors(
188
+ background,
189
+ mode === "dark" ? CHECKOUT_THEME_FALLBACKS.darkShell : CHECKOUT_THEME_FALLBACKS.lightShell,
190
+ mode === "dark" ? 0.82 : 0.64
191
+ );
154
192
  const heroStart = withAlpha(accent, mode === "dark" ? 0.26 : 0.16);
155
193
  const heroEnd = withAlpha(accentSecondary, mode === "dark" ? 0.22 : 0.2);
156
194
  const errorBase = mode === "dark" ? "#F87171" : "#DC2626";
@@ -172,7 +210,7 @@ function resolveThemeTokens(payload, prefersDark) {
172
210
  surfaceStrong,
173
211
  border,
174
212
  borderStrong,
175
- shadow: mode === "dark" ? `0 32px 80px ${withAlpha("#020617", 0.55)}` : `0 28px 72px ${withAlpha(accent, 0.12)}`,
213
+ shadow: mode === "dark" ? `0 32px 80px ${withAlpha(CHECKOUT_THEME_FALLBACKS.darkShell, 0.55)}` : `0 28px 72px ${withAlpha(accent, 0.12)}`,
176
214
  accentShadow: `0 22px 48px ${withAlpha(accent, mode === "dark" ? 0.3 : 0.22)}`,
177
215
  heroBackground: `linear-gradient(145deg, ${heroStart} 0%, ${heroEnd} 100%)`,
178
216
  errorText: mode === "dark" ? "#FCA5A5" : "#B91C1C",
@@ -182,11 +220,18 @@ function resolveThemeTokens(payload, prefersDark) {
182
220
  errorRing: withAlpha(errorBase, mode === "dark" ? 0.28 : 0.18)
183
221
  };
184
222
  }
185
- function buildReadableInputStyle(themeTokens, hasError) {
223
+ function buildReadableInputStyle(themeTokens, hasError, disabled = false) {
224
+ if (disabled) {
225
+ return {
226
+ background: "var(--bg-disabled)",
227
+ color: "var(--text-disabled)",
228
+ borderColor: "var(--border-disabled)"
229
+ };
230
+ }
186
231
  return {
187
- background: "rgba(255, 255, 255, 0.96)",
188
- color: "#0F172A",
189
- borderColor: hasError ? themeTokens.errorBorder : withAlpha("#0F172A", 0.12),
232
+ background: CHECKOUT_THEME_FALLBACKS.inputBackground,
233
+ color: CHECKOUT_THEME_FALLBACKS.inputText,
234
+ borderColor: hasError ? themeTokens.errorBorder : withAlpha(CHECKOUT_THEME_FALLBACKS.inputText, 0.12),
190
235
  boxShadow: hasError ? `0 0 0 3px ${themeTokens.errorRing}` : void 0
191
236
  };
192
237
  }
@@ -196,14 +241,6 @@ function buildValidationMessageStyle(themeTokens) {
196
241
  fontWeight: 500
197
242
  };
198
243
  }
199
- function buildValidationNoticeStyle(themeTokens) {
200
- return {
201
- borderColor: themeTokens.errorBorder,
202
- background: themeTokens.errorSurface,
203
- color: themeTokens.errorText,
204
- boxShadow: `0 0 0 1px ${themeTokens.errorSurfaceStrong} inset`
205
- };
206
- }
207
244
  function formatPublicFieldValue(value, booleanLabels) {
208
245
  if (Array.isArray(value)) return value.map((entry) => String(entry)).join(", ");
209
246
  if (typeof value === "boolean") {
@@ -279,7 +316,7 @@ function buildButtonStyle(themeTokens, variant = "solid") {
279
316
  boxShadow: themeTokens.accentShadow
280
317
  };
281
318
  }
282
- const READABLE_INPUT_CLASSNAME = "border bg-white/95 text-slate-900 placeholder:text-slate-400 disabled:cursor-not-allowed disabled:opacity-50";
319
+ const READABLE_INPUT_CLASSNAME = "border bg-white/95 text-slate-900 placeholder:text-slate-400 disabled:cursor-not-allowed disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-text-disabled";
283
320
  function PayPageSurface({
284
321
  previewBanner,
285
322
  leftColumn,
@@ -481,12 +518,11 @@ function PayPageCustomerForm({
481
518
  },
482
519
  children: [
483
520
  /* @__PURE__ */ jsx(
484
- "input",
521
+ Checkbox,
485
522
  {
486
- type: "checkbox",
487
523
  checked: value === true,
488
524
  disabled: inputsLocked,
489
- onChange: (event) => onFieldChange(field.key, event.target.checked)
525
+ onCheckedChange: (checked) => onFieldChange(field.key, checked === true)
490
526
  }
491
527
  ),
492
528
  /* @__PURE__ */ jsxs("span", { className: "space-y-1", children: [
@@ -511,19 +547,30 @@ function PayPageCustomerForm({
511
547
  disabled: inputsLocked,
512
548
  onChange: (event) => onFieldChange(field.key, event.target.value),
513
549
  placeholder: field.placeholder ?? void 0,
514
- style: buildReadableInputStyle(themeTokens, Boolean(fieldError))
550
+ style: buildReadableInputStyle(themeTokens, Boolean(fieldError), inputsLocked)
515
551
  }
516
552
  ) : field.kind === "select" || field.kind === "radio" ? /* @__PURE__ */ jsxs(
517
- "select",
553
+ Select,
518
554
  {
519
- className: `w-full rounded-xl px-3 py-2.5 text-sm ${READABLE_INPUT_CLASSNAME}`,
520
- value: typeof value === "string" ? value : "",
555
+ value: typeof value === "string" && value.length > 0 ? value : PAY_PAGE_EMPTY_SELECT_VALUE,
521
556
  disabled: inputsLocked,
522
- onChange: (event) => onFieldChange(field.key, event.target.value),
523
- style: buildReadableInputStyle(themeTokens, Boolean(fieldError)),
557
+ onValueChange: (nextValue) => {
558
+ onFieldChange(field.key, nextValue === PAY_PAGE_EMPTY_SELECT_VALUE ? "" : nextValue);
559
+ },
524
560
  children: [
525
- /* @__PURE__ */ jsx("option", { value: "", children: t("checkout.payPage.fields.selectPlaceholder", "Select...") }),
526
- (field.options ?? []).map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
561
+ /* @__PURE__ */ jsx(
562
+ SelectTrigger,
563
+ {
564
+ className: `rounded-xl ${READABLE_INPUT_CLASSNAME}`,
565
+ "aria-invalid": Boolean(fieldError),
566
+ style: buildReadableInputStyle(themeTokens, Boolean(fieldError), inputsLocked),
567
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("checkout.payPage.fields.selectPlaceholder", "Select...") })
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
571
+ /* @__PURE__ */ jsx(SelectItem, { value: PAY_PAGE_EMPTY_SELECT_VALUE, children: t("checkout.payPage.fields.selectPlaceholder", "Select...") }),
572
+ (field.options ?? []).map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value))
573
+ ] })
527
574
  ]
528
575
  }
529
576
  ) : /* @__PURE__ */ jsx(
@@ -536,7 +583,7 @@ function PayPageCustomerForm({
536
583
  onChange: (event) => onFieldChange(field.key, event.target.value),
537
584
  placeholder: field.placeholder ?? void 0,
538
585
  autoComplete: semanticType === "email" ? "email" : semanticType === "phone" ? "tel" : void 0,
539
- style: buildReadableInputStyle(themeTokens, Boolean(fieldError))
586
+ style: buildReadableInputStyle(themeTokens, Boolean(fieldError), inputsLocked)
540
587
  }
541
588
  )
542
589
  ] }),
@@ -583,39 +630,48 @@ function PayPagePricing({
583
630
  payload.customAmountMin != null ? formatAmount(payload.customAmountMin, payload.customAmountCurrencyCode) : null,
584
631
  payload.customAmountMax != null ? formatAmount(payload.customAmountMax, payload.customAmountCurrencyCode) : null
585
632
  ].filter(Boolean).join(" - "),
586
- style: buildReadableInputStyle(themeTokens, Boolean(fieldErrors.amount))
633
+ style: buildReadableInputStyle(themeTokens, Boolean(fieldErrors.amount), inputsLocked)
587
634
  }
588
635
  ),
589
636
  fieldErrors.amount ? /* @__PURE__ */ jsx("p", { className: "text-sm", style: buildValidationMessageStyle(themeTokens), children: translateValidationMessage(fieldErrors.amount, "amount") }) : null
590
637
  ] }) : null,
591
- payload.pricingMode === "price_list" ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
592
- (payload.priceListItems ?? []).map((item) => /* @__PURE__ */ jsxs(
593
- "label",
638
+ payload.pricingMode === "price_list" ? /* @__PURE__ */ jsxs(Fragment, { children: [
639
+ /* @__PURE__ */ jsx(
640
+ RadioGroup,
594
641
  {
595
- className: "flex cursor-pointer items-center justify-between gap-3 rounded-xl border px-4 py-4 text-sm transition",
596
- style: {
597
- background: selectedPriceItemId === item.id ? withAlpha(themeTokens.accent, 0.08) : withAlpha(themeTokens.shellBackground, 0.66),
598
- borderColor: selectedPriceItemId === item.id ? themeTokens.borderStrong : themeTokens.border
642
+ className: "space-y-3",
643
+ value: selectedPriceItemId ?? "",
644
+ disabled: inputsLocked,
645
+ onValueChange: (itemId) => {
646
+ const item = (payload.priceListItems ?? []).find((candidate) => candidate.id === itemId);
647
+ if (item) onPriceItemSelect(item);
599
648
  },
600
- children: [
601
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3", children: [
602
- /* @__PURE__ */ jsx(
603
- "input",
604
- {
605
- type: "radio",
606
- name: "priceItem",
607
- checked: selectedPriceItemId === item.id,
608
- disabled: inputsLocked,
609
- onChange: () => onPriceItemSelect(item)
610
- }
611
- ),
612
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: item.description })
613
- ] }),
614
- /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap font-semibold", children: formatAmount(item.amount, item.currencyCode) })
615
- ]
616
- },
617
- item.id
618
- )),
649
+ children: (payload.priceListItems ?? []).map((item) => /* @__PURE__ */ jsxs(
650
+ "label",
651
+ {
652
+ className: "flex cursor-pointer items-center justify-between gap-3 rounded-xl border px-4 py-4 text-sm transition",
653
+ style: {
654
+ background: selectedPriceItemId === item.id ? withAlpha(themeTokens.accent, 0.08) : withAlpha(themeTokens.shellBackground, 0.66),
655
+ borderColor: selectedPriceItemId === item.id ? themeTokens.borderStrong : themeTokens.border
656
+ },
657
+ children: [
658
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3", children: [
659
+ /* @__PURE__ */ jsx(
660
+ Radio,
661
+ {
662
+ value: item.id,
663
+ disabled: inputsLocked
664
+ }
665
+ ),
666
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: item.description })
667
+ ] }),
668
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap font-semibold", children: formatAmount(item.amount, item.currencyCode) })
669
+ ]
670
+ },
671
+ item.id
672
+ ))
673
+ }
674
+ ),
619
675
  fieldErrors.selectedPriceItemId ? /* @__PURE__ */ jsx("p", { className: "text-sm", style: buildValidationMessageStyle(themeTokens), children: translateValidationMessage(fieldErrors.selectedPriceItemId, "selectedPriceItemId") }) : null
620
676
  ] }) : null
621
677
  ] });
@@ -677,12 +733,11 @@ function PayPageLegalConsent({
677
733
  },
678
734
  children: /* @__PURE__ */ jsxs("label", { className: "flex items-start gap-3 text-sm", children: [
679
735
  /* @__PURE__ */ jsx(
680
- "input",
736
+ Checkbox,
681
737
  {
682
- type: "checkbox",
683
738
  checked: acceptedLegalConsents.terms === true,
684
739
  disabled: inputsLocked,
685
- onChange: (event) => onConsentChange("terms", event.target.checked)
740
+ onCheckedChange: (checked) => onConsentChange("terms", checked === true)
686
741
  }
687
742
  ),
688
743
  /* @__PURE__ */ jsxs("span", { className: "space-y-2", children: [
@@ -721,12 +776,11 @@ function PayPageLegalConsent({
721
776
  },
722
777
  children: /* @__PURE__ */ jsxs("label", { className: "flex items-start gap-3 text-sm", children: [
723
778
  /* @__PURE__ */ jsx(
724
- "input",
779
+ Checkbox,
725
780
  {
726
- type: "checkbox",
727
781
  checked: acceptedLegalConsents.privacyPolicy === true,
728
782
  disabled: inputsLocked,
729
- onChange: (event) => onConsentChange("privacyPolicy", event.target.checked)
783
+ onCheckedChange: (checked) => onConsentChange("privacyPolicy", checked === true)
730
784
  }
731
785
  ),
732
786
  /* @__PURE__ */ jsxs("span", { className: "space-y-2", children: [
@@ -782,7 +836,7 @@ function PayPagePaymentForm({
782
836
  const t = useT();
783
837
  const embeddedPaymentSession = paymentSession?.type === "embedded" ? paymentSession : null;
784
838
  return /* @__PURE__ */ jsxs("section", { className: "space-y-4", children: [
785
- submissionError ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border px-4 py-3 text-sm", style: buildValidationNoticeStyle(themeTokens), children: submissionError }) : null,
839
+ submissionError ? /* @__PURE__ */ jsx(Alert, { status: "error", style: "lighter", size: "default", children: /* @__PURE__ */ jsx(AlertDescription, { children: submissionError }) }) : null,
786
840
  /* @__PURE__ */ jsx(InjectionSpot, { spotId: "checkout.pay-page:gateway-widget:before", context: injectionContext }),
787
841
  embeddedPaymentSession && embeddedRenderer ? /* @__PURE__ */ jsxs(Fragment, { children: [
788
842
  /* @__PURE__ */ jsx(InjectionSpot, { spotId: "checkout.pay-page:gateway-widget:renderer:before", context: injectionContext }),
@@ -947,33 +1001,31 @@ function PayPageFooter({ payload, themeTokens }) {
947
1001
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 sm:justify-end", children: [
948
1002
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", style: { color: themeTokens.mutedText }, children: [
949
1003
  /* @__PURE__ */ jsx("label", { htmlFor: selectId, children: t("common.language", "Language") }),
950
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
951
- /* @__PURE__ */ jsx(
952
- "select",
953
- {
954
- id: selectId,
955
- className: "appearance-none rounded-full border px-3 py-1.5 pr-8 text-xs font-medium shadow-sm outline-none disabled:cursor-not-allowed disabled:opacity-50",
956
- style: {
957
- background: withAlpha(themeTokens.surface, 0.92),
958
- borderColor: themeTokens.borderStrong,
959
- color: themeTokens.text
960
- },
961
- value: locale,
962
- onChange: (event) => setLocale(event.target.value),
963
- disabled: pending,
964
- children: locales.map((entry) => /* @__PURE__ */ jsx("option", { value: entry, children: languageLabels[entry] }, entry))
965
- }
966
- ),
967
- /* @__PURE__ */ jsx(
968
- "span",
969
- {
970
- "aria-hidden": "true",
971
- className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-overline",
972
- style: { color: themeTokens.mutedText },
973
- children: "\u25BC"
974
- }
975
- )
976
- ] })
1004
+ /* @__PURE__ */ jsxs(
1005
+ Select,
1006
+ {
1007
+ value: locale,
1008
+ onValueChange: (nextLocale) => setLocale(nextLocale),
1009
+ disabled: pending,
1010
+ children: [
1011
+ /* @__PURE__ */ jsx(
1012
+ SelectTrigger,
1013
+ {
1014
+ id: selectId,
1015
+ size: "sm",
1016
+ className: "w-auto min-w-28 rounded-full text-xs font-medium",
1017
+ style: {
1018
+ background: withAlpha(themeTokens.surface, 0.92),
1019
+ borderColor: themeTokens.borderStrong,
1020
+ color: themeTokens.text
1021
+ },
1022
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1023
+ }
1024
+ ),
1025
+ /* @__PURE__ */ jsx(SelectContent, { children: locales.map((entry) => /* @__PURE__ */ jsx(SelectItem, { value: entry, children: languageLabels[entry] }, entry)) })
1026
+ ]
1027
+ }
1028
+ )
977
1029
  ] }),
978
1030
  /* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-widest", children: payload.gatewayProviderKey ?? t("checkout.payPage.header.autoProvider", "Configured in checkout") })
979
1031
  ] })
@@ -1484,13 +1536,21 @@ function PayPage({
1484
1536
  );
1485
1537
  }
1486
1538
  if (loadError || !payload) {
1487
- return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-3xl px-4 py-16", children: /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
1488
- /* @__PURE__ */ jsx(AlertTitle, { children: t("checkout.payPage.errors.loadTitle", "Unable to load payment link") }),
1489
- /* @__PURE__ */ jsx(AlertDescription, { children: loadError ?? t("checkout.payPage.errors.loadMessage", "We couldn't load this payment page. Please try again.") }),
1490
- /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
1491
- void loadPayload();
1492
- }, children: t("checkout.payPage.actions.retry", "Retry") }) })
1493
- ] }) });
1539
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-3xl px-4 py-16", children: /* @__PURE__ */ jsx(
1540
+ Alert,
1541
+ {
1542
+ status: "error",
1543
+ style: "lighter",
1544
+ size: "default",
1545
+ action: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
1546
+ void loadPayload();
1547
+ }, children: t("checkout.payPage.actions.retry", "Retry") }),
1548
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1549
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("checkout.payPage.errors.loadTitle", "Unable to load payment link") }),
1550
+ /* @__PURE__ */ jsx(AlertDescription, { children: loadError ?? t("checkout.payPage.errors.loadMessage", "We couldn't load this payment page. Please try again.") })
1551
+ ] })
1552
+ }
1553
+ ) });
1494
1554
  }
1495
1555
  if (payload.requiresPassword) {
1496
1556
  return /* @__PURE__ */ jsx(