@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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Component, type ReactNode } from 'react';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
import { AlertTriangle, RefreshCcw } from 'lucide-react';
|
|
14
15
|
import { Button } from '@/components/ui/button';
|
|
15
16
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
@@ -32,6 +33,51 @@ interface ErrorBoundaryState {
|
|
|
32
33
|
error: Error | null;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/** Default fallback UI — functional component so it can use useTranslations */
|
|
37
|
+
function DefaultErrorFallback({
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
error,
|
|
41
|
+
onReset,
|
|
42
|
+
}: {
|
|
43
|
+
title?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
error: Error | null;
|
|
46
|
+
onReset: () => void;
|
|
47
|
+
}) {
|
|
48
|
+
const t = useTranslations('errors');
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex min-h-[400px] items-center justify-center p-4">
|
|
52
|
+
<Card className="w-full max-w-md">
|
|
53
|
+
<CardHeader className="text-center">
|
|
54
|
+
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10">
|
|
55
|
+
<AlertTriangle className="h-6 w-6 text-destructive" />
|
|
56
|
+
</div>
|
|
57
|
+
<CardTitle className="text-xl">
|
|
58
|
+
{title || t('somethingWentWrong')}
|
|
59
|
+
</CardTitle>
|
|
60
|
+
<CardDescription>
|
|
61
|
+
{description || t('unexpectedError')}
|
|
62
|
+
</CardDescription>
|
|
63
|
+
</CardHeader>
|
|
64
|
+
<CardContent className="flex flex-col gap-3">
|
|
65
|
+
{/* Show error message in development */}
|
|
66
|
+
{process.env.NODE_ENV === 'development' && error && (
|
|
67
|
+
<div className="rounded-md bg-muted p-3 text-xs">
|
|
68
|
+
<p className="font-medium text-destructive">{error.message}</p>
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
<Button onClick={onReset} className="w-full">
|
|
72
|
+
<RefreshCcw className="mr-2 h-4 w-4" />
|
|
73
|
+
{t('tryAgain')}
|
|
74
|
+
</Button>
|
|
75
|
+
</CardContent>
|
|
76
|
+
</Card>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
35
81
|
/**
|
|
36
82
|
* ErrorBoundary - Catches and displays errors gracefully
|
|
37
83
|
*
|
|
@@ -74,35 +120,14 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
74
120
|
return this.props.fallback;
|
|
75
121
|
}
|
|
76
122
|
|
|
77
|
-
// Default fallback UI
|
|
123
|
+
// Default fallback UI (functional component for useTranslations)
|
|
78
124
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<CardTitle className="text-xl">
|
|
86
|
-
{this.props.title || 'Coś poszło nie tak'}
|
|
87
|
-
</CardTitle>
|
|
88
|
-
<CardDescription>
|
|
89
|
-
{this.props.description || 'Wystąpił nieoczekiwany błąd. Spróbuj odświeżyć stronę.'}
|
|
90
|
-
</CardDescription>
|
|
91
|
-
</CardHeader>
|
|
92
|
-
<CardContent className="flex flex-col gap-3">
|
|
93
|
-
{/* Show error message in development */}
|
|
94
|
-
{process.env.NODE_ENV === 'development' && this.state.error && (
|
|
95
|
-
<div className="rounded-md bg-muted p-3 text-xs">
|
|
96
|
-
<p className="font-medium text-destructive">{this.state.error.message}</p>
|
|
97
|
-
</div>
|
|
98
|
-
)}
|
|
99
|
-
<Button onClick={this.handleReset} className="w-full">
|
|
100
|
-
<RefreshCcw className="mr-2 h-4 w-4" />
|
|
101
|
-
Spróbuj ponownie
|
|
102
|
-
</Button>
|
|
103
|
-
</CardContent>
|
|
104
|
-
</Card>
|
|
105
|
-
</div>
|
|
125
|
+
<DefaultErrorFallback
|
|
126
|
+
title={this.props.title}
|
|
127
|
+
description={this.props.description}
|
|
128
|
+
error={this.state.error}
|
|
129
|
+
onReset={this.handleReset}
|
|
130
|
+
/>
|
|
106
131
|
);
|
|
107
132
|
}
|
|
108
133
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Filter, X, ChevronDown, ChevronUp } from "lucide-react";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
6
7
|
import { Button } from "@/components/ui/button";
|
|
@@ -85,6 +86,7 @@ export function DynamicAttributeFilters({
|
|
|
85
86
|
className,
|
|
86
87
|
collapsible = false,
|
|
87
88
|
}: DynamicAttributeFiltersProps) {
|
|
89
|
+
const t = useTranslations("filters");
|
|
88
90
|
const [isExpanded, setIsExpanded] = useState(!collapsible);
|
|
89
91
|
|
|
90
92
|
// Calculate active filter count
|
|
@@ -100,7 +102,7 @@ export function DynamicAttributeFilters({
|
|
|
100
102
|
<div className={cn("p-4", className)}>
|
|
101
103
|
<div className="flex items-center gap-2">
|
|
102
104
|
<Spinner className="h-4 w-4" />
|
|
103
|
-
<span className="text-sm text-muted-foreground">
|
|
105
|
+
<span className="text-sm text-muted-foreground">{t("loading")}</span>
|
|
104
106
|
</div>
|
|
105
107
|
</div>
|
|
106
108
|
);
|
|
@@ -115,7 +117,7 @@ export function DynamicAttributeFilters({
|
|
|
115
117
|
<div className="flex items-center justify-between">
|
|
116
118
|
<div className="flex items-center gap-2">
|
|
117
119
|
<Filter className="h-4 w-4 text-muted-foreground" />
|
|
118
|
-
<h3 className="text-sm font-medium text-foreground">
|
|
120
|
+
<h3 className="text-sm font-medium text-foreground">{t("title")}</h3>
|
|
119
121
|
{activeCount > 0 && (
|
|
120
122
|
<span className="flex items-center justify-center h-5 w-5 rounded-full bg-primary text-primary-foreground text-xs">
|
|
121
123
|
{activeCount}
|
|
@@ -132,7 +134,7 @@ export function DynamicAttributeFilters({
|
|
|
132
134
|
className="h-7 text-xs text-muted-foreground hover:text-foreground"
|
|
133
135
|
>
|
|
134
136
|
<X className="h-3 w-3 mr-1" />
|
|
135
|
-
|
|
137
|
+
{t("clearAll")}
|
|
136
138
|
</Button>
|
|
137
139
|
)}
|
|
138
140
|
|
|
@@ -159,7 +161,7 @@ export function DynamicAttributeFilters({
|
|
|
159
161
|
{/* Price filter */}
|
|
160
162
|
{filters.priceRange && onPriceChange && (
|
|
161
163
|
<div className="space-y-3">
|
|
162
|
-
<h4 className="text-sm font-medium text-foreground">
|
|
164
|
+
<h4 className="text-sm font-medium text-foreground">{t("price")}</h4>
|
|
163
165
|
<div className="flex items-center gap-2">
|
|
164
166
|
<input
|
|
165
167
|
type="number"
|
|
@@ -204,7 +206,7 @@ export function DynamicAttributeFilters({
|
|
|
204
206
|
|
|
205
207
|
{/* Product count */}
|
|
206
208
|
<p className="text-xs text-muted-foreground pt-2 border-t">
|
|
207
|
-
{filters.totalProducts}
|
|
209
|
+
{t("productCount", { count: filters.totalProducts })}
|
|
208
210
|
</p>
|
|
209
211
|
</div>
|
|
210
212
|
);
|
|
@@ -11,6 +11,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
11
11
|
import { Badge } from "@/components/ui/badge";
|
|
12
12
|
import { Progress } from "@/components/ui/progress";
|
|
13
13
|
import { cn } from "@/lib/utils";
|
|
14
|
+
import { useTranslations } from "next-intl";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Gift card status
|
|
@@ -72,35 +73,35 @@ function getStatusConfig(status: GiftCardStatus) {
|
|
|
72
73
|
switch (status) {
|
|
73
74
|
case "ACTIVE":
|
|
74
75
|
return {
|
|
75
|
-
|
|
76
|
+
translationKey: "active",
|
|
76
77
|
variant: "default" as const,
|
|
77
78
|
color: "text-green-600",
|
|
78
79
|
bgColor: "bg-green-100 dark:bg-green-900/30",
|
|
79
80
|
};
|
|
80
81
|
case "USED":
|
|
81
82
|
return {
|
|
82
|
-
|
|
83
|
+
translationKey: "used",
|
|
83
84
|
variant: "secondary" as const,
|
|
84
85
|
color: "text-gray-600",
|
|
85
86
|
bgColor: "bg-gray-100 dark:bg-gray-900/30",
|
|
86
87
|
};
|
|
87
88
|
case "EXPIRED":
|
|
88
89
|
return {
|
|
89
|
-
|
|
90
|
+
translationKey: "expired",
|
|
90
91
|
variant: "destructive" as const,
|
|
91
92
|
color: "text-red-600",
|
|
92
93
|
bgColor: "bg-red-100 dark:bg-red-900/30",
|
|
93
94
|
};
|
|
94
95
|
case "DISABLED":
|
|
95
96
|
return {
|
|
96
|
-
|
|
97
|
+
translationKey: "disabled",
|
|
97
98
|
variant: "secondary" as const,
|
|
98
99
|
color: "text-gray-600",
|
|
99
100
|
bgColor: "bg-gray-100 dark:bg-gray-900/30",
|
|
100
101
|
};
|
|
101
102
|
default:
|
|
102
103
|
return {
|
|
103
|
-
|
|
104
|
+
translationKey: "unknown",
|
|
104
105
|
variant: "secondary" as const,
|
|
105
106
|
color: "text-gray-600",
|
|
106
107
|
bgColor: "bg-gray-100 dark:bg-gray-900/30",
|
|
@@ -136,6 +137,7 @@ export function GiftCardBalance({
|
|
|
136
137
|
giftCard,
|
|
137
138
|
className = "",
|
|
138
139
|
}: GiftCardBalanceProps) {
|
|
140
|
+
const t = useTranslations("giftCardStatus");
|
|
139
141
|
const statusConfig = getStatusConfig(giftCard.status);
|
|
140
142
|
const initialAmount = parseFloat(giftCard.initialAmount.amount);
|
|
141
143
|
const currentBalance = parseFloat(giftCard.balance.amount);
|
|
@@ -153,8 +155,8 @@ export function GiftCardBalance({
|
|
|
153
155
|
</div>
|
|
154
156
|
<div>
|
|
155
157
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
156
|
-
|
|
157
|
-
<Badge variant={statusConfig.variant}>{statusConfig.
|
|
158
|
+
{t("title")}
|
|
159
|
+
<Badge variant={statusConfig.variant}>{t(statusConfig.translationKey as any)}</Badge>
|
|
158
160
|
</CardTitle>
|
|
159
161
|
<p className="text-sm text-muted-foreground font-mono mt-1">
|
|
160
162
|
{giftCard.maskedCode}
|
|
@@ -168,7 +170,7 @@ export function GiftCardBalance({
|
|
|
168
170
|
{/* Balance display */}
|
|
169
171
|
<div className="space-y-2">
|
|
170
172
|
<div className="flex justify-between items-baseline">
|
|
171
|
-
<span className="text-sm text-muted-foreground">
|
|
173
|
+
<span className="text-sm text-muted-foreground">{t("availableBalance")}</span>
|
|
172
174
|
<span className="text-2xl font-bold">
|
|
173
175
|
{formatAmount(
|
|
174
176
|
giftCard.balance.amount,
|
|
@@ -182,14 +184,14 @@ export function GiftCardBalance({
|
|
|
182
184
|
|
|
183
185
|
<div className="flex justify-between text-xs text-muted-foreground">
|
|
184
186
|
<span>
|
|
185
|
-
|
|
187
|
+
{t("usedAmount")}{" "}
|
|
186
188
|
{formatAmount(
|
|
187
189
|
(initialAmount - currentBalance).toFixed(2),
|
|
188
190
|
giftCard.balance.currencyCode
|
|
189
191
|
)}
|
|
190
192
|
</span>
|
|
191
193
|
<span>
|
|
192
|
-
|
|
194
|
+
{t("initialAmount")}{" "}
|
|
193
195
|
{formatAmount(
|
|
194
196
|
giftCard.initialAmount.amount,
|
|
195
197
|
giftCard.initialAmount.currencyCode
|
|
@@ -214,8 +216,9 @@ export function GiftCardBalance({
|
|
|
214
216
|
<Calendar className="h-4 w-4 text-muted-foreground" />
|
|
215
217
|
)}
|
|
216
218
|
<span>
|
|
217
|
-
{expiringSoon
|
|
218
|
-
|
|
219
|
+
{expiringSoon
|
|
220
|
+
? t("expiresSoon", { date: formatDate(giftCard.expiresAt) })
|
|
221
|
+
: t("expiresOn", { date: formatDate(giftCard.expiresAt) })}
|
|
219
222
|
</span>
|
|
220
223
|
</div>
|
|
221
224
|
)}
|
|
@@ -224,7 +227,7 @@ export function GiftCardBalance({
|
|
|
224
227
|
{!giftCard.expiresAt && giftCard.status === "ACTIVE" && (
|
|
225
228
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
226
229
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
227
|
-
<span>
|
|
230
|
+
<span>{t("noExpiration")}</span>
|
|
228
231
|
</div>
|
|
229
232
|
)}
|
|
230
233
|
|
|
@@ -233,7 +236,7 @@ export function GiftCardBalance({
|
|
|
233
236
|
<div className="pt-3 border-t border-border space-y-2">
|
|
234
237
|
{giftCard.recipientName && (
|
|
235
238
|
<p className="text-sm">
|
|
236
|
-
<span className="text-muted-foreground">
|
|
239
|
+
<span className="text-muted-foreground">{t("recipientLabel")} </span>
|
|
237
240
|
<span className="font-medium">{giftCard.recipientName}</span>
|
|
238
241
|
</p>
|
|
239
242
|
)}
|
|
@@ -297,6 +300,7 @@ export function GiftCardBalanceCompact({
|
|
|
297
300
|
status,
|
|
298
301
|
className = "",
|
|
299
302
|
}: GiftCardBalanceCompactProps) {
|
|
303
|
+
const t = useTranslations("giftCardStatus");
|
|
300
304
|
const statusConfig = getStatusConfig(status);
|
|
301
305
|
|
|
302
306
|
return (
|
|
@@ -310,7 +314,7 @@ export function GiftCardBalanceCompact({
|
|
|
310
314
|
<CreditCard className="h-4 w-4 text-muted-foreground" />
|
|
311
315
|
<span className="font-mono text-sm">{maskedCode}</span>
|
|
312
316
|
<Badge variant={statusConfig.variant} className="text-xs">
|
|
313
|
-
{statusConfig.
|
|
317
|
+
{t(statusConfig.translationKey as any)}
|
|
314
318
|
</Badge>
|
|
315
319
|
</div>
|
|
316
320
|
<span className="font-semibold">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { Input } from "@/components/ui/input";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
6
7
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -89,6 +90,8 @@ export function GiftCardInput({
|
|
|
89
90
|
disabled = false,
|
|
90
91
|
className = "",
|
|
91
92
|
}: GiftCardInputProps) {
|
|
93
|
+
const t = useTranslations("cart");
|
|
94
|
+
const tc = useTranslations("common");
|
|
92
95
|
const [code, setCode] = useState("");
|
|
93
96
|
const [isValidating, setIsValidating] = useState(false);
|
|
94
97
|
const [validation, setValidation] = useState<GiftCardValidationResult | null>(
|
|
@@ -124,13 +127,13 @@ export function GiftCardInput({
|
|
|
124
127
|
*/
|
|
125
128
|
const handleValidate = useCallback(async () => {
|
|
126
129
|
if (!code || code.length < 4) {
|
|
127
|
-
setError("
|
|
130
|
+
setError(t("giftCardInvalidCode"));
|
|
128
131
|
return;
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
// Check if already applied
|
|
132
135
|
if (appliedCodes.includes(code)) {
|
|
133
|
-
setError("
|
|
136
|
+
setError(t("giftCardAlreadyApplied"));
|
|
134
137
|
return;
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -145,7 +148,7 @@ export function GiftCardInput({
|
|
|
145
148
|
setError(result.error.message);
|
|
146
149
|
}
|
|
147
150
|
} catch (err: unknown) {
|
|
148
|
-
setError(err instanceof Error ? err.message : "
|
|
151
|
+
setError(err instanceof Error ? err.message : t("giftCardValidationFailed"));
|
|
149
152
|
} finally {
|
|
150
153
|
setIsValidating(false);
|
|
151
154
|
}
|
|
@@ -187,7 +190,7 @@ export function GiftCardInput({
|
|
|
187
190
|
<Gift className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
188
191
|
<Input
|
|
189
192
|
type="text"
|
|
190
|
-
placeholder="
|
|
193
|
+
placeholder={t("giftCardCodePlaceholder")}
|
|
191
194
|
value={code}
|
|
192
195
|
onChange={handleCodeChange}
|
|
193
196
|
onKeyDown={handleKeyDown}
|
|
@@ -207,10 +210,10 @@ export function GiftCardInput({
|
|
|
207
210
|
) : validation?.valid ? (
|
|
208
211
|
<>
|
|
209
212
|
<Check className="mr-2 h-4 w-4" />
|
|
210
|
-
|
|
213
|
+
{tc("apply")}
|
|
211
214
|
</>
|
|
212
215
|
) : (
|
|
213
|
-
"
|
|
216
|
+
t("giftCardCheck")
|
|
214
217
|
)}
|
|
215
218
|
</Button>
|
|
216
219
|
</div>
|
|
@@ -229,7 +232,7 @@ export function GiftCardInput({
|
|
|
229
232
|
<div className="flex items-center justify-between">
|
|
230
233
|
<div className="flex items-center gap-2">
|
|
231
234
|
<Check className="h-4 w-4" />
|
|
232
|
-
<span>
|
|
235
|
+
<span>{t("giftCardValid")}</span>
|
|
233
236
|
</div>
|
|
234
237
|
{validation.availableBalance && (
|
|
235
238
|
<span className="font-semibold">
|
|
@@ -244,7 +247,7 @@ export function GiftCardInput({
|
|
|
244
247
|
) : (
|
|
245
248
|
<div className="flex items-center gap-2">
|
|
246
249
|
<X className="h-4 w-4" />
|
|
247
|
-
<span>{validation.error?.message || "
|
|
250
|
+
<span>{validation.error?.message || t("giftCardInvalid")}</span>
|
|
248
251
|
</div>
|
|
249
252
|
)}
|
|
250
253
|
</div>
|
|
@@ -262,7 +265,7 @@ export function GiftCardInput({
|
|
|
262
265
|
{appliedCodes.length > 0 && (
|
|
263
266
|
<div className="space-y-2">
|
|
264
267
|
<p className="text-sm font-medium text-muted-foreground">
|
|
265
|
-
|
|
268
|
+
{t("appliedGiftCards")}
|
|
266
269
|
</p>
|
|
267
270
|
<div className="flex flex-wrap gap-2">
|
|
268
271
|
{appliedCodes.map((appliedCode) => (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Link } from "@/i18n/navigation";
|
|
4
5
|
import { cn } from "@/lib/utils";
|
|
5
6
|
import { useCollections } from "@/lib/graphql/hooks";
|
|
6
7
|
import type { CollectionCardFields } from "@/lib/graphql/fragments";
|
|
@@ -10,6 +11,8 @@ export interface CategoryGridProps {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function CategoryGrid({ className }: CategoryGridProps) {
|
|
14
|
+
const t = useTranslations("home");
|
|
15
|
+
|
|
13
16
|
// Use collections for homepage marketing (not categories)
|
|
14
17
|
// Collections are better for homepage because they're curated and flexible
|
|
15
18
|
const { data, isLoading, error } = useCollections({
|
|
@@ -22,9 +25,9 @@ export function CategoryGrid({ className }: CategoryGridProps) {
|
|
|
22
25
|
return (
|
|
23
26
|
<section className={cn("container mx-auto px-4", className)}>
|
|
24
27
|
<div className="mb-8 text-center">
|
|
25
|
-
<h2 className="text-3xl font-bold text-foreground">
|
|
28
|
+
<h2 className="text-3xl font-bold text-foreground">{t("shopByCategory")}</h2>
|
|
26
29
|
<p className="mt-2 text-muted-foreground">
|
|
27
|
-
|
|
30
|
+
{t("shopByCategoryDescription")}
|
|
28
31
|
</p>
|
|
29
32
|
</div>
|
|
30
33
|
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4">
|
|
@@ -46,9 +49,9 @@ export function CategoryGrid({ className }: CategoryGridProps) {
|
|
|
46
49
|
return (
|
|
47
50
|
<section className={cn("container mx-auto px-4", className)}>
|
|
48
51
|
<div className="mb-8 text-center">
|
|
49
|
-
<h2 className="text-3xl font-bold text-foreground">
|
|
52
|
+
<h2 className="text-3xl font-bold text-foreground">{t("shopByCategory")}</h2>
|
|
50
53
|
<p className="mt-2 text-muted-foreground">
|
|
51
|
-
|
|
54
|
+
{t("shopByCategoryDescription")}
|
|
52
55
|
</p>
|
|
53
56
|
</div>
|
|
54
57
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Link } from "@/i18n/navigation";
|
|
4
5
|
import { ProductGrid } from "@/components/product/product-grid";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
6
7
|
import { useProducts } from "@/lib/graphql/hooks";
|
|
7
8
|
|
|
8
9
|
export function FeaturedProducts() {
|
|
10
|
+
const t = useTranslations("home");
|
|
11
|
+
const tc = useTranslations("common");
|
|
12
|
+
|
|
9
13
|
// Fetch featured products using GraphQL
|
|
10
14
|
const { data, isLoading, error } = useProducts({
|
|
11
15
|
first: 8,
|
|
@@ -19,9 +23,9 @@ export function FeaturedProducts() {
|
|
|
19
23
|
<section className="container mx-auto px-4">
|
|
20
24
|
<div className="mb-8 flex items-center justify-between">
|
|
21
25
|
<div>
|
|
22
|
-
<h2 className="text-3xl font-bold text-foreground">
|
|
26
|
+
<h2 className="text-3xl font-bold text-foreground">{t("featuredProducts")}</h2>
|
|
23
27
|
<p className="mt-2 text-muted-foreground">
|
|
24
|
-
|
|
28
|
+
{t("featuredDescription")}
|
|
25
29
|
</p>
|
|
26
30
|
</div>
|
|
27
31
|
</div>
|
|
@@ -53,13 +57,13 @@ export function FeaturedProducts() {
|
|
|
53
57
|
<section className="container mx-auto px-4">
|
|
54
58
|
<div className="mb-8 flex items-center justify-between">
|
|
55
59
|
<div>
|
|
56
|
-
<h2 className="text-3xl font-bold text-foreground">
|
|
60
|
+
<h2 className="text-3xl font-bold text-foreground">{t("featuredProducts")}</h2>
|
|
57
61
|
<p className="mt-2 text-muted-foreground">
|
|
58
|
-
|
|
62
|
+
{t("featuredDescription")}
|
|
59
63
|
</p>
|
|
60
64
|
</div>
|
|
61
65
|
<Button variant="outline" asChild>
|
|
62
|
-
<Link href="/products">
|
|
66
|
+
<Link href="/products">{tc("viewAll")}</Link>
|
|
63
67
|
</Button>
|
|
64
68
|
</div>
|
|
65
69
|
|
|
@@ -73,10 +77,10 @@ export function FeaturedProducts() {
|
|
|
73
77
|
) : (
|
|
74
78
|
<div className="rounded-lg border border-border bg-muted/50 p-12 text-center">
|
|
75
79
|
<p className="text-muted-foreground">
|
|
76
|
-
|
|
80
|
+
{t("noFeaturedProducts")}
|
|
77
81
|
</p>
|
|
78
82
|
<Button className="mt-4" asChild>
|
|
79
|
-
<Link href="/products">
|
|
83
|
+
<Link href="/products">{t("browseAllProducts")}</Link>
|
|
80
84
|
</Button>
|
|
81
85
|
</div>
|
|
82
86
|
)}
|
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useTranslations } from "next-intl";
|
|
2
|
+
import { Link } from "@/i18n/navigation";
|
|
2
3
|
import { Button } from "@/components/ui/button";
|
|
3
4
|
|
|
4
5
|
export function HeroSection() {
|
|
6
|
+
const t = useTranslations("home");
|
|
7
|
+
|
|
5
8
|
return (
|
|
6
9
|
<section className="container mx-auto px-4">
|
|
7
10
|
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-r from-primary/10 via-primary/5 to-background p-8 md:p-16">
|
|
8
11
|
<div className="relative z-10 mx-auto max-w-3xl text-center">
|
|
9
12
|
<h1 className="mb-4 text-4xl font-bold tracking-tight text-foreground sm:text-5xl md:text-6xl">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
{t.rich("welcomeTo", {
|
|
14
|
+
storeName: process.env.NEXT_PUBLIC_SITE_NAME || "Store",
|
|
15
|
+
highlight: (chunks) => (
|
|
16
|
+
<span className="text-primary">{chunks}</span>
|
|
17
|
+
),
|
|
18
|
+
})}
|
|
14
19
|
</h1>
|
|
15
20
|
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted-foreground">
|
|
16
|
-
|
|
21
|
+
{t("heroDescription")}
|
|
17
22
|
</p>
|
|
18
23
|
<div className="flex flex-col justify-center gap-4 sm:flex-row">
|
|
19
24
|
<Button size="lg" asChild>
|
|
20
|
-
<Link href="/products">
|
|
25
|
+
<Link href="/products">{t("shopNow")}</Link>
|
|
21
26
|
</Button>
|
|
22
27
|
<Button size="lg" variant="outline" asChild>
|
|
23
|
-
<Link href="/products">
|
|
28
|
+
<Link href="/products">{t("browseProducts")}</Link>
|
|
24
29
|
</Button>
|
|
25
30
|
</div>
|
|
26
31
|
</div>
|
|
@@ -5,12 +5,14 @@ import { Mail, Check } from "lucide-react";
|
|
|
5
5
|
import { Input } from "@/components/ui/input";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { cn } from "@/lib/utils";
|
|
8
|
+
import { useTranslations } from "next-intl";
|
|
8
9
|
|
|
9
10
|
export interface NewsletterSignupProps {
|
|
10
11
|
className?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
15
|
+
const t = useTranslations("footer");
|
|
14
16
|
const [email, setEmail] = useState("");
|
|
15
17
|
const [isLoading, setIsLoading] = useState(false);
|
|
16
18
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
@@ -20,7 +22,7 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
20
22
|
e.preventDefault();
|
|
21
23
|
|
|
22
24
|
if (!email || !email.includes("@")) {
|
|
23
|
-
setError("
|
|
25
|
+
setError(t("invalidEmail"));
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -42,7 +44,7 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
42
44
|
setIsSuccess(false);
|
|
43
45
|
}, 3000);
|
|
44
46
|
} catch (err) {
|
|
45
|
-
setError("
|
|
47
|
+
setError(t("failedSubscribe"));
|
|
46
48
|
} finally {
|
|
47
49
|
setIsLoading(false);
|
|
48
50
|
}
|
|
@@ -57,17 +59,17 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
57
59
|
</div>
|
|
58
60
|
|
|
59
61
|
<h2 className="mb-2 text-3xl font-bold text-foreground">
|
|
60
|
-
|
|
62
|
+
{t("stayInLoop")}
|
|
61
63
|
</h2>
|
|
62
64
|
<p className="mb-6 text-muted-foreground">
|
|
63
|
-
|
|
65
|
+
{t("newsletterLongDescription")}
|
|
64
66
|
</p>
|
|
65
67
|
|
|
66
68
|
{isSuccess ? (
|
|
67
69
|
<div className="flex items-center justify-center gap-2 rounded-lg bg-green-50 p-4 text-green-700 dark:bg-green-950 dark:text-green-400">
|
|
68
70
|
<Check className="h-5 w-5" />
|
|
69
71
|
<span className="font-medium">
|
|
70
|
-
|
|
72
|
+
{t("thanksForSubscribing")}
|
|
71
73
|
</span>
|
|
72
74
|
</div>
|
|
73
75
|
) : (
|
|
@@ -75,7 +77,7 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
75
77
|
<div className="flex flex-col gap-3 sm:flex-row">
|
|
76
78
|
<Input
|
|
77
79
|
type="email"
|
|
78
|
-
placeholder="
|
|
80
|
+
placeholder={t("emailPlaceholder")}
|
|
79
81
|
value={email}
|
|
80
82
|
onChange={(e) => setEmail(e.target.value)}
|
|
81
83
|
disabled={isLoading}
|
|
@@ -88,7 +90,7 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
88
90
|
size="lg"
|
|
89
91
|
className="sm:w-auto"
|
|
90
92
|
>
|
|
91
|
-
{isLoading ? "
|
|
93
|
+
{isLoading ? t("subscribing") : t("subscribe")}
|
|
92
94
|
</Button>
|
|
93
95
|
</div>
|
|
94
96
|
|
|
@@ -97,7 +99,7 @@ export function NewsletterSignup({ className }: NewsletterSignupProps) {
|
|
|
97
99
|
)}
|
|
98
100
|
|
|
99
101
|
<p className="mt-3 text-xs text-muted-foreground">
|
|
100
|
-
|
|
102
|
+
{t("privacyConsent")}
|
|
101
103
|
</p>
|
|
102
104
|
</form>
|
|
103
105
|
)}
|