@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,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { useRouter } from "
|
|
5
|
-
import
|
|
4
|
+
import { useRouter, Link } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
8
8
|
import { useExecute } from "@/lib/graphql/client";
|
|
@@ -14,33 +14,12 @@ const { setToken: setAuthToken } = createAuthTokenClient();
|
|
|
14
14
|
import { Button } from "@/components/ui/button";
|
|
15
15
|
import { Input } from "@/components/ui/input";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.max(50, "First name must be less than 50 characters"),
|
|
24
|
-
lastName: z
|
|
25
|
-
.string()
|
|
26
|
-
.min(1, "Last name is required")
|
|
27
|
-
.min(2, "Last name must be at least 2 characters")
|
|
28
|
-
.max(50, "Last name must be less than 50 characters"),
|
|
29
|
-
email: z
|
|
30
|
-
.string()
|
|
31
|
-
.min(1, "Email is required")
|
|
32
|
-
.email("Please enter a valid email address"),
|
|
33
|
-
password: z
|
|
34
|
-
.string()
|
|
35
|
-
.min(1, "Password is required")
|
|
36
|
-
.min(8, "Password must be at least 8 characters")
|
|
37
|
-
.regex(
|
|
38
|
-
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
|
39
|
-
"Password must contain at least one uppercase letter, one lowercase letter, and one number"
|
|
40
|
-
),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
type RegisterFormData = z.infer<typeof registerSchema>;
|
|
17
|
+
type RegisterFormData = {
|
|
18
|
+
firstName: string;
|
|
19
|
+
lastName: string;
|
|
20
|
+
email: string;
|
|
21
|
+
password: string;
|
|
22
|
+
};
|
|
44
23
|
|
|
45
24
|
export interface RegisterFormProps {
|
|
46
25
|
onSuccess?: () => void;
|
|
@@ -49,12 +28,38 @@ export interface RegisterFormProps {
|
|
|
49
28
|
|
|
50
29
|
/**
|
|
51
30
|
* RegisterForm - Reusable registration form component with Zod validation
|
|
52
|
-
*
|
|
31
|
+
*
|
|
53
32
|
* Validates user input before submission and displays field-level errors
|
|
54
33
|
*/
|
|
55
34
|
export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFormProps) {
|
|
35
|
+
const t = useTranslations("auth");
|
|
56
36
|
const router = useRouter();
|
|
57
37
|
|
|
38
|
+
const registerSchema = z.object({
|
|
39
|
+
firstName: z
|
|
40
|
+
.string()
|
|
41
|
+
.min(1, t("validation.firstNameMinLength"))
|
|
42
|
+
.min(2, t("validation.firstNameMinLength"))
|
|
43
|
+
.max(50, t("validation.firstNameMaxLength")),
|
|
44
|
+
lastName: z
|
|
45
|
+
.string()
|
|
46
|
+
.min(1, t("validation.lastNameMinLength"))
|
|
47
|
+
.min(2, t("validation.lastNameMinLength"))
|
|
48
|
+
.max(50, t("validation.lastNameMaxLength")),
|
|
49
|
+
email: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1, t("validation.emailInvalid"))
|
|
52
|
+
.email(t("validation.emailInvalid")),
|
|
53
|
+
password: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1, t("validation.passwordMinLength"))
|
|
56
|
+
.min(8, t("validation.passwordMinLength"))
|
|
57
|
+
.regex(
|
|
58
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
|
59
|
+
t("validation.passwordRequirements"),
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
|
|
58
63
|
const [email, setEmail] = useState("");
|
|
59
64
|
const [password, setPassword] = useState("");
|
|
60
65
|
const [firstName, setFirstName] = useState("");
|
|
@@ -115,9 +120,9 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
115
120
|
|
|
116
121
|
// Check for user errors first
|
|
117
122
|
const userErrors = mutationResult?.customerCreate?.userErrors || [];
|
|
118
|
-
|
|
123
|
+
|
|
119
124
|
if (userErrors.length > 0) {
|
|
120
|
-
setError(userErrors[0].message || "
|
|
125
|
+
setError(userErrors[0].message || t("registerFailed"));
|
|
121
126
|
return;
|
|
122
127
|
}
|
|
123
128
|
|
|
@@ -150,14 +155,14 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
150
155
|
router.push(redirectTo);
|
|
151
156
|
}
|
|
152
157
|
} else {
|
|
153
|
-
setError("
|
|
158
|
+
setError(t("registerSuccess"));
|
|
154
159
|
setTimeout(() => router.push("/auth/login"), 2000);
|
|
155
160
|
}
|
|
156
161
|
} else {
|
|
157
|
-
setError("
|
|
162
|
+
setError(t("registerFailed"));
|
|
158
163
|
}
|
|
159
164
|
} catch (err) {
|
|
160
|
-
setError("
|
|
165
|
+
setError(t("unexpectedError"));
|
|
161
166
|
}
|
|
162
167
|
};
|
|
163
168
|
|
|
@@ -175,7 +180,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
175
180
|
htmlFor="firstName"
|
|
176
181
|
className="text-sm font-medium text-foreground"
|
|
177
182
|
>
|
|
178
|
-
|
|
183
|
+
{t("firstName")}
|
|
179
184
|
</label>
|
|
180
185
|
<Input
|
|
181
186
|
id="firstName"
|
|
@@ -200,7 +205,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
200
205
|
htmlFor="lastName"
|
|
201
206
|
className="text-sm font-medium text-foreground"
|
|
202
207
|
>
|
|
203
|
-
|
|
208
|
+
{t("lastName")}
|
|
204
209
|
</label>
|
|
205
210
|
<Input
|
|
206
211
|
id="lastName"
|
|
@@ -226,7 +231,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
226
231
|
htmlFor="email"
|
|
227
232
|
className="text-sm font-medium text-foreground"
|
|
228
233
|
>
|
|
229
|
-
|
|
234
|
+
{t("email")}
|
|
230
235
|
</label>
|
|
231
236
|
<Input
|
|
232
237
|
id="email"
|
|
@@ -251,7 +256,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
251
256
|
htmlFor="password"
|
|
252
257
|
className="text-sm font-medium text-foreground"
|
|
253
258
|
>
|
|
254
|
-
|
|
259
|
+
{t("password")}
|
|
255
260
|
</label>
|
|
256
261
|
<Input
|
|
257
262
|
id="password"
|
|
@@ -271,7 +276,7 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
271
276
|
</p>
|
|
272
277
|
) : (
|
|
273
278
|
<p className="text-xs text-muted-foreground">
|
|
274
|
-
|
|
279
|
+
{t("passwordRequirements")}
|
|
275
280
|
</p>
|
|
276
281
|
)}
|
|
277
282
|
</div>
|
|
@@ -281,16 +286,16 @@ export function RegisterForm({ onSuccess, redirectTo = "/account" }: RegisterFor
|
|
|
281
286
|
className="w-full"
|
|
282
287
|
disabled={registerMutation.isPending}
|
|
283
288
|
>
|
|
284
|
-
{registerMutation.isPending ? "
|
|
289
|
+
{registerMutation.isPending ? t("creatingAccount") : t("createAccount")}
|
|
285
290
|
</Button>
|
|
286
291
|
|
|
287
292
|
<div className="text-center text-sm text-muted-foreground">
|
|
288
|
-
|
|
293
|
+
{t("hasAccount")}{" "}
|
|
289
294
|
<Link
|
|
290
295
|
href="/auth/login"
|
|
291
296
|
className="font-medium text-primary hover:underline"
|
|
292
297
|
>
|
|
293
|
-
|
|
298
|
+
{t("signIn")}
|
|
294
299
|
</Link>
|
|
295
300
|
</div>
|
|
296
301
|
</form>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import Image from 'next/image';
|
|
10
|
-
import Link from '
|
|
10
|
+
import { Link } from '@/i18n/navigation';
|
|
11
11
|
import { Calendar, Clock, Eye, User } from 'lucide-react';
|
|
12
12
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
|
13
13
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Sidebar with categories, tags, recent posts, and search.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import Link from '
|
|
9
|
+
import { Link } from '@/i18n/navigation';
|
|
10
10
|
import { Search, FolderOpen, Tag, TrendingUp } from 'lucide-react';
|
|
11
11
|
import { Input } from '@/components/ui/input';
|
|
12
12
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
-
import { useRouter } from "
|
|
4
|
+
import { useRouter } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
import { X, ShoppingBag, Loader2 } from "lucide-react";
|
|
6
7
|
import { useCartStore } from "@/stores/cart-store";
|
|
7
8
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
@@ -24,6 +25,8 @@ export interface CartDrawerProps {
|
|
|
24
25
|
*/
|
|
25
26
|
export function CartDrawer({ className }: CartDrawerProps) {
|
|
26
27
|
const router = useRouter();
|
|
28
|
+
const t = useTranslations("cart");
|
|
29
|
+
const tc = useTranslations("common");
|
|
27
30
|
|
|
28
31
|
// UI state from Zustand
|
|
29
32
|
const { isOpen, closeCart } = useCartStore();
|
|
@@ -71,14 +74,14 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
71
74
|
)}
|
|
72
75
|
role="dialog"
|
|
73
76
|
aria-modal="true"
|
|
74
|
-
aria-label="
|
|
77
|
+
aria-label={t("title")}
|
|
75
78
|
>
|
|
76
79
|
{/* Header */}
|
|
77
80
|
<div className="flex items-center justify-between border-b border-border p-4">
|
|
78
81
|
<div className="flex items-center gap-2">
|
|
79
82
|
<ShoppingBag className="h-5 w-5" />
|
|
80
83
|
<h2 className="text-lg font-semibold">
|
|
81
|
-
|
|
84
|
+
{t("title")}
|
|
82
85
|
{totalQuantity > 0 && (
|
|
83
86
|
<span className="ml-2 text-sm font-normal text-muted-foreground">
|
|
84
87
|
({totalQuantity})
|
|
@@ -91,7 +94,7 @@ export function CartDrawer({ className }: CartDrawerProps) {
|
|
|
91
94
|
size="sm"
|
|
92
95
|
onClick={closeCart}
|
|
93
96
|
className="h-8 w-8 p-0"
|
|
94
|
-
aria-label="
|
|
97
|
+
aria-label={tc("close")}
|
|
95
98
|
>
|
|
96
99
|
<X className="h-5 w-5" />
|
|
97
100
|
</Button>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useRouter } from "
|
|
3
|
+
import { useRouter } from "@/i18n/navigation";
|
|
4
4
|
import { ShoppingCart } from "lucide-react";
|
|
5
5
|
import { useCartSync } from "@/hooks/use-cart-sync";
|
|
6
6
|
import { useHydrated } from "@doswiftly/storefront-sdk/react";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { X, Gift } from "lucide-react";
|
|
4
|
-
import Link from "
|
|
4
|
+
import { Link } from "@/i18n/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
import { ProductImage } from "@/components/product/product-image";
|
|
6
7
|
import { ProductQuantitySelector } from "@/components/product/product-quantity-selector";
|
|
7
8
|
import { Button } from "@/components/ui/button";
|
|
@@ -25,6 +26,7 @@ export function CartItem({
|
|
|
25
26
|
onRemove,
|
|
26
27
|
className,
|
|
27
28
|
}: CartItemProps) {
|
|
29
|
+
const t = useTranslations("cart");
|
|
28
30
|
const itemTotal = parseFloat(item.price.amount) * item.quantity;
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -56,7 +58,7 @@ export function CartItem({
|
|
|
56
58
|
{item.productType === "GIFT_CARD" && (
|
|
57
59
|
<p className="mt-1 flex items-center gap-1 text-xs text-primary">
|
|
58
60
|
<Gift className="h-3 w-3" />
|
|
59
|
-
|
|
61
|
+
{t("giftCardLabel")}
|
|
60
62
|
</p>
|
|
61
63
|
)}
|
|
62
64
|
{item.variantTitle && (
|
|
@@ -72,7 +74,7 @@ export function CartItem({
|
|
|
72
74
|
size="sm"
|
|
73
75
|
onClick={() => onRemove(item.lineId)}
|
|
74
76
|
className="h-8 w-8 p-0"
|
|
75
|
-
aria-label="
|
|
77
|
+
aria-label={t("remove")}
|
|
76
78
|
>
|
|
77
79
|
<X className="h-4 w-4" />
|
|
78
80
|
</Button>
|
|
@@ -94,7 +96,7 @@ export function CartItem({
|
|
|
94
96
|
</p>
|
|
95
97
|
{item.quantity > 1 && (
|
|
96
98
|
<p className="text-xs text-muted-foreground">
|
|
97
|
-
{formatPrice(item.price)} each
|
|
99
|
+
{formatPrice(item.price)} {t("each")}
|
|
98
100
|
</p>
|
|
99
101
|
)}
|
|
100
102
|
</div>
|
|
@@ -103,7 +105,7 @@ export function CartItem({
|
|
|
103
105
|
{/* Out of stock warning */}
|
|
104
106
|
{!item.available && (
|
|
105
107
|
<p className="mt-2 text-xs text-destructive">
|
|
106
|
-
|
|
108
|
+
{t("outOfStockWarning")}
|
|
107
109
|
</p>
|
|
108
110
|
)}
|
|
109
111
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { Button } from "@/components/ui/button";
|
|
4
5
|
import { cn } from "@/lib/utils";
|
|
5
6
|
import { formatAmount } from "@doswiftly/storefront-sdk";
|
|
@@ -26,6 +27,7 @@ export function CartSummary({
|
|
|
26
27
|
onCheckout,
|
|
27
28
|
className,
|
|
28
29
|
}: CartSummaryProps) {
|
|
30
|
+
const t = useTranslations("cart");
|
|
29
31
|
const displayTotal = total ?? subtotal;
|
|
30
32
|
const hasDiscount = (totalDiscount ?? 0) > 0;
|
|
31
33
|
|
|
@@ -33,7 +35,7 @@ export function CartSummary({
|
|
|
33
35
|
<div className={cn("space-y-4", className)}>
|
|
34
36
|
{/* Subtotal */}
|
|
35
37
|
<div className="flex items-center justify-between border-t border-border pt-4">
|
|
36
|
-
<span className="text-base font-medium text-foreground">
|
|
38
|
+
<span className="text-base font-medium text-foreground">{t("subtotal")}</span>
|
|
37
39
|
<span className={cn("text-lg font-semibold text-foreground", hasDiscount && "text-muted-foreground line-through text-base")}>
|
|
38
40
|
{formatAmount(subtotal, currencyCode)}
|
|
39
41
|
</span>
|
|
@@ -43,11 +45,11 @@ export function CartSummary({
|
|
|
43
45
|
{hasDiscount && (
|
|
44
46
|
<>
|
|
45
47
|
<div className="flex items-center justify-between text-sm text-green-600 dark:text-green-400">
|
|
46
|
-
<span>
|
|
48
|
+
<span>{t("discount")}</span>
|
|
47
49
|
<span>-{formatAmount(totalDiscount!, currencyCode)}</span>
|
|
48
50
|
</div>
|
|
49
51
|
<div className="flex items-center justify-between">
|
|
50
|
-
<span className="text-base font-medium text-foreground">
|
|
52
|
+
<span className="text-base font-medium text-foreground">{t("total")}</span>
|
|
51
53
|
<span className="text-lg font-semibold text-foreground">
|
|
52
54
|
{formatAmount(displayTotal, currencyCode)}
|
|
53
55
|
</span>
|
|
@@ -57,12 +59,12 @@ export function CartSummary({
|
|
|
57
59
|
|
|
58
60
|
{/* Item count */}
|
|
59
61
|
<p className="text-sm text-muted-foreground">
|
|
60
|
-
{itemCount
|
|
62
|
+
{t("itemCount", { count: itemCount })} {t("inCart")}
|
|
61
63
|
</p>
|
|
62
64
|
|
|
63
65
|
{/* Shipping note */}
|
|
64
66
|
<p className="text-xs text-muted-foreground">
|
|
65
|
-
|
|
67
|
+
{t("shippingTaxNote")}
|
|
66
68
|
</p>
|
|
67
69
|
|
|
68
70
|
{/* Checkout button */}
|
|
@@ -72,12 +74,12 @@ export function CartSummary({
|
|
|
72
74
|
className="w-full"
|
|
73
75
|
disabled={itemCount === 0}
|
|
74
76
|
>
|
|
75
|
-
|
|
77
|
+
{t("proceedToCheckout")}
|
|
76
78
|
</Button>
|
|
77
79
|
|
|
78
80
|
{/* Continue shopping */}
|
|
79
81
|
<Button variant="outline" size="lg" className="w-full" asChild>
|
|
80
|
-
<a href="/products">
|
|
82
|
+
<a href="/products">{t("continueShopping")}</a>
|
|
81
83
|
</Button>
|
|
82
84
|
</div>
|
|
83
85
|
);
|
|
@@ -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 { Tag, Check, X } from "lucide-react";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
import { Button } from "@/components/ui/button";
|
|
@@ -22,6 +23,8 @@ export function PromoCodeInput({
|
|
|
22
23
|
onRemove,
|
|
23
24
|
className,
|
|
24
25
|
}: PromoCodeInputProps) {
|
|
26
|
+
const t = useTranslations("cart");
|
|
27
|
+
const tc = useTranslations("common");
|
|
25
28
|
const [code, setCode] = useState("");
|
|
26
29
|
const [isLoading, setIsLoading] = useState(false);
|
|
27
30
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -38,13 +41,13 @@ export function PromoCodeInput({
|
|
|
38
41
|
const result = await onApply(code.trim());
|
|
39
42
|
|
|
40
43
|
if (result.success) {
|
|
41
|
-
setSuccess(result.message || "
|
|
44
|
+
setSuccess(result.message || t("promoApplied"));
|
|
42
45
|
setCode("");
|
|
43
46
|
} else {
|
|
44
|
-
setError(result.message || "
|
|
47
|
+
setError(result.message || t("promoError"));
|
|
45
48
|
}
|
|
46
49
|
} catch (err) {
|
|
47
|
-
setError("
|
|
50
|
+
setError(t("promoError"));
|
|
48
51
|
} finally {
|
|
49
52
|
setIsLoading(false);
|
|
50
53
|
}
|
|
@@ -88,7 +91,7 @@ export function PromoCodeInput({
|
|
|
88
91
|
<Tag className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
89
92
|
<Input
|
|
90
93
|
type="text"
|
|
91
|
-
placeholder="
|
|
94
|
+
placeholder={t("enterPromoCode")}
|
|
92
95
|
value={code}
|
|
93
96
|
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
|
94
97
|
onKeyDown={(e) => e.key === "Enter" && handleApply()}
|
|
@@ -101,7 +104,7 @@ export function PromoCodeInput({
|
|
|
101
104
|
disabled={!code.trim() || isLoading}
|
|
102
105
|
size="default"
|
|
103
106
|
>
|
|
104
|
-
{isLoading ? "
|
|
107
|
+
{isLoading ? t("applying") : tc("apply")}
|
|
105
108
|
</Button>
|
|
106
109
|
</div>
|
|
107
110
|
|
|
@@ -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 { Truck, MapPin } from "lucide-react";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
import { Button } from "@/components/ui/button";
|
|
@@ -39,6 +40,8 @@ export function ShippingEstimator({
|
|
|
39
40
|
selectedOption,
|
|
40
41
|
className,
|
|
41
42
|
}: ShippingEstimatorProps) {
|
|
43
|
+
const t = useTranslations("cart");
|
|
44
|
+
const tCountries = useTranslations("countries");
|
|
42
45
|
const [country, setCountry] = useState("PL");
|
|
43
46
|
const [postalCode, setPostalCode] = useState("");
|
|
44
47
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -46,16 +49,16 @@ export function ShippingEstimator({
|
|
|
46
49
|
const [error, setError] = useState<string | null>(null);
|
|
47
50
|
|
|
48
51
|
const countries = [
|
|
49
|
-
{ value: "PL", label: "
|
|
50
|
-
{ value: "DE", label: "
|
|
51
|
-
{ value: "FR", label: "
|
|
52
|
-
{ value: "GB", label: "
|
|
53
|
-
{ value: "US", label: "
|
|
52
|
+
{ value: "PL", label: tCountries("PL") },
|
|
53
|
+
{ value: "DE", label: tCountries("DE") },
|
|
54
|
+
{ value: "FR", label: tCountries("FR") },
|
|
55
|
+
{ value: "GB", label: tCountries("GB") },
|
|
56
|
+
{ value: "US", label: tCountries("US") },
|
|
54
57
|
];
|
|
55
58
|
|
|
56
59
|
const handleEstimate = async () => {
|
|
57
60
|
if (!postalCode.trim()) {
|
|
58
|
-
setError("
|
|
61
|
+
setError(t("enterPostalCode"));
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -68,10 +71,10 @@ export function ShippingEstimator({
|
|
|
68
71
|
setOptions(result);
|
|
69
72
|
|
|
70
73
|
if (result.length === 0) {
|
|
71
|
-
setError("
|
|
74
|
+
setError(t("noShippingOptions"));
|
|
72
75
|
}
|
|
73
76
|
} catch (err) {
|
|
74
|
-
setError("
|
|
77
|
+
setError(t("failedCalculate"));
|
|
75
78
|
} finally {
|
|
76
79
|
setIsLoading(false);
|
|
77
80
|
}
|
|
@@ -90,17 +93,17 @@ export function ShippingEstimator({
|
|
|
90
93
|
<div className="flex items-center gap-2">
|
|
91
94
|
<Truck className="h-5 w-5 text-muted-foreground" />
|
|
92
95
|
<h3 className="text-sm font-medium text-foreground">
|
|
93
|
-
|
|
96
|
+
{t("estimateShipping")}
|
|
94
97
|
</h3>
|
|
95
98
|
</div>
|
|
96
99
|
|
|
97
100
|
{/* Form */}
|
|
98
101
|
<div className="space-y-3">
|
|
99
102
|
<div>
|
|
100
|
-
<label className="text-sm font-medium text-foreground">
|
|
103
|
+
<label className="text-sm font-medium text-foreground">{t("country")}</label>
|
|
101
104
|
<Select value={country} onValueChange={setCountry}>
|
|
102
105
|
<SelectTrigger>
|
|
103
|
-
<SelectValue placeholder="
|
|
106
|
+
<SelectValue placeholder={t("selectCountry")} />
|
|
104
107
|
</SelectTrigger>
|
|
105
108
|
<SelectContent>
|
|
106
109
|
{countries.map((c) => (
|
|
@@ -117,7 +120,7 @@ export function ShippingEstimator({
|
|
|
117
120
|
<MapPin className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
118
121
|
<Input
|
|
119
122
|
type="text"
|
|
120
|
-
placeholder="
|
|
123
|
+
placeholder={t("postalCode")}
|
|
121
124
|
value={postalCode}
|
|
122
125
|
onChange={(e) => setPostalCode(e.target.value)}
|
|
123
126
|
onKeyDown={(e) => e.key === "Enter" && handleEstimate()}
|
|
@@ -129,7 +132,7 @@ export function ShippingEstimator({
|
|
|
129
132
|
onClick={handleEstimate}
|
|
130
133
|
disabled={!postalCode.trim() || isLoading}
|
|
131
134
|
>
|
|
132
|
-
{isLoading ? "
|
|
135
|
+
{isLoading ? t("calculating") : t("calculate")}
|
|
133
136
|
</Button>
|
|
134
137
|
</div>
|
|
135
138
|
|
|
@@ -140,7 +143,7 @@ export function ShippingEstimator({
|
|
|
140
143
|
{options.length > 0 && (
|
|
141
144
|
<div className="space-y-2">
|
|
142
145
|
<p className="text-sm font-medium text-foreground">
|
|
143
|
-
|
|
146
|
+
{t("availableOptions")}
|
|
144
147
|
</p>
|
|
145
148
|
<div className="space-y-2">
|
|
146
149
|
{options.map((option) => (
|
|
@@ -160,7 +163,7 @@ export function ShippingEstimator({
|
|
|
160
163
|
{option.name}
|
|
161
164
|
</p>
|
|
162
165
|
<p className="text-xs text-muted-foreground">
|
|
163
|
-
|
|
166
|
+
{t("estimatedDelivery")} {option.estimatedDays}
|
|
164
167
|
</p>
|
|
165
168
|
</div>
|
|
166
169
|
<p className="text-sm font-semibold text-foreground">
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { CreditCard, Building2, Wallet, Banknote, Check, Smartphone } from "lucide-react";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Payment method type enum (matches GraphQL PaymentMethodType)
|
|
@@ -65,30 +66,18 @@ function getPaymentTypeIcon(type: PaymentMethodType) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
|
-
*
|
|
69
|
+
* Payment type to translation key mapping
|
|
69
70
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
case "APPLE_PAY":
|
|
81
|
-
return "Apple Pay";
|
|
82
|
-
case "GOOGLE_PAY":
|
|
83
|
-
return "Google Pay";
|
|
84
|
-
case "PAYPAL":
|
|
85
|
-
return "PayPal";
|
|
86
|
-
case "OTHER":
|
|
87
|
-
return "Inna metoda płatności";
|
|
88
|
-
default:
|
|
89
|
-
return type;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
71
|
+
const PAYMENT_TYPE_KEYS: Record<string, string> = {
|
|
72
|
+
CARD: "creditCard",
|
|
73
|
+
BLIK: "blik",
|
|
74
|
+
BANK_TRANSFER: "bankTransfer",
|
|
75
|
+
CASH_ON_DELIVERY: "cashOnDelivery",
|
|
76
|
+
APPLE_PAY: "applePay",
|
|
77
|
+
GOOGLE_PAY: "googlePay",
|
|
78
|
+
PAYPAL: "paypal",
|
|
79
|
+
OTHER: "other",
|
|
80
|
+
};
|
|
92
81
|
|
|
93
82
|
/**
|
|
94
83
|
* PaymentMethodCard - Selectable payment method card
|
|
@@ -108,6 +97,7 @@ export function PaymentMethodCard({
|
|
|
108
97
|
disabled = false,
|
|
109
98
|
className,
|
|
110
99
|
}: PaymentMethodCardProps) {
|
|
100
|
+
const t = useTranslations("paymentMethods");
|
|
111
101
|
const Icon = getPaymentTypeIcon(method.type);
|
|
112
102
|
|
|
113
103
|
return (
|
|
@@ -160,12 +150,12 @@ export function PaymentMethodCard({
|
|
|
160
150
|
</span>
|
|
161
151
|
{method.isDefault && (
|
|
162
152
|
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-primary/10 text-primary">
|
|
163
|
-
|
|
153
|
+
{t("default")}
|
|
164
154
|
</span>
|
|
165
155
|
)}
|
|
166
156
|
</div>
|
|
167
157
|
<p className="text-sm text-muted-foreground truncate">
|
|
168
|
-
{method.description ||
|
|
158
|
+
{method.description || t(PAYMENT_TYPE_KEYS[method.type] || "other")}
|
|
169
159
|
</p>
|
|
170
160
|
</div>
|
|
171
161
|
|