@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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { useState } from 'react';
|
|
15
|
+
import { useTranslations } from 'next-intl';
|
|
15
16
|
import { Users, Copy, Check, Share2, Gift, UserPlus, Clock, Award } from 'lucide-react';
|
|
16
17
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
17
18
|
import { Button } from '@/components/ui/button';
|
|
@@ -36,6 +37,7 @@ export function ReferralSection({
|
|
|
36
37
|
bonusPoints = 0,
|
|
37
38
|
className,
|
|
38
39
|
}: ReferralSectionProps) {
|
|
40
|
+
const t = useTranslations("loyalty.referral");
|
|
39
41
|
const { referralCode, shareUrl } = referralStats;
|
|
40
42
|
const [copied, setCopied] = useState<'code' | 'url' | null>(null);
|
|
41
43
|
|
|
@@ -47,10 +49,10 @@ export function ReferralSection({
|
|
|
47
49
|
try {
|
|
48
50
|
await navigator.clipboard.writeText(text);
|
|
49
51
|
setCopied(type);
|
|
50
|
-
toast.success(type === 'code' ? '
|
|
52
|
+
toast.success(type === 'code' ? t('codeCopied') : t('linkCopied'));
|
|
51
53
|
setTimeout(() => setCopied(null), 2000);
|
|
52
54
|
} catch {
|
|
53
|
-
toast.error('
|
|
55
|
+
toast.error(t('copyFailed'));
|
|
54
56
|
}
|
|
55
57
|
};
|
|
56
58
|
|
|
@@ -58,14 +60,14 @@ export function ReferralSection({
|
|
|
58
60
|
if (navigator.share) {
|
|
59
61
|
try {
|
|
60
62
|
await navigator.share({
|
|
61
|
-
title: '
|
|
62
|
-
text:
|
|
63
|
+
title: t('shareFriends'),
|
|
64
|
+
text: t('shareText', { code: referralCode }),
|
|
63
65
|
url: shareUrl,
|
|
64
66
|
});
|
|
65
67
|
} catch (err) {
|
|
66
68
|
// User cancelled or error
|
|
67
69
|
if ((err as Error).name !== 'AbortError') {
|
|
68
|
-
toast.error('
|
|
70
|
+
toast.error(t('shareFailed'));
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
} else {
|
|
@@ -81,11 +83,10 @@ export function ReferralSection({
|
|
|
81
83
|
<CardHeader>
|
|
82
84
|
<CardTitle className="flex items-center gap-2">
|
|
83
85
|
<Users className="h-5 w-5" />
|
|
84
|
-
|
|
86
|
+
{t('yourCode')}
|
|
85
87
|
</CardTitle>
|
|
86
88
|
<CardDescription>
|
|
87
|
-
|
|
88
|
-
otrzymacie punkty!
|
|
89
|
+
{t('shareDescription')}
|
|
89
90
|
</CardDescription>
|
|
90
91
|
</CardHeader>
|
|
91
92
|
<CardContent className="space-y-4">
|
|
@@ -132,7 +133,7 @@ export function ReferralSection({
|
|
|
132
133
|
{/* Share Button */}
|
|
133
134
|
<Button onClick={handleShare} className="w-full">
|
|
134
135
|
<Share2 className="h-4 w-4 mr-2" />
|
|
135
|
-
|
|
136
|
+
{t('shareFriends')}
|
|
136
137
|
</Button>
|
|
137
138
|
</CardContent>
|
|
138
139
|
</Card>
|
|
@@ -140,7 +141,7 @@ export function ReferralSection({
|
|
|
140
141
|
{/* How it Works */}
|
|
141
142
|
<Card>
|
|
142
143
|
<CardHeader>
|
|
143
|
-
<CardTitle className="text-lg">
|
|
144
|
+
<CardTitle className="text-lg">{t('howItWorks')}</CardTitle>
|
|
144
145
|
</CardHeader>
|
|
145
146
|
<CardContent>
|
|
146
147
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@@ -148,9 +149,9 @@ export function ReferralSection({
|
|
|
148
149
|
<div className="p-3 bg-primary/10 rounded-full mb-3">
|
|
149
150
|
<Share2 className="h-6 w-6 text-primary" />
|
|
150
151
|
</div>
|
|
151
|
-
<h4 className="font-medium mb-1">
|
|
152
|
+
<h4 className="font-medium mb-1">{t('step1Title')}</h4>
|
|
152
153
|
<p className="text-sm text-muted-foreground">
|
|
153
|
-
|
|
154
|
+
{t('step1Desc')}
|
|
154
155
|
</p>
|
|
155
156
|
</div>
|
|
156
157
|
|
|
@@ -158,9 +159,9 @@ export function ReferralSection({
|
|
|
158
159
|
<div className="p-3 bg-primary/10 rounded-full mb-3">
|
|
159
160
|
<UserPlus className="h-6 w-6 text-primary" />
|
|
160
161
|
</div>
|
|
161
|
-
<h4 className="font-medium mb-1">
|
|
162
|
+
<h4 className="font-medium mb-1">{t('step2Title')}</h4>
|
|
162
163
|
<p className="text-sm text-muted-foreground">
|
|
163
|
-
|
|
164
|
+
{t('step2Desc')}
|
|
164
165
|
</p>
|
|
165
166
|
</div>
|
|
166
167
|
|
|
@@ -168,10 +169,9 @@ export function ReferralSection({
|
|
|
168
169
|
<div className="p-3 bg-primary/10 rounded-full mb-3">
|
|
169
170
|
<Gift className="h-6 w-6 text-primary" />
|
|
170
171
|
</div>
|
|
171
|
-
<h4 className="font-medium mb-1">
|
|
172
|
+
<h4 className="font-medium mb-1">{t('step3Title')}</h4>
|
|
172
173
|
<p className="text-sm text-muted-foreground">
|
|
173
|
-
|
|
174
|
-
{formatNumber(bonusPoints)} {pointsName}
|
|
174
|
+
{t('step3Desc', { points: formatNumber(referralPoints), name: pointsName })}
|
|
175
175
|
</p>
|
|
176
176
|
</div>
|
|
177
177
|
</div>
|
|
@@ -183,7 +183,7 @@ export function ReferralSection({
|
|
|
183
183
|
<CardHeader>
|
|
184
184
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
185
185
|
<Award className="h-5 w-5" />
|
|
186
|
-
|
|
186
|
+
{t('yourStats')}
|
|
187
187
|
</CardTitle>
|
|
188
188
|
</CardHeader>
|
|
189
189
|
<CardContent>
|
|
@@ -192,7 +192,7 @@ export function ReferralSection({
|
|
|
192
192
|
<div className="text-2xl font-bold text-primary">
|
|
193
193
|
{formatNumber(referralStats.totalReferred)}
|
|
194
194
|
</div>
|
|
195
|
-
<div className="text-sm text-muted-foreground">
|
|
195
|
+
<div className="text-sm text-muted-foreground">{t('invited')}</div>
|
|
196
196
|
</div>
|
|
197
197
|
|
|
198
198
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
@@ -202,7 +202,7 @@ export function ReferralSection({
|
|
|
202
202
|
</div>
|
|
203
203
|
<Check className="h-5 w-5 text-green-600" />
|
|
204
204
|
</div>
|
|
205
|
-
<div className="text-sm text-muted-foreground">
|
|
205
|
+
<div className="text-sm text-muted-foreground">{t('completed')}</div>
|
|
206
206
|
</div>
|
|
207
207
|
|
|
208
208
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
@@ -212,7 +212,7 @@ export function ReferralSection({
|
|
|
212
212
|
</div>
|
|
213
213
|
<Clock className="h-5 w-5 text-amber-600" />
|
|
214
214
|
</div>
|
|
215
|
-
<div className="text-sm text-muted-foreground">
|
|
215
|
+
<div className="text-sm text-muted-foreground">{t('pending')}</div>
|
|
216
216
|
</div>
|
|
217
217
|
|
|
218
218
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
@@ -220,7 +220,7 @@ export function ReferralSection({
|
|
|
220
220
|
{formatNumber(referralStats.totalPointsEarned)}
|
|
221
221
|
</div>
|
|
222
222
|
<div className="text-sm text-muted-foreground">
|
|
223
|
-
|
|
223
|
+
{t('earned', { name: pointsName })}
|
|
224
224
|
</div>
|
|
225
225
|
</div>
|
|
226
226
|
</div>
|
|
@@ -229,8 +229,7 @@ export function ReferralSection({
|
|
|
229
229
|
<div className="mt-4 p-3 bg-amber-50 dark:bg-amber-950/30 rounded-lg border border-amber-200 dark:border-amber-800">
|
|
230
230
|
<p className="text-sm text-amber-800 dark:text-amber-200">
|
|
231
231
|
<Clock className="h-4 w-4 inline mr-1" />
|
|
232
|
-
|
|
233
|
-
przyznane po dokonaniu pierwszego zakupu przez poleconą osobę.
|
|
232
|
+
{t('pendingReferrals', { count: referralStats.pendingReferrals })}
|
|
234
233
|
</p>
|
|
235
234
|
</div>
|
|
236
235
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { ShoppingCart, Check } from "lucide-react";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
6
7
|
import { Spinner } from "@/components/ui/spinner";
|
|
@@ -74,6 +75,7 @@ export function AddToCartButton({
|
|
|
74
75
|
size = "md",
|
|
75
76
|
fullWidth = false,
|
|
76
77
|
}: AddToCartButtonProps) {
|
|
78
|
+
const t = useTranslations("product");
|
|
77
79
|
const [isAdded, setIsAdded] = useState(false);
|
|
78
80
|
const { addToCart, isLoading } = useCartActions();
|
|
79
81
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
@@ -95,17 +97,7 @@ export function AddToCartButton({
|
|
|
95
97
|
if (onAddToCart) {
|
|
96
98
|
await onAddToCart(variantId, quantity);
|
|
97
99
|
} else {
|
|
98
|
-
await addToCart(
|
|
99
|
-
variantId,
|
|
100
|
-
productId,
|
|
101
|
-
productHandle,
|
|
102
|
-
productTitle,
|
|
103
|
-
variantTitle,
|
|
104
|
-
price,
|
|
105
|
-
image,
|
|
106
|
-
available,
|
|
107
|
-
quantity,
|
|
108
|
-
});
|
|
100
|
+
await addToCart(variantId, quantity);
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
setIsAdded(true);
|
|
@@ -143,17 +135,17 @@ export function AddToCartButton({
|
|
|
143
135
|
{isLoading ? (
|
|
144
136
|
<>
|
|
145
137
|
<Spinner size="sm" className="mr-2" />
|
|
146
|
-
|
|
138
|
+
{t("adding")}
|
|
147
139
|
</>
|
|
148
140
|
) : isAdded ? (
|
|
149
141
|
<>
|
|
150
142
|
<Check className="mr-2 h-5 w-5" />
|
|
151
|
-
|
|
143
|
+
{t("addedToCart")}
|
|
152
144
|
</>
|
|
153
145
|
) : (
|
|
154
146
|
<>
|
|
155
147
|
<ShoppingCart className="mr-2 h-5 w-5" />
|
|
156
|
-
{!available ? "
|
|
148
|
+
{!available ? t("outOfStock") : t("addToCart")}
|
|
157
149
|
</>
|
|
158
150
|
)}
|
|
159
151
|
</Button>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { CURRENCY_LOCALES } from "@doswiftly/storefront-sdk";
|
|
5
5
|
import { Tag, LogIn, TrendingDown } from "lucide-react";
|
|
6
|
-
import Link from "
|
|
6
|
+
import { Link } from "@/i18n/navigation";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Types matching GraphQL schema
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { X } from "lucide-react";
|
|
4
5
|
import { Badge } from "@/components/ui/badge";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
@@ -30,6 +31,8 @@ export function FilterActivePills({
|
|
|
30
31
|
onRemove,
|
|
31
32
|
onClearAll,
|
|
32
33
|
}: FilterActivePillsProps) {
|
|
34
|
+
const t = useTranslations("product");
|
|
35
|
+
|
|
33
36
|
if (pills.length === 0) return null;
|
|
34
37
|
|
|
35
38
|
return (
|
|
@@ -62,7 +65,7 @@ export function FilterActivePills({
|
|
|
62
65
|
className="h-6 shrink-0 px-2 text-xs text-muted-foreground"
|
|
63
66
|
onClick={onClearAll}
|
|
64
67
|
>
|
|
65
|
-
|
|
68
|
+
{t("clearAllFilters")}
|
|
66
69
|
</Button>
|
|
67
70
|
</div>
|
|
68
71
|
);
|
|
@@ -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 { SlidersHorizontal } from "lucide-react";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
6
7
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -34,6 +35,8 @@ export function FilterMobileSheet({
|
|
|
34
35
|
...filterProps
|
|
35
36
|
}: FilterMobileSheetProps) {
|
|
36
37
|
const [open, setOpen] = useState(false);
|
|
38
|
+
const t = useTranslations("product");
|
|
39
|
+
const tFilters = useTranslations("filters");
|
|
37
40
|
|
|
38
41
|
return (
|
|
39
42
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
@@ -44,7 +47,7 @@ export function FilterMobileSheet({
|
|
|
44
47
|
className="shrink-0 lg:hidden"
|
|
45
48
|
>
|
|
46
49
|
<SlidersHorizontal className="mr-2 h-4 w-4" />
|
|
47
|
-
|
|
50
|
+
{t("filters")}
|
|
48
51
|
{activeFilterCount > 0 && (
|
|
49
52
|
<Badge variant="secondary" className="ml-2 h-5 px-1.5 py-0 text-xs">
|
|
50
53
|
{activeFilterCount}
|
|
@@ -54,10 +57,10 @@ export function FilterMobileSheet({
|
|
|
54
57
|
</SheetTrigger>
|
|
55
58
|
<SheetContent side="left" className="flex w-[300px] flex-col overflow-hidden">
|
|
56
59
|
<SheetHeader>
|
|
57
|
-
<SheetTitle>
|
|
60
|
+
<SheetTitle>{t("filters")}</SheetTitle>
|
|
58
61
|
<SheetDescription>
|
|
59
62
|
{totalProducts !== undefined && (
|
|
60
|
-
<span>{totalProducts}
|
|
63
|
+
<span>{tFilters("productCount", { count: totalProducts })}</span>
|
|
61
64
|
)}
|
|
62
65
|
</SheetDescription>
|
|
63
66
|
</SheetHeader>
|
|
@@ -74,7 +77,7 @@ export function FilterMobileSheet({
|
|
|
74
77
|
setOpen(false);
|
|
75
78
|
}}
|
|
76
79
|
>
|
|
77
|
-
|
|
80
|
+
{t("clearAllFilters")} ({activeFilterCount})
|
|
78
81
|
</Button>
|
|
79
82
|
</div>
|
|
80
83
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Slider } from "@/components/ui/slider";
|
|
5
6
|
import { Input } from "@/components/ui/input";
|
|
6
7
|
|
|
@@ -28,6 +29,7 @@ export function FilterPriceRange({
|
|
|
28
29
|
currency = "PLN",
|
|
29
30
|
onChange,
|
|
30
31
|
}: FilterPriceRangeProps) {
|
|
32
|
+
const t = useTranslations("product");
|
|
31
33
|
const [localMin, setLocalMin] = useState<number>(currentMin ?? min);
|
|
32
34
|
const [localMax, setLocalMax] = useState<number>(currentMax ?? max);
|
|
33
35
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
@@ -94,7 +96,7 @@ export function FilterPriceRange({
|
|
|
94
96
|
step={step}
|
|
95
97
|
onValueChange={handleSliderChange}
|
|
96
98
|
onValueCommit={handleSliderCommit}
|
|
97
|
-
aria-label="
|
|
99
|
+
aria-label={t("priceRange")}
|
|
98
100
|
/>
|
|
99
101
|
|
|
100
102
|
{/* Input fields */}
|
|
@@ -109,7 +111,7 @@ export function FilterPriceRange({
|
|
|
109
111
|
max={max}
|
|
110
112
|
step={step}
|
|
111
113
|
className="pr-12 text-sm"
|
|
112
|
-
aria-label="
|
|
114
|
+
aria-label={t("minPrice")}
|
|
113
115
|
/>
|
|
114
116
|
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
|
115
117
|
{currency}
|
|
@@ -126,7 +128,7 @@ export function FilterPriceRange({
|
|
|
126
128
|
max={max}
|
|
127
129
|
step={step}
|
|
128
130
|
className="pr-12 text-sm"
|
|
129
|
-
aria-label="
|
|
131
|
+
aria-label={t("maxPrice")}
|
|
130
132
|
/>
|
|
131
133
|
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
|
132
134
|
{currency}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import Link from "
|
|
3
|
+
import { Link } from "@/i18n/navigation";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Gift } from "lucide-react";
|
|
5
6
|
import { ProductImage } from "./product-image";
|
|
6
7
|
import { ProductPrice } from "./product-price";
|
|
@@ -35,6 +36,7 @@ export function ProductCard({
|
|
|
35
36
|
priority = false,
|
|
36
37
|
showBadges = true,
|
|
37
38
|
}: ProductCardProps) {
|
|
39
|
+
const t = useTranslations("product");
|
|
38
40
|
const isOnSale = product.compareAtPriceRange &&
|
|
39
41
|
parseFloat(product.compareAtPriceRange.minVariantPrice.amount) >
|
|
40
42
|
parseFloat(product.priceRange.minVariantPrice.amount);
|
|
@@ -70,22 +72,22 @@ export function ProductCard({
|
|
|
70
72
|
{isGiftCard && (
|
|
71
73
|
<Badge variant="default" className="shadow-sm">
|
|
72
74
|
<Gift className="mr-1 h-3 w-3" />
|
|
73
|
-
|
|
75
|
+
{t("giftCardLabel")}
|
|
74
76
|
</Badge>
|
|
75
77
|
)}
|
|
76
78
|
{isNew && !isGiftCard && (
|
|
77
79
|
<Badge variant="default" className="shadow-sm">
|
|
78
|
-
|
|
80
|
+
{t("badgeNew")}
|
|
79
81
|
</Badge>
|
|
80
82
|
)}
|
|
81
83
|
{isOnSale && (
|
|
82
84
|
<Badge variant="destructive" className="shadow-sm">
|
|
83
|
-
|
|
85
|
+
{t("badgeSale")}
|
|
84
86
|
</Badge>
|
|
85
87
|
)}
|
|
86
88
|
{isOutOfStock && (
|
|
87
89
|
<Badge variant="secondary" className="shadow-sm">
|
|
88
|
-
|
|
90
|
+
{t("outOfStock")}
|
|
89
91
|
</Badge>
|
|
90
92
|
)}
|
|
91
93
|
</div>
|
|
@@ -110,7 +112,7 @@ export function ProductCard({
|
|
|
110
112
|
|
|
111
113
|
{isOutOfStock && (
|
|
112
114
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
113
|
-
|
|
115
|
+
{t("currentlyUnavailable")}
|
|
114
116
|
</p>
|
|
115
117
|
)}
|
|
116
118
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
5
6
|
import { Label } from "@/components/ui/label";
|
|
6
7
|
import {
|
|
@@ -55,6 +56,7 @@ export function ProductFilters({
|
|
|
55
56
|
onClearAll,
|
|
56
57
|
className,
|
|
57
58
|
}: ProductFiltersProps) {
|
|
59
|
+
const t = useTranslations("product");
|
|
58
60
|
const handleCheckboxChange = (filterId: string, value: string) => {
|
|
59
61
|
const current = selectedFilters[filterId] || [];
|
|
60
62
|
const updated = current.includes(value)
|
|
@@ -95,7 +97,7 @@ export function ProductFilters({
|
|
|
95
97
|
onClick={onClearAll}
|
|
96
98
|
className="w-full"
|
|
97
99
|
>
|
|
98
|
-
|
|
100
|
+
{t("clearAllFilters")}
|
|
99
101
|
</Button>
|
|
100
102
|
)}
|
|
101
103
|
|
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import Image from "next/image";
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
6
|
+
import type { ImageData } from "@doswiftly/storefront-sdk";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
url: string;
|
|
9
|
-
altText?: string | null;
|
|
10
|
-
width?: number | null;
|
|
11
|
-
height?: number | null;
|
|
12
|
-
}
|
|
8
|
+
export type ProductImageData = ImageData;
|
|
13
9
|
|
|
14
10
|
export interface ProductImageProps {
|
|
15
11
|
image?: ProductImageData | null;
|
|
@@ -115,7 +111,7 @@ export function ProductImage({
|
|
|
115
111
|
|
|
116
112
|
const imageAlt = alt || image.altText || "Product image";
|
|
117
113
|
|
|
118
|
-
//
|
|
114
|
+
// Global loaderFile (next.config.ts) handles imgproxy resize — no per-component loader needed
|
|
119
115
|
const commonProps = {
|
|
120
116
|
src: image.url,
|
|
121
117
|
alt: imageAlt,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
3
5
|
import {
|
|
4
6
|
Select,
|
|
5
7
|
SelectContent,
|
|
@@ -26,36 +28,47 @@ export interface ProductSortProps {
|
|
|
26
28
|
className?: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
const
|
|
30
|
-
{ value: "relevance",
|
|
31
|
-
{ value: "best-selling",
|
|
32
|
-
{ value: "price-low-to-high",
|
|
33
|
-
{ value: "price-high-to-low",
|
|
34
|
-
{ value: "title-asc",
|
|
35
|
-
{ value: "title-desc",
|
|
36
|
-
{ value: "created-desc",
|
|
37
|
-
{ value: "created-asc",
|
|
31
|
+
const SORT_OPTION_KEYS: { value: SortOption; labelKey: string }[] = [
|
|
32
|
+
{ value: "relevance", labelKey: "sortOptions.relevance" },
|
|
33
|
+
{ value: "best-selling", labelKey: "sortOptions.bestSelling" },
|
|
34
|
+
{ value: "price-low-to-high", labelKey: "sortOptions.priceLowToHigh" },
|
|
35
|
+
{ value: "price-high-to-low", labelKey: "sortOptions.priceHighToLow" },
|
|
36
|
+
{ value: "title-asc", labelKey: "sortOptions.titleAsc" },
|
|
37
|
+
{ value: "title-desc", labelKey: "sortOptions.titleDesc" },
|
|
38
|
+
{ value: "created-desc", labelKey: "sortOptions.newestFirst" },
|
|
39
|
+
{ value: "created-asc", labelKey: "sortOptions.oldestFirst" },
|
|
38
40
|
];
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* ProductSort - Sorting dropdown for product listings
|
|
42
|
-
*
|
|
44
|
+
*
|
|
43
45
|
* @example
|
|
44
46
|
* ```tsx
|
|
45
47
|
* const [sort, setSort] = useState<SortOption>("featured");
|
|
46
|
-
*
|
|
47
|
-
* <ProductSort
|
|
48
|
+
*
|
|
49
|
+
* <ProductSort
|
|
48
50
|
* value={sort}
|
|
49
51
|
* onChange={setSort}
|
|
50
52
|
* />
|
|
51
53
|
* ```
|
|
52
54
|
*/
|
|
53
55
|
export function ProductSort({ value, onChange, className }: ProductSortProps) {
|
|
56
|
+
const t = useTranslations("product");
|
|
57
|
+
|
|
58
|
+
const sortOptions = useMemo(
|
|
59
|
+
() =>
|
|
60
|
+
SORT_OPTION_KEYS.map((opt) => ({
|
|
61
|
+
value: opt.value,
|
|
62
|
+
label: t(opt.labelKey),
|
|
63
|
+
})),
|
|
64
|
+
[t]
|
|
65
|
+
);
|
|
66
|
+
|
|
54
67
|
return (
|
|
55
68
|
<div className={className}>
|
|
56
69
|
<Select value={value} onValueChange={(val) => onChange(val as SortOption)}>
|
|
57
70
|
<SelectTrigger>
|
|
58
|
-
<SelectValue placeholder="
|
|
71
|
+
<SelectValue placeholder={t("sortBy")} />
|
|
59
72
|
</SelectTrigger>
|
|
60
73
|
<SelectContent>
|
|
61
74
|
{sortOptions.map((option) => (
|