@doswiftly/cli 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/deploy.d.ts +20 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +219 -6
- package/dist/commands/deploy.js.map +1 -1
- package/package.json +4 -4
- package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-minimal/wrangler.toml +11 -0
- package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-nextjs/wrangler.toml +11 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +29 -5
- package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/addresses/page.tsx +19 -15
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/error.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +39 -34
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/page.tsx +9 -7
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/page.tsx +13 -9
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +1 -2
- package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/settings/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +14 -12
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +14 -10
- package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/category-products-client.tsx +4 -2
- package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/error.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +228 -184
- package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
- package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
- package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
- package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
- package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +11 -8
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
- package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx +14 -10
- package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +21 -25
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +6 -68
- package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
- package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +14 -12
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +28 -18
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +27 -22
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +48 -43
- package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +9 -7
- package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +18 -15
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
- package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +8 -7
- package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +21 -11
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +16 -13
- package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
- package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +12 -9
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
- package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
- package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +37 -12
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +5 -2
- package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +20 -12
- package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
- package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
- package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +23 -24
- package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +4 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +5 -3
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +8 -6
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +26 -13
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +25 -27
- package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
- package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +40 -7
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +56 -70
- package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
- package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
- package/templates/storefront-nextjs-shadcn/generated/graphql.ts +1159 -551
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +1 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +22 -249
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +3 -3
- package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
- package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
- package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +1 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +41 -8
- package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +20 -18
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +2 -1
- package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
- package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
- package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
- package/templates/storefront-nextjs-shadcn/package.json +3 -2
- package/templates/storefront-nextjs-shadcn/proxy.ts +115 -46
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -58
- package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
- /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/loading.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
- /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
|
@@ -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('
|
|
88
|
+
toast.error(t('selectRating'));
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
if (!content.trim()) {
|
|
91
|
-
toast.error('
|
|
93
|
+
toast.error(t('writeReview'));
|
|
92
94
|
return;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
if (!authorName.trim()) {
|
|
96
|
-
toast.error('
|
|
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('
|
|
126
|
+
toast.success(t('thankYou'));
|
|
125
127
|
} catch (error) {
|
|
126
|
-
toast.error('
|
|
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>
|
|
137
|
+
<CardTitle>{t('addReview')}</CardTitle>
|
|
136
138
|
<p className="text-sm text-muted-foreground">
|
|
137
|
-
|
|
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">
|
|
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
|
|
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">
|
|
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=
|
|
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">
|
|
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=
|
|
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>
|
|
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=
|
|
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>
|
|
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=
|
|
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">
|
|
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">
|
|
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=
|
|
306
|
+
placeholder={t('emailPlaceholder')}
|
|
309
307
|
className="mt-1.5"
|
|
310
308
|
/>
|
|
311
309
|
<p className="text-xs text-muted-foreground mt-1">
|
|
312
|
-
|
|
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
|
-
|
|
320
|
+
{t('submitting')}
|
|
323
321
|
</>
|
|
324
322
|
) : (
|
|
325
|
-
'
|
|
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 {
|
|
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
|
|
18
|
-
const
|
|
19
|
-
const
|
|
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={
|
|
23
|
-
<CheckoutProvider store={
|
|
24
|
-
<WishlistProvider store={
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
label: "
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
value: "
|
|
115
|
-
label: "
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
value: "
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|
-
|
|
255
|
+
{t("submitted")}
|
|
268
256
|
</h3>
|
|
269
257
|
<p className="text-muted-foreground mb-6">
|
|
270
|
-
|
|
271
|
-
receive an email with further instructions.
|
|
258
|
+
{t("submittedDesc")}
|
|
272
259
|
</p>
|
|
273
260
|
<Button variant="outline" onClick={onCancel}>
|
|
274
|
-
|
|
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
|
-
|
|
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">
|
|
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>
|
|
291
|
+
<AlertTitle>{t("noReturnableItems")}</AlertTitle>
|
|
305
292
|
<AlertDescription>
|
|
306
|
-
|
|
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
|
-
|
|
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>
|
|
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>
|
|
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="
|
|
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}
|
|
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">
|
|
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>
|
|
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="
|
|
477
|
+
<SelectValue placeholder={t("selectReason")} />
|
|
491
478
|
</SelectTrigger>
|
|
492
479
|
</FormControl>
|
|
493
480
|
<SelectContent>
|
|
494
|
-
{
|
|
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>
|
|
498
|
+
<FormLabel>{t("additionalDetails")}</FormLabel>
|
|
512
499
|
<FormControl>
|
|
513
500
|
<Textarea
|
|
514
|
-
placeholder="
|
|
501
|
+
placeholder={t("additionalDetailsPlaceholder")}
|
|
515
502
|
className="resize-none"
|
|
516
503
|
{...field}
|
|
517
504
|
/>
|
|
518
505
|
</FormControl>
|
|
519
506
|
<FormDescription>
|
|
520
|
-
|
|
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">
|
|
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
|
-
|
|
536
|
+
{t("refundOriginal")}
|
|
550
537
|
</span>
|
|
551
538
|
<p className="text-sm text-muted-foreground">
|
|
552
|
-
|
|
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">
|
|
548
|
+
<span className="font-medium">{t("storeCredit")}</span>
|
|
563
549
|
<p className="text-sm text-muted-foreground">
|
|
564
|
-
|
|
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>
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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={
|
|
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="
|
|
126
|
+
aria-label={t("clearSearch")}
|
|
124
127
|
>
|
|
125
128
|
<X className="h-5 w-5" />
|
|
126
129
|
</button>
|