@reblu/site-blocks 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { ShopProductListItem, AcademyModulePublic, AcademyLessonPublic, DataRequestType, DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts';
4
+ import { ShopProductListItem, AcademyModulePublic, AcademyLessonPublic, DataRequestType, DataRequestInput, DataRequestSubmission, WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts';
5
5
  export { DATA_REQUEST_TYPES } from '@reblu/site-contracts';
6
6
  import { H as HeadingLevel } from './_heading-BsNbc1wc.cjs';
7
7
 
@@ -460,4 +460,88 @@ interface DataRequestFormProps {
460
460
  }
461
461
  declare function DataRequestForm({ getCaptchaToken, submit, endpoint, onInteract, onSubmitted, requestTypes, labels, locale, className, }: DataRequestFormProps): react_jsx_runtime.JSX.Element;
462
462
 
463
- export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, CourseSyllabus, type CourseSyllabusLabels, type CourseSyllabusModule, type CourseSyllabusProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_COURSE_SYLLABUS_LABELS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, useCollection };
463
+ /**
464
+ * `WithdrawalRequestForm` — drop-in withdrawal form (REB-1201) over
465
+ * `POST /api/site/withdrawal-requests`. The buyer gives the order reference,
466
+ * their email, what they withdraw from and, optionally, a reason; base-api
467
+ * stores it, emails the tenant team and sends the buyer an acknowledgement.
468
+ *
469
+ * It decides nothing: it does not check the order, nor exclude any product
470
+ * or service. Whether the right applies is the tenant's decision, explained on
471
+ * its own withdrawal page (`LegalDocument` over `legal.withdrawalPolicy`).
472
+ *
473
+ * Stateful (client-only — ships from `./client`) but transport-agnostic, like
474
+ * `DataRequestForm`:
475
+ * - `getCaptchaToken` mints the reCAPTCHA v3 token; `undefined` blocks the
476
+ * submission (base-api rejects a request without one).
477
+ * - `submit` sends the body. By default it POSTs JSON to the SITE's own Route
478
+ * Handler (`endpoint`, default `/api/withdrawal-requests`) and unwraps the
479
+ * `{ data }` envelope — the API key never reaches the browser.
480
+ *
481
+ * On success the form is replaced by a receipt. When the acknowledgement email
482
+ * could not be sent, the receipt repeats what was communicated so the buyer
483
+ * can keep it. Errors never show server text: they map by HTTP status to the
484
+ * fixed (overridable) copy in `labels`.
485
+ */
486
+ type WithdrawalRequestSubmitResult = {
487
+ ok: true;
488
+ data: WithdrawalRequestSubmission;
489
+ }
490
+ /** `status` is the HTTP status, or `0` when the request never got an answer. */
491
+ | {
492
+ ok: false;
493
+ status: number;
494
+ };
495
+ /** The server's field limits (base-api `POST /api/site/withdrawal-requests`). */
496
+ declare const WITHDRAWAL_REQUEST_FIELD_LIMITS: Readonly<{
497
+ orderReference: 100;
498
+ email: 320;
499
+ name: 200;
500
+ items: 2000;
501
+ reason: 2000;
502
+ }>;
503
+ interface WithdrawalRequestFormLabels {
504
+ orderReferenceLabel: string;
505
+ nameLabel: string;
506
+ emailLabel: string;
507
+ itemsLabel: string;
508
+ itemsPlaceholder: string;
509
+ reasonLabel: string;
510
+ /** Small print under the fields, e.g. how the data is used. `null` hides it. */
511
+ notice: ReactNode;
512
+ submit: string;
513
+ submitting: string;
514
+ orderReferenceRequired: string;
515
+ emailInvalid: string;
516
+ itemsRequired: string;
517
+ /** Receipt when the buyer was emailed a copy; receives the formatted received-at date. */
518
+ success: (receivedAt: string) => ReactNode;
519
+ /** Receipt when no copy could be emailed; the communication is listed below it. */
520
+ successWithoutEmail: (receivedAt: string) => ReactNode;
521
+ receiptOrderReference: string;
522
+ receiptItems: string;
523
+ captchaError: string;
524
+ validationError: string;
525
+ rateLimitError: string;
526
+ genericError: string;
527
+ }
528
+ declare const DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS: WithdrawalRequestFormLabels;
529
+ interface WithdrawalRequestFormProps {
530
+ /** Mints the reCAPTCHA v3 token; `undefined` blocks the submission. */
531
+ getCaptchaToken: () => Promise<string | undefined>;
532
+ /** Sends the body. Defaults to a JSON POST to `endpoint`. */
533
+ submit?: (input: WithdrawalRequestInput) => Promise<WithdrawalRequestSubmitResult>;
534
+ /** The SITE's Route Handler used by the default `submit`. @default '/api/withdrawal-requests' */
535
+ endpoint?: string;
536
+ /** First interaction with the form — wire it to the deferred reCAPTCHA `arm`. */
537
+ onInteract?: () => void;
538
+ /** Called with the acknowledgement after a successful submission. */
539
+ onSubmitted?: (ack: WithdrawalRequestSubmission) => void;
540
+ labels?: Partial<WithdrawalRequestFormLabels>;
541
+ /** Locale for the received-at date. @default 'es-ES' */
542
+ locale?: string;
543
+ className?: string;
544
+ }
545
+ declare function WithdrawalRequestForm({ getCaptchaToken, submit, endpoint, onInteract, onSubmitted, labels, locale, className, }: WithdrawalRequestFormProps): react_jsx_runtime.JSX.Element;
546
+
547
+ export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, CourseSyllabus, type CourseSyllabusLabels, type CourseSyllabusModule, type CourseSyllabusProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_COURSE_SYLLABUS_LABELS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, WITHDRAWAL_REQUEST_FIELD_LIMITS, WithdrawalRequestForm, type WithdrawalRequestFormLabels, type WithdrawalRequestFormProps, type WithdrawalRequestSubmitResult, useCollection };
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { ShopProductListItem, AcademyModulePublic, AcademyLessonPublic, DataRequestType, DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts';
4
+ import { ShopProductListItem, AcademyModulePublic, AcademyLessonPublic, DataRequestType, DataRequestInput, DataRequestSubmission, WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts';
5
5
  export { DATA_REQUEST_TYPES } from '@reblu/site-contracts';
6
6
  import { H as HeadingLevel } from './_heading-BsNbc1wc.js';
7
7
 
@@ -460,4 +460,88 @@ interface DataRequestFormProps {
460
460
  }
461
461
  declare function DataRequestForm({ getCaptchaToken, submit, endpoint, onInteract, onSubmitted, requestTypes, labels, locale, className, }: DataRequestFormProps): react_jsx_runtime.JSX.Element;
462
462
 
463
- export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, CourseSyllabus, type CourseSyllabusLabels, type CourseSyllabusModule, type CourseSyllabusProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_COURSE_SYLLABUS_LABELS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, useCollection };
463
+ /**
464
+ * `WithdrawalRequestForm` — drop-in withdrawal form (REB-1201) over
465
+ * `POST /api/site/withdrawal-requests`. The buyer gives the order reference,
466
+ * their email, what they withdraw from and, optionally, a reason; base-api
467
+ * stores it, emails the tenant team and sends the buyer an acknowledgement.
468
+ *
469
+ * It decides nothing: it does not check the order, nor exclude any product
470
+ * or service. Whether the right applies is the tenant's decision, explained on
471
+ * its own withdrawal page (`LegalDocument` over `legal.withdrawalPolicy`).
472
+ *
473
+ * Stateful (client-only — ships from `./client`) but transport-agnostic, like
474
+ * `DataRequestForm`:
475
+ * - `getCaptchaToken` mints the reCAPTCHA v3 token; `undefined` blocks the
476
+ * submission (base-api rejects a request without one).
477
+ * - `submit` sends the body. By default it POSTs JSON to the SITE's own Route
478
+ * Handler (`endpoint`, default `/api/withdrawal-requests`) and unwraps the
479
+ * `{ data }` envelope — the API key never reaches the browser.
480
+ *
481
+ * On success the form is replaced by a receipt. When the acknowledgement email
482
+ * could not be sent, the receipt repeats what was communicated so the buyer
483
+ * can keep it. Errors never show server text: they map by HTTP status to the
484
+ * fixed (overridable) copy in `labels`.
485
+ */
486
+ type WithdrawalRequestSubmitResult = {
487
+ ok: true;
488
+ data: WithdrawalRequestSubmission;
489
+ }
490
+ /** `status` is the HTTP status, or `0` when the request never got an answer. */
491
+ | {
492
+ ok: false;
493
+ status: number;
494
+ };
495
+ /** The server's field limits (base-api `POST /api/site/withdrawal-requests`). */
496
+ declare const WITHDRAWAL_REQUEST_FIELD_LIMITS: Readonly<{
497
+ orderReference: 100;
498
+ email: 320;
499
+ name: 200;
500
+ items: 2000;
501
+ reason: 2000;
502
+ }>;
503
+ interface WithdrawalRequestFormLabels {
504
+ orderReferenceLabel: string;
505
+ nameLabel: string;
506
+ emailLabel: string;
507
+ itemsLabel: string;
508
+ itemsPlaceholder: string;
509
+ reasonLabel: string;
510
+ /** Small print under the fields, e.g. how the data is used. `null` hides it. */
511
+ notice: ReactNode;
512
+ submit: string;
513
+ submitting: string;
514
+ orderReferenceRequired: string;
515
+ emailInvalid: string;
516
+ itemsRequired: string;
517
+ /** Receipt when the buyer was emailed a copy; receives the formatted received-at date. */
518
+ success: (receivedAt: string) => ReactNode;
519
+ /** Receipt when no copy could be emailed; the communication is listed below it. */
520
+ successWithoutEmail: (receivedAt: string) => ReactNode;
521
+ receiptOrderReference: string;
522
+ receiptItems: string;
523
+ captchaError: string;
524
+ validationError: string;
525
+ rateLimitError: string;
526
+ genericError: string;
527
+ }
528
+ declare const DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS: WithdrawalRequestFormLabels;
529
+ interface WithdrawalRequestFormProps {
530
+ /** Mints the reCAPTCHA v3 token; `undefined` blocks the submission. */
531
+ getCaptchaToken: () => Promise<string | undefined>;
532
+ /** Sends the body. Defaults to a JSON POST to `endpoint`. */
533
+ submit?: (input: WithdrawalRequestInput) => Promise<WithdrawalRequestSubmitResult>;
534
+ /** The SITE's Route Handler used by the default `submit`. @default '/api/withdrawal-requests' */
535
+ endpoint?: string;
536
+ /** First interaction with the form — wire it to the deferred reCAPTCHA `arm`. */
537
+ onInteract?: () => void;
538
+ /** Called with the acknowledgement after a successful submission. */
539
+ onSubmitted?: (ack: WithdrawalRequestSubmission) => void;
540
+ labels?: Partial<WithdrawalRequestFormLabels>;
541
+ /** Locale for the received-at date. @default 'es-ES' */
542
+ locale?: string;
543
+ className?: string;
544
+ }
545
+ declare function WithdrawalRequestForm({ getCaptchaToken, submit, endpoint, onInteract, onSubmitted, labels, locale, className, }: WithdrawalRequestFormProps): react_jsx_runtime.JSX.Element;
546
+
547
+ export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, CourseSyllabus, type CourseSyllabusLabels, type CourseSyllabusModule, type CourseSyllabusProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_COURSE_SYLLABUS_LABELS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, WITHDRAWAL_REQUEST_FIELD_LIMITS, WithdrawalRequestForm, type WithdrawalRequestFormLabels, type WithdrawalRequestFormProps, type WithdrawalRequestSubmitResult, useCollection };
package/dist/client.js CHANGED
@@ -1279,6 +1279,277 @@ function DataRequestForm({
1279
1279
  }
1280
1280
  );
1281
1281
  }
1282
+
1283
+ // src/components/withdrawal-request-form.tsx
1284
+ import { useEffect as useEffect5, useId as useId4, useRef as useRef5, useState as useState8 } from "react";
1285
+ import { cn as cn10 } from "@reblu/site-ui/utils";
1286
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1287
+ var WITHDRAWAL_REQUEST_FIELD_LIMITS = Object.freeze({
1288
+ orderReference: 100,
1289
+ email: 320,
1290
+ name: 200,
1291
+ items: 2e3,
1292
+ reason: 2e3
1293
+ });
1294
+ var DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS = {
1295
+ orderReferenceLabel: "N\xFAmero de pedido",
1296
+ nameLabel: "Nombre y apellidos (opcional)",
1297
+ emailLabel: "Email",
1298
+ itemsLabel: "Qu\xE9 productos o servicios quieres devolver o cancelar",
1299
+ itemsPlaceholder: "Indica los productos, bonos o cursos y las unidades",
1300
+ reasonLabel: "Motivo (opcional)",
1301
+ notice: "Usaremos estos datos solo para gestionar tu desistimiento.",
1302
+ submit: "Enviar desistimiento",
1303
+ submitting: "Enviando\u2026",
1304
+ orderReferenceRequired: "Indica el n\xFAmero de pedido.",
1305
+ emailInvalid: "Introduce un email v\xE1lido.",
1306
+ itemsRequired: "Indica de qu\xE9 productos o servicios desistes.",
1307
+ success: (receivedAt) => /* @__PURE__ */ jsxs8(Fragment3, { children: [
1308
+ "Hemos recibido tu desistimiento el ",
1309
+ receivedAt,
1310
+ ". Te hemos enviado una copia por email."
1311
+ ] }),
1312
+ successWithoutEmail: (receivedAt) => /* @__PURE__ */ jsxs8(Fragment3, { children: [
1313
+ "Hemos recibido tu desistimiento el ",
1314
+ receivedAt,
1315
+ ". Guarda esta pantalla como justificante de lo que nos has comunicado:"
1316
+ ] }),
1317
+ receiptOrderReference: "Pedido",
1318
+ receiptItems: "Productos o servicios",
1319
+ captchaError: "No hemos podido verificar que no eres un robot. Recarga la p\xE1gina e int\xE9ntalo de nuevo.",
1320
+ validationError: "Revisa los datos del formulario e int\xE9ntalo de nuevo.",
1321
+ rateLimitError: "Has enviado demasiadas solicitudes. Espera unos minutos e int\xE9ntalo de nuevo.",
1322
+ genericError: "No hemos podido enviar tu desistimiento. Int\xE9ntalo de nuevo m\xE1s tarde."
1323
+ };
1324
+ var FIELD_ORDER2 = ["orderReference", "email", "items"];
1325
+ var EMAIL_PATTERN2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1326
+ async function postToRoute2(endpoint, input) {
1327
+ try {
1328
+ const res = await fetch(endpoint, {
1329
+ method: "POST",
1330
+ headers: { "Content-Type": "application/json" },
1331
+ body: JSON.stringify(input)
1332
+ });
1333
+ if (!res.ok) return { ok: false, status: res.status };
1334
+ const json = await res.json();
1335
+ return json.data ? { ok: true, data: json.data } : { ok: false, status: res.status };
1336
+ } catch {
1337
+ return { ok: false, status: 0 };
1338
+ }
1339
+ }
1340
+ function rejectionMessage2(status, t) {
1341
+ if (status === 400) return t.validationError;
1342
+ if (status === 422) return t.captchaError;
1343
+ if (status === 429) return t.rateLimitError;
1344
+ return t.genericError;
1345
+ }
1346
+ var FIELD_CLASS2 = "w-full rounded-lg border bg-background px-3 py-2 text-sm transition focus:outline-none focus:ring-2 focus:ring-primary/20";
1347
+ function WithdrawalRequestForm({
1348
+ getCaptchaToken,
1349
+ submit,
1350
+ endpoint = "/api/withdrawal-requests",
1351
+ onInteract,
1352
+ onSubmitted,
1353
+ labels,
1354
+ locale = "es-ES",
1355
+ className
1356
+ }) {
1357
+ const t = { ...DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS, ...labels };
1358
+ const id = useId4();
1359
+ const [orderReference, setOrderReference] = useState8("");
1360
+ const [name, setName] = useState8("");
1361
+ const [email, setEmail] = useState8("");
1362
+ const [items, setItems] = useState8("");
1363
+ const [reason, setReason] = useState8("");
1364
+ const [errors, setErrors] = useState8({});
1365
+ const [formError, setFormError] = useState8(null);
1366
+ const [submitting, setSubmitting] = useState8(false);
1367
+ const [receipt, setReceipt] = useState8(null);
1368
+ const statusRef = useRef5(null);
1369
+ useEffect5(() => {
1370
+ if (receipt) statusRef.current?.focus();
1371
+ }, [receipt]);
1372
+ async function handleSubmit(event) {
1373
+ event.preventDefault();
1374
+ if (submitting) return;
1375
+ setFormError(null);
1376
+ const next = {};
1377
+ if (!orderReference.trim()) next.orderReference = t.orderReferenceRequired;
1378
+ if (!EMAIL_PATTERN2.test(email.trim())) next.email = t.emailInvalid;
1379
+ if (!items.trim()) next.items = t.itemsRequired;
1380
+ setErrors(next);
1381
+ const firstInvalid = FIELD_ORDER2.find((field) => next[field]);
1382
+ if (firstInvalid) {
1383
+ document.getElementById(`${id}-${firstInvalid}`)?.focus();
1384
+ return;
1385
+ }
1386
+ setSubmitting(true);
1387
+ try {
1388
+ const captcha = await getCaptchaToken().catch(() => void 0);
1389
+ if (!captcha) {
1390
+ setFormError(t.captchaError);
1391
+ return;
1392
+ }
1393
+ const input = {
1394
+ orderReference: orderReference.trim(),
1395
+ email: email.trim(),
1396
+ ...name.trim() ? { name: name.trim() } : {},
1397
+ items: items.trim(),
1398
+ ...reason.trim() ? { reason: reason.trim() } : {},
1399
+ captcha
1400
+ };
1401
+ const result = await (submit ?? ((body) => postToRoute2(endpoint, body)))(input).catch(
1402
+ () => ({ ok: false, status: 0 })
1403
+ );
1404
+ if (!result.ok) {
1405
+ setFormError(rejectionMessage2(result.status, t));
1406
+ return;
1407
+ }
1408
+ setReceipt({ ack: result.data, orderReference: input.orderReference, items: input.items });
1409
+ onSubmitted?.(result.data);
1410
+ } finally {
1411
+ setSubmitting(false);
1412
+ }
1413
+ }
1414
+ if (receipt) {
1415
+ const receivedAt = new Intl.DateTimeFormat(locale, {
1416
+ dateStyle: "long",
1417
+ timeStyle: "short"
1418
+ }).format(new Date(receipt.ack.receivedAt));
1419
+ return /* @__PURE__ */ jsx10(
1420
+ "div",
1421
+ {
1422
+ ref: statusRef,
1423
+ tabIndex: -1,
1424
+ role: "status",
1425
+ "data-slot": "withdrawal-request-form-success",
1426
+ className: cn10("space-y-3 rounded-lg border border-border p-6 text-sm", className),
1427
+ children: receipt.ack.acknowledgementSent ? /* @__PURE__ */ jsx10("p", { children: t.success(receivedAt) }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
1428
+ /* @__PURE__ */ jsx10("p", { children: t.successWithoutEmail(receivedAt) }),
1429
+ /* @__PURE__ */ jsxs8("dl", { className: "space-y-1", children: [
1430
+ /* @__PURE__ */ jsxs8("div", { children: [
1431
+ /* @__PURE__ */ jsx10("dt", { className: "font-medium", children: t.receiptOrderReference }),
1432
+ /* @__PURE__ */ jsx10("dd", { children: receipt.orderReference })
1433
+ ] }),
1434
+ /* @__PURE__ */ jsxs8("div", { children: [
1435
+ /* @__PURE__ */ jsx10("dt", { className: "font-medium", children: t.receiptItems }),
1436
+ /* @__PURE__ */ jsx10("dd", { className: "whitespace-pre-line", children: receipt.items })
1437
+ ] })
1438
+ ] })
1439
+ ] })
1440
+ }
1441
+ );
1442
+ }
1443
+ const errorId = (field) => errors[field] ? `${id}-${field}-error` : void 0;
1444
+ const fieldError = (field) => errors[field] ? /* @__PURE__ */ jsx10("p", { id: `${id}-${field}-error`, className: "text-xs text-destructive", children: errors[field] }) : null;
1445
+ const borderFor = (field) => errors[field] ? "border-destructive" : "border-border";
1446
+ return /* @__PURE__ */ jsxs8(
1447
+ "form",
1448
+ {
1449
+ noValidate: true,
1450
+ onSubmit: handleSubmit,
1451
+ onFocusCapture: onInteract,
1452
+ onPointerDown: onInteract,
1453
+ "data-slot": "withdrawal-request-form",
1454
+ className: cn10("space-y-4", className),
1455
+ children: [
1456
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1457
+ /* @__PURE__ */ jsx10("label", { htmlFor: `${id}-orderReference`, className: "text-sm font-medium", children: t.orderReferenceLabel }),
1458
+ /* @__PURE__ */ jsx10(
1459
+ "input",
1460
+ {
1461
+ id: `${id}-orderReference`,
1462
+ type: "text",
1463
+ maxLength: WITHDRAWAL_REQUEST_FIELD_LIMITS.orderReference,
1464
+ value: orderReference,
1465
+ onChange: (e) => setOrderReference(e.target.value),
1466
+ "aria-invalid": errors.orderReference ? true : void 0,
1467
+ "aria-describedby": errorId("orderReference"),
1468
+ className: cn10(FIELD_CLASS2, borderFor("orderReference"))
1469
+ }
1470
+ ),
1471
+ fieldError("orderReference")
1472
+ ] }),
1473
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1474
+ /* @__PURE__ */ jsx10("label", { htmlFor: `${id}-name`, className: "text-sm font-medium", children: t.nameLabel }),
1475
+ /* @__PURE__ */ jsx10(
1476
+ "input",
1477
+ {
1478
+ id: `${id}-name`,
1479
+ type: "text",
1480
+ autoComplete: "name",
1481
+ maxLength: WITHDRAWAL_REQUEST_FIELD_LIMITS.name,
1482
+ value: name,
1483
+ onChange: (e) => setName(e.target.value),
1484
+ className: cn10(FIELD_CLASS2, "border-border")
1485
+ }
1486
+ )
1487
+ ] }),
1488
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1489
+ /* @__PURE__ */ jsx10("label", { htmlFor: `${id}-email`, className: "text-sm font-medium", children: t.emailLabel }),
1490
+ /* @__PURE__ */ jsx10(
1491
+ "input",
1492
+ {
1493
+ id: `${id}-email`,
1494
+ type: "email",
1495
+ autoComplete: "email",
1496
+ maxLength: WITHDRAWAL_REQUEST_FIELD_LIMITS.email,
1497
+ value: email,
1498
+ onChange: (e) => setEmail(e.target.value),
1499
+ "aria-invalid": errors.email ? true : void 0,
1500
+ "aria-describedby": errorId("email"),
1501
+ className: cn10(FIELD_CLASS2, borderFor("email"))
1502
+ }
1503
+ ),
1504
+ fieldError("email")
1505
+ ] }),
1506
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1507
+ /* @__PURE__ */ jsx10("label", { htmlFor: `${id}-items`, className: "text-sm font-medium", children: t.itemsLabel }),
1508
+ /* @__PURE__ */ jsx10(
1509
+ "textarea",
1510
+ {
1511
+ id: `${id}-items`,
1512
+ rows: 3,
1513
+ maxLength: WITHDRAWAL_REQUEST_FIELD_LIMITS.items,
1514
+ value: items,
1515
+ onChange: (e) => setItems(e.target.value),
1516
+ placeholder: t.itemsPlaceholder,
1517
+ "aria-invalid": errors.items ? true : void 0,
1518
+ "aria-describedby": errorId("items"),
1519
+ className: cn10(FIELD_CLASS2, borderFor("items"))
1520
+ }
1521
+ ),
1522
+ fieldError("items")
1523
+ ] }),
1524
+ /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
1525
+ /* @__PURE__ */ jsx10("label", { htmlFor: `${id}-reason`, className: "text-sm font-medium", children: t.reasonLabel }),
1526
+ /* @__PURE__ */ jsx10(
1527
+ "textarea",
1528
+ {
1529
+ id: `${id}-reason`,
1530
+ rows: 3,
1531
+ maxLength: WITHDRAWAL_REQUEST_FIELD_LIMITS.reason,
1532
+ value: reason,
1533
+ onChange: (e) => setReason(e.target.value),
1534
+ className: cn10(FIELD_CLASS2, "border-border")
1535
+ }
1536
+ )
1537
+ ] }),
1538
+ t.notice ? /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground", children: t.notice }) : null,
1539
+ formError && /* @__PURE__ */ jsx10("p", { role: "alert", className: "text-sm text-destructive", children: formError }),
1540
+ /* @__PURE__ */ jsx10(
1541
+ "button",
1542
+ {
1543
+ type: "submit",
1544
+ disabled: submitting,
1545
+ className: "cursor-pointer rounded-lg bg-primary px-6 py-2 text-sm font-semibold text-primary-foreground disabled:cursor-not-allowed disabled:opacity-60",
1546
+ children: submitting ? t.submitting : t.submit
1547
+ }
1548
+ )
1549
+ ]
1550
+ }
1551
+ );
1552
+ }
1282
1553
  export {
1283
1554
  AddToCartButton,
1284
1555
  CartIcon,
@@ -1289,9 +1560,12 @@ export {
1289
1560
  DEFAULT_COURSE_SYLLABUS_LABELS,
1290
1561
  DEFAULT_DATA_REQUEST_FORM_LABELS,
1291
1562
  DEFAULT_PRODUCT_CAROUSEL_LABELS,
1563
+ DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS,
1292
1564
  DataRequestForm,
1293
1565
  ProductCarousel,
1294
1566
  ShareButtons,
1567
+ WITHDRAWAL_REQUEST_FIELD_LIMITS,
1568
+ WithdrawalRequestForm,
1295
1569
  useCollection
1296
1570
  };
1297
1571
  //# sourceMappingURL=client.js.map