@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.
Files changed (147) hide show
  1. package/dist/commands/deploy.d.ts +20 -0
  2. package/dist/commands/deploy.d.ts.map +1 -1
  3. package/dist/commands/deploy.js +219 -6
  4. package/dist/commands/deploy.js.map +1 -1
  5. package/package.json +4 -4
  6. package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
  7. package/templates/storefront-minimal/wrangler.toml +11 -0
  8. package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
  9. package/templates/storefront-nextjs/wrangler.toml +11 -0
  10. package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
  11. package/templates/storefront-nextjs-shadcn/CLAUDE.md +29 -5
  12. package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
  13. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/addresses/page.tsx +19 -15
  14. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/error.tsx +8 -5
  15. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +39 -34
  16. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/page.tsx +9 -7
  17. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
  18. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/page.tsx +13 -9
  19. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +1 -2
  20. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/settings/page.tsx +1 -1
  21. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +14 -12
  22. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
  23. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
  24. package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +1 -1
  25. package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +14 -10
  26. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/category-products-client.tsx +4 -2
  27. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +13 -8
  28. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/error.tsx +1 -1
  29. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +228 -184
  30. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
  31. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +5 -3
  32. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +13 -8
  33. package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
  34. package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
  35. package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
  36. package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
  37. package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
  38. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx +1 -1
  39. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +11 -8
  40. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +3 -1
  41. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
  42. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx +14 -10
  43. package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +21 -25
  44. package/templates/storefront-nextjs-shadcn/app/layout.tsx +6 -68
  45. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
  46. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
  47. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +14 -12
  48. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +28 -18
  49. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +10 -8
  50. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +27 -22
  51. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +48 -43
  52. package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
  53. package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
  54. package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
  55. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +7 -4
  56. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +1 -1
  57. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +7 -5
  58. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +9 -7
  59. package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
  60. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +18 -15
  61. package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
  62. package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
  63. package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
  64. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +5 -3
  65. package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
  66. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +5 -3
  67. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +8 -7
  68. package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
  69. package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
  70. package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
  71. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +21 -11
  72. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +16 -13
  73. package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
  74. package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
  75. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
  76. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +12 -9
  77. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +8 -5
  78. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +1 -1
  79. package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
  80. package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
  81. package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
  82. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +37 -12
  83. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +5 -2
  84. package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
  85. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +20 -12
  86. package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
  87. package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
  88. package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
  89. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +23 -24
  90. package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
  91. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +1 -1
  92. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +4 -1
  93. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +7 -4
  94. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +5 -3
  95. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +8 -6
  96. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
  98. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +26 -13
  99. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +25 -27
  100. package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
  101. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +40 -7
  102. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +56 -70
  103. package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
  104. package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
  105. package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
  106. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
  107. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +1 -1
  108. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
  109. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +1159 -551
  110. package/templates/storefront-nextjs-shadcn/hooks/index.ts +1 -0
  111. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +22 -249
  112. package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
  113. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +3 -3
  114. package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
  115. package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
  116. package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
  117. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +1 -0
  118. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +41 -8
  119. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +20 -18
  120. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +2 -1
  121. package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
  122. package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
  123. package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
  124. package/templates/storefront-nextjs-shadcn/package.json +3 -2
  125. package/templates/storefront-nextjs-shadcn/proxy.ts +115 -46
  126. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -58
  127. package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
  128. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loading.tsx +0 -0
  129. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/loading.tsx +0 -0
  130. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/loading.tsx +0 -0
  131. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/loading.tsx +0 -0
  132. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
  133. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
  134. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +0 -0
  135. /package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/loading.tsx +0 -0
  136. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/loading.tsx +0 -0
  137. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/page.tsx +0 -0
  138. /package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/loading.tsx +0 -0
  139. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/loading.tsx +0 -0
  140. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/loading.tsx +0 -0
  141. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/loading.tsx +0 -0
  142. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/loading.tsx +0 -0
  143. /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
  144. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/loading.tsx +0 -0
  145. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
  146. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
  147. /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 "next/navigation";
5
- import Link from "next/link";
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
- // Zod validation schema for registration form
18
- const registerSchema = z.object({
19
- firstName: z
20
- .string()
21
- .min(1, "First name is required")
22
- .min(2, "First name must be at least 2 characters")
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 || "Registration failed");
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("Registration successful! Please log in with your credentials.");
158
+ setError(t("registerSuccess"));
154
159
  setTimeout(() => router.push("/auth/login"), 2000);
155
160
  }
156
161
  } else {
157
- setError("Registration failed. Please try again.");
162
+ setError(t("registerFailed"));
158
163
  }
159
164
  } catch (err) {
160
- setError("An error occurred. Please try again.");
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
- First Name
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
- Last Name
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
- Email
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
- Password
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
- Must be at least 8 characters with uppercase, lowercase, and number
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 ? "Creating account..." : "Create Account"}
289
+ {registerMutation.isPending ? t("creatingAccount") : t("createAccount")}
285
290
  </Button>
286
291
 
287
292
  <div className="text-center text-sm text-muted-foreground">
288
- Already have an account?{" "}
293
+ {t("hasAccount")}{" "}
289
294
  <Link
290
295
  href="/auth/login"
291
296
  className="font-medium text-primary hover:underline"
292
297
  >
293
- Sign in
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 'next/link';
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 'next/link';
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,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
3
+ import { Link } from "@/i18n/navigation";
4
4
  import Image from "next/image";
5
5
  import { Card } from "@/components/ui/card";
6
6
 
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useEffect } from "react";
4
- import { useRouter } from "next/navigation";
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="Shopping cart"
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
- Shopping Cart
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="Close cart"
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 "next/navigation";
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 "next/link";
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
- Karta podarunkowa
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="Remove item"
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
- This item is currently out of stock
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">Subtotal</span>
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>Discount</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">Total</span>
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} {itemCount === 1 ? "item" : "items"} in cart
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
- Shipping and taxes calculated at checkout
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
- Proceed to Checkout
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">Continue Shopping</a>
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 || "Promo code applied successfully!");
44
+ setSuccess(result.message || t("promoApplied"));
42
45
  setCode("");
43
46
  } else {
44
- setError(result.message || "Invalid promo code");
47
+ setError(result.message || t("promoError"));
45
48
  }
46
49
  } catch (err) {
47
- setError("Failed to apply promo code");
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="Enter promo code"
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 ? "Applying..." : "Apply"}
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: "Poland" },
50
- { value: "DE", label: "Germany" },
51
- { value: "FR", label: "France" },
52
- { value: "GB", label: "United Kingdom" },
53
- { value: "US", label: "United States" },
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("Please enter a postal code");
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("No shipping options available for this location");
74
+ setError(t("noShippingOptions"));
72
75
  }
73
76
  } catch (err) {
74
- setError("Failed to calculate shipping");
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
- Estimate Shipping
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">Country</label>
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="Select country" />
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="Postal code"
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 ? "Calculating..." : "Calculate"}
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
- Available shipping options:
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
- Estimated delivery: {option.estimatedDays}
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
- * Get human-readable label for payment type
69
+ * Payment type to translation key mapping
69
70
  */
70
- function getPaymentTypeLabel(type: PaymentMethodType): string {
71
- switch (type) {
72
- case "CARD":
73
- return "Karta płatnicza";
74
- case "BLIK":
75
- return "BLIK";
76
- case "BANK_TRANSFER":
77
- return "Przelew bankowy";
78
- case "CASH_ON_DELIVERY":
79
- return "Płatność przy odbiorze";
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
- Default
153
+ {t("default")}
164
154
  </span>
165
155
  )}
166
156
  </div>
167
157
  <p className="text-sm text-muted-foreground truncate">
168
- {method.description || getPaymentTypeLabel(method.type)}
158
+ {method.description || t(PAYMENT_TYPE_KEYS[method.type] || "other")}
169
159
  </p>
170
160
  </div>
171
161