@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,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useCallback } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { cn } from "@/lib/utils";
5
6
  import { CreditCard, AlertCircle, Loader2 } from "lucide-react";
6
7
  import { PaymentMethodCard, PaymentMethod } from "./payment-method-card";
@@ -46,6 +47,7 @@ export function PaymentStep({
46
47
  onContinue,
47
48
  className,
48
49
  }: PaymentStepProps) {
50
+ const t = useTranslations("checkout.payment");
49
51
  const [localSelectedId, setLocalSelectedId] = useState<string | null>(
50
52
  selectedPaymentMethodId ||
51
53
  availablePaymentMethods.find((m) => m.isDefault)?.id ||
@@ -67,13 +69,13 @@ export function PaymentStep({
67
69
  <div className={cn("space-y-4", className)}>
68
70
  <div className="flex items-center gap-2">
69
71
  <CreditCard className="h-5 w-5 text-muted-foreground" />
70
- <h3 className="text-lg font-semibold text-foreground">Payment Method</h3>
72
+ <h3 className="text-lg font-semibold text-foreground">{t("title")}</h3>
71
73
  </div>
72
74
 
73
75
  <Alert variant="destructive">
74
76
  <AlertCircle className="h-4 w-4" />
75
77
  <AlertDescription>
76
- No payment methods are available. Please contact support.
78
+ {t("noMethods")}
77
79
  </AlertDescription>
78
80
  </Alert>
79
81
  </div>
@@ -89,12 +91,12 @@ export function PaymentStep({
89
91
  {/* Header */}
90
92
  <div className="flex items-center gap-2">
91
93
  <CreditCard className="h-5 w-5 text-muted-foreground" />
92
- <h3 className="text-lg font-semibold text-foreground">Payment Method</h3>
94
+ <h3 className="text-lg font-semibold text-foreground">{t("title")}</h3>
93
95
  </div>
94
96
 
95
97
  {/* Description */}
96
98
  <p className="text-sm text-muted-foreground">
97
- Choose how you'd like to pay for your order.
99
+ {t("chooseMethod")}
98
100
  </p>
99
101
 
100
102
  {/* Error Alert */}
@@ -124,7 +126,7 @@ export function PaymentStep({
124
126
  {isLoading && (
125
127
  <div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
126
128
  <Loader2 className="h-4 w-4 animate-spin" />
127
- <span>Updating payment method...</span>
129
+ <span>{t("updating")}</span>
128
130
  </div>
129
131
  )}
130
132
 
@@ -138,10 +140,10 @@ export function PaymentStep({
138
140
  {isLoading ? (
139
141
  <>
140
142
  <Loader2 className="h-4 w-4 animate-spin mr-2" />
141
- Processing...
143
+ {t("processing")}
142
144
  </>
143
145
  ) : (
144
- "Continue to Review"
146
+ t("continueToReview")
145
147
  )}
146
148
  </Button>
147
149
  )}
@@ -149,7 +151,7 @@ export function PaymentStep({
149
151
  {/* Selected Method Info */}
150
152
  {selectedMethod && (
151
153
  <p className="text-xs text-muted-foreground text-center">
152
- Selected: {selectedMethod.name}
154
+ {t("selected")} {selectedMethod.name}
153
155
  {selectedMethod.description && ` - ${selectedMethod.description}`}
154
156
  </p>
155
157
  )}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { cn } from "@/lib/utils";
4
+ import { useTranslations } from "next-intl";
4
5
  import { Receipt } from "lucide-react";
5
6
 
6
7
  /**
@@ -44,6 +45,7 @@ export function TaxBreakdown({
44
45
  showHeader = true,
45
46
  compact = false,
46
47
  }: TaxBreakdownProps) {
48
+ const t = useTranslations("checkout.tax");
47
49
  const formatPrice = (money: Money) => {
48
50
  const amount = parseFloat(money.amount);
49
51
  return new Intl.NumberFormat("pl-PL", {
@@ -65,7 +67,7 @@ export function TaxBreakdown({
65
67
  if (compact) {
66
68
  return (
67
69
  <div className={cn("flex items-center justify-between", className)}>
68
- <span className="text-sm text-muted-foreground">Tax</span>
70
+ <span className="text-sm text-muted-foreground">{t("title")}</span>
69
71
  <span className="text-sm font-medium text-foreground">
70
72
  {formatPrice(totalTax)}
71
73
  </span>
@@ -79,7 +81,7 @@ export function TaxBreakdown({
79
81
  {showHeader && (
80
82
  <div className="flex items-center gap-2">
81
83
  <Receipt className="h-4 w-4 text-muted-foreground" />
82
- <h4 className="text-sm font-medium text-foreground">Tax Breakdown</h4>
84
+ <h4 className="text-sm font-medium text-foreground">{t("breakdown")}</h4>
83
85
  </div>
84
86
  )}
85
87
 
@@ -104,7 +106,7 @@ export function TaxBreakdown({
104
106
  <>
105
107
  <div className="border-t border-border my-2" />
106
108
  <div className="flex items-center justify-between text-sm">
107
- <span className="font-medium text-foreground">Total Tax</span>
109
+ <span className="font-medium text-foreground">{t("total")}</span>
108
110
  <span className="font-semibold text-foreground">
109
111
  {formatPrice(totalTax)}
110
112
  </span>
@@ -116,7 +118,7 @@ export function TaxBreakdown({
116
118
  {/* Tax exemption note (for B2B) */}
117
119
  {parseFloat(totalTax.amount) === 0 && taxLines.length === 0 && (
118
120
  <p className="text-xs text-muted-foreground">
119
- Tax exempt order
121
+ {t("exempt")}
120
122
  </p>
121
123
  )}
122
124
  </div>
@@ -128,13 +130,14 @@ export function TaxBreakdown({
128
130
  */
129
131
  export function TaxSummaryLine({
130
132
  totalTax,
131
- label = "Tax",
133
+ label,
132
134
  className,
133
135
  }: {
134
136
  totalTax: Money;
135
137
  label?: string;
136
138
  className?: string;
137
139
  }) {
140
+ const t = useTranslations("checkout.tax");
138
141
  const formatPrice = (money: Money) => {
139
142
  const amount = parseFloat(money.amount);
140
143
  return new Intl.NumberFormat("pl-PL", {
@@ -145,7 +148,7 @@ export function TaxSummaryLine({
145
148
 
146
149
  return (
147
150
  <div className={cn("flex items-center justify-between text-sm", className)}>
148
- <span className="text-muted-foreground">{label}</span>
151
+ <span className="text-muted-foreground">{label ?? t("title")}</span>
149
152
  <span className="text-foreground">{formatPrice(totalTax)}</span>
150
153
  </div>
151
154
  );
@@ -4,6 +4,7 @@ import { useState, useRef, useEffect } from "react";
4
4
  import { ChevronDown, Check, Globe } from "lucide-react";
5
5
  import { useCurrencyStore } from "@doswiftly/storefront-sdk/react";
6
6
  import { useQueryClient } from "@tanstack/react-query";
7
+ import { useTranslations } from "next-intl";
7
8
 
8
9
  // ============================================================================
9
10
  // CURRENCY DATA
@@ -81,6 +82,7 @@ export function CurrencySelector({
81
82
  className = "",
82
83
  variant = "default",
83
84
  }: CurrencySelectorProps) {
85
+ const t = useTranslations("currency");
84
86
  const [isOpen, setIsOpen] = useState(false);
85
87
  const dropdownRef = useRef<HTMLDivElement>(null);
86
88
  const queryClient = useQueryClient();
@@ -141,7 +143,7 @@ export function CurrencySelector({
141
143
  <button
142
144
  onClick={() => setIsOpen(!isOpen)}
143
145
  className="flex items-center gap-1 rounded-md px-2 py-1 text-sm hover:bg-muted"
144
- aria-label="Select currency"
146
+ aria-label={t("selectCurrency")}
145
147
  >
146
148
  <span className="font-medium">{currentSymbol}</span>
147
149
  <ChevronDown className="h-3 w-3" />
@@ -191,10 +193,10 @@ export function CurrencySelector({
191
193
  <div className="absolute right-0 top-full z-50 mt-2 w-64 rounded-md border border-border bg-background shadow-lg">
192
194
  <div className="border-b border-border p-3">
193
195
  <p className="text-sm font-medium text-foreground">
194
- Select Currency
196
+ {t("selectTitle")}
195
197
  </p>
196
198
  <p className="text-xs text-muted-foreground">
197
- Prices will be displayed in your selected currency
199
+ {t("selectDescription")}
198
200
  </p>
199
201
  </div>
200
202
  <div className="max-h-64 overflow-y-auto p-1">
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { useSearchParams, usePathname } from "next/navigation";
4
- import Link from "next/link";
3
+ import { useSearchParams } from "next/navigation";
4
+ import { useTranslations } from "next-intl";
5
+ import { usePathname, Link } from "@/i18n/navigation";
5
6
 
6
7
  interface PaginationProps {
7
8
  hasMore: boolean;
@@ -21,6 +22,8 @@ export function Pagination({
21
22
  }: PaginationProps) {
22
23
  const pathname = usePathname();
23
24
  const searchParams = useSearchParams();
25
+ const t = useTranslations("product");
26
+ const tCommon = useTranslations("common");
24
27
 
25
28
  // Build URL preserving current filters
26
29
  const buildUrl = (cursor?: string | null) => {
@@ -44,10 +47,10 @@ export function Pagination({
44
47
  !currentCursor ? "pointer-events-none opacity-50" : ""
45
48
  }`}
46
49
  >
47
- First Page
50
+ {t("firstPage")}
48
51
  </Link>
49
52
  <span className="flex items-center px-4 text-gray-600">
50
- Showing {totalShown} products
53
+ {t("showingProducts", { count: totalShown })}
51
54
  </span>
52
55
  <Link
53
56
  href={hasMore && endCursor ? buildUrl(endCursor) : "#"}
@@ -55,7 +58,7 @@ export function Pagination({
55
58
  !hasMore ? "pointer-events-none opacity-50" : ""
56
59
  }`}
57
60
  >
58
- Next
61
+ {tCommon("next")}
59
62
  </Link>
60
63
  </div>
61
64
  );
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useMemo } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { VariantSelector } from "@/components/commerce/variant-selector";
5
6
  import { AddToCartButton } from "@/components/product/add-to-cart-button";
6
7
  import { formatPrice } from "@doswiftly/storefront-sdk";
@@ -48,6 +49,7 @@ export function ProductActions({
48
49
  productTitle,
49
50
  productImages = []
50
51
  }: ProductActionsProps) {
52
+ const t = useTranslations("product");
51
53
  // Store only variant ID - derive variant object from props (SSOT)
52
54
  const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
53
55
  () => variants[0]?.id ?? null
@@ -99,12 +101,12 @@ export function ProductActions({
99
101
  {inStock ? (
100
102
  <span className="inline-flex items-center gap-2 text-green-600">
101
103
  <span className="h-2 w-2 rounded-full bg-green-600" />
102
- In Stock
104
+ {t("inStock")}
103
105
  </span>
104
106
  ) : (
105
107
  <span className="inline-flex items-center gap-2 text-red-600">
106
108
  <span className="h-2 w-2 rounded-full bg-red-600" />
107
- Out of Stock
109
+ {t("outOfStock")}
108
110
  </span>
109
111
  )}
110
112
  </div>
@@ -112,7 +114,7 @@ export function ProductActions({
112
114
  {/* Quantity & Add to Cart */}
113
115
  <div className="space-y-4">
114
116
  <div className="flex items-center gap-4">
115
- <label className="text-sm font-medium text-gray-700">Quantity:</label>
117
+ <label className="text-sm font-medium text-gray-700">{t("quantity")}</label>
116
118
  <div className="flex items-center rounded-lg border border-gray-300">
117
119
  <button
118
120
  type="button"
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useRef } from "react";
4
- import { useRouter } from "next/navigation";
4
+ import { useTranslations } from "next-intl";
5
+ import { useRouter, Link } from "@/i18n/navigation";
5
6
  import { Search, X, Loader2 } from "lucide-react";
6
7
  import { useGraphQLQuery } from "@/lib/graphql/hooks";
7
8
  import { ProductSearchDocument } from "@/generated/graphql";
8
9
  import { useDebouncedValue } from "@doswiftly/storefront-sdk/react";
9
- import Link from "next/link";
10
10
 
11
11
  interface SearchInputProps {
12
12
  placeholder?: string;
@@ -20,9 +20,10 @@ interface SearchInputProps {
20
20
  * Pressing Enter navigates to full search page.
21
21
  */
22
22
  export function SearchInput({
23
- placeholder = "Search products...",
23
+ placeholder,
24
24
  className = "",
25
25
  }: SearchInputProps) {
26
+ const t = useTranslations("search");
26
27
  const router = useRouter();
27
28
  const [query, setQuery] = useState("");
28
29
  const [isOpen, setIsOpen] = useState(false);
@@ -93,7 +94,7 @@ export function SearchInput({
93
94
  setIsOpen(true);
94
95
  }}
95
96
  onFocus={() => query.length >= 2 && setIsOpen(true)}
96
- placeholder={placeholder}
97
+ placeholder={placeholder ?? t("placeholder")}
97
98
  className="h-10 w-full rounded-md border border-border bg-background pl-9 pr-9 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
98
99
  />
99
100
  {query && (
@@ -118,7 +119,7 @@ export function SearchInput({
118
119
  <div className="flex items-center justify-center p-4">
119
120
  <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
120
121
  <span className="ml-2 text-sm text-muted-foreground">
121
- Searching...
122
+ {t("searching")}
122
123
  </span>
123
124
  </div>
124
125
  ) : hasResults ? (
@@ -159,12 +160,12 @@ export function SearchInput({
159
160
  }}
160
161
  className="block border-t border-border p-3 text-center text-sm text-primary hover:bg-muted"
161
162
  >
162
- View all results for "{query}"
163
+ {t("viewAllResults", { query })}
163
164
  </Link>
164
165
  </div>
165
166
  ) : (
166
167
  <div className="p-4 text-center text-sm text-muted-foreground">
167
- No products found for "{debouncedQuery}"
168
+ {t("noProductsFound", { query: debouncedQuery })}
168
169
  </div>
169
170
  )}
170
171
  </div>
@@ -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,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
  import { Badge } from "@/components/ui/badge";
@@ -3,6 +3,7 @@
3
3
  import { useState } from "react";
4
4
  import { Facebook, Twitter, Linkedin, Link2, Check } from "lucide-react";
5
5
  import { Button } from "@/components/ui/button";
6
+ import { useTranslations } from "next-intl";
6
7
 
7
8
  export interface SocialShareProps {
8
9
  url: string;
@@ -20,6 +21,7 @@ export function SocialShare({
20
21
  description = "",
21
22
  className = "",
22
23
  }: SocialShareProps) {
24
+ const t = useTranslations("socialShare");
23
25
  const [copied, setCopied] = useState(false);
24
26
 
25
27
  const encodedUrl = encodeURIComponent(url);
@@ -58,14 +60,14 @@ export function SocialShare({
58
60
  return (
59
61
  <div className={`flex items-center gap-2 ${className}`}>
60
62
  <span className="text-sm font-medium text-muted-foreground mr-2">
61
- Share:
63
+ {t("share")}
62
64
  </span>
63
65
 
64
66
  <Button
65
67
  variant="outline"
66
68
  size="sm"
67
69
  onClick={() => handleShare("facebook")}
68
- aria-label="Share on Facebook"
70
+ aria-label={t("shareOnFacebook")}
69
71
  className="h-9 w-9 p-0"
70
72
  >
71
73
  <Facebook className="h-4 w-4" />
@@ -75,7 +77,7 @@ export function SocialShare({
75
77
  variant="outline"
76
78
  size="sm"
77
79
  onClick={() => handleShare("twitter")}
78
- aria-label="Share on Twitter"
80
+ aria-label={t("shareOnTwitter")}
79
81
  className="h-9 w-9 p-0"
80
82
  >
81
83
  <Twitter className="h-4 w-4" />
@@ -85,7 +87,7 @@ export function SocialShare({
85
87
  variant="outline"
86
88
  size="sm"
87
89
  onClick={() => handleShare("linkedin")}
88
- aria-label="Share on LinkedIn"
90
+ aria-label={t("shareOnLinkedIn")}
89
91
  className="h-9 w-9 p-0"
90
92
  >
91
93
  <Linkedin className="h-4 w-4" />
@@ -95,7 +97,7 @@ export function SocialShare({
95
97
  variant="outline"
96
98
  size="sm"
97
99
  onClick={handleCopyLink}
98
- aria-label="Copy link"
100
+ aria-label={t("copyLink")}
99
101
  className="h-9 w-9 p-0"
100
102
  >
101
103
  {copied ? (
@@ -124,6 +126,7 @@ export function SocialShareButton({
124
126
  description = "",
125
127
  className = "",
126
128
  }: SocialShareButtonProps) {
129
+ const t = useTranslations("socialShare");
127
130
  const [isOpen, setIsOpen] = useState(false);
128
131
 
129
132
  const handleNativeShare = async () => {
@@ -148,7 +151,7 @@ export function SocialShareButton({
148
151
  <div className={`relative ${className}`}>
149
152
  <Button variant="outline" size="sm" onClick={handleNativeShare}>
150
153
  <Link2 className="mr-2 h-4 w-4" />
151
- Share
154
+ {t("shareButton")}
152
155
  </Button>
153
156
 
154
157
  {isOpen && !navigator.share && (
@@ -16,6 +16,7 @@ import { cn } from "@/lib/utils";
16
16
  import { Tag, X, Percent, Truck, Gift } from "lucide-react";
17
17
  import { Button } from "@/components/ui/button";
18
18
  import { formatPrice } from "@doswiftly/storefront-sdk";
19
+ import { useTranslations } from "next-intl";
19
20
 
20
21
  export interface AppliedDiscount {
21
22
  id: string;
@@ -63,20 +64,25 @@ function getDiscountIcon(type: AppliedDiscount["type"]) {
63
64
  }
64
65
 
65
66
  /**
66
- * Get label for discount type
67
+ * Get translation key for discount type
67
68
  */
68
- function getDiscountTypeLabel(discount: AppliedDiscount): string {
69
+ function getDiscountTypeLabel(
70
+ discount: AppliedDiscount,
71
+ t: ReturnType<typeof useTranslations<"discountBreakdown">>
72
+ ): string {
69
73
  switch (discount.type) {
70
74
  case "PERCENTAGE":
71
- return discount.value ? `${discount.value}% zniżki` : "Rabat procentowy";
75
+ return discount.value
76
+ ? t("percentage", { value: discount.value })
77
+ : t("percentageDefault");
72
78
  case "FIXED_AMOUNT":
73
- return "Rabat kwotowy";
79
+ return t("fixedAmount");
74
80
  case "FREE_SHIPPING":
75
- return "Darmowa dostawa";
81
+ return t("freeShipping");
76
82
  case "BUY_X_GET_Y":
77
- return "Kup X, dostań Y";
83
+ return t("buyXGetY");
78
84
  default:
79
- return "Rabat";
85
+ return t("default");
80
86
  }
81
87
  }
82
88
 
@@ -95,6 +101,8 @@ export function DiscountBreakdown({
95
101
  compact = false,
96
102
  className,
97
103
  }: DiscountBreakdownProps) {
104
+ const t = useTranslations("discountBreakdown");
105
+
98
106
  if (discounts.length === 0) {
99
107
  return null;
100
108
  }
@@ -133,7 +141,7 @@ export function DiscountBreakdown({
133
141
  </span>
134
142
  {!compact && (
135
143
  <span className="text-xs text-muted-foreground">
136
- {getDiscountTypeLabel(discount)}
144
+ {getDiscountTypeLabel(discount, t)}
137
145
  </span>
138
146
  )}
139
147
  </div>
@@ -180,7 +188,7 @@ export function DiscountBreakdown({
180
188
  )}
181
189
  >
182
190
  <span className={cn("font-medium", compact && "text-sm")}>
183
- Łączne oszczędności
191
+ {t("totalSavings")}
184
192
  </span>
185
193
  <span
186
194
  className={cn(
@@ -211,18 +219,20 @@ export interface DiscountSummaryLineProps {
211
219
  }
212
220
 
213
221
  export function DiscountSummaryLine({
214
- label = "Rabat",
222
+ label,
215
223
  amount,
216
224
  code,
217
225
  onRemove,
218
226
  className,
219
227
  }: DiscountSummaryLineProps) {
228
+ const t = useTranslations("discountBreakdown");
229
+ const displayLabel = label ?? t("default");
220
230
  return (
221
231
  <div className={cn("flex items-center justify-between text-sm", className)}>
222
232
  <div className="flex items-center gap-1.5">
223
233
  <Tag className="h-3.5 w-3.5 text-green-600" />
224
234
  <span className="text-muted-foreground">
225
- {label}
235
+ {displayLabel}
226
236
  {code && <span className="ml-1 font-medium text-foreground">({code})</span>}
227
237
  </span>
228
238
  </div>
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import { useState, useCallback } from "react";
16
+ import { useTranslations } from "next-intl";
16
17
  import { cn } from "@/lib/utils";
17
18
  import { Tag, Loader2, Check, X, AlertCircle } from "lucide-react";
18
19
  import { Input } from "@/components/ui/input";
@@ -64,10 +65,12 @@ export function DiscountCodeInput({
64
65
  onValidate,
65
66
  appliedCodes = [],
66
67
  isApplying = false,
67
- placeholder = "Wpisz kod rabatowy",
68
+ placeholder,
68
69
  disabled = false,
69
70
  className,
70
71
  }: DiscountCodeInputProps) {
72
+ const t = useTranslations("checkout.discount");
73
+ const tc = useTranslations("common");
71
74
  const [code, setCode] = useState("");
72
75
  const [isValidating, setIsValidating] = useState(false);
73
76
  const [validationResult, setValidationResult] = useState<DiscountValidationResult | null>(null);
@@ -79,13 +82,13 @@ export function DiscountCodeInput({
79
82
  const trimmedCode = code.trim().toUpperCase();
80
83
 
81
84
  if (!trimmedCode) {
82
- setError("Wpisz kod rabatowy");
85
+ setError(t("enterCode"));
83
86
  return;
84
87
  }
85
88
 
86
89
  // Check if already applied
87
90
  if (appliedCodes.includes(trimmedCode)) {
88
- setError("Ten kod jest już zastosowany");
91
+ setError(t("alreadyApplied"));
89
92
  return;
90
93
  }
91
94
 
@@ -99,10 +102,10 @@ export function DiscountCodeInput({
99
102
  setValidationResult(result);
100
103
 
101
104
  if (!result.valid) {
102
- setError(result.errorMessage || "Kod jest nieprawidłowy");
105
+ setError(result.errorMessage || t("invalidCode"));
103
106
  }
104
107
  } catch (e: unknown) {
105
- setError(e instanceof Error ? e.message : "Nie udało się zweryfikować kodu");
108
+ setError(e instanceof Error ? e.message : t("validationFailed"));
106
109
  } finally {
107
110
  setIsValidating(false);
108
111
  }
@@ -148,18 +151,18 @@ export function DiscountCodeInput({
148
151
  if (!validationResult?.valid) return null;
149
152
 
150
153
  if (validationResult.estimatedSavings) {
151
- return `Oszczędzisz ${formatPrice(validationResult.estimatedSavings)}`;
154
+ return t("savings", { savings: formatPrice(validationResult.estimatedSavings) });
152
155
  }
153
156
 
154
157
  if (validationResult.discountType === "PERCENTAGE" && validationResult.discountValue) {
155
- return `${validationResult.discountValue}% zniżki`;
158
+ return t("percentOff", { value: validationResult.discountValue });
156
159
  }
157
160
 
158
161
  if (validationResult.discountType === "FREE_SHIPPING") {
159
- return "Darmowa dostawa";
162
+ return t("freeShippingDiscount");
160
163
  }
161
164
 
162
- return "Kod jest prawidłowy";
165
+ return t("codeValid");
163
166
  };
164
167
 
165
168
  return (
@@ -178,7 +181,7 @@ export function DiscountCodeInput({
178
181
  }}
179
182
  onBlur={handleValidate}
180
183
  onKeyDown={handleKeyDown}
181
- placeholder={placeholder}
184
+ placeholder={placeholder ?? t("enterCode")}
182
185
  disabled={disabled || isLoading}
183
186
  className={cn(
184
187
  "pl-9 pr-8 uppercase",
@@ -207,10 +210,10 @@ export function DiscountCodeInput({
207
210
  ) : validationResult?.valid ? (
208
211
  <>
209
212
  <Check className="mr-2 h-4 w-4" />
210
- Zastosuj
213
+ {tc("apply")}
211
214
  </>
212
215
  ) : (
213
- "Zastosuj"
216
+ tc("apply")
214
217
  )}
215
218
  </Button>
216
219
  </div>
@@ -236,7 +239,7 @@ export function DiscountCodeInput({
236
239
  {/* Minimum order info */}
237
240
  {validationResult?.minimumOrderAmount && (
238
241
  <p className="text-xs text-muted-foreground">
239
- Minimalna kwota zamówienia: {formatPrice(validationResult.minimumOrderAmount)}
242
+ {t("minOrderAmount")} {formatPrice(validationResult.minimumOrderAmount)}
240
243
  </p>
241
244
  )}
242
245
  </div>