@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
@@ -15,6 +15,7 @@ import { Textarea } from '@/components/ui/textarea';
15
15
  import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
16
16
  import { cn } from '@/lib/utils';
17
17
  import { toast } from 'sonner';
18
+ import { useTranslations } from 'next-intl';
18
19
 
19
20
 
20
21
  interface ReviewFormProps {
@@ -45,6 +46,7 @@ export function ReviewForm({
45
46
  orderId,
46
47
  className,
47
48
  }: ReviewFormProps) {
49
+ const t = useTranslations('product.review');
48
50
  const [isSubmitting, setIsSubmitting] = useState(false);
49
51
  const [rating, setRating] = useState(0);
50
52
  const [hoverRating, setHoverRating] = useState(0);
@@ -83,17 +85,17 @@ export function ReviewForm({
83
85
  e.preventDefault();
84
86
 
85
87
  if (rating === 0) {
86
- toast.error('Wybierz ocenę');
88
+ toast.error(t('selectRating'));
87
89
  return;
88
90
  }
89
91
 
90
92
  if (!content.trim()) {
91
- toast.error('Napisz treść opinii');
93
+ toast.error(t('writeReview'));
92
94
  return;
93
95
  }
94
96
 
95
97
  if (!authorName.trim()) {
96
- toast.error('Podaj swoje imię');
98
+ toast.error(t('enterName'));
97
99
  return;
98
100
  }
99
101
 
@@ -121,9 +123,9 @@ export function ReviewForm({
121
123
  setAuthorName('');
122
124
  setAuthorEmail('');
123
125
 
124
- toast.success('Dziękujemy za opinię!');
126
+ toast.success(t('thankYou'));
125
127
  } catch (error) {
126
- toast.error('Nie udało się dodać opinii. Spróbuj ponownie.');
128
+ toast.error(t('failed'));
127
129
  } finally {
128
130
  setIsSubmitting(false);
129
131
  }
@@ -132,16 +134,16 @@ export function ReviewForm({
132
134
  return (
133
135
  <Card className={className}>
134
136
  <CardHeader>
135
- <CardTitle>Dodaj opinię</CardTitle>
137
+ <CardTitle>{t('addReview')}</CardTitle>
136
138
  <p className="text-sm text-muted-foreground">
137
- Podziel się swoją opinią o produkcie: {productTitle}
139
+ {t('shareOpinion', { productTitle })}
138
140
  </p>
139
141
  </CardHeader>
140
142
  <CardContent>
141
143
  <form onSubmit={handleSubmit} className="space-y-6">
142
144
  {/* Rating */}
143
145
  <div>
144
- <Label className="mb-2 block">Ocena *</Label>
146
+ <Label className="mb-2 block">{t('rating')} *</Label>
145
147
  <div className="flex items-center gap-1">
146
148
  {[1, 2, 3, 4, 5].map((star) => (
147
149
  <button
@@ -164,11 +166,7 @@ export function ReviewForm({
164
166
  ))}
165
167
  {rating > 0 && (
166
168
  <span className="ml-2 text-sm text-muted-foreground">
167
- {rating === 1 && 'Bardzo słabo'}
168
- {rating === 2 && 'Słabo'}
169
- {rating === 3 && 'Średnio'}
170
- {rating === 4 && 'Dobrze'}
171
- {rating === 5 && 'Doskonale'}
169
+ {t(`ratingLabels.${rating}` as 'ratingLabels.1' | 'ratingLabels.2' | 'ratingLabels.3' | 'ratingLabels.4' | 'ratingLabels.5')}
172
170
  </span>
173
171
  )}
174
172
  </div>
@@ -176,12 +174,12 @@ export function ReviewForm({
176
174
 
177
175
  {/* Title */}
178
176
  <div>
179
- <Label htmlFor="title">Tytuł opinii</Label>
177
+ <Label htmlFor="title">{t('title')}</Label>
180
178
  <Input
181
179
  id="title"
182
180
  value={title}
183
181
  onChange={(e) => setTitle(e.target.value)}
184
- placeholder="Krótkie podsumowanie..."
182
+ placeholder={t('titlePlaceholder')}
185
183
  maxLength={100}
186
184
  className="mt-1.5"
187
185
  />
@@ -189,12 +187,12 @@ export function ReviewForm({
189
187
 
190
188
  {/* Content */}
191
189
  <div>
192
- <Label htmlFor="content">Treść opinii *</Label>
190
+ <Label htmlFor="content">{t('content')} *</Label>
193
191
  <Textarea
194
192
  id="content"
195
193
  value={content}
196
194
  onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)}
197
- placeholder="Opisz swoje doświadczenia z produktem..."
195
+ placeholder={t('contentPlaceholder')}
198
196
  rows={4}
199
197
  className="mt-1.5"
200
198
  required
@@ -203,12 +201,12 @@ export function ReviewForm({
203
201
 
204
202
  {/* Pros */}
205
203
  <div>
206
- <Label>Zalety</Label>
204
+ <Label>{t('pros')}</Label>
207
205
  <div className="flex gap-2 mt-1.5">
208
206
  <Input
209
207
  value={newPro}
210
208
  onChange={(e) => setNewPro(e.target.value)}
211
- placeholder="Dodaj zaletę..."
209
+ placeholder={t('addPro')}
212
210
  maxLength={50}
213
211
  onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), handleAddPro())}
214
212
  />
@@ -245,12 +243,12 @@ export function ReviewForm({
245
243
 
246
244
  {/* Cons */}
247
245
  <div>
248
- <Label>Wady</Label>
246
+ <Label>{t('cons')}</Label>
249
247
  <div className="flex gap-2 mt-1.5">
250
248
  <Input
251
249
  value={newCon}
252
250
  onChange={(e) => setNewCon(e.target.value)}
253
- placeholder="Dodaj wadę..."
251
+ placeholder={t('addCon')}
254
252
  maxLength={50}
255
253
  onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), handleAddCon())}
256
254
  />
@@ -288,7 +286,7 @@ export function ReviewForm({
288
286
  {/* Author Info */}
289
287
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
290
288
  <div>
291
- <Label htmlFor="authorName">Twoje imię *</Label>
289
+ <Label htmlFor="authorName">{t('yourName')} *</Label>
292
290
  <Input
293
291
  id="authorName"
294
292
  value={authorName}
@@ -299,17 +297,17 @@ export function ReviewForm({
299
297
  />
300
298
  </div>
301
299
  <div>
302
- <Label htmlFor="authorEmail">Email (opcjonalnie)</Label>
300
+ <Label htmlFor="authorEmail">{t('emailOptional')}</Label>
303
301
  <Input
304
302
  id="authorEmail"
305
303
  type="email"
306
304
  value={authorEmail}
307
305
  onChange={(e) => setAuthorEmail(e.target.value)}
308
- placeholder="jan@email.com"
306
+ placeholder={t('emailPlaceholder')}
309
307
  className="mt-1.5"
310
308
  />
311
309
  <p className="text-xs text-muted-foreground mt-1">
312
- Nie będzie widoczny publicznie
310
+ {t('emailNotPublic')}
313
311
  </p>
314
312
  </div>
315
313
  </div>
@@ -319,10 +317,10 @@ export function ReviewForm({
319
317
  {isSubmitting ? (
320
318
  <>
321
319
  <Loader2 className="h-4 w-4 mr-2 animate-spin" />
322
- Wysyłanie...
320
+ {t('submitting')}
323
321
  </>
324
322
  ) : (
325
- 'Dodaj opinię'
323
+ t('addReview')
326
324
  )}
327
325
  </Button>
328
326
  </form>
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { useLanguageStore } from "@doswiftly/storefront-sdk/react";
5
+
6
+ interface LanguageSyncProviderProps {
7
+ locale: string;
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ /**
12
+ * Syncs the URL locale (from next-intl) with the SDK language store.
13
+ *
14
+ * Flow: URL (/en) -> next-intl -> LanguageSyncProvider -> SDK store -> X-Lang header -> backend
15
+ */
16
+ export function LanguageSyncProvider({
17
+ locale,
18
+ children,
19
+ }: LanguageSyncProviderProps) {
20
+ const setLanguage = useLanguageStore((s) => s.setLanguage);
21
+
22
+ useEffect(() => {
23
+ setLanguage(locale);
24
+ }, [locale, setLanguage]);
25
+
26
+ return <>{children}</>;
27
+ }
@@ -1,9 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { useRef, type ReactNode } from 'react';
4
- import { CartProvider, createCartStore } from '@/stores/cart-store';
4
+ import { useQueryClient } from '@tanstack/react-query';
5
+ import { createCartStore, CartProvider } from '@/stores/cart-store';
5
6
  import { CheckoutProvider, createCheckoutStore } from '@/stores/checkout-store';
6
7
  import { WishlistProvider, createWishlistStore } from '@/stores/wishlist-store';
8
+ import { useCartDI } from '@/hooks/use-cart-di';
9
+ import { queryKeys } from '@/lib/graphql/query-keys';
10
+ import { toast } from 'sonner';
7
11
 
8
12
  /**
9
13
  * Provides Context-based Zustand stores to the component tree.
@@ -11,17 +15,46 @@ import { WishlistProvider, createWishlistStore } from '@/stores/wishlist-store';
11
15
  * Creates store instances once via useRef (stable across re-renders)
12
16
  * and wraps children in Provider components.
13
17
  *
18
+ * Cart store uses SDK DI pattern — template provides CartActions implementation,
19
+ * SDK orchestrates state (init, mutations, error handling).
20
+ *
14
21
  * Must be placed inside StorefrontProvider (SDK stores) and QueryProvider.
15
22
  */
16
23
  export function StoresProvider({ children }: { children: ReactNode }) {
17
- const cartStore = useRef(createCartStore()).current;
18
- const checkoutStore = useRef(createCheckoutStore()).current;
19
- const wishlistStore = useRef(createWishlistStore()).current;
24
+ const queryClient = useQueryClient();
25
+ const cartActions = useCartDI();
26
+ const actionsRef = useRef(cartActions);
27
+ actionsRef.current = cartActions;
28
+
29
+ const cartStoreRef = useRef<ReturnType<typeof createCartStore> | null>(null);
30
+ if (!cartStoreRef.current) {
31
+ cartStoreRef.current = createCartStore({
32
+ getActions: () => actionsRef.current,
33
+ onMutationSuccess: (action) => {
34
+ queryClient.invalidateQueries({ queryKey: queryKeys.cart.all() });
35
+ if (action === 'addToCart') {
36
+ toast.success('Added to cart');
37
+ } else if (action === 'removeFromCart') {
38
+ toast.success('Removed from cart');
39
+ }
40
+ },
41
+ onMutationError: (_action, error) => {
42
+ const message = error instanceof Error ? error.message : 'Cart operation failed';
43
+ toast.error(message);
44
+ },
45
+ });
46
+ }
47
+
48
+ const checkoutStoreRef = useRef<ReturnType<typeof createCheckoutStore> | null>(null);
49
+ if (!checkoutStoreRef.current) checkoutStoreRef.current = createCheckoutStore();
50
+
51
+ const wishlistStoreRef = useRef<ReturnType<typeof createWishlistStore> | null>(null);
52
+ if (!wishlistStoreRef.current) wishlistStoreRef.current = createWishlistStore();
20
53
 
21
54
  return (
22
- <CartProvider store={cartStore}>
23
- <CheckoutProvider store={checkoutStore}>
24
- <WishlistProvider store={wishlistStore}>
55
+ <CartProvider store={cartStoreRef.current}>
56
+ <CheckoutProvider store={checkoutStoreRef.current}>
57
+ <WishlistProvider store={wishlistStoreRef.current}>
25
58
  {children}
26
59
  </WishlistProvider>
27
60
  </CheckoutProvider>
@@ -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 { useForm } from "react-hook-form";
5
6
  import { zodResolver } from "@hookform/resolvers/zod";
6
7
  import * as z from "zod";
@@ -104,46 +105,28 @@ export interface ReturnFormData {
104
105
  }[];
105
106
  }
106
107
 
107
- const defaultReasons: ReturnReasonOption[] = [
108
- {
109
- value: "DEFECTIVE",
110
- label: "Defective Product",
111
- description: "The product has a manufacturing defect",
112
- },
113
- {
114
- value: "NOT_AS_DESCRIBED",
115
- label: "Not as Described",
116
- description: "The product does not match the description",
117
- },
118
- {
119
- value: "WRONG_ITEM",
120
- label: "Wrong Item Received",
121
- description: "You received a different item",
122
- },
123
- {
124
- value: "CHANGED_MIND",
125
- label: "Changed My Mind",
126
- description: "You no longer want the product",
127
- },
128
- {
129
- value: "BETTER_PRICE",
130
- label: "Found Better Price",
131
- description: "Found the same product cheaper elsewhere",
132
- },
133
- {
134
- value: "DAMAGED_SHIPPING",
135
- label: "Damaged in Shipping",
136
- description: "The product was damaged during delivery",
137
- },
138
- { value: "OTHER", label: "Other", description: "Another reason" },
139
- ];
140
-
141
- const itemConditions = [
142
- { value: "NEW", label: "New (unused, in original packaging)" },
143
- { value: "OPENED", label: "Opened (packaging opened but unused)" },
144
- { value: "USED", label: "Used" },
145
- { value: "DAMAGED", label: "Damaged" },
146
- ];
108
+ function useDefaultReasons(): ReturnReasonOption[] {
109
+ const t = useTranslations("returns");
110
+ return [
111
+ { value: "DEFECTIVE", label: t("reasonDefective"), description: t("reasonDefectiveDesc") },
112
+ { value: "NOT_AS_DESCRIBED", label: t("reasonNotAsDescribed"), description: t("reasonNotAsDescribedDesc") },
113
+ { value: "WRONG_ITEM", label: t("reasonWrongItem"), description: t("reasonWrongItemDesc") },
114
+ { value: "CHANGED_MIND", label: t("reasonChangedMind"), description: t("reasonChangedMindDesc") },
115
+ { value: "BETTER_PRICE", label: t("reasonBetterPrice"), description: t("reasonBetterPriceDesc") },
116
+ { value: "DAMAGED_SHIPPING", label: t("reasonDamagedShipping"), description: t("reasonDamagedShippingDesc") },
117
+ { value: "OTHER", label: t("reasonOther"), description: t("reasonOtherDesc") },
118
+ ];
119
+ }
120
+
121
+ function useItemConditions() {
122
+ const t = useTranslations("returns");
123
+ return [
124
+ { value: "NEW", label: t("conditionNew") },
125
+ { value: "OPENED", label: t("conditionOpened") },
126
+ { value: "USED", label: t("conditionUsed") },
127
+ { value: "DAMAGED", label: t("conditionDamaged") },
128
+ ];
129
+ }
147
130
 
148
131
  const formSchema = z.object({
149
132
  reason: z.enum([
@@ -197,11 +180,16 @@ export function ReturnRequestForm({
197
180
  orderId,
198
181
  orderNumber,
199
182
  items,
200
- reasons = defaultReasons,
183
+ reasons,
201
184
  onSubmit,
202
185
  onCancel,
203
186
  className = "",
204
187
  }: ReturnRequestFormProps) {
188
+ const t = useTranslations("returns");
189
+ const tc = useTranslations("common");
190
+ const defaultReasons = useDefaultReasons();
191
+ const itemConditions = useItemConditions();
192
+ const resolvedReasons = reasons ?? defaultReasons;
205
193
  const [isSubmitting, setIsSubmitting] = useState(false);
206
194
  const [submitError, setSubmitError] = useState<string | null>(null);
207
195
  const [submitSuccess, setSubmitSuccess] = useState(false);
@@ -264,14 +252,13 @@ export function ReturnRequestForm({
264
252
  <div className="text-center py-8">
265
253
  <CheckCircle className="mx-auto h-16 w-16 text-green-600 mb-4" />
266
254
  <h3 className="text-xl font-semibold mb-2">
267
- Return Request Submitted
255
+ {t("submitted")}
268
256
  </h3>
269
257
  <p className="text-muted-foreground mb-6">
270
- Your return request has been submitted successfully. You will
271
- receive an email with further instructions.
258
+ {t("submittedDesc")}
272
259
  </p>
273
260
  <Button variant="outline" onClick={onCancel}>
274
- Back to Order
261
+ {t("backToOrder")}
275
262
  </Button>
276
263
  </div>
277
264
  </CardContent>
@@ -284,7 +271,7 @@ export function ReturnRequestForm({
284
271
  <CardHeader>
285
272
  <CardTitle className="flex items-center gap-2">
286
273
  <Package className="h-5 w-5" />
287
- Return Request for Order #{orderNumber}
274
+ {t("title", { orderNumber })}
288
275
  </CardTitle>
289
276
  </CardHeader>
290
277
 
@@ -296,14 +283,14 @@ export function ReturnRequestForm({
296
283
  >
297
284
  {/* Step 1: Select Items */}
298
285
  <div className="space-y-4">
299
- <h3 className="font-medium">1. Select Items to Return</h3>
286
+ <h3 className="font-medium">{t("selectItems")}</h3>
300
287
 
301
288
  {items.length === 0 ? (
302
289
  <Alert>
303
290
  <AlertCircle className="h-4 w-4" />
304
- <AlertTitle>No Returnable Items</AlertTitle>
291
+ <AlertTitle>{t("noReturnableItems")}</AlertTitle>
305
292
  <AlertDescription>
306
- There are no items eligible for return in this order.
293
+ {t("noReturnableItemsDesc")}
307
294
  </AlertDescription>
308
295
  </Alert>
309
296
  ) : (
@@ -361,7 +348,7 @@ export function ReturnRequestForm({
361
348
  </p>
362
349
  )}
363
350
  <p className="text-sm mt-1">
364
- Ordered: {item.quantity} | Returnable:{" "}
351
+ {t("ordered")} {item.quantity} | {t("returnable")}{" "}
365
352
  {item.returnableQuantity}
366
353
  </p>
367
354
  </div>
@@ -384,7 +371,7 @@ export function ReturnRequestForm({
384
371
  name={`items.${index}.quantity`}
385
372
  render={({ field }) => (
386
373
  <FormItem>
387
- <FormLabel>Quantity to Return</FormLabel>
374
+ <FormLabel>{t("quantityToReturn")}</FormLabel>
388
375
  <FormControl>
389
376
  <div className="flex items-center gap-2">
390
377
  <Button
@@ -432,14 +419,14 @@ export function ReturnRequestForm({
432
419
  name={`items.${index}.condition`}
433
420
  render={({ field }) => (
434
421
  <FormItem>
435
- <FormLabel>Item Condition</FormLabel>
422
+ <FormLabel>{t("itemCondition")}</FormLabel>
436
423
  <Select
437
424
  onValueChange={field.onChange}
438
425
  defaultValue={field.value}
439
426
  >
440
427
  <FormControl>
441
428
  <SelectTrigger>
442
- <SelectValue placeholder="Select condition" />
429
+ <SelectValue placeholder={t("selectCondition")} />
443
430
  </SelectTrigger>
444
431
  </FormControl>
445
432
  <SelectContent>
@@ -466,32 +453,32 @@ export function ReturnRequestForm({
466
453
 
467
454
  {selectedCount > 0 && (
468
455
  <Badge variant="secondary">
469
- {selectedCount} item{selectedCount > 1 ? "s" : ""} selected
456
+ {selectedCount} {t("itemsSelected")}
470
457
  </Badge>
471
458
  )}
472
459
  </div>
473
460
 
474
461
  {/* Step 2: Return Reason */}
475
462
  <div className="space-y-4">
476
- <h3 className="font-medium">2. Reason for Return</h3>
463
+ <h3 className="font-medium">{t("returnReason")}</h3>
477
464
 
478
465
  <FormField
479
466
  control={form.control}
480
467
  name="reason"
481
468
  render={({ field }) => (
482
469
  <FormItem>
483
- <FormLabel>Primary Reason</FormLabel>
470
+ <FormLabel>{t("primaryReason")}</FormLabel>
484
471
  <Select
485
472
  onValueChange={field.onChange}
486
473
  defaultValue={field.value}
487
474
  >
488
475
  <FormControl>
489
476
  <SelectTrigger>
490
- <SelectValue placeholder="Select a reason" />
477
+ <SelectValue placeholder={t("selectReason")} />
491
478
  </SelectTrigger>
492
479
  </FormControl>
493
480
  <SelectContent>
494
- {reasons.map((reason) => (
481
+ {resolvedReasons.map((reason) => (
495
482
  <SelectItem key={reason.value} value={reason.value}>
496
483
  {reason.label}
497
484
  </SelectItem>
@@ -508,16 +495,16 @@ export function ReturnRequestForm({
508
495
  name="customerNote"
509
496
  render={({ field }) => (
510
497
  <FormItem>
511
- <FormLabel>Additional Details (Optional)</FormLabel>
498
+ <FormLabel>{t("additionalDetails")}</FormLabel>
512
499
  <FormControl>
513
500
  <Textarea
514
- placeholder="Please provide any additional details about your return..."
501
+ placeholder={t("additionalDetailsPlaceholder")}
515
502
  className="resize-none"
516
503
  {...field}
517
504
  />
518
505
  </FormControl>
519
506
  <FormDescription>
520
- Help us process your return faster by providing details.
507
+ {t("additionalDetailsHint")}
521
508
  </FormDescription>
522
509
  <FormMessage />
523
510
  </FormItem>
@@ -527,7 +514,7 @@ export function ReturnRequestForm({
527
514
 
528
515
  {/* Step 3: Compensation Type */}
529
516
  <div className="space-y-4">
530
- <h3 className="font-medium">3. Compensation Preference</h3>
517
+ <h3 className="font-medium">{t("compensationPreference")}</h3>
531
518
 
532
519
  <FormField
533
520
  control={form.control}
@@ -546,11 +533,10 @@ export function ReturnRequestForm({
546
533
  </FormControl>
547
534
  <FormLabel className="font-normal cursor-pointer">
548
535
  <span className="font-medium">
549
- Refund to Original Payment
536
+ {t("refundOriginal")}
550
537
  </span>
551
538
  <p className="text-sm text-muted-foreground">
552
- Refund will be processed to your original payment
553
- method
539
+ {t("refundOriginalDesc")}
554
540
  </p>
555
541
  </FormLabel>
556
542
  </FormItem>
@@ -559,9 +545,9 @@ export function ReturnRequestForm({
559
545
  <RadioGroupItem value="STORE_CREDIT" />
560
546
  </FormControl>
561
547
  <FormLabel className="font-normal cursor-pointer">
562
- <span className="font-medium">Store Credit</span>
548
+ <span className="font-medium">{t("storeCredit")}</span>
563
549
  <p className="text-sm text-muted-foreground">
564
- Receive store credit for future purchases
550
+ {t("storeCreditDesc")}
565
551
  </p>
566
552
  </FormLabel>
567
553
  </FormItem>
@@ -577,7 +563,7 @@ export function ReturnRequestForm({
577
563
  {submitError && (
578
564
  <Alert variant="destructive">
579
565
  <AlertCircle className="h-4 w-4" />
580
- <AlertTitle>Error</AlertTitle>
566
+ <AlertTitle>{t("error")}</AlertTitle>
581
567
  <AlertDescription>{submitError}</AlertDescription>
582
568
  </Alert>
583
569
  )}
@@ -591,14 +577,14 @@ export function ReturnRequestForm({
591
577
  onClick={onCancel}
592
578
  disabled={isSubmitting}
593
579
  >
594
- Cancel
580
+ {tc("cancel")}
595
581
  </Button>
596
582
  )}
597
583
  <Button type="submit" disabled={isSubmitting || selectedCount === 0}>
598
584
  {isSubmitting && (
599
585
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
600
586
  )}
601
- Submit Return Request
587
+ {t("submitReturn")}
602
588
  </Button>
603
589
  </div>
604
590
  </form>
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useRef, useCallback } from "react";
4
- import { useRouter } from "next/navigation";
4
+ import { useTranslations } from "next-intl";
5
+ import { useRouter } from "@/i18n/navigation";
5
6
  import { Search, X } from "lucide-react";
6
7
  import { Input } from "@/components/ui/input";
7
8
  import { SearchSuggestions } from "./search-suggestions";
@@ -19,10 +20,12 @@ export interface SearchBarProps {
19
20
  */
20
21
  export function SearchBar({
21
22
  defaultValue = "",
22
- placeholder = "Search products...",
23
+ placeholder,
23
24
  className,
24
25
  showSuggestions = true,
25
26
  }: SearchBarProps) {
27
+ const t = useTranslations("search");
28
+ const resolvedPlaceholder = placeholder ?? t("placeholder");
26
29
  const router = useRouter();
27
30
  const [query, setQuery] = useState(defaultValue);
28
31
  const [isOpen, setIsOpen] = useState(false);
@@ -104,7 +107,7 @@ export function SearchBar({
104
107
  <Input
105
108
  ref={inputRef}
106
109
  type="search"
107
- placeholder={placeholder}
110
+ placeholder={resolvedPlaceholder}
108
111
  value={query}
109
112
  onChange={(e) => setQuery(e.target.value)}
110
113
  onFocus={() => setIsOpen(true)}
@@ -120,7 +123,7 @@ export function SearchBar({
120
123
  type="button"
121
124
  onClick={handleClear}
122
125
  className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
123
- aria-label="Clear search"
126
+ aria-label={t("clearSearch")}
124
127
  >
125
128
  <X className="h-5 w-5" />
126
129
  </button>