@doswiftly/cli 0.1.17 → 0.1.19
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/README.md +23 -323
- package/dist/commands/check.js +1 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +43 -20
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +4 -4
- package/dist/commands/sdk.js +5 -5
- package/dist/commands/sdk.js.map +1 -1
- package/dist/commands/template.js +4 -4
- package/dist/commands/template.js.map +1 -1
- package/dist/commands/types.js +5 -5
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/verify.js +2 -2
- package/dist/commands/verify.js.map +1 -1
- package/dist/lib/package-manager.d.ts +1 -1
- package/dist/lib/package-manager.js +1 -1
- package/package.json +1 -1
- package/templates/storefront-minimal/wrangler.toml +4 -0
- package/templates/storefront-nextjs/README.md +16 -12
- package/templates/storefront-nextjs/app/account/orders/page.tsx +2 -2
- package/templates/storefront-nextjs/app/account/page.tsx +2 -2
- package/templates/storefront-nextjs/app/auth/login/page.tsx +1 -1
- package/templates/storefront-nextjs/app/auth/register/page.tsx +1 -1
- package/templates/storefront-nextjs/app/cart/page.tsx +1 -1
- package/templates/storefront-nextjs/app/categories/[slug]/page.tsx +2 -2
- package/templates/storefront-nextjs/app/categories/page.tsx +1 -1
- package/templates/storefront-nextjs/app/collections/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs/app/collections/page.tsx +1 -1
- package/templates/storefront-nextjs/app/page.tsx +1 -1
- package/templates/storefront-nextjs/app/products/[slug]/page.tsx +1 -1
- package/templates/storefront-nextjs/app/products/page.tsx +2 -2
- package/templates/storefront-nextjs/app/search/page.tsx +1 -1
- package/templates/storefront-nextjs/components/auth/auth-guard.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/add-to-cart-button.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/cart-icon.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs/components/commerce/product-filters.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/product-price.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/search-input.tsx +1 -1
- package/templates/storefront-nextjs/components/commerce/sort-select.tsx +1 -1
- package/templates/storefront-nextjs/components/providers.tsx +1 -1
- package/templates/storefront-nextjs/lib/currency.tsx +3 -3
- package/templates/storefront-nextjs/lib/format.ts +1 -1
- package/templates/storefront-nextjs/lib/graphql-queries.ts +3 -3
- package/templates/storefront-nextjs/package.dev.json +1 -1
- package/templates/storefront-nextjs/package.json +1 -1
- package/templates/storefront-nextjs/package.json.template +1 -1
- package/templates/storefront-nextjs/wrangler.toml +4 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/deploy.yml +47 -0
- package/templates/storefront-nextjs-shadcn/.github/workflows/preview.yml +47 -0
- package/templates/storefront-nextjs-shadcn/CLAUDE.md +148 -35
- package/templates/storefront-nextjs-shadcn/README.md +29 -162
- package/templates/storefront-nextjs-shadcn/app/account/addresses/page.tsx +98 -91
- package/templates/storefront-nextjs-shadcn/app/account/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/account/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/account/loyalty/page.tsx +53 -162
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/loading.tsx +60 -0
- package/templates/storefront-nextjs-shadcn/app/account/orders/[id]/page.tsx +36 -47
- package/templates/storefront-nextjs-shadcn/app/account/orders/page.tsx +46 -29
- package/templates/storefront-nextjs-shadcn/app/account/page.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/app/account/settings/page.tsx +108 -71
- package/templates/storefront-nextjs-shadcn/app/api/auth/clear-token/route.ts +2 -86
- package/templates/storefront-nextjs-shadcn/app/api/auth/set-token/route.ts +2 -124
- package/templates/storefront-nextjs-shadcn/app/auth/forgot-password/page.tsx +10 -5
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/loading.tsx +17 -0
- package/templates/storefront-nextjs-shadcn/app/blog/[slug]/page.tsx +43 -2
- package/templates/storefront-nextjs-shadcn/app/blog/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/brands/page.tsx +2 -1
- package/templates/storefront-nextjs-shadcn/app/cart/loading.tsx +26 -0
- package/templates/storefront-nextjs-shadcn/app/cart/page.tsx +6 -3
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/category-products-client.tsx +56 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/categories/[slug]/page.tsx +76 -59
- package/templates/storefront-nextjs-shadcn/app/categories/page.tsx +8 -4
- package/templates/storefront-nextjs-shadcn/app/checkout/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/loading.tsx +31 -0
- package/templates/storefront-nextjs-shadcn/app/checkout/page.tsx +116 -79
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/loading.tsx +19 -0
- package/templates/storefront-nextjs-shadcn/app/collections/[handle]/page.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/app/collections/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/collections/page.tsx +7 -4
- package/templates/storefront-nextjs-shadcn/app/global-error.tsx +117 -0
- package/templates/storefront-nextjs-shadcn/app/globals.css +8 -0
- package/templates/storefront-nextjs-shadcn/app/layout.tsx +46 -11
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/error.tsx +43 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/loading.tsx +29 -0
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/page.tsx +6 -6
- package/templates/storefront-nextjs-shadcn/app/products/[slug]/product-client.tsx +15 -61
- package/templates/storefront-nextjs-shadcn/app/products/loading.tsx +32 -0
- package/templates/storefront-nextjs-shadcn/app/products/products-client.tsx +405 -151
- package/templates/storefront-nextjs-shadcn/app/search/loading.tsx +18 -0
- package/templates/storefront-nextjs-shadcn/app/wishlist/page.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/codegen.ts +48 -31
- package/templates/storefront-nextjs-shadcn/components/account/customer-info.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +26 -24
- package/templates/storefront-nextjs-shadcn/components/account/order-summary.fragment.graphql +36 -0
- package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +9 -9
- package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +11 -37
- package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +37 -23
- package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +4 -3
- package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +8 -5
- package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/cart-line.fragment.graphql +53 -0
- package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +22 -7
- package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/common/price-display.tsx +35 -11
- package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +3 -3
- package/templates/storefront-nextjs-shadcn/components/filters/range-slider-filter.tsx +5 -5
- package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +2 -1
- package/templates/storefront-nextjs-shadcn/components/home/collection-card.fragment.graphql +21 -0
- package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +2 -12
- package/templates/storefront-nextjs-shadcn/components/home/index.ts +0 -1
- package/templates/storefront-nextjs-shadcn/components/hydrated.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +4 -4
- package/templates/storefront-nextjs-shadcn/components/layout/category-node.fragment.graphql +22 -0
- package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +33 -23
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-balance.tsx +2 -11
- package/templates/storefront-nextjs-shadcn/components/loyalty/points-history.tsx +8 -25
- package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +10 -19
- package/templates/storefront-nextjs-shadcn/components/loyalty/rewards-catalog.tsx +17 -41
- package/templates/storefront-nextjs-shadcn/components/loyalty/tier-progress.tsx +2 -29
- package/templates/storefront-nextjs-shadcn/components/order/index.ts +6 -1
- package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +69 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +84 -0
- package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +138 -0
- package/templates/storefront-nextjs-shadcn/components/product/index.ts +9 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-card.fragment.graphql +49 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +3 -31
- package/templates/storefront-nextjs-shadcn/components/product/product-detail.fragment.graphql +52 -0
- package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +176 -123
- package/templates/storefront-nextjs-shadcn/components/product/product-grid.tsx +3 -5
- package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-price.tsx +2 -2
- package/templates/storefront-nextjs-shadcn/components/product/product-reviews.tsx +5 -4
- package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +19 -7
- package/templates/storefront-nextjs-shadcn/components/product/product-variant-selector.tsx +8 -23
- package/templates/storefront-nextjs-shadcn/components/product/product-variant.fragment.graphql +51 -0
- package/templates/storefront-nextjs-shadcn/components/product/review-card.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +1 -7
- package/templates/storefront-nextjs-shadcn/components/product/savings-display.tsx +17 -2
- package/templates/storefront-nextjs-shadcn/components/product/similar-products.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/providers/index.ts +1 -1
- package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +30 -0
- package/templates/storefront-nextjs-shadcn/components/providers/theme-provider.tsx +1 -1
- package/templates/storefront-nextjs-shadcn/components/returns/index.ts +2 -2
- package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/search/search-results.tsx +3 -2
- package/templates/storefront-nextjs-shadcn/components/ui/form.tsx +174 -0
- package/templates/storefront-nextjs-shadcn/components/ui/index.ts +30 -2
- package/templates/storefront-nextjs-shadcn/components/ui/progress.tsx +40 -0
- package/templates/storefront-nextjs-shadcn/components/ui/sheet.tsx +107 -0
- package/templates/storefront-nextjs-shadcn/components/ui/slider.tsx +33 -0
- package/templates/storefront-nextjs-shadcn/components/ui/textarea.tsx +24 -0
- package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +3 -1
- package/templates/storefront-nextjs-shadcn/generated/graphql.ts +12779 -0
- package/templates/storefront-nextjs-shadcn/graphql/custom.example.graphql +159 -0
- package/templates/storefront-nextjs-shadcn/hooks/index.ts +2 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth-sync.ts +42 -0
- package/templates/storefront-nextjs-shadcn/hooks/use-auth.ts +17 -295
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +51 -19
- package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +13 -9
- package/templates/storefront-nextjs-shadcn/lib/auth/routes.ts +4 -17
- package/templates/storefront-nextjs-shadcn/lib/graphql/client.ts +22 -99
- package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +32 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/fragments.ts +34 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +687 -632
- package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +86 -0
- package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +131 -182
- package/templates/storefront-nextjs-shadcn/lib/graphql/types.ts +62 -0
- package/templates/storefront-nextjs-shadcn/lib/theme/theme-config.ts +0 -17
- package/templates/storefront-nextjs-shadcn/next-env.d.ts +6 -0
- package/templates/storefront-nextjs-shadcn/package.dev.json +1 -3
- package/templates/storefront-nextjs-shadcn/package.json +12 -13
- package/templates/storefront-nextjs-shadcn/package.json.template +6 -7
- package/templates/storefront-nextjs-shadcn/proxy.ts +3 -4
- package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +41 -39
- package/templates/storefront-nextjs-shadcn/stores/checkout-store.ts +64 -75
- package/templates/storefront-nextjs-shadcn/stores/wishlist-store.ts +178 -177
- package/templates/storefront-nextjs-shadcn/tsconfig.json +23 -5
- package/templates/storefront-nextjs-shadcn/wrangler.toml +4 -0
- package/templates/storefront-nextjs-shadcn/CART_INTEGRATION.md +0 -282
- package/templates/storefront-nextjs-shadcn/GRAPHQL_DOCUMENT_NAMES.md +0 -190
- package/templates/storefront-nextjs-shadcn/GRAPHQL_ERROR_HANDLING.md +0 -263
- package/templates/storefront-nextjs-shadcn/GRAPHQL_FIXES_SUMMARY.md +0 -135
- package/templates/storefront-nextjs-shadcn/GRAPHQL_INTEGRATION_COMPLETE.md +0 -142
- package/templates/storefront-nextjs-shadcn/INTEGRATION_CHECKLIST.md +0 -448
- package/templates/storefront-nextjs-shadcn/PRODUCT_DETAIL_PAGE_IMPLEMENTATION.md +0 -307
- package/templates/storefront-nextjs-shadcn/THEME_CUSTOMIZATION.md +0 -245
- package/templates/storefront-nextjs-shadcn/components/providers/currency-provider.tsx +0 -103
- package/templates/storefront-nextjs-shadcn/graphql/collections.example.ts +0 -168
- package/templates/storefront-nextjs-shadcn/graphql/products.example.ts +0 -160
- package/templates/storefront-nextjs-shadcn/lib/auth/cookies.ts +0 -220
- package/templates/storefront-nextjs-shadcn/lib/config.ts +0 -46
- package/templates/storefront-nextjs-shadcn/lib/currency/IMPLEMENTATION_SUMMARY.md +0 -254
- package/templates/storefront-nextjs-shadcn/lib/currency/README.md +0 -464
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.test.ts +0 -328
- package/templates/storefront-nextjs-shadcn/lib/currency/cookie-manager.ts +0 -295
- package/templates/storefront-nextjs-shadcn/lib/currency/index.ts +0 -27
- package/templates/storefront-nextjs-shadcn/lib/format.ts +0 -226
- package/templates/storefront-nextjs-shadcn/lib/hooks.ts +0 -30
- package/templates/storefront-nextjs-shadcn/stores/auth-store.ts +0 -66
- package/templates/storefront-nextjs-shadcn/stores/currency-store.ts +0 -103
|
@@ -19,18 +19,10 @@ import { Input } from '@/components/ui/input';
|
|
|
19
19
|
import { Badge } from '@/components/ui/badge';
|
|
20
20
|
import { cn } from '@/lib/utils';
|
|
21
21
|
import { toast } from 'sonner';
|
|
22
|
-
|
|
23
|
-
interface ReferralStats {
|
|
24
|
-
totalReferred: number;
|
|
25
|
-
completedReferrals: number;
|
|
26
|
-
pendingReferrals: number;
|
|
27
|
-
totalPointsEarned: number;
|
|
28
|
-
}
|
|
22
|
+
import type { ReferralStats } from '@/lib/graphql/fragments';
|
|
29
23
|
|
|
30
24
|
interface ReferralSectionProps {
|
|
31
|
-
|
|
32
|
-
shareUrl: string;
|
|
33
|
-
stats: ReferralStats;
|
|
25
|
+
referralStats: ReferralStats;
|
|
34
26
|
pointsName?: string;
|
|
35
27
|
referralPoints?: number;
|
|
36
28
|
bonusPoints?: number;
|
|
@@ -38,14 +30,13 @@ interface ReferralSectionProps {
|
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
export function ReferralSection({
|
|
41
|
-
|
|
42
|
-
shareUrl,
|
|
43
|
-
stats,
|
|
33
|
+
referralStats,
|
|
44
34
|
pointsName = 'punktów',
|
|
45
35
|
referralPoints = 0,
|
|
46
36
|
bonusPoints = 0,
|
|
47
37
|
className,
|
|
48
38
|
}: ReferralSectionProps) {
|
|
39
|
+
const { referralCode, shareUrl } = referralStats;
|
|
49
40
|
const [copied, setCopied] = useState<'code' | 'url' | null>(null);
|
|
50
41
|
|
|
51
42
|
const formatNumber = (num: number) => {
|
|
@@ -199,7 +190,7 @@ export function ReferralSection({
|
|
|
199
190
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
200
191
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
201
192
|
<div className="text-2xl font-bold text-primary">
|
|
202
|
-
{formatNumber(
|
|
193
|
+
{formatNumber(referralStats.totalReferred)}
|
|
203
194
|
</div>
|
|
204
195
|
<div className="text-sm text-muted-foreground">Zaproszonych</div>
|
|
205
196
|
</div>
|
|
@@ -207,7 +198,7 @@ export function ReferralSection({
|
|
|
207
198
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
208
199
|
<div className="flex items-center justify-center gap-1">
|
|
209
200
|
<div className="text-2xl font-bold text-green-600">
|
|
210
|
-
{formatNumber(
|
|
201
|
+
{formatNumber(referralStats.completedReferrals)}
|
|
211
202
|
</div>
|
|
212
203
|
<Check className="h-5 w-5 text-green-600" />
|
|
213
204
|
</div>
|
|
@@ -217,7 +208,7 @@ export function ReferralSection({
|
|
|
217
208
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
218
209
|
<div className="flex items-center justify-center gap-1">
|
|
219
210
|
<div className="text-2xl font-bold text-amber-600">
|
|
220
|
-
{formatNumber(
|
|
211
|
+
{formatNumber(referralStats.pendingReferrals)}
|
|
221
212
|
</div>
|
|
222
213
|
<Clock className="h-5 w-5 text-amber-600" />
|
|
223
214
|
</div>
|
|
@@ -226,7 +217,7 @@ export function ReferralSection({
|
|
|
226
217
|
|
|
227
218
|
<div className="p-4 bg-muted/50 rounded-lg text-center">
|
|
228
219
|
<div className="text-2xl font-bold text-primary">
|
|
229
|
-
{formatNumber(
|
|
220
|
+
{formatNumber(referralStats.totalPointsEarned)}
|
|
230
221
|
</div>
|
|
231
222
|
<div className="text-sm text-muted-foreground">
|
|
232
223
|
Zdobytych {pointsName}
|
|
@@ -234,11 +225,11 @@ export function ReferralSection({
|
|
|
234
225
|
</div>
|
|
235
226
|
</div>
|
|
236
227
|
|
|
237
|
-
{
|
|
228
|
+
{referralStats.pendingReferrals > 0 && (
|
|
238
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">
|
|
239
230
|
<p className="text-sm text-amber-800 dark:text-amber-200">
|
|
240
231
|
<Clock className="h-4 w-4 inline mr-1" />
|
|
241
|
-
Masz {
|
|
232
|
+
Masz {referralStats.pendingReferrals} oczekujących poleceń. Punkty zostaną
|
|
242
233
|
przyznane po dokonaniu pierwszego zakupu przez poleconą osobę.
|
|
243
234
|
</p>
|
|
244
235
|
</div>
|
|
@@ -15,41 +15,17 @@ import { Badge } from '@/components/ui/badge';
|
|
|
15
15
|
import { cn } from '@/lib/utils';
|
|
16
16
|
import { TierBadge } from './tier-badge';
|
|
17
17
|
import { toast } from 'sonner';
|
|
18
|
-
|
|
19
|
-
type TierType = 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM' | 'DIAMOND';
|
|
20
|
-
|
|
21
|
-
interface Reward {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
slug: string;
|
|
25
|
-
type: string;
|
|
26
|
-
pointsCost: number;
|
|
27
|
-
discountPercent?: number;
|
|
28
|
-
discountAmount?: {
|
|
29
|
-
amount: string;
|
|
30
|
-
currencyCode: string;
|
|
31
|
-
};
|
|
32
|
-
description?: string;
|
|
33
|
-
image?: {
|
|
34
|
-
url: string;
|
|
35
|
-
altText?: string | null;
|
|
36
|
-
} | null;
|
|
37
|
-
available: boolean;
|
|
38
|
-
tierRequired?: TierType | null;
|
|
39
|
-
remainingRedemptions?: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface RedeemResult {
|
|
43
|
-
discountCode?: string | null;
|
|
44
|
-
productDiscountCode?: string | null;
|
|
45
|
-
giftCardCode?: string | null;
|
|
46
|
-
}
|
|
18
|
+
import type { LoyaltyReward, LoyaltyTier } from '@/lib/graphql/fragments';
|
|
47
19
|
|
|
48
20
|
interface RewardsCatalogProps {
|
|
49
|
-
rewards:
|
|
21
|
+
rewards: LoyaltyReward[];
|
|
50
22
|
currentPoints: number;
|
|
51
|
-
currentTier?:
|
|
52
|
-
onRedeem?: (rewardId: string) => Promise<
|
|
23
|
+
currentTier?: LoyaltyTier['type'] | null;
|
|
24
|
+
onRedeem?: (rewardId: string) => Promise<{
|
|
25
|
+
discountCode?: string | null;
|
|
26
|
+
productDiscountCode?: string | null;
|
|
27
|
+
giftCardCode?: string | null;
|
|
28
|
+
}>;
|
|
53
29
|
className?: string;
|
|
54
30
|
}
|
|
55
31
|
|
|
@@ -73,13 +49,13 @@ export function RewardsCatalog({
|
|
|
73
49
|
}).format(parseFloat(amount));
|
|
74
50
|
};
|
|
75
51
|
|
|
76
|
-
const tierOrder:
|
|
77
|
-
const canAccessTier = (
|
|
78
|
-
if (!
|
|
79
|
-
return tierOrder.indexOf(currentTier) >= tierOrder.indexOf(
|
|
52
|
+
const tierOrder: string[] = ['BRONZE', 'SILVER', 'GOLD', 'PLATINUM', 'DIAMOND'];
|
|
53
|
+
const canAccessTier = (requiredTierType?: string | null) => {
|
|
54
|
+
if (!requiredTierType || !currentTier) return true;
|
|
55
|
+
return tierOrder.indexOf(currentTier) >= tierOrder.indexOf(requiredTierType);
|
|
80
56
|
};
|
|
81
57
|
|
|
82
|
-
const handleRedeem = async (reward:
|
|
58
|
+
const handleRedeem = async (reward: LoyaltyReward) => {
|
|
83
59
|
if (!onRedeem) return;
|
|
84
60
|
|
|
85
61
|
setRedeemingId(reward.id);
|
|
@@ -125,7 +101,7 @@ export function RewardsCatalog({
|
|
|
125
101
|
<div className={cn('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4', className)}>
|
|
126
102
|
{rewards.map((reward) => {
|
|
127
103
|
const canAfford = currentPoints >= reward.pointsCost;
|
|
128
|
-
const hasTierAccess = canAccessTier(reward.tierRequired);
|
|
104
|
+
const hasTierAccess = canAccessTier(reward.tierRequired?.type);
|
|
129
105
|
const canRedeem = reward.available && canAfford && hasTierAccess;
|
|
130
106
|
|
|
131
107
|
return (
|
|
@@ -144,8 +120,8 @@ export function RewardsCatalog({
|
|
|
144
120
|
<CardContent className="p-4">
|
|
145
121
|
<div className="flex items-start justify-between gap-2 mb-2">
|
|
146
122
|
<h3 className="font-semibold">{reward.name}</h3>
|
|
147
|
-
{reward.tierRequired && (
|
|
148
|
-
<TierBadge tier={reward.tierRequired} size="sm" />
|
|
123
|
+
{reward.tierRequired?.type && (
|
|
124
|
+
<TierBadge tier={reward.tierRequired.type} size="sm" />
|
|
149
125
|
)}
|
|
150
126
|
</div>
|
|
151
127
|
|
|
@@ -183,7 +159,7 @@ export function RewardsCatalog({
|
|
|
183
159
|
{!hasTierAccess ? (
|
|
184
160
|
<Button variant="outline" className="w-full" disabled>
|
|
185
161
|
<Lock className="h-4 w-4 mr-2" />
|
|
186
|
-
Wymaga {reward.tierRequired}
|
|
162
|
+
Wymaga {reward.tierRequired?.name ?? reward.tierRequired?.type}
|
|
187
163
|
</Button>
|
|
188
164
|
) : !canAfford ? (
|
|
189
165
|
<Button variant="outline" className="w-full" disabled>
|
|
@@ -11,37 +11,10 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
11
11
|
import { Progress } from '@/components/ui/progress';
|
|
12
12
|
import { TierBadge } from './tier-badge';
|
|
13
13
|
import { cn } from '@/lib/utils';
|
|
14
|
-
|
|
15
|
-
type TierType = 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM' | 'DIAMOND';
|
|
16
|
-
|
|
17
|
-
interface CustomBenefit {
|
|
18
|
-
name: string;
|
|
19
|
-
description?: string | null;
|
|
20
|
-
icon?: string | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface Tier {
|
|
24
|
-
id: string;
|
|
25
|
-
name: string;
|
|
26
|
-
type?: TierType | null;
|
|
27
|
-
minPoints: number;
|
|
28
|
-
pointsMultiplier: number;
|
|
29
|
-
customBenefits?: CustomBenefit[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface TierProgressData {
|
|
33
|
-
currentTier: Tier;
|
|
34
|
-
nextTier?: Tier;
|
|
35
|
-
pointsToNextTier: number;
|
|
36
|
-
progressPercent: number;
|
|
37
|
-
spendToNextTier?: {
|
|
38
|
-
amount: string;
|
|
39
|
-
currencyCode: string;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
14
|
+
import type { TierProgress as TierProgressType } from '@/lib/graphql/fragments';
|
|
42
15
|
|
|
43
16
|
interface TierProgressProps {
|
|
44
|
-
progress:
|
|
17
|
+
progress: TierProgressType;
|
|
45
18
|
className?: string;
|
|
46
19
|
}
|
|
47
20
|
|
|
@@ -7,5 +7,10 @@
|
|
|
7
7
|
export * from "./order-tracking";
|
|
8
8
|
export * from "./shipment-card";
|
|
9
9
|
export * from "./tracking-timeline";
|
|
10
|
-
export
|
|
10
|
+
export {
|
|
11
|
+
TrackingStatus,
|
|
12
|
+
CompactTrackingStatus,
|
|
13
|
+
type TrackingStatusProps,
|
|
14
|
+
type CompactTrackingStatusProps,
|
|
15
|
+
} from "./tracking-status";
|
|
11
16
|
export * from "./delivery-estimate";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
|
+
import { CURRENCY_LOCALES } from "@doswiftly/storefront-sdk";
|
|
4
5
|
import { Tag, LogIn, TrendingDown } from "lucide-react";
|
|
5
6
|
import Link from "next/link";
|
|
6
7
|
|
|
@@ -67,7 +68,8 @@ export function B2BPriceDisplayComponent({
|
|
|
67
68
|
|
|
68
69
|
const formatPrice = (money: Money) => {
|
|
69
70
|
const amount = parseFloat(money.amount);
|
|
70
|
-
|
|
71
|
+
const locale = CURRENCY_LOCALES[money.currencyCode] || "en-US";
|
|
72
|
+
return new Intl.NumberFormat(locale, {
|
|
71
73
|
style: "currency",
|
|
72
74
|
currency: money.currencyCode,
|
|
73
75
|
}).format(amount);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
export interface ActivePill {
|
|
8
|
+
filterId: string;
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
displayValue: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface FilterActivePillsProps {
|
|
15
|
+
pills: ActivePill[];
|
|
16
|
+
onRemove: (filterId: string, value: string) => void;
|
|
17
|
+
onClearAll: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* FilterActivePills - Shows active filters as removable badges
|
|
22
|
+
*
|
|
23
|
+
* Pattern from Saleor Storefront:
|
|
24
|
+
* - Each pill shows "Label: Value" with X button
|
|
25
|
+
* - "Clear all" link at the end
|
|
26
|
+
* - Horizontally scrollable on mobile
|
|
27
|
+
*/
|
|
28
|
+
export function FilterActivePills({
|
|
29
|
+
pills,
|
|
30
|
+
onRemove,
|
|
31
|
+
onClearAll,
|
|
32
|
+
}: FilterActivePillsProps) {
|
|
33
|
+
if (pills.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className="scrollbar-hide flex items-center gap-2 overflow-x-auto py-1"
|
|
38
|
+
role="region"
|
|
39
|
+
aria-label="Active filters"
|
|
40
|
+
>
|
|
41
|
+
{pills.map((pill) => (
|
|
42
|
+
<Badge
|
|
43
|
+
key={`${pill.filterId}-${pill.value}`}
|
|
44
|
+
variant="secondary"
|
|
45
|
+
className="shrink-0 gap-1.5 pr-1.5"
|
|
46
|
+
>
|
|
47
|
+
<span className="text-xs text-muted-foreground">{pill.label}:</span>
|
|
48
|
+
{pill.displayValue}
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onClick={() => onRemove(pill.filterId, pill.value)}
|
|
52
|
+
className="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-background/50"
|
|
53
|
+
aria-label={`Remove ${pill.displayValue} filter`}
|
|
54
|
+
>
|
|
55
|
+
<X className="h-3 w-3" />
|
|
56
|
+
</button>
|
|
57
|
+
</Badge>
|
|
58
|
+
))}
|
|
59
|
+
<Button
|
|
60
|
+
variant="ghost"
|
|
61
|
+
size="sm"
|
|
62
|
+
className="h-6 shrink-0 px-2 text-xs text-muted-foreground"
|
|
63
|
+
onClick={onClearAll}
|
|
64
|
+
>
|
|
65
|
+
Clear all
|
|
66
|
+
</Button>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { SlidersHorizontal } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Badge } from "@/components/ui/badge";
|
|
7
|
+
import {
|
|
8
|
+
Sheet,
|
|
9
|
+
SheetTrigger,
|
|
10
|
+
SheetContent,
|
|
11
|
+
SheetHeader,
|
|
12
|
+
SheetTitle,
|
|
13
|
+
SheetDescription,
|
|
14
|
+
} from "@/components/ui/sheet";
|
|
15
|
+
import { ProductFilters, type ProductFiltersProps } from "./product-filters";
|
|
16
|
+
|
|
17
|
+
interface FilterMobileSheetProps extends ProductFiltersProps {
|
|
18
|
+
activeFilterCount: number;
|
|
19
|
+
totalProducts?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* FilterMobileSheet - Mobile filter panel (slides from left)
|
|
24
|
+
*
|
|
25
|
+
* Pattern from Saleor Storefront:
|
|
26
|
+
* - Trigger button visible only on mobile (lg:hidden)
|
|
27
|
+
* - Sheet slides from left with full filter panel
|
|
28
|
+
* - Footer shows "Clear all" when filters active
|
|
29
|
+
* - Auto-closes after clearing
|
|
30
|
+
*/
|
|
31
|
+
export function FilterMobileSheet({
|
|
32
|
+
activeFilterCount,
|
|
33
|
+
totalProducts,
|
|
34
|
+
...filterProps
|
|
35
|
+
}: FilterMobileSheetProps) {
|
|
36
|
+
const [open, setOpen] = useState(false);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
40
|
+
<SheetTrigger asChild>
|
|
41
|
+
<Button
|
|
42
|
+
variant="outline"
|
|
43
|
+
size="sm"
|
|
44
|
+
className="shrink-0 lg:hidden"
|
|
45
|
+
>
|
|
46
|
+
<SlidersHorizontal className="mr-2 h-4 w-4" />
|
|
47
|
+
Filters
|
|
48
|
+
{activeFilterCount > 0 && (
|
|
49
|
+
<Badge variant="secondary" className="ml-2 h-5 px-1.5 py-0 text-xs">
|
|
50
|
+
{activeFilterCount}
|
|
51
|
+
</Badge>
|
|
52
|
+
)}
|
|
53
|
+
</Button>
|
|
54
|
+
</SheetTrigger>
|
|
55
|
+
<SheetContent side="left" className="flex w-[300px] flex-col overflow-hidden">
|
|
56
|
+
<SheetHeader>
|
|
57
|
+
<SheetTitle>Filters</SheetTitle>
|
|
58
|
+
<SheetDescription>
|
|
59
|
+
{totalProducts !== undefined && (
|
|
60
|
+
<span>{totalProducts} products</span>
|
|
61
|
+
)}
|
|
62
|
+
</SheetDescription>
|
|
63
|
+
</SheetHeader>
|
|
64
|
+
<div className="flex-1 overflow-y-auto px-4 pb-4">
|
|
65
|
+
<ProductFilters {...filterProps} />
|
|
66
|
+
</div>
|
|
67
|
+
{activeFilterCount > 0 && filterProps.onClearAll && (
|
|
68
|
+
<div className="border-t border-border p-4">
|
|
69
|
+
<Button
|
|
70
|
+
variant="outline"
|
|
71
|
+
className="w-full"
|
|
72
|
+
onClick={() => {
|
|
73
|
+
filterProps.onClearAll?.();
|
|
74
|
+
setOpen(false);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
Clear all filters ({activeFilterCount})
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</SheetContent>
|
|
82
|
+
</Sheet>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
4
|
+
import { Slider } from "@/components/ui/slider";
|
|
5
|
+
import { Input } from "@/components/ui/input";
|
|
6
|
+
|
|
7
|
+
interface FilterPriceRangeProps {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
currentMin?: number;
|
|
11
|
+
currentMax?: number;
|
|
12
|
+
currency?: string;
|
|
13
|
+
onChange: (min: number | undefined, max: number | undefined) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FilterPriceRange - Dual-thumb range slider with number inputs
|
|
18
|
+
*
|
|
19
|
+
* - Slider commits on thumb release (onValueCommit) — natural debounce
|
|
20
|
+
* - Input fields commit on blur — no Apply button needed
|
|
21
|
+
* - Shows formatted currency values
|
|
22
|
+
*/
|
|
23
|
+
export function FilterPriceRange({
|
|
24
|
+
min,
|
|
25
|
+
max,
|
|
26
|
+
currentMin,
|
|
27
|
+
currentMax,
|
|
28
|
+
currency = "PLN",
|
|
29
|
+
onChange,
|
|
30
|
+
}: FilterPriceRangeProps) {
|
|
31
|
+
const [localMin, setLocalMin] = useState<number>(currentMin ?? min);
|
|
32
|
+
const [localMax, setLocalMax] = useState<number>(currentMax ?? max);
|
|
33
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
34
|
+
|
|
35
|
+
// Sync local state when props change (e.g., URL navigation)
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setLocalMin(currentMin ?? min);
|
|
38
|
+
setLocalMax(currentMax ?? max);
|
|
39
|
+
}, [currentMin, currentMax, min, max]);
|
|
40
|
+
|
|
41
|
+
const commitChange = useCallback(
|
|
42
|
+
(newMin: number, newMax: number) => {
|
|
43
|
+
const effectiveMin = newMin <= min ? undefined : newMin;
|
|
44
|
+
const effectiveMax = newMax >= max ? undefined : newMax;
|
|
45
|
+
onChange(effectiveMin, effectiveMax);
|
|
46
|
+
},
|
|
47
|
+
[min, max, onChange]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const handleSliderChange = useCallback(
|
|
51
|
+
(values: number[]) => {
|
|
52
|
+
setLocalMin(values[0]);
|
|
53
|
+
setLocalMax(values[1]);
|
|
54
|
+
},
|
|
55
|
+
[]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const handleSliderCommit = useCallback(
|
|
59
|
+
(values: number[]) => {
|
|
60
|
+
commitChange(values[0], values[1]);
|
|
61
|
+
},
|
|
62
|
+
[commitChange]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const handleInputBlur = useCallback(() => {
|
|
66
|
+
// Clamp values
|
|
67
|
+
const clampedMin = Math.max(min, Math.min(localMin, localMax));
|
|
68
|
+
const clampedMax = Math.min(max, Math.max(localMin, localMax));
|
|
69
|
+
setLocalMin(clampedMin);
|
|
70
|
+
setLocalMax(clampedMax);
|
|
71
|
+
|
|
72
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
73
|
+
debounceRef.current = setTimeout(() => {
|
|
74
|
+
commitChange(clampedMin, clampedMax);
|
|
75
|
+
}, 150);
|
|
76
|
+
}, [localMin, localMax, min, max, commitChange]);
|
|
77
|
+
|
|
78
|
+
// Cleanup timeout
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
return () => {
|
|
81
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const step = max - min > 1000 ? 10 : max - min > 100 ? 1 : 0.01;
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="space-y-4 px-1">
|
|
89
|
+
{/* Slider */}
|
|
90
|
+
<Slider
|
|
91
|
+
value={[localMin, localMax]}
|
|
92
|
+
min={min}
|
|
93
|
+
max={max}
|
|
94
|
+
step={step}
|
|
95
|
+
onValueChange={handleSliderChange}
|
|
96
|
+
onValueCommit={handleSliderCommit}
|
|
97
|
+
aria-label="Price range"
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
{/* Input fields */}
|
|
101
|
+
<div className="flex items-center gap-2">
|
|
102
|
+
<div className="relative flex-1">
|
|
103
|
+
<Input
|
|
104
|
+
type="number"
|
|
105
|
+
value={localMin}
|
|
106
|
+
onChange={(e) => setLocalMin(parseFloat(e.target.value) || min)}
|
|
107
|
+
onBlur={handleInputBlur}
|
|
108
|
+
min={min}
|
|
109
|
+
max={max}
|
|
110
|
+
step={step}
|
|
111
|
+
className="pr-12 text-sm"
|
|
112
|
+
aria-label="Minimum price"
|
|
113
|
+
/>
|
|
114
|
+
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
|
115
|
+
{currency}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
<span className="text-muted-foreground">—</span>
|
|
119
|
+
<div className="relative flex-1">
|
|
120
|
+
<Input
|
|
121
|
+
type="number"
|
|
122
|
+
value={localMax}
|
|
123
|
+
onChange={(e) => setLocalMax(parseFloat(e.target.value) || max)}
|
|
124
|
+
onBlur={handleInputBlur}
|
|
125
|
+
min={min}
|
|
126
|
+
max={max}
|
|
127
|
+
step={step}
|
|
128
|
+
className="pr-12 text-sm"
|
|
129
|
+
aria-label="Maximum price"
|
|
130
|
+
/>
|
|
131
|
+
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
|
132
|
+
{currency}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ProductCard } from './product-card';
|
|
2
|
-
export type { ProductCardProps
|
|
2
|
+
export type { ProductCardProps } from './product-card';
|
|
3
3
|
|
|
4
4
|
export { ProductGrid } from './product-grid';
|
|
5
5
|
export type { ProductGridProps } from './product-grid';
|
|
@@ -16,8 +16,15 @@ export type { ProductFiltersProps, FilterGroup, FilterOption } from './product-f
|
|
|
16
16
|
export { ProductSort } from './product-sort';
|
|
17
17
|
export type { ProductSortProps, SortOption } from './product-sort';
|
|
18
18
|
|
|
19
|
+
export { FilterActivePills } from './filter-active-pills';
|
|
20
|
+
export type { ActivePill } from './filter-active-pills';
|
|
21
|
+
|
|
22
|
+
export { FilterPriceRange } from './filter-price-range';
|
|
23
|
+
|
|
24
|
+
export { FilterMobileSheet } from './filter-mobile-sheet';
|
|
25
|
+
|
|
19
26
|
export { ProductVariantSelector } from './product-variant-selector';
|
|
20
|
-
export type { ProductVariantSelectorProps
|
|
27
|
+
export type { ProductVariantSelectorProps } from './product-variant-selector';
|
|
21
28
|
|
|
22
29
|
export { ProductGallery } from './product-gallery';
|
|
23
30
|
export type { ProductGalleryProps } from './product-gallery';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Fragment for ProductCard component.
|
|
2
|
+
#
|
|
3
|
+
# Defines the minimal product data needed for listing views:
|
|
4
|
+
# grids, search results, collection products, recommendations.
|
|
5
|
+
#
|
|
6
|
+
# Usage in components:
|
|
7
|
+
# import type { ProductCardFieldsFragment } from '@/generated/graphql';
|
|
8
|
+
# interface Props { product: ProductCardFieldsFragment }
|
|
9
|
+
#
|
|
10
|
+
# Usage in custom queries:
|
|
11
|
+
# query FeaturedProducts {
|
|
12
|
+
# products(first: 8) {
|
|
13
|
+
# edges { node { ...ProductCardFields } }
|
|
14
|
+
# }
|
|
15
|
+
# }
|
|
16
|
+
|
|
17
|
+
fragment ProductCardFields on Product {
|
|
18
|
+
id
|
|
19
|
+
handle
|
|
20
|
+
title
|
|
21
|
+
vendor
|
|
22
|
+
productType
|
|
23
|
+
availableForSale
|
|
24
|
+
averageRating
|
|
25
|
+
reviewCount
|
|
26
|
+
tags
|
|
27
|
+
featuredImage {
|
|
28
|
+
url
|
|
29
|
+
altText
|
|
30
|
+
width
|
|
31
|
+
height
|
|
32
|
+
}
|
|
33
|
+
priceRange {
|
|
34
|
+
minVariantPrice {
|
|
35
|
+
amount
|
|
36
|
+
currencyCode
|
|
37
|
+
}
|
|
38
|
+
maxVariantPrice {
|
|
39
|
+
amount
|
|
40
|
+
currencyCode
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
compareAtPriceRange {
|
|
44
|
+
minVariantPrice {
|
|
45
|
+
amount
|
|
46
|
+
currencyCode
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -6,38 +6,10 @@ import { ProductImage } from "./product-image";
|
|
|
6
6
|
import { ProductPrice } from "./product-price";
|
|
7
7
|
import { Badge } from "@/components/ui/badge";
|
|
8
8
|
import { cn } from "@/lib/utils";
|
|
9
|
-
|
|
10
|
-
export interface ProductCardProduct {
|
|
11
|
-
id: string;
|
|
12
|
-
handle: string;
|
|
13
|
-
title: string;
|
|
14
|
-
featuredImage?: {
|
|
15
|
-
url: string;
|
|
16
|
-
altText?: string | null;
|
|
17
|
-
} | null;
|
|
18
|
-
priceRange: {
|
|
19
|
-
minVariantPrice: {
|
|
20
|
-
amount: string;
|
|
21
|
-
currencyCode: string;
|
|
22
|
-
};
|
|
23
|
-
maxVariantPrice: {
|
|
24
|
-
amount: string;
|
|
25
|
-
currencyCode: string;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
compareAtPriceRange?: {
|
|
29
|
-
minVariantPrice: {
|
|
30
|
-
amount: string;
|
|
31
|
-
currencyCode: string;
|
|
32
|
-
};
|
|
33
|
-
} | null;
|
|
34
|
-
availableForSale: boolean;
|
|
35
|
-
tags?: string[];
|
|
36
|
-
type?: string;
|
|
37
|
-
}
|
|
9
|
+
import type { ProductCardFields } from "@/lib/graphql/fragments";
|
|
38
10
|
|
|
39
11
|
export interface ProductCardProps {
|
|
40
|
-
product:
|
|
12
|
+
product: ProductCardFields;
|
|
41
13
|
className?: string;
|
|
42
14
|
priority?: boolean;
|
|
43
15
|
showBadges?: boolean;
|
|
@@ -72,7 +44,7 @@ export function ProductCard({
|
|
|
72
44
|
);
|
|
73
45
|
|
|
74
46
|
const isOutOfStock = !product.availableForSale;
|
|
75
|
-
const isGiftCard = product.
|
|
47
|
+
const isGiftCard = product.productType === "GIFT_CARD";
|
|
76
48
|
|
|
77
49
|
return (
|
|
78
50
|
<Link
|