@doswiftly/cli 0.1.19 → 0.1.20
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/commands/deploy.d.ts +20 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +219 -6
- package/dist/commands/deploy.js.map +1 -1
- package/package.json +4 -4
- package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-minimal/wrangler.toml +11 -0
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-nextjs/wrangler.toml +11 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +29 -5
- package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/addresses/page.tsx +19 -15
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/error.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +39 -34
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/page.tsx +9 -7
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/page.tsx +13 -9
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +1 -2
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/settings/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +14 -12
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +14 -10
- package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/category-products-client.tsx +4 -2
- package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/error.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +228 -184
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
- package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
- package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
- package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
- package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +11 -8
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx +14 -10
- package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +21 -25
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +6 -68
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +14 -12
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +28 -18
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +27 -22
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +48 -43
- package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +9 -7
- package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +18 -15
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +8 -7
- package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +21 -11
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +16 -13
- package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
- package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +12 -9
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
- package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +37 -12
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +20 -12
- package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
- package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
- package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +23 -24
- package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +4 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +8 -6
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +26 -13
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +25 -27
- package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
- package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +40 -7
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +56 -70
- package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
- package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
- package/templates/storefront-nextjs-shadcn/generated/graphql.ts +1159 -551
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +1 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +22 -249
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +3 -3
- package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
- package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
- package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +1 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +41 -8
- package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +20 -18
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +2 -1
- package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
- package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
- package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
- package/templates/storefront-nextjs-shadcn/package.json +3 -2
- package/templates/storefront-nextjs-shadcn/proxy.ts +115 -46
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -58
- package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
- /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { useRouter } from "
|
|
5
|
-
import Link from "next/link";
|
|
4
|
+
import { useRouter, Link } from "@/i18n/navigation";
|
|
6
5
|
import { StorefrontError } from "@doswiftly/storefront-sdk";
|
|
7
6
|
import { useCartStore } from "@/stores/cart-store";
|
|
8
7
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
@@ -16,9 +15,12 @@ import {
|
|
|
16
15
|
useCheckoutDiscountCodeRemove,
|
|
17
16
|
useCheckoutGiftCardApply,
|
|
18
17
|
useCheckoutGiftCardRemove,
|
|
18
|
+
useCheckoutGiftCardRecipientUpdate,
|
|
19
19
|
useCheckoutComplete,
|
|
20
20
|
useCheckout,
|
|
21
21
|
} from "@/lib/graphql/hooks";
|
|
22
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
23
|
+
import { queryKeys } from "@/lib/graphql/query-keys";
|
|
22
24
|
import { formatAmount } from "@doswiftly/storefront-sdk";
|
|
23
25
|
import { Button } from "@/components/ui/button";
|
|
24
26
|
import { Input } from "@/components/ui/input";
|
|
@@ -38,6 +40,7 @@ import { PaymentStep } from "@/components/checkout/payment-step";
|
|
|
38
40
|
import type { PaymentMethod } from "@/components/checkout/payment-method-card";
|
|
39
41
|
import { toast } from "sonner";
|
|
40
42
|
import { z } from "zod";
|
|
43
|
+
import { useTranslations } from "next-intl";
|
|
41
44
|
import {
|
|
42
45
|
Loader2,
|
|
43
46
|
ChevronLeft,
|
|
@@ -58,45 +61,7 @@ import {
|
|
|
58
61
|
// CONSTANTS
|
|
59
62
|
// ============================================================================
|
|
60
63
|
|
|
61
|
-
const
|
|
62
|
-
{ code: "PL", name: "Polska" },
|
|
63
|
-
{ code: "DE", name: "Niemcy" },
|
|
64
|
-
{ code: "CZ", name: "Czechy" },
|
|
65
|
-
{ code: "SK", name: "Słowacja" },
|
|
66
|
-
{ code: "AT", name: "Austria" },
|
|
67
|
-
{ code: "FR", name: "Francja" },
|
|
68
|
-
{ code: "NL", name: "Holandia" },
|
|
69
|
-
{ code: "BE", name: "Belgia" },
|
|
70
|
-
{ code: "IT", name: "Włochy" },
|
|
71
|
-
{ code: "ES", name: "Hiszpania" },
|
|
72
|
-
{ code: "GB", name: "Wielka Brytania" },
|
|
73
|
-
{ code: "US", name: "Stany Zjednoczone" },
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
// ============================================================================
|
|
77
|
-
// VALIDATION SCHEMAS
|
|
78
|
-
// ============================================================================
|
|
79
|
-
|
|
80
|
-
const contactSchema = z.object({
|
|
81
|
-
email: z.string().email("Podaj prawidłowy adres email"),
|
|
82
|
-
phone: z
|
|
83
|
-
.string()
|
|
84
|
-
.min(9, "Numer telefonu jest wymagany")
|
|
85
|
-
.regex(/^\+?[0-9\s\-]{9,15}$/, "Nieprawidłowy format numeru telefonu"),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const addressSchema = z.object({
|
|
89
|
-
firstName: z.string().min(1, "Imię jest wymagane"),
|
|
90
|
-
lastName: z.string().min(1, "Nazwisko jest wymagane"),
|
|
91
|
-
address1: z.string().min(1, "Adres jest wymagany"),
|
|
92
|
-
address2: z.string().optional(),
|
|
93
|
-
city: z.string().min(1, "Miasto jest wymagane"),
|
|
94
|
-
province: z.string().optional(),
|
|
95
|
-
zip: z.string().min(1, "Kod pocztowy jest wymagany"),
|
|
96
|
-
country: z.string().min(2, "Kraj jest wymagany"),
|
|
97
|
-
phone: z.string().optional(),
|
|
98
|
-
company: z.string().optional(),
|
|
99
|
-
});
|
|
64
|
+
const COUNTRY_CODES = ["PL", "DE", "CZ", "SK", "AT", "FR", "NL", "BE", "IT", "ES", "GB", "US"] as const;
|
|
100
65
|
|
|
101
66
|
// ============================================================================
|
|
102
67
|
// TYPES
|
|
@@ -166,11 +131,20 @@ interface AddressFormFieldsProps {
|
|
|
166
131
|
}
|
|
167
132
|
|
|
168
133
|
function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFieldsProps) {
|
|
134
|
+
const t = useTranslations("checkout");
|
|
135
|
+
const tc = useTranslations("common");
|
|
136
|
+
const tCountries = useTranslations("countries");
|
|
137
|
+
|
|
138
|
+
const countries = COUNTRY_CODES.map((code) => ({
|
|
139
|
+
code,
|
|
140
|
+
name: tCountries(code),
|
|
141
|
+
}));
|
|
142
|
+
|
|
169
143
|
return (
|
|
170
144
|
<div className="space-y-4">
|
|
171
145
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
172
146
|
<div>
|
|
173
|
-
<Label htmlFor={`${prefix}_firstName`}>
|
|
147
|
+
<Label htmlFor={`${prefix}_firstName`}>{t("address.firstName")} *</Label>
|
|
174
148
|
<Input
|
|
175
149
|
id={`${prefix}_firstName`}
|
|
176
150
|
value={values.firstName}
|
|
@@ -182,7 +156,7 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
182
156
|
)}
|
|
183
157
|
</div>
|
|
184
158
|
<div>
|
|
185
|
-
<Label htmlFor={`${prefix}_lastName`}>
|
|
159
|
+
<Label htmlFor={`${prefix}_lastName`}>{t("address.lastName")} *</Label>
|
|
186
160
|
<Input
|
|
187
161
|
id={`${prefix}_lastName`}
|
|
188
162
|
value={values.lastName}
|
|
@@ -196,7 +170,7 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
196
170
|
</div>
|
|
197
171
|
|
|
198
172
|
<div>
|
|
199
|
-
<Label htmlFor={`${prefix}_company`}>
|
|
173
|
+
<Label htmlFor={`${prefix}_company`}>{t("address.company")} ({tc("optional")})</Label>
|
|
200
174
|
<Input
|
|
201
175
|
id={`${prefix}_company`}
|
|
202
176
|
value={values.company}
|
|
@@ -205,10 +179,10 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
205
179
|
</div>
|
|
206
180
|
|
|
207
181
|
<div>
|
|
208
|
-
<Label htmlFor={`${prefix}_address1`}>
|
|
182
|
+
<Label htmlFor={`${prefix}_address1`}>{t("address.address1")} *</Label>
|
|
209
183
|
<Input
|
|
210
184
|
id={`${prefix}_address1`}
|
|
211
|
-
placeholder="
|
|
185
|
+
placeholder={t("address.address1Placeholder")}
|
|
212
186
|
value={values.address1}
|
|
213
187
|
onChange={(e) => onChange("address1", e.target.value)}
|
|
214
188
|
className={errors[`${prefix}_address1`] ? "border-destructive" : ""}
|
|
@@ -219,7 +193,7 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
219
193
|
</div>
|
|
220
194
|
|
|
221
195
|
<div>
|
|
222
|
-
<Label htmlFor={`${prefix}_address2`}>
|
|
196
|
+
<Label htmlFor={`${prefix}_address2`}>{t("address.address2")} ({tc("optional")})</Label>
|
|
223
197
|
<Input
|
|
224
198
|
id={`${prefix}_address2`}
|
|
225
199
|
value={values.address2}
|
|
@@ -229,10 +203,10 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
229
203
|
|
|
230
204
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
231
205
|
<div>
|
|
232
|
-
<Label htmlFor={`${prefix}_zip`}>
|
|
206
|
+
<Label htmlFor={`${prefix}_zip`}>{t("address.zip")} *</Label>
|
|
233
207
|
<Input
|
|
234
208
|
id={`${prefix}_zip`}
|
|
235
|
-
placeholder="
|
|
209
|
+
placeholder={t("address.zipPlaceholder")}
|
|
236
210
|
value={values.zip}
|
|
237
211
|
onChange={(e) => onChange("zip", e.target.value)}
|
|
238
212
|
className={errors[`${prefix}_zip`] ? "border-destructive" : ""}
|
|
@@ -242,7 +216,7 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
242
216
|
)}
|
|
243
217
|
</div>
|
|
244
218
|
<div>
|
|
245
|
-
<Label htmlFor={`${prefix}_city`}>
|
|
219
|
+
<Label htmlFor={`${prefix}_city`}>{t("address.city")} *</Label>
|
|
246
220
|
<Input
|
|
247
221
|
id={`${prefix}_city`}
|
|
248
222
|
value={values.city}
|
|
@@ -257,13 +231,13 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
257
231
|
|
|
258
232
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
259
233
|
<div>
|
|
260
|
-
<Label htmlFor={`${prefix}_country`}>
|
|
234
|
+
<Label htmlFor={`${prefix}_country`}>{t("address.country")} *</Label>
|
|
261
235
|
<Select value={values.country} onValueChange={(value) => onChange("country", value)}>
|
|
262
236
|
<SelectTrigger className={errors[`${prefix}_country`] ? "border-destructive" : ""}>
|
|
263
|
-
<SelectValue placeholder="
|
|
237
|
+
<SelectValue placeholder={t("address.countryPlaceholder")} />
|
|
264
238
|
</SelectTrigger>
|
|
265
239
|
<SelectContent>
|
|
266
|
-
{
|
|
240
|
+
{countries.map((country) => (
|
|
267
241
|
<SelectItem key={country.code} value={country.code}>
|
|
268
242
|
{country.name}
|
|
269
243
|
</SelectItem>
|
|
@@ -275,7 +249,7 @@ function AddressFormFields({ prefix, values, onChange, errors }: AddressFormFiel
|
|
|
275
249
|
)}
|
|
276
250
|
</div>
|
|
277
251
|
<div>
|
|
278
|
-
<Label htmlFor={`${prefix}_province`}>
|
|
252
|
+
<Label htmlFor={`${prefix}_province`}>{t("address.province")} ({tc("optional")})</Label>
|
|
279
253
|
<Input
|
|
280
254
|
id={`${prefix}_province`}
|
|
281
255
|
value={values.province}
|
|
@@ -296,6 +270,42 @@ export default function CheckoutPage() {
|
|
|
296
270
|
const { cartId, clearCart } = useCartStore();
|
|
297
271
|
const { items, isLoading: isCartLoading } = useCartSync();
|
|
298
272
|
|
|
273
|
+
const t = useTranslations("checkout");
|
|
274
|
+
const tc = useTranslations("common");
|
|
275
|
+
const tCountries = useTranslations("countries");
|
|
276
|
+
const tCart = useTranslations("cart");
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// VALIDATION SCHEMAS (inside component to use translations)
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
const contactSchema = z.object({
|
|
283
|
+
email: z.string().email(t("validation.emailRequired")),
|
|
284
|
+
phone: z
|
|
285
|
+
.string()
|
|
286
|
+
.min(9, t("validation.phoneRequired"))
|
|
287
|
+
.regex(/^\+?[0-9\s\-]{9,15}$/, t("validation.phoneInvalid")),
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const addressSchema = z.object({
|
|
291
|
+
firstName: z.string().min(1, t("validation.firstNameRequired")),
|
|
292
|
+
lastName: z.string().min(1, t("validation.lastNameRequired")),
|
|
293
|
+
address1: z.string().min(1, t("validation.addressRequired")),
|
|
294
|
+
address2: z.string().optional(),
|
|
295
|
+
city: z.string().min(1, t("validation.cityRequired")),
|
|
296
|
+
province: z.string().optional(),
|
|
297
|
+
zip: z.string().min(1, t("validation.zipRequired")),
|
|
298
|
+
country: z.string().min(2, t("validation.countryRequired")),
|
|
299
|
+
phone: z.string().optional(),
|
|
300
|
+
company: z.string().optional(),
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Countries for review section
|
|
304
|
+
const countries = COUNTRY_CODES.map((code) => ({
|
|
305
|
+
code,
|
|
306
|
+
name: tCountries(code),
|
|
307
|
+
}));
|
|
308
|
+
|
|
299
309
|
// Step management
|
|
300
310
|
const [currentStep, setCurrentStep] = useState<CheckoutStep>("contact");
|
|
301
311
|
const [checkoutId, setCheckoutId] = useState<string | null>(null);
|
|
@@ -338,7 +348,9 @@ export default function CheckoutPage() {
|
|
|
338
348
|
const removeDiscount = useCheckoutDiscountCodeRemove();
|
|
339
349
|
const applyGiftCard = useCheckoutGiftCardApply();
|
|
340
350
|
const removeGiftCard = useCheckoutGiftCardRemove();
|
|
351
|
+
const updateGiftCardRecipient = useCheckoutGiftCardRecipientUpdate();
|
|
341
352
|
const completeCheckout = useCheckoutComplete();
|
|
353
|
+
const queryClient = useQueryClient();
|
|
342
354
|
|
|
343
355
|
// Query checkout for shipping rates
|
|
344
356
|
const { data: checkoutData, refetch: refetchCheckout } = useCheckout(checkoutId);
|
|
@@ -368,6 +380,7 @@ export default function CheckoutPage() {
|
|
|
368
380
|
removeDiscount.isPending ||
|
|
369
381
|
applyGiftCard.isPending ||
|
|
370
382
|
removeGiftCard.isPending ||
|
|
383
|
+
updateGiftCardRecipient.isPending ||
|
|
371
384
|
completeCheckout.isPending;
|
|
372
385
|
|
|
373
386
|
// Calculate totals from checkout or cart
|
|
@@ -399,6 +412,8 @@ export default function CheckoutPage() {
|
|
|
399
412
|
setErrors({});
|
|
400
413
|
|
|
401
414
|
try {
|
|
415
|
+
let newCheckoutId = checkoutId;
|
|
416
|
+
|
|
402
417
|
// Create checkout if not exists
|
|
403
418
|
if (!checkoutId) {
|
|
404
419
|
const createResult = await createCheckout.mutateAsync({
|
|
@@ -414,7 +429,8 @@ export default function CheckoutPage() {
|
|
|
414
429
|
}
|
|
415
430
|
|
|
416
431
|
if (createResult.checkoutCreate.checkout) {
|
|
417
|
-
|
|
432
|
+
newCheckoutId = createResult.checkoutCreate.checkout.id;
|
|
433
|
+
setCheckoutId(newCheckoutId);
|
|
418
434
|
}
|
|
419
435
|
} else {
|
|
420
436
|
// Update email on existing checkout
|
|
@@ -429,9 +445,37 @@ export default function CheckoutPage() {
|
|
|
429
445
|
}
|
|
430
446
|
}
|
|
431
447
|
|
|
448
|
+
// Send gift card recipient data (if any)
|
|
449
|
+
if (hasGiftCards && newCheckoutId) {
|
|
450
|
+
const giftCardItems = items.filter((item) => item.productType === "GIFT_CARD");
|
|
451
|
+
const recipientMutations = giftCardItems
|
|
452
|
+
.filter((gc) => giftCardRecipients[gc.lineId]?.recipientEmail)
|
|
453
|
+
.map((gc) =>
|
|
454
|
+
updateGiftCardRecipient.mutateAsync({
|
|
455
|
+
input: {
|
|
456
|
+
checkoutId: newCheckoutId!,
|
|
457
|
+
lineItemId: gc.lineId,
|
|
458
|
+
...giftCardRecipients[gc.lineId],
|
|
459
|
+
},
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
if (recipientMutations.length > 0) {
|
|
464
|
+
const results = await Promise.allSettled(recipientMutations);
|
|
465
|
+
const failed = results.filter((r) => r.status === "rejected");
|
|
466
|
+
if (failed.length > 0) {
|
|
467
|
+
toast.warning(
|
|
468
|
+
t("giftCard.recipientSaveError", { count: failed.length })
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
// Refetch checkout after recipient updates — prevents stale UI
|
|
472
|
+
await queryClient.refetchQueries({ queryKey: queryKeys.checkout.detail(newCheckoutId, currencyCode) });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
432
476
|
setCurrentStep(allGiftCards ? "payment" : "shipping");
|
|
433
477
|
} catch (error: unknown) {
|
|
434
|
-
const message = error instanceof Error ? error.message : "
|
|
478
|
+
const message = error instanceof Error ? error.message : t("errors.contactProcessing");
|
|
435
479
|
toast.error(message);
|
|
436
480
|
}
|
|
437
481
|
};
|
|
@@ -472,7 +516,7 @@ export default function CheckoutPage() {
|
|
|
472
516
|
setErrors({});
|
|
473
517
|
|
|
474
518
|
if (!checkoutId) {
|
|
475
|
-
toast.error("
|
|
519
|
+
toast.error(t("errors.checkoutNotFound"));
|
|
476
520
|
return;
|
|
477
521
|
}
|
|
478
522
|
|
|
@@ -513,7 +557,7 @@ export default function CheckoutPage() {
|
|
|
513
557
|
await refetchCheckout();
|
|
514
558
|
setCurrentStep("delivery");
|
|
515
559
|
} catch (error: unknown) {
|
|
516
|
-
const message = error instanceof Error ? error.message : "
|
|
560
|
+
const message = error instanceof Error ? error.message : t("errors.addressUpdate");
|
|
517
561
|
toast.error(message);
|
|
518
562
|
}
|
|
519
563
|
};
|
|
@@ -524,13 +568,13 @@ export default function CheckoutPage() {
|
|
|
524
568
|
|
|
525
569
|
const handleDeliverySubmit = async () => {
|
|
526
570
|
if (!formState.selectedShippingRate) {
|
|
527
|
-
setErrors({ shipping: "
|
|
571
|
+
setErrors({ shipping: t("delivery.selectMethod") });
|
|
528
572
|
return;
|
|
529
573
|
}
|
|
530
574
|
setErrors({});
|
|
531
575
|
|
|
532
576
|
if (!checkoutId) {
|
|
533
|
-
toast.error("
|
|
577
|
+
toast.error(t("errors.checkoutNotFound"));
|
|
534
578
|
return;
|
|
535
579
|
}
|
|
536
580
|
|
|
@@ -548,7 +592,7 @@ export default function CheckoutPage() {
|
|
|
548
592
|
await refetchCheckout();
|
|
549
593
|
setCurrentStep("payment");
|
|
550
594
|
} catch (error: unknown) {
|
|
551
|
-
const message = error instanceof Error ? error.message : "
|
|
595
|
+
const message = error instanceof Error ? error.message : t("errors.deliverySelect");
|
|
552
596
|
toast.error(message);
|
|
553
597
|
}
|
|
554
598
|
};
|
|
@@ -567,7 +611,7 @@ export default function CheckoutPage() {
|
|
|
567
611
|
const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
|
|
568
612
|
if (!supportsCurrency) {
|
|
569
613
|
setErrors({
|
|
570
|
-
payment:
|
|
614
|
+
payment: t("payment.unsupportedCurrency", { currency: currencyCode }),
|
|
571
615
|
});
|
|
572
616
|
return;
|
|
573
617
|
}
|
|
@@ -579,7 +623,7 @@ export default function CheckoutPage() {
|
|
|
579
623
|
|
|
580
624
|
const handlePaymentSubmit = async () => {
|
|
581
625
|
if (!formState.selectedPaymentMethodId) {
|
|
582
|
-
setErrors({ payment: "
|
|
626
|
+
setErrors({ payment: t("payment.selectMethod") });
|
|
583
627
|
return;
|
|
584
628
|
}
|
|
585
629
|
|
|
@@ -589,7 +633,7 @@ export default function CheckoutPage() {
|
|
|
589
633
|
);
|
|
590
634
|
|
|
591
635
|
if (!selectedMethod) {
|
|
592
|
-
setErrors({ payment: "
|
|
636
|
+
setErrors({ payment: t("payment.unavailable") });
|
|
593
637
|
setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
|
|
594
638
|
return;
|
|
595
639
|
}
|
|
@@ -599,7 +643,7 @@ export default function CheckoutPage() {
|
|
|
599
643
|
const supportsCurrency = selectedMethod.supportedCurrencies.includes(currencyCode);
|
|
600
644
|
if (!supportsCurrency) {
|
|
601
645
|
setErrors({
|
|
602
|
-
payment:
|
|
646
|
+
payment: t("payment.unsupportedCurrency", { currency: currencyCode }),
|
|
603
647
|
});
|
|
604
648
|
return;
|
|
605
649
|
}
|
|
@@ -619,7 +663,7 @@ export default function CheckoutPage() {
|
|
|
619
663
|
}
|
|
620
664
|
|
|
621
665
|
if (!checkoutId) {
|
|
622
|
-
toast.error("
|
|
666
|
+
toast.error(t("errors.checkoutNotFound"));
|
|
623
667
|
return;
|
|
624
668
|
}
|
|
625
669
|
|
|
@@ -637,7 +681,7 @@ export default function CheckoutPage() {
|
|
|
637
681
|
}
|
|
638
682
|
|
|
639
683
|
if (!payload?.checkout) {
|
|
640
|
-
toast.error("
|
|
684
|
+
toast.error(t("errors.discountApply"));
|
|
641
685
|
return;
|
|
642
686
|
}
|
|
643
687
|
|
|
@@ -647,9 +691,9 @@ export default function CheckoutPage() {
|
|
|
647
691
|
discountCode: "",
|
|
648
692
|
}));
|
|
649
693
|
await refetchCheckout();
|
|
650
|
-
toast.success("
|
|
694
|
+
toast.success(t("discount.applied"));
|
|
651
695
|
} catch (error: unknown) {
|
|
652
|
-
const message = error instanceof Error ? error.message : "
|
|
696
|
+
const message = error instanceof Error ? error.message : t("errors.discountApply");
|
|
653
697
|
toast.error(message);
|
|
654
698
|
}
|
|
655
699
|
};
|
|
@@ -675,9 +719,9 @@ export default function CheckoutPage() {
|
|
|
675
719
|
appliedDiscountCode: null,
|
|
676
720
|
}));
|
|
677
721
|
await refetchCheckout();
|
|
678
|
-
toast.success("
|
|
722
|
+
toast.success(t("discount.removed"));
|
|
679
723
|
} catch (error: unknown) {
|
|
680
|
-
const message = error instanceof Error ? error.message : "
|
|
724
|
+
const message = error instanceof Error ? error.message : t("errors.discountRemove");
|
|
681
725
|
toast.error(message);
|
|
682
726
|
}
|
|
683
727
|
};
|
|
@@ -692,7 +736,7 @@ export default function CheckoutPage() {
|
|
|
692
736
|
}
|
|
693
737
|
|
|
694
738
|
if (!checkoutId) {
|
|
695
|
-
toast.error("
|
|
739
|
+
toast.error(t("errors.checkoutNotFound"));
|
|
696
740
|
return;
|
|
697
741
|
}
|
|
698
742
|
|
|
@@ -706,11 +750,11 @@ export default function CheckoutPage() {
|
|
|
706
750
|
const error = result.checkoutGiftCardApply.userErrors[0];
|
|
707
751
|
// Translate common error codes to user-friendly messages
|
|
708
752
|
const errorMessages: Record<string, string> = {
|
|
709
|
-
GIFT_CARD_NOT_FOUND: "
|
|
710
|
-
GIFT_CARD_EXPIRED: "
|
|
711
|
-
GIFT_CARD_DISABLED: "
|
|
712
|
-
GIFT_CARD_DEPLETED: "
|
|
713
|
-
GIFT_CARD_UNUSABLE: "
|
|
753
|
+
GIFT_CARD_NOT_FOUND: t("errors.giftCardNotFound"),
|
|
754
|
+
GIFT_CARD_EXPIRED: t("errors.giftCardExpired"),
|
|
755
|
+
GIFT_CARD_DISABLED: t("errors.giftCardDisabled"),
|
|
756
|
+
GIFT_CARD_DEPLETED: t("errors.giftCardDepleted"),
|
|
757
|
+
GIFT_CARD_UNUSABLE: t("errors.giftCardUnusable"),
|
|
714
758
|
};
|
|
715
759
|
toast.error((error.code && errorMessages[error.code]) || error.message);
|
|
716
760
|
return;
|
|
@@ -721,9 +765,9 @@ export default function CheckoutPage() {
|
|
|
721
765
|
giftCardCode: "",
|
|
722
766
|
}));
|
|
723
767
|
await refetchCheckout();
|
|
724
|
-
toast.success("
|
|
768
|
+
toast.success(tCart("giftCardApplied"));
|
|
725
769
|
} catch (error: unknown) {
|
|
726
|
-
const message = error instanceof Error ? error.message : "
|
|
770
|
+
const message = error instanceof Error ? error.message : t("errors.giftCardApply");
|
|
727
771
|
toast.error(message);
|
|
728
772
|
}
|
|
729
773
|
};
|
|
@@ -745,9 +789,9 @@ export default function CheckoutPage() {
|
|
|
745
789
|
}
|
|
746
790
|
|
|
747
791
|
await refetchCheckout();
|
|
748
|
-
toast.success("
|
|
792
|
+
toast.success(tCart("giftCardRemoved"));
|
|
749
793
|
} catch (error: unknown) {
|
|
750
|
-
const message = error instanceof Error ? error.message : "
|
|
794
|
+
const message = error instanceof Error ? error.message : t("errors.giftCardRemove");
|
|
751
795
|
toast.error(message);
|
|
752
796
|
}
|
|
753
797
|
};
|
|
@@ -758,12 +802,12 @@ export default function CheckoutPage() {
|
|
|
758
802
|
|
|
759
803
|
const handleCompleteCheckout = async () => {
|
|
760
804
|
if (!formState.acceptTerms) {
|
|
761
|
-
setErrors({ terms: "
|
|
805
|
+
setErrors({ terms: t("review.mustAcceptTerms") });
|
|
762
806
|
return;
|
|
763
807
|
}
|
|
764
808
|
|
|
765
809
|
if (!formState.selectedPaymentMethodId) {
|
|
766
|
-
setErrors({ payment: "
|
|
810
|
+
setErrors({ payment: t("payment.selectMethod") });
|
|
767
811
|
setCurrentStep("payment");
|
|
768
812
|
return;
|
|
769
813
|
}
|
|
@@ -771,7 +815,7 @@ export default function CheckoutPage() {
|
|
|
771
815
|
setErrors({});
|
|
772
816
|
|
|
773
817
|
if (!checkoutId) {
|
|
774
|
-
toast.error("
|
|
818
|
+
toast.error(t("errors.checkoutNotFound"));
|
|
775
819
|
return;
|
|
776
820
|
}
|
|
777
821
|
|
|
@@ -794,19 +838,19 @@ export default function CheckoutPage() {
|
|
|
794
838
|
switch (codeStr) {
|
|
795
839
|
case "INVALID_PAYMENT_METHOD":
|
|
796
840
|
case "NO_PAYMENT_METHODS":
|
|
797
|
-
setErrors({ payment: "
|
|
841
|
+
setErrors({ payment: t("payment.unavailable") });
|
|
798
842
|
setFormState((prev) => ({ ...prev, selectedPaymentMethodId: null }));
|
|
799
843
|
setCurrentStep("payment");
|
|
800
|
-
toast.error("
|
|
844
|
+
toast.error(t("errors.paymentUnavailable"));
|
|
801
845
|
break;
|
|
802
846
|
case "ALREADY_COMPLETED":
|
|
803
|
-
toast.error("
|
|
847
|
+
toast.error(t("errors.sessionExpired"));
|
|
804
848
|
break;
|
|
805
849
|
case "NOT_ENOUGH_IN_STOCK":
|
|
806
|
-
toast.error("
|
|
850
|
+
toast.error(t("errors.outOfStock"));
|
|
807
851
|
break;
|
|
808
852
|
default:
|
|
809
|
-
toast.error(errorMessage || "
|
|
853
|
+
toast.error(errorMessage || t("errors.checkoutComplete"));
|
|
810
854
|
}
|
|
811
855
|
return;
|
|
812
856
|
}
|
|
@@ -831,10 +875,10 @@ export default function CheckoutPage() {
|
|
|
831
875
|
router.push(`/checkout/success/${orderId}${searchParams}`);
|
|
832
876
|
} else {
|
|
833
877
|
// Fallback: no payment URL and no order - show error
|
|
834
|
-
toast.error("
|
|
878
|
+
toast.error(t("errors.unexpected"));
|
|
835
879
|
}
|
|
836
880
|
} catch (error: unknown) {
|
|
837
|
-
const message = error instanceof Error ? error.message : "
|
|
881
|
+
const message = error instanceof Error ? error.message : t("errors.checkoutComplete");
|
|
838
882
|
toast.error(message);
|
|
839
883
|
}
|
|
840
884
|
};
|
|
@@ -865,7 +909,7 @@ export default function CheckoutPage() {
|
|
|
865
909
|
return (
|
|
866
910
|
<div className="container mx-auto px-4 py-16 text-center">
|
|
867
911
|
<Loader2 className="mx-auto h-8 w-8 animate-spin text-muted-foreground" />
|
|
868
|
-
<p className="mt-4 text-muted-foreground"
|
|
912
|
+
<p className="mt-4 text-muted-foreground">{tc("loading")}</p>
|
|
869
913
|
</div>
|
|
870
914
|
);
|
|
871
915
|
}
|
|
@@ -875,12 +919,12 @@ export default function CheckoutPage() {
|
|
|
875
919
|
return (
|
|
876
920
|
<div className="container mx-auto px-4 py-16 text-center">
|
|
877
921
|
<ShoppingBag className="mx-auto h-16 w-16 text-muted-foreground" />
|
|
878
|
-
<h1 className="mt-4 text-2xl font-bold">
|
|
922
|
+
<h1 className="mt-4 text-2xl font-bold">{tCart("empty")}</h1>
|
|
879
923
|
<p className="mt-2 text-muted-foreground">
|
|
880
|
-
|
|
924
|
+
{tCart("emptyDescription")}
|
|
881
925
|
</p>
|
|
882
926
|
<Button className="mt-6" asChild>
|
|
883
|
-
<Link href="/products">
|
|
927
|
+
<Link href="/products">{tCart("browseProducts")}</Link>
|
|
884
928
|
</Button>
|
|
885
929
|
</div>
|
|
886
930
|
);
|
|
@@ -891,11 +935,11 @@ export default function CheckoutPage() {
|
|
|
891
935
|
// ============================================================================
|
|
892
936
|
|
|
893
937
|
const allSteps: { id: CheckoutStep; label: string; icon: React.ReactNode }[] = [
|
|
894
|
-
{ id: "contact", label: "
|
|
895
|
-
{ id: "shipping", label: "
|
|
896
|
-
{ id: "delivery", label: "
|
|
897
|
-
{ id: "payment", label: "
|
|
898
|
-
{ id: "review", label: "
|
|
938
|
+
{ id: "contact", label: t("steps.contact"), icon: <User className="h-4 w-4" /> },
|
|
939
|
+
{ id: "shipping", label: t("steps.shipping"), icon: <Package className="h-4 w-4" /> },
|
|
940
|
+
{ id: "delivery", label: t("steps.delivery"), icon: <Truck className="h-4 w-4" /> },
|
|
941
|
+
{ id: "payment", label: t("steps.payment"), icon: <CreditCard className="h-4 w-4" /> },
|
|
942
|
+
{ id: "review", label: t("steps.review"), icon: <ClipboardCheck className="h-4 w-4" /> },
|
|
899
943
|
];
|
|
900
944
|
// Skip shipping and delivery steps when all items are gift cards
|
|
901
945
|
const steps = allGiftCards
|
|
@@ -913,9 +957,9 @@ export default function CheckoutPage() {
|
|
|
913
957
|
<Breadcrumbs className="mb-6" />
|
|
914
958
|
|
|
915
959
|
<div className="mb-8 flex items-center justify-between">
|
|
916
|
-
<h1 className="text-3xl font-bold">
|
|
960
|
+
<h1 className="text-3xl font-bold">{t("title")}</h1>
|
|
917
961
|
<Link href="/auth/login" className="text-sm text-primary hover:underline">
|
|
918
|
-
|
|
962
|
+
{t("haveAccount")}
|
|
919
963
|
</Link>
|
|
920
964
|
</div>
|
|
921
965
|
|
|
@@ -963,19 +1007,19 @@ export default function CheckoutPage() {
|
|
|
963
1007
|
<CardHeader>
|
|
964
1008
|
<CardTitle className="flex items-center gap-2">
|
|
965
1009
|
<User className="h-5 w-5" />
|
|
966
|
-
|
|
1010
|
+
{t("contact.title")}
|
|
967
1011
|
</CardTitle>
|
|
968
1012
|
<CardDescription>
|
|
969
|
-
|
|
1013
|
+
{t("contact.description")}
|
|
970
1014
|
</CardDescription>
|
|
971
1015
|
</CardHeader>
|
|
972
1016
|
<CardContent className="space-y-4">
|
|
973
1017
|
<div>
|
|
974
|
-
<Label htmlFor="email">
|
|
1018
|
+
<Label htmlFor="email">{t("contact.email")} *</Label>
|
|
975
1019
|
<Input
|
|
976
1020
|
id="email"
|
|
977
1021
|
type="email"
|
|
978
|
-
placeholder="
|
|
1022
|
+
placeholder={t("contact.emailPlaceholder")}
|
|
979
1023
|
value={formState.email}
|
|
980
1024
|
onChange={(e) =>
|
|
981
1025
|
setFormState((prev) => ({ ...prev, email: e.target.value }))
|
|
@@ -988,11 +1032,11 @@ export default function CheckoutPage() {
|
|
|
988
1032
|
</div>
|
|
989
1033
|
|
|
990
1034
|
<div>
|
|
991
|
-
<Label htmlFor="phone">
|
|
1035
|
+
<Label htmlFor="phone">{t("contact.phone")} *</Label>
|
|
992
1036
|
<Input
|
|
993
1037
|
id="phone"
|
|
994
1038
|
type="tel"
|
|
995
|
-
placeholder="
|
|
1039
|
+
placeholder={t("contact.phonePlaceholder")}
|
|
996
1040
|
value={formState.phone}
|
|
997
1041
|
onChange={(e) =>
|
|
998
1042
|
setFormState((prev) => ({ ...prev, phone: e.target.value }))
|
|
@@ -1003,7 +1047,7 @@ export default function CheckoutPage() {
|
|
|
1003
1047
|
<p className="mt-1 text-sm text-destructive">{errors.phone}</p>
|
|
1004
1048
|
)}
|
|
1005
1049
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
1006
|
-
|
|
1050
|
+
{t("contact.phoneHint")}
|
|
1007
1051
|
</p>
|
|
1008
1052
|
</div>
|
|
1009
1053
|
|
|
@@ -1016,7 +1060,7 @@ export default function CheckoutPage() {
|
|
|
1016
1060
|
}
|
|
1017
1061
|
/>
|
|
1018
1062
|
<Label htmlFor="marketing" className="text-sm font-normal cursor-pointer">
|
|
1019
|
-
|
|
1063
|
+
{t("contact.marketing")}
|
|
1020
1064
|
</Label>
|
|
1021
1065
|
</div>
|
|
1022
1066
|
|
|
@@ -1025,28 +1069,28 @@ export default function CheckoutPage() {
|
|
|
1025
1069
|
<div className="space-y-4 border-t pt-4">
|
|
1026
1070
|
<div className="flex items-center gap-2 text-sm font-medium">
|
|
1027
1071
|
<Gift className="h-4 w-4" />
|
|
1028
|
-
|
|
1072
|
+
{t("giftCard.recipientTitle")}
|
|
1029
1073
|
</div>
|
|
1030
1074
|
<p className="text-xs text-muted-foreground">
|
|
1031
|
-
|
|
1075
|
+
{t("giftCard.recipientDescription")}
|
|
1032
1076
|
</p>
|
|
1033
1077
|
{items
|
|
1034
1078
|
.filter((item) => item.productType === "GIFT_CARD")
|
|
1035
1079
|
.map((item) => (
|
|
1036
|
-
<div key={item.
|
|
1080
|
+
<div key={item.lineId} className="space-y-3 rounded-lg border p-4">
|
|
1037
1081
|
<p className="text-sm font-medium">{item.productTitle} — {item.variantTitle}</p>
|
|
1038
1082
|
<div>
|
|
1039
|
-
<Label htmlFor={`recipient-email-${item.
|
|
1083
|
+
<Label htmlFor={`recipient-email-${item.lineId}`}>{t("giftCard.recipientEmail")}</Label>
|
|
1040
1084
|
<Input
|
|
1041
|
-
id={`recipient-email-${item.
|
|
1085
|
+
id={`recipient-email-${item.lineId}`}
|
|
1042
1086
|
type="email"
|
|
1043
|
-
placeholder="
|
|
1044
|
-
value={giftCardRecipients[item.
|
|
1087
|
+
placeholder={t("giftCard.recipientEmailPlaceholder")}
|
|
1088
|
+
value={giftCardRecipients[item.lineId]?.recipientEmail || ""}
|
|
1045
1089
|
onChange={(e) =>
|
|
1046
1090
|
setGiftCardRecipients((prev) => ({
|
|
1047
1091
|
...prev,
|
|
1048
|
-
[item.
|
|
1049
|
-
...prev[item.
|
|
1092
|
+
[item.lineId]: {
|
|
1093
|
+
...prev[item.lineId],
|
|
1050
1094
|
recipientEmail: e.target.value,
|
|
1051
1095
|
},
|
|
1052
1096
|
}))
|
|
@@ -1054,17 +1098,17 @@ export default function CheckoutPage() {
|
|
|
1054
1098
|
/>
|
|
1055
1099
|
</div>
|
|
1056
1100
|
<div>
|
|
1057
|
-
<Label htmlFor={`recipient-name-${item.
|
|
1101
|
+
<Label htmlFor={`recipient-name-${item.lineId}`}>{t("giftCard.recipientName")}</Label>
|
|
1058
1102
|
<Input
|
|
1059
|
-
id={`recipient-name-${item.
|
|
1103
|
+
id={`recipient-name-${item.lineId}`}
|
|
1060
1104
|
type="text"
|
|
1061
|
-
placeholder="
|
|
1062
|
-
value={giftCardRecipients[item.
|
|
1105
|
+
placeholder={t("giftCard.recipientNamePlaceholder")}
|
|
1106
|
+
value={giftCardRecipients[item.lineId]?.recipientName || ""}
|
|
1063
1107
|
onChange={(e) =>
|
|
1064
1108
|
setGiftCardRecipients((prev) => ({
|
|
1065
1109
|
...prev,
|
|
1066
|
-
[item.
|
|
1067
|
-
...prev[item.
|
|
1110
|
+
[item.lineId]: {
|
|
1111
|
+
...prev[item.lineId],
|
|
1068
1112
|
recipientName: e.target.value,
|
|
1069
1113
|
},
|
|
1070
1114
|
}))
|
|
@@ -1072,17 +1116,17 @@ export default function CheckoutPage() {
|
|
|
1072
1116
|
/>
|
|
1073
1117
|
</div>
|
|
1074
1118
|
<div>
|
|
1075
|
-
<Label htmlFor={`recipient-message-${item.
|
|
1119
|
+
<Label htmlFor={`recipient-message-${item.lineId}`}>{t("giftCard.message")}</Label>
|
|
1076
1120
|
<Input
|
|
1077
|
-
id={`recipient-message-${item.
|
|
1121
|
+
id={`recipient-message-${item.lineId}`}
|
|
1078
1122
|
type="text"
|
|
1079
|
-
placeholder="
|
|
1080
|
-
value={giftCardRecipients[item.
|
|
1123
|
+
placeholder={t("giftCard.messagePlaceholder")}
|
|
1124
|
+
value={giftCardRecipients[item.lineId]?.message || ""}
|
|
1081
1125
|
onChange={(e) =>
|
|
1082
1126
|
setGiftCardRecipients((prev) => ({
|
|
1083
1127
|
...prev,
|
|
1084
|
-
[item.
|
|
1085
|
-
...prev[item.
|
|
1128
|
+
[item.lineId]: {
|
|
1129
|
+
...prev[item.lineId],
|
|
1086
1130
|
message: e.target.value,
|
|
1087
1131
|
},
|
|
1088
1132
|
}))
|
|
@@ -1100,7 +1144,7 @@ export default function CheckoutPage() {
|
|
|
1100
1144
|
) : (
|
|
1101
1145
|
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1102
1146
|
)}
|
|
1103
|
-
{allGiftCards ? "
|
|
1147
|
+
{allGiftCards ? t("contact.nextPayment") : t("contact.nextShipping")}
|
|
1104
1148
|
</Button>
|
|
1105
1149
|
</CardContent>
|
|
1106
1150
|
</Card>
|
|
@@ -1113,9 +1157,9 @@ export default function CheckoutPage() {
|
|
|
1113
1157
|
<CardHeader>
|
|
1114
1158
|
<CardTitle className="flex items-center gap-2">
|
|
1115
1159
|
<Package className="h-5 w-5" />
|
|
1116
|
-
|
|
1160
|
+
{t("shipping.title")}
|
|
1117
1161
|
</CardTitle>
|
|
1118
|
-
<CardDescription>
|
|
1162
|
+
<CardDescription>{t("shipping.description")}</CardDescription>
|
|
1119
1163
|
</CardHeader>
|
|
1120
1164
|
<CardContent>
|
|
1121
1165
|
<AddressFormFields
|
|
@@ -1136,7 +1180,7 @@ export default function CheckoutPage() {
|
|
|
1136
1180
|
<CardHeader>
|
|
1137
1181
|
<CardTitle className="flex items-center gap-2">
|
|
1138
1182
|
<CreditCard className="h-5 w-5" />
|
|
1139
|
-
|
|
1183
|
+
{t("shipping.billingTitle")}
|
|
1140
1184
|
</CardTitle>
|
|
1141
1185
|
</CardHeader>
|
|
1142
1186
|
<CardContent className="space-y-4">
|
|
@@ -1149,7 +1193,7 @@ export default function CheckoutPage() {
|
|
|
1149
1193
|
}
|
|
1150
1194
|
/>
|
|
1151
1195
|
<Label htmlFor="sameAsBilling" className="cursor-pointer">
|
|
1152
|
-
|
|
1196
|
+
{t("shipping.sameAsShipping")}
|
|
1153
1197
|
</Label>
|
|
1154
1198
|
</div>
|
|
1155
1199
|
|
|
@@ -1172,7 +1216,7 @@ export default function CheckoutPage() {
|
|
|
1172
1216
|
<div className="flex gap-4">
|
|
1173
1217
|
<Button variant="outline" onClick={goBack} size="lg">
|
|
1174
1218
|
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1175
|
-
|
|
1219
|
+
{tc("back")}
|
|
1176
1220
|
</Button>
|
|
1177
1221
|
<Button onClick={handleShippingSubmit} disabled={isLoading} className="flex-1" size="lg">
|
|
1178
1222
|
{isLoading ? (
|
|
@@ -1180,7 +1224,7 @@ export default function CheckoutPage() {
|
|
|
1180
1224
|
) : (
|
|
1181
1225
|
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1182
1226
|
)}
|
|
1183
|
-
|
|
1227
|
+
{t("shipping.nextDelivery")}
|
|
1184
1228
|
</Button>
|
|
1185
1229
|
</div>
|
|
1186
1230
|
</>
|
|
@@ -1192,23 +1236,23 @@ export default function CheckoutPage() {
|
|
|
1192
1236
|
<CardHeader>
|
|
1193
1237
|
<CardTitle className="flex items-center gap-2">
|
|
1194
1238
|
<Truck className="h-5 w-5" />
|
|
1195
|
-
|
|
1239
|
+
{t("delivery.title")}
|
|
1196
1240
|
</CardTitle>
|
|
1197
|
-
<CardDescription>
|
|
1241
|
+
<CardDescription>{t("delivery.description")}</CardDescription>
|
|
1198
1242
|
</CardHeader>
|
|
1199
1243
|
<CardContent className="space-y-4">
|
|
1200
1244
|
{!shippingRatesReady ? (
|
|
1201
1245
|
<div className="flex items-center justify-center py-8">
|
|
1202
1246
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
1203
|
-
<span className="ml-2 text-muted-foreground"
|
|
1247
|
+
<span className="ml-2 text-muted-foreground">{t("delivery.loading")}</span>
|
|
1204
1248
|
</div>
|
|
1205
1249
|
) : availableShippingRates.length === 0 ? (
|
|
1206
1250
|
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-center">
|
|
1207
1251
|
<p className="text-destructive">
|
|
1208
|
-
|
|
1252
|
+
{t("delivery.noMethods")}
|
|
1209
1253
|
</p>
|
|
1210
1254
|
<Button variant="outline" onClick={goBack} className="mt-4">
|
|
1211
|
-
|
|
1255
|
+
{t("delivery.changeAddress")}
|
|
1212
1256
|
</Button>
|
|
1213
1257
|
</div>
|
|
1214
1258
|
) : (
|
|
@@ -1251,7 +1295,7 @@ export default function CheckoutPage() {
|
|
|
1251
1295
|
<div className="flex gap-4 pt-4">
|
|
1252
1296
|
<Button variant="outline" onClick={goBack} size="lg">
|
|
1253
1297
|
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1254
|
-
|
|
1298
|
+
{tc("back")}
|
|
1255
1299
|
</Button>
|
|
1256
1300
|
<Button
|
|
1257
1301
|
onClick={handleDeliverySubmit}
|
|
@@ -1264,7 +1308,7 @@ export default function CheckoutPage() {
|
|
|
1264
1308
|
) : (
|
|
1265
1309
|
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1266
1310
|
)}
|
|
1267
|
-
|
|
1311
|
+
{t("delivery.nextPayment")}
|
|
1268
1312
|
</Button>
|
|
1269
1313
|
</div>
|
|
1270
1314
|
</CardContent>
|
|
@@ -1277,9 +1321,9 @@ export default function CheckoutPage() {
|
|
|
1277
1321
|
<CardHeader>
|
|
1278
1322
|
<CardTitle className="flex items-center gap-2">
|
|
1279
1323
|
<CreditCard className="h-5 w-5" />
|
|
1280
|
-
|
|
1324
|
+
{t("payment.title")}
|
|
1281
1325
|
</CardTitle>
|
|
1282
|
-
<CardDescription>
|
|
1326
|
+
<CardDescription>{t("payment.description")}</CardDescription>
|
|
1283
1327
|
</CardHeader>
|
|
1284
1328
|
<CardContent className="space-y-4">
|
|
1285
1329
|
{/* PaymentStep component handles payment method selection */}
|
|
@@ -1309,7 +1353,7 @@ export default function CheckoutPage() {
|
|
|
1309
1353
|
<div className="flex gap-4 pt-4">
|
|
1310
1354
|
<Button variant="outline" onClick={goBack} size="lg">
|
|
1311
1355
|
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1312
|
-
|
|
1356
|
+
{tc("back")}
|
|
1313
1357
|
</Button>
|
|
1314
1358
|
<Button
|
|
1315
1359
|
onClick={handlePaymentSubmit}
|
|
@@ -1322,7 +1366,7 @@ export default function CheckoutPage() {
|
|
|
1322
1366
|
) : (
|
|
1323
1367
|
<ChevronRight className="mr-2 h-4 w-4" />
|
|
1324
1368
|
)}
|
|
1325
|
-
|
|
1369
|
+
{t("payment.nextReview")}
|
|
1326
1370
|
</Button>
|
|
1327
1371
|
</div>
|
|
1328
1372
|
</CardContent>
|
|
@@ -1335,15 +1379,15 @@ export default function CheckoutPage() {
|
|
|
1335
1379
|
<CardHeader>
|
|
1336
1380
|
<CardTitle className="flex items-center gap-2">
|
|
1337
1381
|
<ClipboardCheck className="h-5 w-5" />
|
|
1338
|
-
|
|
1382
|
+
{t("review.title")}
|
|
1339
1383
|
</CardTitle>
|
|
1340
|
-
<CardDescription>
|
|
1384
|
+
<CardDescription>{t("review.description")}</CardDescription>
|
|
1341
1385
|
</CardHeader>
|
|
1342
1386
|
<CardContent className="space-y-6">
|
|
1343
1387
|
{/* Contact */}
|
|
1344
1388
|
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1345
1389
|
<div>
|
|
1346
|
-
<h3 className="mb-1 font-medium">
|
|
1390
|
+
<h3 className="mb-1 font-medium">{t("review.contact")}</h3>
|
|
1347
1391
|
<p className="text-sm text-muted-foreground">{checkout.email}</p>
|
|
1348
1392
|
<p className="text-sm text-muted-foreground">{formState.phone}</p>
|
|
1349
1393
|
</div>
|
|
@@ -1352,7 +1396,7 @@ export default function CheckoutPage() {
|
|
|
1352
1396
|
size="sm"
|
|
1353
1397
|
onClick={() => setCurrentStep("contact")}
|
|
1354
1398
|
>
|
|
1355
|
-
|
|
1399
|
+
{t("review.change")}
|
|
1356
1400
|
</Button>
|
|
1357
1401
|
</div>
|
|
1358
1402
|
|
|
@@ -1360,7 +1404,7 @@ export default function CheckoutPage() {
|
|
|
1360
1404
|
{!allGiftCards && (
|
|
1361
1405
|
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1362
1406
|
<div>
|
|
1363
|
-
<h3 className="mb-1 font-medium">
|
|
1407
|
+
<h3 className="mb-1 font-medium">{t("review.shippingAddress")}</h3>
|
|
1364
1408
|
{checkout.shippingAddress && (
|
|
1365
1409
|
<p className="text-sm text-muted-foreground">
|
|
1366
1410
|
{checkout.shippingAddress.firstName} {checkout.shippingAddress.lastName}
|
|
@@ -1375,7 +1419,7 @@ export default function CheckoutPage() {
|
|
|
1375
1419
|
<br />
|
|
1376
1420
|
{checkout.shippingAddress.zip} {checkout.shippingAddress.city}
|
|
1377
1421
|
<br />
|
|
1378
|
-
{
|
|
1422
|
+
{countries.find((c) => c.code === checkout.shippingAddress?.country)?.name ||
|
|
1379
1423
|
checkout.shippingAddress.country}
|
|
1380
1424
|
</p>
|
|
1381
1425
|
)}
|
|
@@ -1385,7 +1429,7 @@ export default function CheckoutPage() {
|
|
|
1385
1429
|
size="sm"
|
|
1386
1430
|
onClick={() => setCurrentStep("shipping")}
|
|
1387
1431
|
>
|
|
1388
|
-
|
|
1432
|
+
{t("review.change")}
|
|
1389
1433
|
</Button>
|
|
1390
1434
|
</div>
|
|
1391
1435
|
)}
|
|
@@ -1394,7 +1438,7 @@ export default function CheckoutPage() {
|
|
|
1394
1438
|
{!allGiftCards && (
|
|
1395
1439
|
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1396
1440
|
<div>
|
|
1397
|
-
<h3 className="mb-1 font-medium">
|
|
1441
|
+
<h3 className="mb-1 font-medium">{t("review.shippingMethod")}</h3>
|
|
1398
1442
|
{checkout.shippingLine && (
|
|
1399
1443
|
<p className="text-sm text-muted-foreground">
|
|
1400
1444
|
{checkout.shippingLine.title} -{" "}
|
|
@@ -1407,7 +1451,7 @@ export default function CheckoutPage() {
|
|
|
1407
1451
|
size="sm"
|
|
1408
1452
|
onClick={() => setCurrentStep("delivery")}
|
|
1409
1453
|
>
|
|
1410
|
-
|
|
1454
|
+
{t("review.change")}
|
|
1411
1455
|
</Button>
|
|
1412
1456
|
</div>
|
|
1413
1457
|
)}
|
|
@@ -1415,12 +1459,12 @@ export default function CheckoutPage() {
|
|
|
1415
1459
|
{/* Payment Method */}
|
|
1416
1460
|
<div className="flex items-start justify-between rounded-lg border p-4">
|
|
1417
1461
|
<div>
|
|
1418
|
-
<h3 className="mb-1 font-medium">
|
|
1462
|
+
<h3 className="mb-1 font-medium">{t("review.paymentMethod")}</h3>
|
|
1419
1463
|
{formState.selectedPaymentMethodId && (
|
|
1420
1464
|
<p className="text-sm text-muted-foreground">
|
|
1421
1465
|
{checkout.availablePaymentMethods?.find(
|
|
1422
1466
|
(pm) => pm.id === formState.selectedPaymentMethodId
|
|
1423
|
-
)?.name || "
|
|
1467
|
+
)?.name || t("review.paymentMethod")}
|
|
1424
1468
|
</p>
|
|
1425
1469
|
)}
|
|
1426
1470
|
</div>
|
|
@@ -1429,7 +1473,7 @@ export default function CheckoutPage() {
|
|
|
1429
1473
|
size="sm"
|
|
1430
1474
|
onClick={() => setCurrentStep("payment")}
|
|
1431
1475
|
>
|
|
1432
|
-
|
|
1476
|
+
{t("review.change")}
|
|
1433
1477
|
</Button>
|
|
1434
1478
|
</div>
|
|
1435
1479
|
|
|
@@ -1446,13 +1490,13 @@ export default function CheckoutPage() {
|
|
|
1446
1490
|
/>
|
|
1447
1491
|
<div className="grid gap-1.5 leading-none">
|
|
1448
1492
|
<Label htmlFor="terms" className="cursor-pointer">
|
|
1449
|
-
|
|
1493
|
+
{t("review.acceptTerms")}{" "}
|
|
1450
1494
|
<Link href="/terms" className="text-primary hover:underline">
|
|
1451
|
-
|
|
1495
|
+
{t("review.termsLink")}
|
|
1452
1496
|
</Link>{" "}
|
|
1453
|
-
|
|
1497
|
+
{t("review.and")}{" "}
|
|
1454
1498
|
<Link href="/privacy" className="text-primary hover:underline">
|
|
1455
|
-
|
|
1499
|
+
{t("review.privacyLink")}
|
|
1456
1500
|
</Link>{" "}
|
|
1457
1501
|
*
|
|
1458
1502
|
</Label>
|
|
@@ -1466,7 +1510,7 @@ export default function CheckoutPage() {
|
|
|
1466
1510
|
<div className="flex gap-4">
|
|
1467
1511
|
<Button variant="outline" onClick={goBack} size="lg">
|
|
1468
1512
|
<ChevronLeft className="mr-2 h-4 w-4" />
|
|
1469
|
-
|
|
1513
|
+
{tc("back")}
|
|
1470
1514
|
</Button>
|
|
1471
1515
|
<Button
|
|
1472
1516
|
onClick={handleCompleteCheckout}
|
|
@@ -1479,7 +1523,7 @@ export default function CheckoutPage() {
|
|
|
1479
1523
|
) : (
|
|
1480
1524
|
<CreditCard className="mr-2 h-4 w-4" />
|
|
1481
1525
|
)}
|
|
1482
|
-
|
|
1526
|
+
{t("review.placeOrder")}
|
|
1483
1527
|
</Button>
|
|
1484
1528
|
</div>
|
|
1485
1529
|
</CardContent>
|
|
@@ -1493,7 +1537,7 @@ export default function CheckoutPage() {
|
|
|
1493
1537
|
<CardHeader>
|
|
1494
1538
|
<CardTitle className="flex items-center gap-2">
|
|
1495
1539
|
<ShoppingBag className="h-5 w-5" />
|
|
1496
|
-
|
|
1540
|
+
{tCart("yourOrder")}
|
|
1497
1541
|
</CardTitle>
|
|
1498
1542
|
</CardHeader>
|
|
1499
1543
|
<CardContent className="space-y-4">
|
|
@@ -1597,7 +1641,7 @@ export default function CheckoutPage() {
|
|
|
1597
1641
|
) : (
|
|
1598
1642
|
<div className="flex gap-2">
|
|
1599
1643
|
<Input
|
|
1600
|
-
placeholder="
|
|
1644
|
+
placeholder={t("discount.codePlaceholder")}
|
|
1601
1645
|
value={formState.discountCode}
|
|
1602
1646
|
onChange={(e) =>
|
|
1603
1647
|
setFormState((prev) => ({ ...prev, discountCode: e.target.value }))
|
|
@@ -1612,7 +1656,7 @@ export default function CheckoutPage() {
|
|
|
1612
1656
|
{applyDiscount.isPending ? (
|
|
1613
1657
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1614
1658
|
) : (
|
|
1615
|
-
"
|
|
1659
|
+
tc("apply")
|
|
1616
1660
|
)}
|
|
1617
1661
|
</Button>
|
|
1618
1662
|
</div>
|
|
@@ -1623,7 +1667,7 @@ export default function CheckoutPage() {
|
|
|
1623
1667
|
{/* Gift Card */}
|
|
1624
1668
|
{checkoutId && (
|
|
1625
1669
|
<div className="border-t pt-4 space-y-3">
|
|
1626
|
-
<p className="text-sm font-medium">
|
|
1670
|
+
<p className="text-sm font-medium">{tCart("giftCardLabel")}</p>
|
|
1627
1671
|
|
|
1628
1672
|
{/* Applied Gift Cards */}
|
|
1629
1673
|
{checkout?.appliedGiftCards && checkout.appliedGiftCards.length > 0 && (
|
|
@@ -1661,7 +1705,7 @@ export default function CheckoutPage() {
|
|
|
1661
1705
|
{/* Gift Card Input */}
|
|
1662
1706
|
<div className="flex gap-2">
|
|
1663
1707
|
<Input
|
|
1664
|
-
placeholder="
|
|
1708
|
+
placeholder={tCart("giftCardPlaceholder")}
|
|
1665
1709
|
value={formState.giftCardCode}
|
|
1666
1710
|
onChange={(e) =>
|
|
1667
1711
|
setFormState((prev) => ({ ...prev, giftCardCode: e.target.value.toUpperCase() }))
|
|
@@ -1682,7 +1726,7 @@ export default function CheckoutPage() {
|
|
|
1682
1726
|
{applyGiftCard.isPending ? (
|
|
1683
1727
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
1684
1728
|
) : (
|
|
1685
|
-
"
|
|
1729
|
+
tc("apply")
|
|
1686
1730
|
)}
|
|
1687
1731
|
</Button>
|
|
1688
1732
|
</div>
|
|
@@ -1693,7 +1737,7 @@ export default function CheckoutPage() {
|
|
|
1693
1737
|
<div className="border-t pt-4 space-y-2">
|
|
1694
1738
|
{/* Subtotal */}
|
|
1695
1739
|
<div className="flex justify-between text-sm">
|
|
1696
|
-
<span className="text-muted-foreground">
|
|
1740
|
+
<span className="text-muted-foreground">{tCart("products")}</span>
|
|
1697
1741
|
<span>
|
|
1698
1742
|
{formatPrice(
|
|
1699
1743
|
checkout?.subtotalPrice?.amount ||
|
|
@@ -1708,20 +1752,20 @@ export default function CheckoutPage() {
|
|
|
1708
1752
|
{/* Shipping */}
|
|
1709
1753
|
{checkout?.shippingLine ? (
|
|
1710
1754
|
<div className="flex justify-between text-sm">
|
|
1711
|
-
<span className="text-muted-foreground">
|
|
1755
|
+
<span className="text-muted-foreground">{tCart("shipping")}</span>
|
|
1712
1756
|
<span>{formatPrice(checkout.totalShippingPrice?.amount || "0")}</span>
|
|
1713
1757
|
</div>
|
|
1714
1758
|
) : (
|
|
1715
1759
|
<div className="flex justify-between text-sm">
|
|
1716
|
-
<span className="text-muted-foreground">
|
|
1717
|
-
<span className="text-muted-foreground">
|
|
1760
|
+
<span className="text-muted-foreground">{tCart("shipping")}</span>
|
|
1761
|
+
<span className="text-muted-foreground">{tCart("calculatedNextStep")}</span>
|
|
1718
1762
|
</div>
|
|
1719
1763
|
)}
|
|
1720
1764
|
|
|
1721
1765
|
{/* Discount */}
|
|
1722
1766
|
{checkout?.totalDiscounts && parseFloat(checkout.totalDiscounts.amount) > 0 && (
|
|
1723
1767
|
<div className="flex justify-between text-sm text-green-600">
|
|
1724
|
-
<span>
|
|
1768
|
+
<span>{tCart("discount")}</span>
|
|
1725
1769
|
<span>-{formatPrice(checkout.totalDiscounts.amount)}</span>
|
|
1726
1770
|
</div>
|
|
1727
1771
|
)}
|
|
@@ -1729,7 +1773,7 @@ export default function CheckoutPage() {
|
|
|
1729
1773
|
{/* Tax */}
|
|
1730
1774
|
{checkout?.totalTax && parseFloat(checkout.totalTax.amount) > 0 && (
|
|
1731
1775
|
<div className="flex justify-between text-sm">
|
|
1732
|
-
<span className="text-muted-foreground">
|
|
1776
|
+
<span className="text-muted-foreground">{tCart("tax")}</span>
|
|
1733
1777
|
<span>{formatPrice(checkout.totalTax.amount)}</span>
|
|
1734
1778
|
</div>
|
|
1735
1779
|
)}
|
|
@@ -1737,14 +1781,14 @@ export default function CheckoutPage() {
|
|
|
1737
1781
|
{/* Gift Card Deduction */}
|
|
1738
1782
|
{checkout?.totalGiftCardAmount && parseFloat(checkout.totalGiftCardAmount.amount) > 0 && (
|
|
1739
1783
|
<div className="flex justify-between text-sm text-purple-600">
|
|
1740
|
-
<span>
|
|
1784
|
+
<span>{tCart("giftCard")}</span>
|
|
1741
1785
|
<span>-{formatPrice(checkout.totalGiftCardAmount.amount)}</span>
|
|
1742
1786
|
</div>
|
|
1743
1787
|
)}
|
|
1744
1788
|
|
|
1745
1789
|
{/* Total */}
|
|
1746
1790
|
<div className="flex justify-between border-t pt-2 text-lg font-semibold">
|
|
1747
|
-
<span>
|
|
1791
|
+
<span>{tCart("total")}</span>
|
|
1748
1792
|
<span>
|
|
1749
1793
|
{formatPrice(
|
|
1750
1794
|
checkout?.totalPrice?.amount ||
|
|
@@ -1761,7 +1805,7 @@ export default function CheckoutPage() {
|
|
|
1761
1805
|
checkout?.totalPrice &&
|
|
1762
1806
|
parseFloat(checkout.paymentDue.amount) !== parseFloat(checkout.totalPrice.amount) && (
|
|
1763
1807
|
<div className="flex justify-between border-t pt-2 text-lg font-bold text-primary">
|
|
1764
|
-
<span>
|
|
1808
|
+
<span>{tCart("paymentDue")}</span>
|
|
1765
1809
|
<span>{formatPrice(checkout.paymentDue.amount)}</span>
|
|
1766
1810
|
</div>
|
|
1767
1811
|
)}
|
|
@@ -1772,11 +1816,11 @@ export default function CheckoutPage() {
|
|
|
1772
1816
|
<div className="flex items-center justify-center gap-4 text-xs text-muted-foreground">
|
|
1773
1817
|
<div className="flex items-center gap-1">
|
|
1774
1818
|
<Check className="h-3 w-3" />
|
|
1775
|
-
|
|
1819
|
+
{tc("securePayment")}
|
|
1776
1820
|
</div>
|
|
1777
1821
|
<div className="flex items-center gap-1">
|
|
1778
1822
|
<Check className="h-3 w-3" />
|
|
1779
|
-
|
|
1823
|
+
{tc("encryptedData")}
|
|
1780
1824
|
</div>
|
|
1781
1825
|
</div>
|
|
1782
1826
|
</div>
|