@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,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Button } from "@/components/ui/button";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
import { Card } from "@/components/ui/card";
|
|
@@ -38,6 +39,10 @@ export function AddressForm({
|
|
|
38
39
|
isLoading = false,
|
|
39
40
|
className,
|
|
40
41
|
}: AddressFormProps) {
|
|
42
|
+
const t = useTranslations("checkout");
|
|
43
|
+
const tc = useTranslations("common");
|
|
44
|
+
const tAccount = useTranslations("account");
|
|
45
|
+
|
|
41
46
|
const [formData, setFormData] = useState<AddressFormData>(
|
|
42
47
|
address || {
|
|
43
48
|
firstName: "",
|
|
@@ -68,25 +73,25 @@ export function AddressForm({
|
|
|
68
73
|
const newErrors: Partial<Record<keyof AddressFormData, string>> = {};
|
|
69
74
|
|
|
70
75
|
if (!formData.firstName.trim()) {
|
|
71
|
-
newErrors.firstName = "
|
|
76
|
+
newErrors.firstName = t("validation.firstNameRequired");
|
|
72
77
|
}
|
|
73
78
|
if (!formData.lastName.trim()) {
|
|
74
|
-
newErrors.lastName = "
|
|
79
|
+
newErrors.lastName = t("validation.lastNameRequired");
|
|
75
80
|
}
|
|
76
81
|
if (!formData.address1.trim()) {
|
|
77
|
-
newErrors.address1 = "
|
|
82
|
+
newErrors.address1 = t("validation.addressRequired");
|
|
78
83
|
}
|
|
79
84
|
if (!formData.city.trim()) {
|
|
80
|
-
newErrors.city = "
|
|
85
|
+
newErrors.city = t("validation.cityRequired");
|
|
81
86
|
}
|
|
82
87
|
if (!formData.province.trim()) {
|
|
83
|
-
newErrors.province = "
|
|
88
|
+
newErrors.province = t("validation.provinceRequired");
|
|
84
89
|
}
|
|
85
90
|
if (!formData.zip.trim()) {
|
|
86
|
-
newErrors.zip = "
|
|
91
|
+
newErrors.zip = t("validation.zipRequired");
|
|
87
92
|
}
|
|
88
93
|
if (!formData.country.trim()) {
|
|
89
|
-
newErrors.country = "
|
|
94
|
+
newErrors.country = t("validation.countryRequired");
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
setErrors(newErrors);
|
|
@@ -113,7 +118,7 @@ export function AddressForm({
|
|
|
113
118
|
htmlFor="firstName"
|
|
114
119
|
className="block text-sm font-medium text-foreground mb-1"
|
|
115
120
|
>
|
|
116
|
-
|
|
121
|
+
{t("address.firstName")} *
|
|
117
122
|
</label>
|
|
118
123
|
<Input
|
|
119
124
|
id="firstName"
|
|
@@ -134,7 +139,7 @@ export function AddressForm({
|
|
|
134
139
|
htmlFor="lastName"
|
|
135
140
|
className="block text-sm font-medium text-foreground mb-1"
|
|
136
141
|
>
|
|
137
|
-
|
|
142
|
+
{t("address.lastName")} *
|
|
138
143
|
</label>
|
|
139
144
|
<Input
|
|
140
145
|
id="lastName"
|
|
@@ -156,7 +161,7 @@ export function AddressForm({
|
|
|
156
161
|
htmlFor="company"
|
|
157
162
|
className="block text-sm font-medium text-foreground mb-1"
|
|
158
163
|
>
|
|
159
|
-
|
|
164
|
+
{`${t("address.company")} (${tc("optional")})`}
|
|
160
165
|
</label>
|
|
161
166
|
<Input
|
|
162
167
|
id="company"
|
|
@@ -173,7 +178,7 @@ export function AddressForm({
|
|
|
173
178
|
htmlFor="address1"
|
|
174
179
|
className="block text-sm font-medium text-foreground mb-1"
|
|
175
180
|
>
|
|
176
|
-
|
|
181
|
+
{t("address.address1")} *
|
|
177
182
|
</label>
|
|
178
183
|
<Input
|
|
179
184
|
id="address1"
|
|
@@ -194,7 +199,7 @@ export function AddressForm({
|
|
|
194
199
|
htmlFor="address2"
|
|
195
200
|
className="block text-sm font-medium text-foreground mb-1"
|
|
196
201
|
>
|
|
197
|
-
|
|
202
|
+
{`${t("address.address2")} (${tc("optional")})`}
|
|
198
203
|
</label>
|
|
199
204
|
<Input
|
|
200
205
|
id="address2"
|
|
@@ -212,7 +217,7 @@ export function AddressForm({
|
|
|
212
217
|
htmlFor="city"
|
|
213
218
|
className="block text-sm font-medium text-foreground mb-1"
|
|
214
219
|
>
|
|
215
|
-
|
|
220
|
+
{t("address.city")} *
|
|
216
221
|
</label>
|
|
217
222
|
<Input
|
|
218
223
|
id="city"
|
|
@@ -233,7 +238,7 @@ export function AddressForm({
|
|
|
233
238
|
htmlFor="province"
|
|
234
239
|
className="block text-sm font-medium text-foreground mb-1"
|
|
235
240
|
>
|
|
236
|
-
|
|
241
|
+
{t("address.province")} *
|
|
237
242
|
</label>
|
|
238
243
|
<Input
|
|
239
244
|
id="province"
|
|
@@ -254,7 +259,7 @@ export function AddressForm({
|
|
|
254
259
|
htmlFor="zip"
|
|
255
260
|
className="block text-sm font-medium text-foreground mb-1"
|
|
256
261
|
>
|
|
257
|
-
|
|
262
|
+
{t("address.zip")} *
|
|
258
263
|
</label>
|
|
259
264
|
<Input
|
|
260
265
|
id="zip"
|
|
@@ -276,7 +281,7 @@ export function AddressForm({
|
|
|
276
281
|
htmlFor="country"
|
|
277
282
|
className="block text-sm font-medium text-foreground mb-1"
|
|
278
283
|
>
|
|
279
|
-
|
|
284
|
+
{t("address.country")} *
|
|
280
285
|
</label>
|
|
281
286
|
<Input
|
|
282
287
|
id="country"
|
|
@@ -297,7 +302,7 @@ export function AddressForm({
|
|
|
297
302
|
htmlFor="phone"
|
|
298
303
|
className="block text-sm font-medium text-foreground mb-1"
|
|
299
304
|
>
|
|
300
|
-
|
|
305
|
+
{`${t("address.phone")} (${tc("optional")})`}
|
|
301
306
|
</label>
|
|
302
307
|
<Input
|
|
303
308
|
id="phone"
|
|
@@ -322,14 +327,14 @@ export function AddressForm({
|
|
|
322
327
|
htmlFor="isDefault"
|
|
323
328
|
className="text-sm font-medium text-foreground"
|
|
324
329
|
>
|
|
325
|
-
|
|
330
|
+
{tAccount("setAsDefault")}
|
|
326
331
|
</label>
|
|
327
332
|
</div>
|
|
328
333
|
|
|
329
334
|
{/* Form Actions */}
|
|
330
335
|
<div className="flex gap-3 pt-4">
|
|
331
336
|
<Button type="submit" disabled={isLoading} className="flex-1">
|
|
332
|
-
{isLoading ? "
|
|
337
|
+
{isLoading ? tc("saving") : address ? tAccount("editAddress") : tAccount("addAddress")}
|
|
333
338
|
</Button>
|
|
334
339
|
{onCancel && (
|
|
335
340
|
<Button
|
|
@@ -338,7 +343,7 @@ export function AddressForm({
|
|
|
338
343
|
onClick={onCancel}
|
|
339
344
|
disabled={isLoading}
|
|
340
345
|
>
|
|
341
|
-
|
|
346
|
+
{tc("cancel")}
|
|
342
347
|
</Button>
|
|
343
348
|
)}
|
|
344
349
|
</div>
|
|
@@ -4,6 +4,7 @@ import { MapPin, Edit, Trash2, Plus } from "lucide-react";
|
|
|
4
4
|
import { Button } from "@/components/ui/button";
|
|
5
5
|
import { Badge } from "@/components/ui/badge";
|
|
6
6
|
import { Card } from "@/components/ui/card";
|
|
7
|
+
import { useTranslations } from "next-intl";
|
|
7
8
|
|
|
8
9
|
export interface Address {
|
|
9
10
|
id: string;
|
|
@@ -40,11 +41,11 @@ export function AddressList({
|
|
|
40
41
|
onAdd,
|
|
41
42
|
className,
|
|
42
43
|
}: AddressListProps) {
|
|
44
|
+
const t = useTranslations("account");
|
|
45
|
+
|
|
43
46
|
const handleDelete = (id: string, address: Address) => {
|
|
44
47
|
if (
|
|
45
|
-
confirm(
|
|
46
|
-
`Are you sure you want to delete the address at ${address.address1}?`
|
|
47
|
-
)
|
|
48
|
+
confirm(t("deleteAddressConfirm", { address: address.address1 }))
|
|
48
49
|
) {
|
|
49
50
|
onDelete?.(id);
|
|
50
51
|
}
|
|
@@ -55,15 +56,15 @@ export function AddressList({
|
|
|
55
56
|
<div className="rounded-lg border border-border bg-muted/50 p-12 text-center">
|
|
56
57
|
<MapPin className="mx-auto h-12 w-12 text-muted-foreground mb-4" />
|
|
57
58
|
<h2 className="text-xl font-semibold text-foreground mb-2">
|
|
58
|
-
|
|
59
|
+
{t("noAddressesSaved")}
|
|
59
60
|
</h2>
|
|
60
61
|
<p className="text-muted-foreground mb-6">
|
|
61
|
-
|
|
62
|
+
{t("addAddressDescription")}
|
|
62
63
|
</p>
|
|
63
64
|
{onAdd && (
|
|
64
65
|
<Button onClick={onAdd}>
|
|
65
66
|
<Plus className="mr-2 h-4 w-4" />
|
|
66
|
-
|
|
67
|
+
{t("addFirstAddress")}
|
|
67
68
|
</Button>
|
|
68
69
|
)}
|
|
69
70
|
</div>
|
|
@@ -78,7 +79,7 @@ export function AddressList({
|
|
|
78
79
|
<div className="mb-4 flex items-start justify-between">
|
|
79
80
|
<div className="flex items-center gap-2">
|
|
80
81
|
<MapPin className="h-5 w-5 text-muted-foreground" />
|
|
81
|
-
{address.isDefault && <Badge variant="default">
|
|
82
|
+
{address.isDefault && <Badge variant="default">{t("defaultBadge")}</Badge>}
|
|
82
83
|
</div>
|
|
83
84
|
<div className="flex gap-2">
|
|
84
85
|
{onEdit && (
|
|
@@ -87,7 +88,7 @@ export function AddressList({
|
|
|
87
88
|
size="sm"
|
|
88
89
|
className="h-8 w-8 p-0"
|
|
89
90
|
onClick={() => onEdit(address)}
|
|
90
|
-
aria-label="
|
|
91
|
+
aria-label={t("editAddress")}
|
|
91
92
|
>
|
|
92
93
|
<Edit className="h-4 w-4" />
|
|
93
94
|
</Button>
|
|
@@ -98,7 +99,7 @@ export function AddressList({
|
|
|
98
99
|
size="sm"
|
|
99
100
|
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
|
|
100
101
|
onClick={() => handleDelete(address.id, address)}
|
|
101
|
-
aria-label="
|
|
102
|
+
aria-label={t("deleteAddress")}
|
|
102
103
|
>
|
|
103
104
|
<Trash2 className="h-4 w-4" />
|
|
104
105
|
</Button>
|
|
@@ -133,7 +134,7 @@ export function AddressList({
|
|
|
133
134
|
className="mt-4 w-full"
|
|
134
135
|
onClick={() => onSetDefault(address.id)}
|
|
135
136
|
>
|
|
136
|
-
|
|
137
|
+
{t("setAsDefault")}
|
|
137
138
|
</Button>
|
|
138
139
|
)}
|
|
139
140
|
</Card>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Package, Truck, CheckCircle, XCircle } from "lucide-react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Badge } from "@/components/ui/badge";
|
|
5
6
|
import { Card } from "@/components/ui/card";
|
|
6
7
|
import { Separator } from "@/components/ui/separator";
|
|
@@ -54,6 +55,7 @@ export interface OrderDetailsProps {
|
|
|
54
55
|
* OrderDetails - Display detailed information about a single order
|
|
55
56
|
*/
|
|
56
57
|
export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
58
|
+
const t = useTranslations("account");
|
|
57
59
|
const getStatusIcon = (status: OrderDetailsData["status"]) => {
|
|
58
60
|
switch (status) {
|
|
59
61
|
case "delivered":
|
|
@@ -131,10 +133,10 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
131
133
|
<div className="flex items-start justify-between mb-4">
|
|
132
134
|
<div>
|
|
133
135
|
<h2 className="text-2xl font-bold text-foreground mb-2">
|
|
134
|
-
|
|
136
|
+
{t("orderNumber", { number: order.orderNumber })}
|
|
135
137
|
</h2>
|
|
136
138
|
<p className="text-sm text-muted-foreground">
|
|
137
|
-
|
|
139
|
+
{t("placedOn")} {formatDate(order.date)}
|
|
138
140
|
</p>
|
|
139
141
|
</div>
|
|
140
142
|
<div className="flex items-center gap-2">
|
|
@@ -146,7 +148,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
146
148
|
{order.trackingNumber && (
|
|
147
149
|
<div className="mt-4 p-4 bg-muted/50 rounded-lg">
|
|
148
150
|
<p className="text-sm font-medium text-foreground mb-1">
|
|
149
|
-
|
|
151
|
+
{t("trackingNumber")}
|
|
150
152
|
</p>
|
|
151
153
|
<p className="text-sm text-muted-foreground font-mono">
|
|
152
154
|
{order.trackingNumber}
|
|
@@ -157,7 +159,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
157
159
|
{order.estimatedDelivery && (
|
|
158
160
|
<div className="mt-4 p-4 bg-muted/50 rounded-lg">
|
|
159
161
|
<p className="text-sm font-medium text-foreground mb-1">
|
|
160
|
-
|
|
162
|
+
{t("estimatedDelivery")}
|
|
161
163
|
</p>
|
|
162
164
|
<p className="text-sm text-muted-foreground">
|
|
163
165
|
{formatDate(order.estimatedDelivery)}
|
|
@@ -169,7 +171,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
169
171
|
{/* Order Items */}
|
|
170
172
|
<Card className="p-6 mb-6">
|
|
171
173
|
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
172
|
-
|
|
174
|
+
{t("orderItems")}
|
|
173
175
|
</h3>
|
|
174
176
|
<div className="space-y-4">
|
|
175
177
|
{order.items.map((item) => (
|
|
@@ -191,7 +193,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
191
193
|
</p>
|
|
192
194
|
)}
|
|
193
195
|
<p className="text-sm text-muted-foreground">
|
|
194
|
-
|
|
196
|
+
{t("itemCount", { count: item.quantity })}
|
|
195
197
|
</p>
|
|
196
198
|
</div>
|
|
197
199
|
<div className="text-right">
|
|
@@ -208,26 +210,26 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
208
210
|
{/* Order Summary */}
|
|
209
211
|
<div className="space-y-2">
|
|
210
212
|
<div className="flex justify-between text-sm">
|
|
211
|
-
<span className="text-muted-foreground">
|
|
213
|
+
<span className="text-muted-foreground">{t("subtotal")}</span>
|
|
212
214
|
<span className="text-foreground">
|
|
213
215
|
{formatPrice(order.subtotal, order.currency)}
|
|
214
216
|
</span>
|
|
215
217
|
</div>
|
|
216
218
|
<div className="flex justify-between text-sm">
|
|
217
|
-
<span className="text-muted-foreground">
|
|
219
|
+
<span className="text-muted-foreground">{t("shippingLabel")}</span>
|
|
218
220
|
<span className="text-foreground">
|
|
219
221
|
{formatPrice(order.shipping, order.currency)}
|
|
220
222
|
</span>
|
|
221
223
|
</div>
|
|
222
224
|
<div className="flex justify-between text-sm">
|
|
223
|
-
<span className="text-muted-foreground">
|
|
225
|
+
<span className="text-muted-foreground">{t("taxLabel")}</span>
|
|
224
226
|
<span className="text-foreground">
|
|
225
227
|
{formatPrice(order.tax, order.currency)}
|
|
226
228
|
</span>
|
|
227
229
|
</div>
|
|
228
230
|
<Separator className="my-2" />
|
|
229
231
|
<div className="flex justify-between text-base font-semibold">
|
|
230
|
-
<span className="text-foreground">
|
|
232
|
+
<span className="text-foreground">{t("orderTotal")}</span>
|
|
231
233
|
<span className="text-foreground">
|
|
232
234
|
{formatPrice(order.total, order.currency)}
|
|
233
235
|
</span>
|
|
@@ -240,7 +242,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
240
242
|
{order.shippingAddress && (
|
|
241
243
|
<Card className="p-6">
|
|
242
244
|
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
243
|
-
|
|
245
|
+
{t("shippingAddress")}
|
|
244
246
|
</h3>
|
|
245
247
|
{formatAddress(order.shippingAddress)}
|
|
246
248
|
</Card>
|
|
@@ -249,7 +251,7 @@ export function OrderDetails({ order, className }: OrderDetailsProps) {
|
|
|
249
251
|
{order.billingAddress && (
|
|
250
252
|
<Card className="p-6">
|
|
251
253
|
<h3 className="text-lg font-semibold text-foreground mb-4">
|
|
252
|
-
|
|
254
|
+
{t("billingAddress")}
|
|
253
255
|
</h3>
|
|
254
256
|
{formatAddress(order.billingAddress)}
|
|
255
257
|
</Card>
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import Link from "
|
|
3
|
+
import { Link } from "@/i18n/navigation";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { useLocale } from "next-intl";
|
|
4
6
|
import { Package, ChevronRight } from "lucide-react";
|
|
5
7
|
import { Badge } from "@/components/ui/badge";
|
|
6
8
|
import { Button } from "@/components/ui/button";
|
|
7
9
|
import { formatAmount } from "@doswiftly/storefront-sdk";
|
|
8
10
|
import type { OrderSummaryFields } from "@/lib/graphql/fragments";
|
|
9
11
|
|
|
12
|
+
const STATUS_KEY_MAP: Record<string, string> = {
|
|
13
|
+
DELIVERED: "delivered",
|
|
14
|
+
FULFILLED: "fulfilled",
|
|
15
|
+
IN_TRANSIT: "shipped",
|
|
16
|
+
PARTIALLY_FULFILLED: "partiallyFulfilled",
|
|
17
|
+
UNFULFILLED: "processing",
|
|
18
|
+
RETURNED: "returned",
|
|
19
|
+
};
|
|
20
|
+
|
|
10
21
|
export interface OrderHistoryProps {
|
|
11
22
|
orders: OrderSummaryFields[];
|
|
12
23
|
className?: string;
|
|
@@ -16,6 +27,9 @@ export interface OrderHistoryProps {
|
|
|
16
27
|
* OrderHistory - Display list of customer orders
|
|
17
28
|
*/
|
|
18
29
|
export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
30
|
+
const t = useTranslations("account");
|
|
31
|
+
const locale = useLocale();
|
|
32
|
+
|
|
19
33
|
const getStatusBadge = (status: string) => {
|
|
20
34
|
const variants: Record<string, "default" | "secondary" | "success" | "warning"> = {
|
|
21
35
|
DELIVERED: "success",
|
|
@@ -26,24 +40,20 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
|
26
40
|
RETURNED: "secondary",
|
|
27
41
|
};
|
|
28
42
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
PARTIALLY_FULFILLED: "Partially Fulfilled",
|
|
34
|
-
UNFULFILLED: "Processing",
|
|
35
|
-
RETURNED: "Returned",
|
|
36
|
-
};
|
|
43
|
+
const statusKey = STATUS_KEY_MAP[status];
|
|
44
|
+
const label = statusKey
|
|
45
|
+
? t(`orderStatuses.${statusKey}`)
|
|
46
|
+
: status;
|
|
37
47
|
|
|
38
48
|
return (
|
|
39
49
|
<Badge variant={variants[status] ?? "secondary"}>
|
|
40
|
-
{
|
|
50
|
+
{label}
|
|
41
51
|
</Badge>
|
|
42
52
|
);
|
|
43
53
|
};
|
|
44
54
|
|
|
45
55
|
const formatDate = (dateString: string) => {
|
|
46
|
-
return new Date(dateString).toLocaleDateString(
|
|
56
|
+
return new Date(dateString).toLocaleDateString(locale, {
|
|
47
57
|
year: "numeric",
|
|
48
58
|
month: "long",
|
|
49
59
|
day: "numeric",
|
|
@@ -55,13 +65,13 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
|
55
65
|
<div className="rounded-lg border border-border bg-muted/50 p-12 text-center">
|
|
56
66
|
<Package className="mx-auto h-12 w-12 text-muted-foreground mb-4" />
|
|
57
67
|
<h2 className="text-xl font-semibold text-foreground mb-2">
|
|
58
|
-
|
|
68
|
+
{t("noOrders")}
|
|
59
69
|
</h2>
|
|
60
70
|
<p className="text-muted-foreground mb-6">
|
|
61
|
-
|
|
71
|
+
{t("startShoppingDescription")}
|
|
62
72
|
</p>
|
|
63
73
|
<Button asChild>
|
|
64
|
-
<Link href="/products">
|
|
74
|
+
<Link href="/products">{t("browseProducts")}</Link>
|
|
65
75
|
</Button>
|
|
66
76
|
</div>
|
|
67
77
|
);
|
|
@@ -80,20 +90,20 @@ export function OrderHistory({ orders, className }: OrderHistoryProps) {
|
|
|
80
90
|
<div className="flex-1">
|
|
81
91
|
<div className="flex items-center gap-3 mb-2">
|
|
82
92
|
<h3 className="text-lg font-semibold text-foreground">
|
|
83
|
-
|
|
93
|
+
{t("orderNumber", { number: order.orderNumber })}
|
|
84
94
|
</h3>
|
|
85
95
|
{getStatusBadge(order.fulfillmentStatus)}
|
|
86
96
|
</div>
|
|
87
97
|
<div className="space-y-1 text-sm text-muted-foreground">
|
|
88
|
-
<p>
|
|
98
|
+
<p>{t("placedOn")} {formatDate(order.processedAt)}</p>
|
|
89
99
|
<p>
|
|
90
|
-
{
|
|
100
|
+
{t("itemCount", { count: order.lineItemsCount })}
|
|
91
101
|
</p>
|
|
92
102
|
</div>
|
|
93
103
|
</div>
|
|
94
104
|
<div className="flex items-center gap-4">
|
|
95
105
|
<div className="text-right">
|
|
96
|
-
<p className="text-sm text-muted-foreground">
|
|
106
|
+
<p className="text-sm text-muted-foreground">{t("orderTotal")}</p>
|
|
97
107
|
<p className="text-lg font-semibold text-foreground">
|
|
98
108
|
{formatAmount(order.totalPrice.amount, order.totalPrice.currencyCode)}
|
|
99
109
|
</p>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect } from "react";
|
|
4
|
-
import Link from "
|
|
5
|
-
import {
|
|
4
|
+
import { Link, useRouter } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
6
|
import { User, LogOut, Package, MapPin, Settings } from "lucide-react";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
@@ -14,6 +14,8 @@ export interface AccountMenuProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
17
|
+
const t = useTranslations("account");
|
|
18
|
+
const tAuth = useTranslations("auth");
|
|
17
19
|
const router = useRouter();
|
|
18
20
|
const [isOpen, setIsOpen] = useState(false);
|
|
19
21
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
@@ -46,22 +48,22 @@ export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
|
46
48
|
|
|
47
49
|
const menuItems = [
|
|
48
50
|
{
|
|
49
|
-
label: "
|
|
51
|
+
label: t("title"),
|
|
50
52
|
href: "/account",
|
|
51
53
|
icon: User,
|
|
52
54
|
},
|
|
53
55
|
{
|
|
54
|
-
label: "
|
|
56
|
+
label: t("orders"),
|
|
55
57
|
href: "/account/orders",
|
|
56
58
|
icon: Package,
|
|
57
59
|
},
|
|
58
60
|
{
|
|
59
|
-
label: "
|
|
61
|
+
label: t("addresses"),
|
|
60
62
|
href: "/account/addresses",
|
|
61
63
|
icon: MapPin,
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
|
-
label: "
|
|
66
|
+
label: t("settings"),
|
|
65
67
|
href: "/account/settings",
|
|
66
68
|
icon: Settings,
|
|
67
69
|
},
|
|
@@ -75,7 +77,7 @@ export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
|
75
77
|
size="sm"
|
|
76
78
|
onClick={() => setIsOpen(!isOpen)}
|
|
77
79
|
className="flex items-center gap-2"
|
|
78
|
-
aria-label="
|
|
80
|
+
aria-label={t("title")}
|
|
79
81
|
aria-expanded={isOpen}
|
|
80
82
|
>
|
|
81
83
|
<User className="h-5 w-5" />
|
|
@@ -122,7 +124,7 @@ export function AccountMenu({ customerName, className }: AccountMenuProps) {
|
|
|
122
124
|
className="flex w-full items-center gap-3 px-4 py-2 text-sm text-destructive hover:bg-muted transition-colors disabled:opacity-50"
|
|
123
125
|
>
|
|
124
126
|
<LogOut className="h-4 w-4" />
|
|
125
|
-
{isLoggingOut ? "
|
|
127
|
+
{isLoggingOut ? tAuth("signingOut") : tAuth("signOut")}
|
|
126
128
|
</button>
|
|
127
129
|
</div>
|
|
128
130
|
</div>
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import Link from "
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import { useRouter, Link } from "@/i18n/navigation";
|
|
6
|
+
import { useTranslations } from "next-intl";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { Button } from "@/components/ui/button";
|
|
8
9
|
import { Input } from "@/components/ui/input";
|
|
9
10
|
import { useAuth } from "@/hooks/use-auth";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
email:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.email("Please enter a valid email address"),
|
|
16
|
-
password: z
|
|
17
|
-
.string()
|
|
18
|
-
.min(1, "Password is required")
|
|
19
|
-
.min(8, "Password must be at least 8 characters"),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
type LoginFormData = z.infer<typeof loginSchema>;
|
|
12
|
+
type LoginFormData = {
|
|
13
|
+
email: string;
|
|
14
|
+
password: string;
|
|
15
|
+
};
|
|
23
16
|
|
|
24
17
|
export interface LoginFormProps {
|
|
25
18
|
onSuccess?: () => void;
|
|
@@ -27,10 +20,22 @@ export interface LoginFormProps {
|
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
23
|
+
const t = useTranslations("auth");
|
|
30
24
|
const router = useRouter();
|
|
31
25
|
const searchParams = useSearchParams();
|
|
32
26
|
const defaultRedirect = redirectTo || searchParams?.get("redirect") || "/account";
|
|
33
27
|
|
|
28
|
+
const loginSchema = z.object({
|
|
29
|
+
email: z
|
|
30
|
+
.string()
|
|
31
|
+
.min(1, t("validation.emailInvalid"))
|
|
32
|
+
.email(t("validation.emailInvalid")),
|
|
33
|
+
password: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1, t("validation.passwordMinLength"))
|
|
36
|
+
.min(8, t("validation.passwordMinLength")),
|
|
37
|
+
});
|
|
38
|
+
|
|
34
39
|
const [email, setEmail] = useState("");
|
|
35
40
|
const [password, setPassword] = useState("");
|
|
36
41
|
const [error, setError] = useState("");
|
|
@@ -60,7 +65,7 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
60
65
|
const loginResult = await login(email, password);
|
|
61
66
|
|
|
62
67
|
if (!loginResult.success) {
|
|
63
|
-
setError(loginResult.userErrors[0]?.message || "
|
|
68
|
+
setError(loginResult.userErrors[0]?.message || t("loginFailed"));
|
|
64
69
|
return;
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -70,7 +75,7 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
70
75
|
router.push(defaultRedirect);
|
|
71
76
|
}
|
|
72
77
|
} catch {
|
|
73
|
-
setError("
|
|
78
|
+
setError(t("unexpectedError"));
|
|
74
79
|
}
|
|
75
80
|
};
|
|
76
81
|
|
|
@@ -87,7 +92,7 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
87
92
|
htmlFor="email"
|
|
88
93
|
className="text-sm font-medium text-foreground"
|
|
89
94
|
>
|
|
90
|
-
|
|
95
|
+
{t("email")}
|
|
91
96
|
</label>
|
|
92
97
|
<Input
|
|
93
98
|
id="email"
|
|
@@ -113,13 +118,13 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
113
118
|
htmlFor="password"
|
|
114
119
|
className="text-sm font-medium text-foreground"
|
|
115
120
|
>
|
|
116
|
-
|
|
121
|
+
{t("password")}
|
|
117
122
|
</label>
|
|
118
123
|
<Link
|
|
119
124
|
href="/auth/forgot-password"
|
|
120
125
|
className="text-xs text-primary hover:underline"
|
|
121
126
|
>
|
|
122
|
-
|
|
127
|
+
{t("forgotPassword")}
|
|
123
128
|
</Link>
|
|
124
129
|
</div>
|
|
125
130
|
<Input
|
|
@@ -145,16 +150,16 @@ export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
|
145
150
|
className="w-full"
|
|
146
151
|
disabled={isLoggingIn}
|
|
147
152
|
>
|
|
148
|
-
{isLoggingIn ? "
|
|
153
|
+
{isLoggingIn ? t("signingIn") : t("signIn")}
|
|
149
154
|
</Button>
|
|
150
155
|
|
|
151
156
|
<div className="text-center text-sm text-muted-foreground">
|
|
152
|
-
|
|
157
|
+
{t("noAccount")}{" "}
|
|
153
158
|
<Link
|
|
154
159
|
href="/auth/register"
|
|
155
160
|
className="font-medium text-primary hover:underline"
|
|
156
161
|
>
|
|
157
|
-
|
|
162
|
+
{t("signUp")}
|
|
158
163
|
</Link>
|
|
159
164
|
</div>
|
|
160
165
|
</form>
|