@ecomiq/storefront 2.1.4 → 3.0.1
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/LICENSE +21 -0
- package/README.md +377 -76
- package/dist/account-Dx4e3syi.js +258 -0
- package/dist/account-forms-AVk71ko7.js +39 -0
- package/dist/account-forms.d.ts +13 -0
- package/dist/account.d.ts +240 -0
- package/dist/address-defaults-BqjRhS82.js +14 -0
- package/dist/address-defaults.d.ts +11 -0
- package/dist/analytics/analytics-CG1Qagwl.js +148 -0
- package/dist/analytics/analytics.d.ts +1 -0
- package/dist/analytics/config-DJr-GRhs.js +62 -0
- package/dist/analytics/config.d.ts +22 -0
- package/dist/analytics/consent-B13rBpBs.js +57 -0
- package/dist/analytics/consent-banner-9QvRRy8v.js +239 -0
- package/dist/analytics/consent-banner.d.ts +20 -0
- package/dist/analytics/cookies-pLozllyB.js +100 -0
- package/dist/analytics/cookies.d.ts +6 -0
- package/dist/analytics/destinations-iECrlLIr.js +144 -0
- package/dist/analytics/events-Dt81tIp1.js +102 -0
- package/dist/analytics/events.d.ts +51 -0
- package/dist/analytics/tracker-p4yaoOyk.js +51 -0
- package/dist/api-cache-DsuIn5hX.js +159 -0
- package/dist/bank-accounts-DlEczQQ5.js +81 -0
- package/dist/bank-accounts.d.ts +4 -0
- package/dist/captcha-DAqaIx8m.js +117 -0
- package/dist/captcha.d.ts +34 -0
- package/dist/cart-D7DxZ2lD.js +30 -0
- package/dist/cart-actions-Bsub_FFG.js +116 -0
- package/dist/cart-actions.d.ts +15 -0
- package/dist/cart-drawer-B7PPsR69.js +22 -0
- package/dist/cart-drawer.d.ts +9 -0
- package/dist/cart-form-y9P1r_EX.js +81 -0
- package/dist/cart-form.d.ts +24 -0
- package/dist/cart-summary-ur2VWKr-.js +89 -0
- package/dist/cart-summary.d.ts +31 -0
- package/dist/cart.d.ts +4 -0
- package/dist/catalog-7gJ-l5hW.js +67 -0
- package/dist/catalog-hooks-B7ZP4wYY.js +312 -0
- package/dist/catalog-hooks.d.ts +111 -0
- package/dist/catalog.d.ts +5 -0
- package/dist/checkout-DVWSXANi.js +474 -0
- package/dist/checkout-form-CbOIdENC.js +131 -0
- package/dist/checkout-form.d.ts +20 -0
- package/dist/checkout.d.ts +86 -0
- package/dist/complaint-book-BvwLvfx0.js +117 -0
- package/dist/complaint-book.d.ts +62 -0
- package/dist/culqi-authentication-BGr20vAJ.js +204 -0
- package/dist/culqi-authentication.d.ts +15 -0
- package/dist/culqi-checkout-CTXpKgp_.js +55 -0
- package/dist/culqi-three-ds-HP3mfrvV.js +33 -0
- package/dist/culqi-three-ds.d.ts +29 -0
- package/dist/customer-avatar-CJXSDroK.js +12 -0
- package/dist/customer-avatar.d.ts +5 -0
- package/dist/customer-cookies-DHOgFAMV.js +45 -0
- package/dist/customer-csrf-C1oDX-Py.js +17 -0
- package/dist/customer-csrf.d.ts +3 -0
- package/dist/customer-document-fields-C1K-_DAD.js +70 -0
- package/dist/customer-document-fields.d.ts +9 -0
- package/dist/customer-login-form-C7AC6vXd.js +144 -0
- package/dist/customer-login-form.d.ts +17 -0
- package/dist/customer-registration-form-BEm_NEfP.js +93 -0
- package/dist/customer-registration-form.d.ts +8 -0
- package/dist/document-url-CGsAl5Tf.js +9 -0
- package/dist/documents-T1mzc_j8.js +38 -0
- package/dist/documents.d.ts +16 -0
- package/dist/entries/account.d.ts +11 -0
- package/dist/entries/account.js +10 -0
- package/dist/entries/analytics.d.ts +6 -0
- package/dist/entries/analytics.js +5 -0
- package/dist/entries/cart.d.ts +8 -0
- package/dist/entries/cart.js +7 -0
- package/dist/entries/checkout.d.ts +12 -0
- package/dist/entries/checkout.js +11 -0
- package/dist/entries/order.d.ts +11 -0
- package/dist/entries/order.js +11 -0
- package/dist/favicon-YpyhVJ9H.js +28 -0
- package/dist/favicon.d.ts +4 -0
- package/dist/form-BtQcbXlJ.js +18 -0
- package/dist/form-contracts-BNpHaTGJ.js +166 -0
- package/dist/form-contracts.d.ts +42 -0
- package/dist/form.d.ts +3 -0
- package/dist/hidden-CCWtOWKR.js +3 -0
- package/dist/hidden.d.ts +4 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +50 -0
- package/dist/invoice-fields-BG0HjL6T.js +103 -0
- package/dist/invoice-fields.d.ts +39 -0
- package/dist/links-kFxBZfdb.js +96 -0
- package/dist/links.d.ts +21 -0
- package/dist/mercadopago-checkout-DC7RXa8k.js +27 -0
- package/dist/mercadopago-dialog-UTQev0MG.js +180 -0
- package/dist/mercadopago-dialog.d.ts +21 -0
- package/dist/money-DtGOtDri.js +32 -0
- package/dist/money.d.ts +1 -0
- package/dist/optimistic-cart-BveVOHuh.js +53 -0
- package/dist/optimistic-cart.d.ts +16 -0
- package/dist/order-display-DTt5WBbc.js +112 -0
- package/dist/order-display.d.ts +20 -0
- package/dist/order-draft-DxV1Uhm3.js +38 -0
- package/dist/order-draft.d.ts +16 -0
- package/dist/order-pages-Dm6l5m0m.js +38 -0
- package/dist/order-pages.d.ts +12 -0
- package/dist/order-payment-continuation-D_d5PGA4.js +212 -0
- package/dist/order-payment-continuation.d.ts +10 -0
- package/dist/order-payment-in-review-x4gdLFHZ.js +65 -0
- package/dist/order-payment-in-review.d.ts +5 -0
- package/dist/order-status-BJXLJwxq.js +162 -0
- package/dist/order-status.d.ts +89 -0
- package/dist/order-view-9IlBg5AF.js +62 -0
- package/dist/order-view.d.ts +12 -0
- package/dist/page-action-Ctkf4xC6.js +7 -0
- package/dist/page-seo-a5FJl8x5.js +262 -0
- package/dist/page-seo.d.ts +34 -0
- package/dist/pages-Ca_Gt6mq.js +150 -0
- package/dist/pages.d.ts +45 -0
- package/dist/payment-dialog-frame-B-Q_xQ94.js +57 -0
- package/dist/payment-logo-BoPaGJc_.js +46 -0
- package/dist/payment-logo-svgs-By9lDTLK.js +20 -0
- package/dist/payment-logo.d.ts +11 -0
- package/dist/payment-logos-BYvdwYyJ.js +23 -0
- package/dist/payment-methods-B8ocTgFZ.js +73 -0
- package/dist/payment-methods.d.ts +58 -0
- package/dist/payment-return-5cjypwIw.js +7 -0
- package/dist/place-CmW4hBWY.js +13 -0
- package/dist/place.d.ts +4 -0
- package/dist/preview-BiIBif3C.js +427 -0
- package/dist/preview-address-Cf0sJ-9p.js +21 -0
- package/dist/preview-cart-Dg2CVktS.js +376 -0
- package/dist/product-preview-BspMAecJ.js +51 -0
- package/dist/product-preview.d.ts +5 -0
- package/dist/product-variants-BNDHvs1y.js +46 -0
- package/dist/product-variants.d.ts +12 -0
- package/dist/public-pages-XtwlaLwt.js +194 -0
- package/dist/public-pages.d.ts +67 -0
- package/dist/registration-CpDcv8ql.js +91 -0
- package/dist/registration.d.ts +12 -0
- package/dist/request-security-CRsRMgwj.js +76 -0
- package/dist/rich-text-M-Ju8RL4.js +9 -0
- package/dist/rich-text.d.ts +5 -0
- package/dist/route-text-DcJw2SsK.js +9 -0
- package/dist/route-text.d.ts +4 -0
- package/dist/section-preview-CmsMl3yu.js +83 -0
- package/dist/section-preview.d.ts +12 -0
- package/dist/sections/account-addresses.d.ts +18 -0
- package/dist/sections/account-addresses.js +375 -0
- package/dist/sections/account-order.d.ts +12 -0
- package/dist/sections/account-order.js +592 -0
- package/dist/sections/account-orders.d.ts +8 -0
- package/dist/sections/account-orders.js +359 -0
- package/dist/sections/account-profile.d.ts +16 -0
- package/dist/sections/account-profile.js +123 -0
- package/dist/sections/checkout.d.ts +24 -0
- package/dist/sections/checkout.js +963 -0
- package/dist/sections/complaint-book.d.ts +8 -0
- package/dist/sections/complaint-book.js +538 -0
- package/dist/sections/design-system.d.ts +1 -0
- package/dist/sections/design-system.js +310 -0
- package/dist/sections/draft-invoice.d.ts +20 -0
- package/dist/sections/draft-invoice.js +158 -0
- package/dist/sections/order-tracking.d.ts +19 -0
- package/dist/sections/order-tracking.js +141 -0
- package/dist/sections-CNUMWTF_.js +77 -0
- package/dist/sections.d.ts +19 -0
- package/dist/seo-GpDRhAAA.js +78 -0
- package/dist/session-CKvmFJyg.js +379 -0
- package/dist/session.d.ts +24 -0
- package/dist/settings-B0NO2A6Y.js +78 -0
- package/dist/settings.d.ts +51 -0
- package/dist/source-marker-DhqSkW7n.js +4 -0
- package/dist/sources-B-3JFJfb.js +46 -0
- package/dist/sources.d.ts +7 -0
- package/dist/store-8Q-_LFrQ.js +152 -0
- package/dist/store-contact--hY6CoFb.js +19 -0
- package/dist/store-contact.d.ts +6 -0
- package/dist/store.d.ts +17 -0
- package/dist/storefront-language-DrMjVbmk.js +29 -0
- package/dist/storefront-language.d.ts +14 -0
- package/dist/storefront-text-D25ae1qf.js +11 -0
- package/dist/storefront-text-en-ljjjSS4q.js +1030 -0
- package/dist/storefront-text-pt-BtHaJLcp.js +1030 -0
- package/dist/storefront-text.d.ts +4 -0
- package/dist/stripe-dialog-Ev9s3Z62.js +396 -0
- package/dist/stripe-dialog.d.ts +30 -0
- package/dist/telemetry-HuZXWBPi.js +114 -0
- package/dist/telemetry.d.ts +10 -0
- package/dist/templates-CAqoNyTW.js +40 -0
- package/dist/templates.d.ts +15 -0
- package/dist/urls-CpGQPxvf.js +71 -0
- package/dist/urls.d.ts +18 -0
- package/dist/use-checkout-CZcxXJXi.js +42 -0
- package/dist/use-checkout-payment-9R-zacrJ.js +80 -0
- package/dist/use-checkout-payment.d.ts +18 -0
- package/dist/use-checkout.d.ts +36 -0
- package/dist/use-culqi-checkout-CrjLAipU.js +109 -0
- package/dist/use-order-payment-BKIz_IO0.js +45 -0
- package/dist/use-order-payment.d.ts +18 -0
- package/dist/use-payment-token-CErx-tN-.js +105 -0
- package/dist/vite-payment-logos-kziu7wKb.js +27 -0
- package/dist/vite-payment-logos.d.ts +24 -0
- package/dist/vite-public-assets-DW2fgtVb.js +209 -0
- package/dist/vite-public-assets.d.ts +29 -0
- package/dist/vite-sections-DTBr0-Kg.js +464 -0
- package/dist/vite-sections.d.ts +21 -0
- package/dist/vite-theme-ui-BMO-CDi3.js +41 -0
- package/dist/vite-theme-ui.d.ts +10 -0
- package/dist/vite.d.ts +36 -0
- package/dist/vite.js +127 -0
- package/dist/voucher-payment-CVFDZ1L8.js +66 -0
- package/dist/worker-CNi-gzCu.js +86 -0
- package/dist/worker.d.ts +27 -0
- package/package.json +100 -76
- package/src/sections/account-addresses.tsx +215 -0
- package/src/sections/account-order.tsx +424 -0
- package/src/sections/account-orders.tsx +259 -0
- package/src/sections/account-profile.tsx +70 -0
- package/src/sections/checkout.tsx +586 -0
- package/src/sections/complaint-book.tsx +325 -0
- package/src/sections/design-system.tsx +146 -0
- package/src/sections/draft-invoice.tsx +105 -0
- package/src/sections/order-tracking.tsx +81 -0
- package/src/vite-client.d.ts +4 -0
- package/bin/ecomiq.mjs +0 -179
- package/public/design-bridge.js +0 -318
- package/src/api-proxy.ts +0 -194
- package/src/auth/create-storefront-auth.ts +0 -94
- package/src/auth/ecomiq-oauth-password-client.ts +0 -26
- package/src/auth/ecomiq-oauth-password-constants.ts +0 -24
- package/src/auth/ecomiq-oauth-password-plugin.ts +0 -294
- package/src/auth/ecomiq-oauth-password-types.ts +0 -24
- package/src/auth/ecomiq-storefront-sign-in.ts +0 -44
- package/src/auth/env.ts +0 -7
- package/src/auth/secure-cookies.ts +0 -23
- package/src/auth/storefront-auth-base-url.ts +0 -117
- package/src/auth/storefront-auth.ts +0 -7
- package/src/cart-session.ts +0 -239
- package/src/client.ts +0 -48
- package/src/codegen.mjs +0 -801
- package/src/components/account/customer-addresses-panel.tsx +0 -678
- package/src/components/account/customer-order-detail-panel.tsx +0 -285
- package/src/components/account/customer-orders-panel.tsx +0 -192
- package/src/components/account/customer-profile-panel.tsx +0 -379
- package/src/components/checkout/cart-sync.ts +0 -143
- package/src/components/checkout/checkout-contact-section.tsx +0 -76
- package/src/components/checkout/checkout-external-update-banner.tsx +0 -15
- package/src/components/checkout/checkout-form-controls.tsx +0 -139
- package/src/components/checkout/checkout-line-items.tsx +0 -43
- package/src/components/checkout/checkout-order-summary.tsx +0 -165
- package/src/components/checkout/checkout-payment-section.tsx +0 -212
- package/src/components/checkout/checkout-session-view.tsx +0 -564
- package/src/components/checkout/checkout-shipping-address-section.tsx +0 -400
- package/src/components/checkout/checkout-shipping-section.tsx +0 -98
- package/src/components/checkout/checkout-states.tsx +0 -73
- package/src/components/checkout/checkout-step-card.tsx +0 -101
- package/src/components/checkout/checkout-steps.ts +0 -111
- package/src/components/checkout/form-schema.ts +0 -281
- package/src/components/checkout/format.ts +0 -15
- package/src/components/checkout/index.ts +0 -5
- package/src/components/checkout/map-customer-to-checkout.ts +0 -188
- package/src/components/checkout/types.ts +0 -41
- package/src/components/checkout/use-checkout-customer-prefill.ts +0 -409
- package/src/components/checkout/use-checkout-geo.ts +0 -50
- package/src/components/checkout/use-checkout-session.ts +0 -640
- package/src/components/hero.tsx +0 -291
- package/src/config.d.ts +0 -94
- package/src/config.mjs +0 -146
- package/src/format-money.ts +0 -31
- package/src/health.ts +0 -90
- package/src/hooks/index.ts +0 -9
- package/src/hooks/recently-viewed.ts +0 -125
- package/src/index.ts +0 -349
- package/src/link.tsx +0 -2
- package/src/pages/account/account-page-skeleton.tsx +0 -69
- package/src/pages/account/account-page-view.tsx +0 -270
- package/src/pages/account/account-shell.tsx +0 -101
- package/src/pages/account/parse-account-route.ts +0 -36
- package/src/pages/account.tsx +0 -16
- package/src/pages/api.$.ts +0 -20
- package/src/pages/api.auth.$.ts +0 -16
- package/src/pages/checkout.$checkoutId.tsx +0 -60
- package/src/pages/checkout.tsx +0 -31
- package/src/pages/pages.$pageSlug.tsx +0 -74
- package/src/pages/thank-you.tsx +0 -53
- package/src/providers/auth.tsx +0 -96
- package/src/providers/cart.tsx +0 -275
- package/src/providers/ecomiq.ts +0 -137
- package/src/providers/index.ts +0 -26
- package/src/proxy-api.ts +0 -20
- package/src/public-config.ts +0 -24
- package/src/review-submission.ts +0 -89
- package/src/runtime/context.ts +0 -2
- package/src/runtime/default-layout.tsx +0 -64
- package/src/runtime/head.ts +0 -149
- package/src/runtime/page.tsx +0 -143
- package/src/runtime/postcss-browser-stub.mjs +0 -5
- package/src/server-config-store.ts +0 -35
- package/src/server.ts +0 -286
- package/src/styles.css +0 -76
- package/src/ui/badge.tsx +0 -32
- package/src/ui/button.tsx +0 -31
- package/src/ui/card.tsx +0 -31
- package/src/ui/carousel.tsx +0 -218
- package/src/ui/cn.ts +0 -6
- package/src/ui/index.ts +0 -8
- package/src/ui/separator.tsx +0 -6
- package/src/ui/skeleton.tsx +0 -6
- package/src/ui/storefront-error.tsx +0 -49
- package/src/utils.ts +0 -36
- package/src/vite-inline.mjs +0 -263
- package/templates/__root.tsx +0 -83
- package/templates/router.tsx +0 -19
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { COMPLAINT_BOOK_ATTACHMENT_TYPES, formatOrderDate, type ComplaintBook as ComplaintBookConfiguration } from "@ecomiq/core";
|
|
2
|
+
import { Fragment, useRef, useState, type ComponentProps } from "react";
|
|
3
|
+
import { Form, useNavigation } from "react-router";
|
|
4
|
+
import { Button } from "@theme/ui/button";
|
|
5
|
+
import { Checkbox } from "@theme/ui/checkbox";
|
|
6
|
+
import { Input } from "@theme/ui/input";
|
|
7
|
+
import { Label } from "@theme/ui/label";
|
|
8
|
+
import { RadioGroup, RadioGroupItem } from "@theme/ui/radio-group";
|
|
9
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@theme/ui/select";
|
|
10
|
+
import { Textarea } from "@theme/ui/textarea";
|
|
11
|
+
import {
|
|
12
|
+
Captcha,
|
|
13
|
+
COMPLAINT_DOCUMENT_TYPES,
|
|
14
|
+
identityDocumentRule,
|
|
15
|
+
useStorefrontText,
|
|
16
|
+
complaintBookAttachmentError,
|
|
17
|
+
complaintBookNumber,
|
|
18
|
+
CustomerCsrfField,
|
|
19
|
+
money,
|
|
20
|
+
type ComplaintBookActionData,
|
|
21
|
+
type ComplaintBookData,
|
|
22
|
+
type SectionCatalog,
|
|
23
|
+
type SectionRoot,
|
|
24
|
+
} from "../index";
|
|
25
|
+
|
|
26
|
+
export const catalog: SectionCatalog = {
|
|
27
|
+
name: "Libro de Reclamaciones",
|
|
28
|
+
description: "Formulario de reclamos y quejas con la hoja para imprimir.",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Filed = Extract<ComplaintBookActionData, { entry: unknown }>;
|
|
32
|
+
type Row = readonly [string, string | null];
|
|
33
|
+
|
|
34
|
+
const TYPES = [
|
|
35
|
+
{ value: "claim", label: "Reclamo" },
|
|
36
|
+
{ value: "complaint", label: "Queja" },
|
|
37
|
+
] as const;
|
|
38
|
+
const ITEMS = [
|
|
39
|
+
{ value: "product", label: "Producto" },
|
|
40
|
+
{ value: "service", label: "Servicio" },
|
|
41
|
+
] as const;
|
|
42
|
+
const LEGEND = "mb-3 text-lg font-medium";
|
|
43
|
+
const ATTACHMENT_ACCEPT = [...COMPLAINT_BOOK_ATTACHMENT_TYPES, ".jpg", ".jpeg", ".png", ".webp", ".pdf"].join(",");
|
|
44
|
+
|
|
45
|
+
function Rows({ title, rows }: { title: string; rows: readonly Row[] }) {
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex flex-col gap-2">
|
|
48
|
+
<h3 className="font-medium">{title}</h3>
|
|
49
|
+
<dl className="grid gap-x-6 gap-y-1.5 text-sm sm:grid-cols-[11rem_minmax(0,1fr)]">
|
|
50
|
+
{rows.map(([label, value]) =>
|
|
51
|
+
value ? (
|
|
52
|
+
<Fragment key={label}>
|
|
53
|
+
<dt className="text-muted-foreground">{label}</dt>
|
|
54
|
+
<dd className="break-words whitespace-pre-line">{value}</dd>
|
|
55
|
+
</Fragment>
|
|
56
|
+
) : null,
|
|
57
|
+
)}
|
|
58
|
+
</dl>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function providerRows(book: ComplaintBookConfiguration): Row[] {
|
|
64
|
+
return [
|
|
65
|
+
["Razón social", book.providerName],
|
|
66
|
+
["RUC", book.providerTaxId],
|
|
67
|
+
["Dirección", book.providerAddress],
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Field({ label, name, className, ...props }: ComponentProps<"input"> & { label: string; name: string }) {
|
|
72
|
+
return (
|
|
73
|
+
<div className={`flex flex-col gap-2 ${className ?? ""}`}>
|
|
74
|
+
<Label htmlFor={`complaint-${name}`}>{label}</Label>
|
|
75
|
+
<Input id={`complaint-${name}`} name={name} {...props} />
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Area({ label, name, maxLength }: { label: string; name: string; maxLength: number }) {
|
|
81
|
+
return (
|
|
82
|
+
<div className="flex flex-col gap-2">
|
|
83
|
+
<Label htmlFor={`complaint-${name}`}>{label}</Label>
|
|
84
|
+
<Textarea id={`complaint-${name}`} name={name} rows={4} maxLength={maxLength} required />
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function Choices({ name, options }: { name: string; options: readonly { value: string; label: string }[] }) {
|
|
90
|
+
return (
|
|
91
|
+
<RadioGroup name={name} defaultValue={options[0]?.value} required className="flex flex-wrap gap-6">
|
|
92
|
+
{options.map((option) => (
|
|
93
|
+
<div key={option.value} className="flex items-center gap-2">
|
|
94
|
+
<RadioGroupItem id={`complaint-${name}-${option.value}`} value={option.value} />
|
|
95
|
+
<Label htmlFor={`complaint-${name}-${option.value}`}>{option.label}</Label>
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
</RadioGroup>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Attachments() {
|
|
103
|
+
const storefrontTranslate = useStorefrontText();
|
|
104
|
+
const input = useRef<HTMLInputElement>(null);
|
|
105
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
106
|
+
const [error, setError] = useState<string | null>(null);
|
|
107
|
+
|
|
108
|
+
function attach(next: File[]) {
|
|
109
|
+
const transfer = new DataTransfer();
|
|
110
|
+
for (const file of next) transfer.items.add(file);
|
|
111
|
+
if (input.current) input.current.files = transfer.files;
|
|
112
|
+
setFiles(next);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function pick(picked: FileList | null) {
|
|
116
|
+
const next = [...files, ...Array.from(picked ?? [])];
|
|
117
|
+
const invalid = complaintBookAttachmentError(next);
|
|
118
|
+
setError(invalid);
|
|
119
|
+
attach(invalid ? files : next);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function remove(index: number) {
|
|
123
|
+
setError(null);
|
|
124
|
+
attach(files.filter((_, position) => position !== index));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex flex-col gap-2">
|
|
129
|
+
<Field ref={input} label={storefrontTranslate("Adjuntos (opcional)")} name="files" type="file" multiple accept={ATTACHMENT_ACCEPT} aria-describedby="complaint-files-help" onChange={(event) => pick(event.currentTarget.files)} />
|
|
130
|
+
<p id="complaint-files-help" className="text-sm text-muted-foreground">
|
|
131
|
+
{storefrontTranslate("Hasta 3 fotos o PDF de 5 MB.")} </p>
|
|
132
|
+
{error ? (
|
|
133
|
+
<p role="alert" className="text-sm text-destructive">
|
|
134
|
+
{storefrontTranslate(error)}
|
|
135
|
+
</p>
|
|
136
|
+
) : null}
|
|
137
|
+
{files.length > 0 ? (
|
|
138
|
+
<ul className="flex flex-col gap-1 text-sm">
|
|
139
|
+
{files.map((file, index) => (
|
|
140
|
+
<li key={`${index}-${file.name}`} className="flex items-center justify-between gap-3">
|
|
141
|
+
<span className="min-w-0 truncate">{file.name}</span>
|
|
142
|
+
<Button type="button" variant="ghost" size="sm" aria-label={storefrontTranslate("Quitar {value0}", { value0: file.name })} onClick={() => remove(index)}>
|
|
143
|
+
{storefrontTranslate("Quitar")}</Button>
|
|
144
|
+
</li>
|
|
145
|
+
))}
|
|
146
|
+
</ul>
|
|
147
|
+
) : null}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ComplaintForm({ book, error, countries }: { book: ComplaintBookConfiguration; error: string | null; countries: ComplaintBookData["countries"] }) {
|
|
153
|
+
const storefrontTranslate = useStorefrontText();
|
|
154
|
+
const busy = useNavigation().state !== "idle";
|
|
155
|
+
const [kind, setKind] = useState<keyof typeof COMPLAINT_DOCUMENT_TYPES>("nationalId");
|
|
156
|
+
const [countryCode, setCountryCode] = useState("");
|
|
157
|
+
const [minor, setMinor] = useState(false);
|
|
158
|
+
const rule = identityDocumentRule(COMPLAINT_DOCUMENT_TYPES[kind]);
|
|
159
|
+
return (
|
|
160
|
+
<Form method="post" encType="multipart/form-data" className="flex flex-col gap-8">
|
|
161
|
+
<CustomerCsrfField />
|
|
162
|
+
<fieldset>
|
|
163
|
+
<legend className={LEGEND}>{storefrontTranslate("Tipo")}</legend>
|
|
164
|
+
<Choices name="type" options={TYPES} />
|
|
165
|
+
</fieldset>
|
|
166
|
+
<fieldset className="grid gap-4 sm:grid-cols-2">
|
|
167
|
+
<legend className={LEGEND}>{storefrontTranslate("Tus datos")}</legend>
|
|
168
|
+
<Field label={storefrontTranslate("Nombre completo")} name="consumerName" autoComplete="name" maxLength={200} required className="sm:col-span-2" />
|
|
169
|
+
<div className="flex flex-col gap-2">
|
|
170
|
+
<Label htmlFor="complaint-documentType">{storefrontTranslate("Tipo de documento")}</Label>
|
|
171
|
+
<Select name="documentType" value={kind} onValueChange={(value) => setKind(value === "taxId" || value === "foreignId" || value === "passport" ? value : "nationalId")}>
|
|
172
|
+
<SelectTrigger id="complaint-documentType" className="w-full">
|
|
173
|
+
<SelectValue />
|
|
174
|
+
</SelectTrigger>
|
|
175
|
+
<SelectContent>
|
|
176
|
+
{Object.entries(COMPLAINT_DOCUMENT_TYPES).map(([item, type]) => (
|
|
177
|
+
<SelectItem key={item} value={item}>
|
|
178
|
+
{identityDocumentRule(type).label}
|
|
179
|
+
</SelectItem>
|
|
180
|
+
))}
|
|
181
|
+
</SelectContent>
|
|
182
|
+
</Select>
|
|
183
|
+
</div>
|
|
184
|
+
<label className="flex flex-col gap-2">{storefrontTranslate("País emisor del documento")} <select name="documentCountryCode" required value={countryCode} onChange={(event) => setCountryCode(event.currentTarget.value)} className="h-10 rounded-md border bg-background px-3 text-base md:text-sm">
|
|
185
|
+
<option value="">{storefrontTranslate("Elige…")}</option>
|
|
186
|
+
{countries.map((country) => <option key={country.code} value={country.code}>{country.name}</option>)}
|
|
187
|
+
</select>
|
|
188
|
+
</label>
|
|
189
|
+
<Field label={storefrontTranslate("Número de documento")} name="documentNumber" inputMode={rule.inputMode} maxLength={rule.maxLength} pattern={rule.pattern} title={storefrontTranslate(rule.error)} autoComplete="off" required />
|
|
190
|
+
<Field label={storefrontTranslate("Dirección")} name="consumerAddress" autoComplete="street-address" maxLength={300} required className="sm:col-span-2" />
|
|
191
|
+
<Field label={storefrontTranslate("Celular")} name="consumerPhone" type="tel" autoComplete="tel" maxLength={30} required />
|
|
192
|
+
<Field label={storefrontTranslate("Correo")} name="consumerEmail" type="email" autoComplete="email" maxLength={254} required />
|
|
193
|
+
<div className="flex items-center gap-2 sm:col-span-2">
|
|
194
|
+
<Checkbox id="complaint-minor" name="minor" checked={minor} onCheckedChange={(checked) => setMinor(checked === true)} />
|
|
195
|
+
<Label htmlFor="complaint-minor">{storefrontTranslate("Soy menor de edad")}</Label>
|
|
196
|
+
</div>
|
|
197
|
+
</fieldset>
|
|
198
|
+
{minor ? (
|
|
199
|
+
<fieldset className="grid gap-4 sm:grid-cols-2">
|
|
200
|
+
<legend className={LEGEND}>{storefrontTranslate("Padre, madre o apoderado")}</legend>
|
|
201
|
+
<Field label={storefrontTranslate("Nombre completo")} name="guardianName" maxLength={200} required className="sm:col-span-2" />
|
|
202
|
+
<Field label={storefrontTranslate("Dirección")} name="guardianAddress" maxLength={300} required className="sm:col-span-2" />
|
|
203
|
+
<Field label={storefrontTranslate("Celular")} name="guardianPhone" type="tel" maxLength={30} required />
|
|
204
|
+
<Field label={storefrontTranslate("Correo")} name="guardianEmail" type="email" maxLength={254} required />
|
|
205
|
+
</fieldset>
|
|
206
|
+
) : null}
|
|
207
|
+
<fieldset className="grid gap-4 sm:grid-cols-2">
|
|
208
|
+
<legend className={LEGEND}>{storefrontTranslate("Producto o servicio")}</legend>
|
|
209
|
+
<div className="sm:col-span-2">
|
|
210
|
+
<Choices name="itemKind" options={ITEMS} />
|
|
211
|
+
</div>
|
|
212
|
+
<Field label={storefrontTranslate("Descripción")} name="itemDescription" maxLength={500} required className="sm:col-span-2" />
|
|
213
|
+
<Field label={storefrontTranslate("Monto reclamado en {value0} (opcional)", { value0: book.currency })} name="itemAmount" type="number" inputMode="decimal" min={0} step="0.01" />
|
|
214
|
+
<Field label={storefrontTranslate("N.° de pedido (opcional)")} name="orderNumber" maxLength={50} />
|
|
215
|
+
</fieldset>
|
|
216
|
+
<fieldset className="flex flex-col gap-4">
|
|
217
|
+
<legend className={LEGEND}>{storefrontTranslate("Detalle")}</legend>
|
|
218
|
+
<Area label={storefrontTranslate("Qué pasó")} name="detail" maxLength={4000} />
|
|
219
|
+
<Area label={storefrontTranslate("Qué solicitas")} name="request" maxLength={2000} />
|
|
220
|
+
<Attachments />
|
|
221
|
+
</fieldset>
|
|
222
|
+
<div className="flex items-center gap-2">
|
|
223
|
+
<Checkbox id="complaint-confirmed" name="confirmed" required />
|
|
224
|
+
<Label htmlFor="complaint-confirmed">{storefrontTranslate("Declaro que los datos son verdaderos.")}</Label>
|
|
225
|
+
</div>
|
|
226
|
+
<Captcha captcha={book.captcha} action="complaint_book" errorClass="text-sm text-destructive" busy={busy} />
|
|
227
|
+
{error ? (
|
|
228
|
+
<p role="alert" className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
|
229
|
+
{storefrontTranslate(error)}
|
|
230
|
+
</p>
|
|
231
|
+
) : null}
|
|
232
|
+
<Button type="submit" disabled={busy || !book.captcha.isConfigured} className="sm:w-fit">
|
|
233
|
+
{busy ? "Enviando…" : "Enviar"}
|
|
234
|
+
</Button>
|
|
235
|
+
</Form>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function Sheet({ book, filed, language, timeZone }: { book: ComplaintBookConfiguration; filed: Filed; language: string; timeZone: string }) {
|
|
240
|
+
const storefrontTranslate = useStorefrontText();
|
|
241
|
+
const { entry, sheet, attachments } = filed;
|
|
242
|
+
const { consumer, guardian, item } = sheet;
|
|
243
|
+
const type = TYPES.find((option) => option.value === sheet.type)?.label ?? "";
|
|
244
|
+
const date = (iso: string) => formatOrderDate(iso, language, timeZone);
|
|
245
|
+
return (
|
|
246
|
+
<article className="flex flex-col gap-6 rounded-lg border bg-card p-6 text-card-foreground md:p-8">
|
|
247
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
248
|
+
<div className="flex flex-col gap-1">
|
|
249
|
+
<p className="text-sm text-muted-foreground">{storefrontTranslate("Hoja de reclamación ·")} {type}</p>
|
|
250
|
+
<h2 className="font-heading text-3xl">{complaintBookNumber(entry.number)}</h2>
|
|
251
|
+
</div>
|
|
252
|
+
<Button type="button" variant="outline" onClick={() => window.print()} className="print:hidden">
|
|
253
|
+
{storefrontTranslate("Imprimir")} </Button>
|
|
254
|
+
</div>
|
|
255
|
+
<Rows
|
|
256
|
+
title={storefrontTranslate("Fechas")}
|
|
257
|
+
rows={[
|
|
258
|
+
["Presentado", date(entry.filedOn)],
|
|
259
|
+
["Respuesta hasta", date(entry.responseDueOn)],
|
|
260
|
+
]}
|
|
261
|
+
/>
|
|
262
|
+
<Rows title={storefrontTranslate("Proveedor")} rows={providerRows(book)} />
|
|
263
|
+
<Rows
|
|
264
|
+
title={storefrontTranslate("Consumidor")}
|
|
265
|
+
rows={[
|
|
266
|
+
[storefrontTranslate("Nombre"), consumer.name],
|
|
267
|
+
[identityDocumentRule(COMPLAINT_DOCUMENT_TYPES[consumer.documentType]).label, consumer.documentNumber],
|
|
268
|
+
[storefrontTranslate("Dirección"), consumer.address],
|
|
269
|
+
[storefrontTranslate("Celular"), consumer.phone],
|
|
270
|
+
[storefrontTranslate("Correo"), consumer.email],
|
|
271
|
+
]}
|
|
272
|
+
/>
|
|
273
|
+
{guardian ? (
|
|
274
|
+
<Rows
|
|
275
|
+
title={storefrontTranslate("Padre, madre o apoderado")}
|
|
276
|
+
rows={[
|
|
277
|
+
[storefrontTranslate("Nombre"), guardian.name],
|
|
278
|
+
[storefrontTranslate("Dirección"), guardian.address],
|
|
279
|
+
[storefrontTranslate("Celular"), guardian.phone],
|
|
280
|
+
[storefrontTranslate("Correo"), guardian.email],
|
|
281
|
+
]}
|
|
282
|
+
/>
|
|
283
|
+
) : null}
|
|
284
|
+
<Rows
|
|
285
|
+
title={ITEMS.find((option) => option.value === item.kind)?.label ?? ""}
|
|
286
|
+
rows={[
|
|
287
|
+
[storefrontTranslate("Descripción"), item.description],
|
|
288
|
+
["Monto reclamado", item.amount === null ? null : money(item.amount, book.currency, language)],
|
|
289
|
+
["N.° de pedido", sheet.orderNumber],
|
|
290
|
+
]}
|
|
291
|
+
/>
|
|
292
|
+
<Rows
|
|
293
|
+
title={type}
|
|
294
|
+
rows={[
|
|
295
|
+
[storefrontTranslate("Qué pasó"), sheet.detail],
|
|
296
|
+
[storefrontTranslate("Qué solicitas"), sheet.request],
|
|
297
|
+
["Adjuntos", attachments.join("\n")],
|
|
298
|
+
]}
|
|
299
|
+
/>
|
|
300
|
+
</article>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function ComplaintBook({ loaderData, actionData, ...root }: { loaderData: ComplaintBookData; actionData?: ComplaintBookActionData } & SectionRoot) {
|
|
305
|
+
const storefrontTranslate = useStorefrontText();
|
|
306
|
+
const { book, language, timeZone } = loaderData;
|
|
307
|
+
const filed = actionData && "entry" in actionData ? actionData : null;
|
|
308
|
+
const error = actionData && "error" in actionData ? actionData.error : null;
|
|
309
|
+
return (
|
|
310
|
+
<section {...root} className="mx-auto flex w-full max-w-3xl flex-col gap-8 px-4 py-12 md:py-20">
|
|
311
|
+
<div className="flex flex-col gap-3">
|
|
312
|
+
<h1 className="text-4xl leading-tight md:text-5xl">{storefrontTranslate("Libro de Reclamaciones")}</h1>
|
|
313
|
+
<p className="text-muted-foreground">{storefrontTranslate("Reclamo: disconformidad con el producto o servicio. Queja: malestar con la atención.")}</p>
|
|
314
|
+
</div>
|
|
315
|
+
{filed ? (
|
|
316
|
+
<Sheet book={book} filed={filed} language={`${language}-${loaderData.countryCode}`} timeZone={timeZone} />
|
|
317
|
+
) : (
|
|
318
|
+
<>
|
|
319
|
+
<Rows title={storefrontTranslate("Proveedor")} rows={providerRows(book)} />
|
|
320
|
+
<ComplaintForm countries={loaderData.countries} book={book} error={error} />
|
|
321
|
+
</>
|
|
322
|
+
)}
|
|
323
|
+
</section>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useStorefrontText } from "../index";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Badge } from "@theme/ui/badge";
|
|
4
|
+
import { Button } from "@theme/ui/button";
|
|
5
|
+
import { Checkbox } from "@theme/ui/checkbox";
|
|
6
|
+
import { Input } from "@theme/ui/input";
|
|
7
|
+
import { Label } from "@theme/ui/label";
|
|
8
|
+
import { Textarea } from "@theme/ui/textarea";
|
|
9
|
+
|
|
10
|
+
const COLORS = [
|
|
11
|
+
{ token: "primary", name: "Principal" },
|
|
12
|
+
{ token: "primary-foreground", name: "Sobre principal" },
|
|
13
|
+
{ token: "background", name: "Fondo" },
|
|
14
|
+
{ token: "foreground", name: "Texto" },
|
|
15
|
+
{ token: "secondary", name: "Secundario" },
|
|
16
|
+
{ token: "muted", name: "Suave" },
|
|
17
|
+
{ token: "muted-foreground", name: "Texto suave" },
|
|
18
|
+
{ token: "accent", name: "Acento" },
|
|
19
|
+
{ token: "border", name: "Borde" },
|
|
20
|
+
{ token: "destructive", name: "Error" },
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
const SCHEMES = [
|
|
24
|
+
{ scheme: "claro", name: "Claro" },
|
|
25
|
+
{ scheme: "oscuro", name: "Oscuro" },
|
|
26
|
+
{ scheme: "marca", name: "Marca" },
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const CORNERS = [
|
|
30
|
+
{ className: "rounded-none", name: "Sin redondeo" },
|
|
31
|
+
{ className: "rounded-sm", name: "Pequeño" },
|
|
32
|
+
{ className: "rounded-md", name: "Mediano" },
|
|
33
|
+
{ className: "rounded-lg", name: "Grande" },
|
|
34
|
+
{ className: "rounded-xl", name: "Muy grande" },
|
|
35
|
+
{ className: "rounded-full", name: "Círculo" },
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
function Group({ title, children }: { title: string; children: ReactNode }) {
|
|
39
|
+
return (
|
|
40
|
+
<section className="flex flex-col gap-4">
|
|
41
|
+
<h2 className="font-body text-xs font-medium uppercase tracking-wide text-muted-foreground">{title}</h2>
|
|
42
|
+
{children}
|
|
43
|
+
</section>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function DesignSystem() {
|
|
48
|
+
const storefrontTranslate = useStorefrontText();
|
|
49
|
+
return (
|
|
50
|
+
<div data-ecomiq-design="" className="fixed inset-0 z-[100] overflow-y-auto bg-background text-foreground">
|
|
51
|
+
<div className="mx-auto flex max-w-5xl flex-col gap-12 px-6 py-10">
|
|
52
|
+
<header className="flex flex-col gap-1">
|
|
53
|
+
<h1 className="font-heading text-3xl">Sistema de diseño</h1>
|
|
54
|
+
<p className="text-sm text-muted-foreground">Colores, letras y piezas de tu tema. Los cambios del panel Tema se ven aquí al instante.</p>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<Group title="Colores">
|
|
58
|
+
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
|
|
59
|
+
{COLORS.map((color) => (
|
|
60
|
+
<div key={color.token} className="flex flex-col gap-2">
|
|
61
|
+
<div className="h-16 rounded-md border border-border" style={{ background: `var(--${color.token})` }} />
|
|
62
|
+
<div className="flex flex-col">
|
|
63
|
+
<span className="text-sm font-medium">{color.name}</span>
|
|
64
|
+
<code className="text-xs text-muted-foreground">--{color.token}</code>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</Group>
|
|
70
|
+
|
|
71
|
+
<Group title="Esquemas">
|
|
72
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
73
|
+
{SCHEMES.map((item) => (
|
|
74
|
+
<div key={item.scheme} data-scheme={item.scheme} className="flex flex-col gap-3 rounded-lg border border-border p-5">
|
|
75
|
+
<span className="text-xs text-muted-foreground">{item.name}</span>
|
|
76
|
+
<p className="font-heading text-xl">Nueva colección</p>
|
|
77
|
+
<p className="text-sm text-muted-foreground">Piezas hechas para durar.</p>
|
|
78
|
+
<Button size="sm" className="self-start">
|
|
79
|
+
{storefrontTranslate("Comprar")}</Button>
|
|
80
|
+
</div>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
</Group>
|
|
84
|
+
|
|
85
|
+
<Group title="Tipografía">
|
|
86
|
+
<div className="flex flex-col gap-3">
|
|
87
|
+
<p className="font-heading text-5xl leading-tight">Títulos que se recuerdan</p>
|
|
88
|
+
<p className="font-heading text-3xl">Subtítulo de sección</p>
|
|
89
|
+
<p className="font-heading text-xl">Título de tarjeta</p>
|
|
90
|
+
<p className="max-w-2xl text-base">Este es el texto de tu tienda: descripciones de productos, políticas y todo lo que lee tu cliente antes de comprar.</p>
|
|
91
|
+
<p className="text-sm text-muted-foreground">Texto pequeño para notas y ayudas.</p>
|
|
92
|
+
</div>
|
|
93
|
+
</Group>
|
|
94
|
+
|
|
95
|
+
<Group title="Botones">
|
|
96
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
97
|
+
<Button>{storefrontTranslate("Principal")}</Button>
|
|
98
|
+
<Button variant="secondary">Secundario</Button>
|
|
99
|
+
<Button variant="outline">Con borde</Button>
|
|
100
|
+
<Button variant="ghost">Sin fondo</Button>
|
|
101
|
+
<Button variant="link">Enlace</Button>
|
|
102
|
+
</div>
|
|
103
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
104
|
+
<Button size="sm">Pequeño</Button>
|
|
105
|
+
<Button>Normal</Button>
|
|
106
|
+
<Button size="lg">Grande</Button>
|
|
107
|
+
</div>
|
|
108
|
+
</Group>
|
|
109
|
+
|
|
110
|
+
<Group title="Campos">
|
|
111
|
+
<div className="grid max-w-2xl gap-4 sm:grid-cols-2">
|
|
112
|
+
<div className="flex flex-col gap-2">
|
|
113
|
+
<Label htmlFor="ecomiq-design-email">{storefrontTranslate("Correo")}</Label>
|
|
114
|
+
<Input id="ecomiq-design-email" placeholder={storefrontTranslate("tu@correo.com")} />
|
|
115
|
+
</div>
|
|
116
|
+
<div className="flex flex-col gap-2">
|
|
117
|
+
<Label htmlFor="ecomiq-design-note">Nota</Label>
|
|
118
|
+
<Textarea id="ecomiq-design-note" placeholder="Escribe aquí" rows={2} />
|
|
119
|
+
</div>
|
|
120
|
+
<div className="flex items-center gap-2">
|
|
121
|
+
<Checkbox id="ecomiq-design-news" defaultChecked />
|
|
122
|
+
<Label htmlFor="ecomiq-design-news">Quiero recibir novedades</Label>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
125
|
+
<Badge>Nuevo</Badge>
|
|
126
|
+
<Badge variant="secondary">Envío gratis</Badge>
|
|
127
|
+
<Badge variant="outline">{storefrontTranslate("Agotado")}</Badge>
|
|
128
|
+
<Badge variant="destructive">-20 %</Badge>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</Group>
|
|
132
|
+
|
|
133
|
+
<Group title="Esquinas">
|
|
134
|
+
<div className="flex flex-wrap gap-4">
|
|
135
|
+
{CORNERS.map((corner) => (
|
|
136
|
+
<div key={corner.className} className="flex flex-col items-center gap-2">
|
|
137
|
+
<div className={`size-16 border border-border bg-muted ${corner.className}`} />
|
|
138
|
+
<span className="text-xs text-muted-foreground">{corner.name}</span>
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
</Group>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Form, Link, useNavigation } from "react-router";
|
|
2
|
+
import { Button } from "@theme/ui/button";
|
|
3
|
+
import { useStorefrontText, CustomerCsrfField, money, setting, type SectionCatalog, type SectionRoot } from "../index";
|
|
4
|
+
import { orderSummaryLines, type OrderDraftActionData, type OrderDraftData } from "../entries/order";
|
|
5
|
+
|
|
6
|
+
export const catalog: SectionCatalog = {
|
|
7
|
+
name: "Pedido por pagar",
|
|
8
|
+
description: "Productos y totales de un pedido por pagar, con el botón para pagarlo.",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const settings = {
|
|
12
|
+
invoice: {
|
|
13
|
+
eyebrow: setting.text("Factura", "Antetítulo"),
|
|
14
|
+
title: setting.text("Tu pedido", "Título"),
|
|
15
|
+
description: setting.text("Revisa los productos y paga cuando quieras.", "Descripción"),
|
|
16
|
+
pay: setting.text("Pagar pedido", "Botón"),
|
|
17
|
+
note: setting.text("Se respetarán los importes y el envío de este pedido.", "Nota"),
|
|
18
|
+
split: setting.text("Tu compra se dividió en varios pedidos. Revisa y paga cada uno.", "Pedidos separados"),
|
|
19
|
+
closed: setting.text("Este pedido ya no está disponible para pagar. Escríbenos si necesitas ayuda.", "Pedido cerrado"),
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function DraftInvoice({ loaderData, actionData, ...root }: { loaderData: OrderDraftData; actionData?: OrderDraftActionData } & SectionRoot) {
|
|
24
|
+
const storefrontTranslate = useStorefrontText();
|
|
25
|
+
const { draft, payable, language } = loaderData;
|
|
26
|
+
const orderPaths = actionData?.orderPaths ?? loaderData.orderPaths;
|
|
27
|
+
const busy = useNavigation().state !== "idle";
|
|
28
|
+
const price = (value: number) => money(value, draft.currencyCode, language);
|
|
29
|
+
return (
|
|
30
|
+
<section {...root} className="mx-auto flex w-full max-w-2xl flex-col gap-8 px-4 py-12 md:py-20">
|
|
31
|
+
<div className="flex flex-col gap-3">
|
|
32
|
+
<p className="text-xs font-medium tracking-widest text-muted-foreground uppercase">
|
|
33
|
+
{settings.invoice.eyebrow} {draft.draftNumber}
|
|
34
|
+
</p>
|
|
35
|
+
<h1 className="text-4xl leading-tight md:text-5xl">{settings.invoice.title}</h1>
|
|
36
|
+
<p className="text-muted-foreground">{settings.invoice.description}</p>
|
|
37
|
+
</div>
|
|
38
|
+
<ul className="divide-y rounded-lg border bg-card text-card-foreground">
|
|
39
|
+
{draft.items.map((item) => (
|
|
40
|
+
<li key={item.id} className="flex items-center gap-4 p-4">
|
|
41
|
+
<span className="block w-16 shrink-0 overflow-hidden rounded-md bg-muted">
|
|
42
|
+
{item.thumbnail ? (
|
|
43
|
+
<img src={item.thumbnail} alt="" width={64} height={80} loading="lazy" decoding="async" className="aspect-[4/5] w-full object-cover" />
|
|
44
|
+
) : (
|
|
45
|
+
<span className="block aspect-[4/5]" />
|
|
46
|
+
)}
|
|
47
|
+
</span>
|
|
48
|
+
<span className="flex flex-1 flex-col gap-0.5">
|
|
49
|
+
<span className="font-medium">{item.title}</span>
|
|
50
|
+
{item.subtitle ? <span className="text-sm text-muted-foreground">{item.subtitle}</span> : null}
|
|
51
|
+
<span className="text-sm text-muted-foreground tabular-nums">×{item.quantity}</span>
|
|
52
|
+
</span>
|
|
53
|
+
<span className="font-medium tabular-nums">{price(item.total)}</span>
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
<dl className="flex flex-col gap-2.5 rounded-lg border bg-card p-6 text-sm text-card-foreground">
|
|
58
|
+
{orderSummaryLines(draft, { discount: "Descuento" }).map((line) =>
|
|
59
|
+
line.key === "total" ? (
|
|
60
|
+
<div key={line.key} className="flex items-baseline justify-between border-t pt-3 font-medium">
|
|
61
|
+
<dt>{storefrontTranslate(line.label)}</dt>
|
|
62
|
+
<dd className="font-heading text-xl tabular-nums">{price(line.amount ?? 0)}</dd>
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
<div key={line.key} className="flex justify-between">
|
|
66
|
+
<dt className="text-muted-foreground">{storefrontTranslate(line.label)}</dt>
|
|
67
|
+
<dd className="tabular-nums">{line.amount === null ? storefrontTranslate(line.text ?? "") : price(line.amount)}</dd>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
)}
|
|
71
|
+
</dl>
|
|
72
|
+
{orderPaths.length > 0 ? (
|
|
73
|
+
<nav aria-label={storefrontTranslate("Pedidos para pagar")} className="flex flex-col gap-3 rounded-lg border p-5">
|
|
74
|
+
<p>{settings.invoice.split}</p>
|
|
75
|
+
<ul className="flex flex-col gap-3">
|
|
76
|
+
{orderPaths.map((path, index) => (
|
|
77
|
+
<li key={path}>
|
|
78
|
+
<Link to={path} className="font-medium underline underline-offset-4">
|
|
79
|
+
{storefrontTranslate("Ver y pagar pedido")} {index + 1}
|
|
80
|
+
</Link>
|
|
81
|
+
</li>
|
|
82
|
+
))}
|
|
83
|
+
</ul>
|
|
84
|
+
</nav>
|
|
85
|
+
) : payable ? (
|
|
86
|
+
<Form method="post" className="flex flex-col gap-3">
|
|
87
|
+
<CustomerCsrfField />
|
|
88
|
+
{actionData?.error ? (
|
|
89
|
+
<p role="alert" className="text-sm text-destructive">
|
|
90
|
+
{storefrontTranslate(actionData.error)}
|
|
91
|
+
</p>
|
|
92
|
+
) : null}
|
|
93
|
+
<Button type="submit" size="lg" disabled={busy}>
|
|
94
|
+
{busy ? storefrontTranslate("Preparando…") : settings.invoice.pay}
|
|
95
|
+
</Button>
|
|
96
|
+
<p className="text-center text-xs text-muted-foreground">{settings.invoice.note}</p>
|
|
97
|
+
</Form>
|
|
98
|
+
) : (
|
|
99
|
+
<p role="status" className="rounded-md bg-muted px-4 py-3 text-sm">
|
|
100
|
+
{settings.invoice.closed}
|
|
101
|
+
</p>
|
|
102
|
+
)}
|
|
103
|
+
</section>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { formatTrackingStatus } from "@ecomiq/core";
|
|
2
|
+
import { Form } from "react-router";
|
|
3
|
+
import { Button } from "@theme/ui/button";
|
|
4
|
+
import { Input } from "@theme/ui/input";
|
|
5
|
+
import { setting, type SectionCatalog, type SectionRoot, type TrackingData } from "../index";
|
|
6
|
+
|
|
7
|
+
export const catalog: SectionCatalog = {
|
|
8
|
+
name: "Seguimiento",
|
|
9
|
+
description: "Buscador por código para seguir el envío de un pedido.",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const settings = {
|
|
13
|
+
tracking: {
|
|
14
|
+
eyebrow: setting.text("Envíos", "Antetítulo"),
|
|
15
|
+
title: setting.text("Sigue tu pedido", "Título"),
|
|
16
|
+
description: setting.text("Ingresa el código de seguimiento que te enviamos por correo.", "Descripción"),
|
|
17
|
+
field: setting.text("Código de seguimiento", "Campo"),
|
|
18
|
+
button: setting.text("Buscar", "Botón"),
|
|
19
|
+
missing: setting.text("No encontramos ese código. Revísalo e intenta de nuevo.", "Código no encontrado"),
|
|
20
|
+
shipment: setting.text("Envío", "Envío sin courier"),
|
|
21
|
+
carrier: setting.text("Ver en el courier", "Enlace al courier"),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function OrderTracking({ loaderData, ...root }: { loaderData: TrackingData } & SectionRoot) {
|
|
26
|
+
const { code, tracking, missing, language, timeZone } = loaderData;
|
|
27
|
+
const date = new Intl.DateTimeFormat(language, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", timeZone });
|
|
28
|
+
return (
|
|
29
|
+
<section {...root} className="mx-auto flex w-full max-w-2xl flex-col gap-8 px-4 py-12 md:py-20">
|
|
30
|
+
<div className="flex flex-col gap-3">
|
|
31
|
+
<p className="text-xs font-medium tracking-widest text-muted-foreground uppercase">{settings.tracking.eyebrow}</p>
|
|
32
|
+
<h1 className="text-4xl leading-tight md:text-5xl">{settings.tracking.title}</h1>
|
|
33
|
+
<p className="text-muted-foreground">{settings.tracking.description}</p>
|
|
34
|
+
</div>
|
|
35
|
+
<Form method="get" action="/track" className="flex flex-col gap-2 sm:flex-row">
|
|
36
|
+
<Input name="code" defaultValue={code} placeholder={settings.tracking.field} aria-label={settings.tracking.field} required />
|
|
37
|
+
<Button type="submit">{settings.tracking.button}</Button>
|
|
38
|
+
</Form>
|
|
39
|
+
{missing ? (
|
|
40
|
+
<p role="alert" className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
|
41
|
+
{settings.tracking.missing}
|
|
42
|
+
</p>
|
|
43
|
+
) : null}
|
|
44
|
+
{tracking ? (
|
|
45
|
+
<div className="flex flex-col gap-6 rounded-lg border bg-card p-6 text-card-foreground md:p-8">
|
|
46
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
47
|
+
<div className="flex flex-col gap-1">
|
|
48
|
+
<p className="text-sm text-muted-foreground">
|
|
49
|
+
{tracking.carrierName ?? settings.tracking.shipment} · {tracking.trackingNumber}
|
|
50
|
+
</p>
|
|
51
|
+
<p className="font-heading text-3xl">{formatTrackingStatus(tracking.status)}</p>
|
|
52
|
+
</div>
|
|
53
|
+
{tracking.trackingLink ? (
|
|
54
|
+
<a href={tracking.trackingLink} target="_blank" rel="noopener noreferrer" className="text-sm font-medium underline underline-offset-4">
|
|
55
|
+
{settings.tracking.carrier}
|
|
56
|
+
</a>
|
|
57
|
+
) : null}
|
|
58
|
+
</div>
|
|
59
|
+
{tracking.message ? <p className="text-sm text-muted-foreground">{tracking.message}</p> : null}
|
|
60
|
+
<ol className="flex flex-col">
|
|
61
|
+
{tracking.events.map((event, index) => (
|
|
62
|
+
<li key={`${event.name}-${index}`} className="relative flex gap-4 pb-6 text-sm last:pb-0">
|
|
63
|
+
{index < tracking.events.length - 1 ? <span aria-hidden="true" className="absolute top-4 left-[5px] h-full w-px bg-border" /> : null}
|
|
64
|
+
<span aria-hidden="true" className="relative mt-1.5 size-2.5 shrink-0 rounded-full bg-primary" />
|
|
65
|
+
<span className="flex flex-col gap-0.5">
|
|
66
|
+
<span className="font-medium">{event.name}</span>
|
|
67
|
+
{event.description ? <span className="text-muted-foreground">{event.description}</span> : null}
|
|
68
|
+
{event.date ? (
|
|
69
|
+
<time dateTime={event.date} className="text-xs text-muted-foreground">
|
|
70
|
+
{date.format(new Date(event.date))}
|
|
71
|
+
</time>
|
|
72
|
+
) : null}
|
|
73
|
+
</span>
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ol>
|
|
77
|
+
</div>
|
|
78
|
+
) : null}
|
|
79
|
+
</section>
|
|
80
|
+
);
|
|
81
|
+
}
|