@doswiftly/cli 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/commands/deploy.d.ts +20 -0
  2. package/dist/commands/deploy.d.ts.map +1 -1
  3. package/dist/commands/deploy.js +219 -6
  4. package/dist/commands/deploy.js.map +1 -1
  5. package/package.json +4 -4
  6. package/templates/storefront-minimal/.github/workflows/build-template.yml +10 -0
  7. package/templates/storefront-minimal/wrangler.toml +11 -0
  8. package/templates/storefront-nextjs/.github/workflows/build-template.yml +10 -0
  9. package/templates/storefront-nextjs/wrangler.toml +11 -0
  10. package/templates/storefront-nextjs-shadcn/.github/workflows/build-template.yml +10 -0
  11. package/templates/storefront-nextjs-shadcn/CLAUDE.md +29 -5
  12. package/templates/storefront-nextjs-shadcn/app/{about → [locale]/about}/page.tsx +17 -14
  13. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/addresses/page.tsx +19 -15
  14. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/error.tsx +8 -5
  15. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loyalty/page.tsx +39 -34
  16. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/page.tsx +9 -7
  17. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/tracking/page.tsx +27 -25
  18. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/page.tsx +13 -9
  19. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/page.tsx +1 -2
  20. package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/settings/page.tsx +1 -1
  21. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/forgot-password/page.tsx +14 -12
  22. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/login/page.tsx +5 -2
  23. package/templates/storefront-nextjs-shadcn/app/{auth → [locale]/auth}/register/page.tsx +5 -2
  24. package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/page.tsx +1 -1
  25. package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/page.tsx +14 -10
  26. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/category-products-client.tsx +4 -2
  27. package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/page.tsx +13 -8
  28. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/error.tsx +1 -1
  29. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/page.tsx +228 -184
  30. package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/success/[orderId]/page.tsx +36 -34
  31. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/page.tsx +5 -3
  32. package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/page.tsx +13 -8
  33. package/templates/storefront-nextjs-shadcn/app/{contact → [locale]/contact}/page.tsx +24 -21
  34. package/templates/storefront-nextjs-shadcn/app/{error.tsx → [locale]/error.tsx} +13 -8
  35. package/templates/storefront-nextjs-shadcn/app/[locale]/layout.tsx +92 -0
  36. package/templates/storefront-nextjs-shadcn/app/{not-found.tsx → [locale]/not-found.tsx} +13 -18
  37. package/templates/storefront-nextjs-shadcn/app/{page.tsx → [locale]/page.tsx} +8 -4
  38. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx +1 -1
  39. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx +11 -8
  40. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/product-client.tsx +3 -1
  41. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/page.tsx +6 -3
  42. package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx +14 -10
  43. package/templates/storefront-nextjs-shadcn/app/{wishlist → [locale]/wishlist}/page.tsx +21 -25
  44. package/templates/storefront-nextjs-shadcn/app/layout.tsx +6 -68
  45. package/templates/storefront-nextjs-shadcn/components/account/address-form.tsx +25 -20
  46. package/templates/storefront-nextjs-shadcn/components/account/address-list.tsx +11 -10
  47. package/templates/storefront-nextjs-shadcn/components/account/order-details.tsx +14 -12
  48. package/templates/storefront-nextjs-shadcn/components/account/order-history.tsx +28 -18
  49. package/templates/storefront-nextjs-shadcn/components/auth/account-menu.tsx +10 -8
  50. package/templates/storefront-nextjs-shadcn/components/auth/login-form.tsx +27 -22
  51. package/templates/storefront-nextjs-shadcn/components/auth/register-form.tsx +48 -43
  52. package/templates/storefront-nextjs-shadcn/components/blog/blog-card.tsx +1 -1
  53. package/templates/storefront-nextjs-shadcn/components/blog/blog-sidebar.tsx +1 -1
  54. package/templates/storefront-nextjs-shadcn/components/brand/brand-card.tsx +1 -1
  55. package/templates/storefront-nextjs-shadcn/components/cart/cart-drawer.tsx +7 -4
  56. package/templates/storefront-nextjs-shadcn/components/cart/cart-icon.tsx +1 -1
  57. package/templates/storefront-nextjs-shadcn/components/cart/cart-item.tsx +7 -5
  58. package/templates/storefront-nextjs-shadcn/components/cart/cart-summary.tsx +9 -7
  59. package/templates/storefront-nextjs-shadcn/components/cart/promo-code-input.tsx +8 -5
  60. package/templates/storefront-nextjs-shadcn/components/cart/shipping-estimator.tsx +18 -15
  61. package/templates/storefront-nextjs-shadcn/components/checkout/payment-method-card.tsx +15 -25
  62. package/templates/storefront-nextjs-shadcn/components/checkout/payment-step.tsx +10 -8
  63. package/templates/storefront-nextjs-shadcn/components/checkout/tax-breakdown.tsx +9 -6
  64. package/templates/storefront-nextjs-shadcn/components/commerce/currency-selector.tsx +5 -3
  65. package/templates/storefront-nextjs-shadcn/components/commerce/pagination.tsx +8 -5
  66. package/templates/storefront-nextjs-shadcn/components/commerce/product-actions.tsx +5 -3
  67. package/templates/storefront-nextjs-shadcn/components/commerce/search-input.tsx +8 -7
  68. package/templates/storefront-nextjs-shadcn/components/common/category-card.tsx +1 -1
  69. package/templates/storefront-nextjs-shadcn/components/common/collection-card.tsx +1 -1
  70. package/templates/storefront-nextjs-shadcn/components/common/social-share.tsx +9 -6
  71. package/templates/storefront-nextjs-shadcn/components/discount/discount-breakdown.tsx +21 -11
  72. package/templates/storefront-nextjs-shadcn/components/discount/discount-code-input.tsx +16 -13
  73. package/templates/storefront-nextjs-shadcn/components/error/error-boundary.tsx +53 -28
  74. package/templates/storefront-nextjs-shadcn/components/filters/dynamic-attribute-filters.tsx +7 -5
  75. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-balance.tsx +19 -15
  76. package/templates/storefront-nextjs-shadcn/components/gift-card/gift-card-input.tsx +12 -9
  77. package/templates/storefront-nextjs-shadcn/components/home/category-grid.tsx +8 -5
  78. package/templates/storefront-nextjs-shadcn/components/home/featured-collections.tsx +1 -1
  79. package/templates/storefront-nextjs-shadcn/components/home/featured-products.tsx +12 -8
  80. package/templates/storefront-nextjs-shadcn/components/home/hero-section.tsx +13 -8
  81. package/templates/storefront-nextjs-shadcn/components/home/newsletter-signup.tsx +10 -8
  82. package/templates/storefront-nextjs-shadcn/components/layout/breadcrumbs.tsx +37 -12
  83. package/templates/storefront-nextjs-shadcn/components/layout/currency-selector.tsx +5 -2
  84. package/templates/storefront-nextjs-shadcn/components/layout/footer.tsx +24 -23
  85. package/templates/storefront-nextjs-shadcn/components/layout/header.tsx +20 -12
  86. package/templates/storefront-nextjs-shadcn/components/layout/language-switcher.tsx +54 -0
  87. package/templates/storefront-nextjs-shadcn/components/layout/mobile-menu.tsx +33 -30
  88. package/templates/storefront-nextjs-shadcn/components/layout/navigation.tsx +27 -24
  89. package/templates/storefront-nextjs-shadcn/components/loyalty/referral-section.tsx +23 -24
  90. package/templates/storefront-nextjs-shadcn/components/product/add-to-cart-button.tsx +6 -14
  91. package/templates/storefront-nextjs-shadcn/components/product/b2b-price-display.tsx +1 -1
  92. package/templates/storefront-nextjs-shadcn/components/product/filter-active-pills.tsx +4 -1
  93. package/templates/storefront-nextjs-shadcn/components/product/filter-mobile-sheet.tsx +7 -4
  94. package/templates/storefront-nextjs-shadcn/components/product/filter-price-range.tsx +5 -3
  95. package/templates/storefront-nextjs-shadcn/components/product/product-card.tsx +8 -6
  96. package/templates/storefront-nextjs-shadcn/components/product/product-filters.tsx +3 -1
  97. package/templates/storefront-nextjs-shadcn/components/product/product-image.tsx +3 -7
  98. package/templates/storefront-nextjs-shadcn/components/product/product-sort.tsx +26 -13
  99. package/templates/storefront-nextjs-shadcn/components/product/review-form.tsx +25 -27
  100. package/templates/storefront-nextjs-shadcn/components/providers/language-sync-provider.tsx +27 -0
  101. package/templates/storefront-nextjs-shadcn/components/providers/stores-provider.tsx +40 -7
  102. package/templates/storefront-nextjs-shadcn/components/returns/return-request-form.tsx +56 -70
  103. package/templates/storefront-nextjs-shadcn/components/search/search-bar.tsx +7 -4
  104. package/templates/storefront-nextjs-shadcn/components/shipping/shipping-method-selector.tsx +12 -9
  105. package/templates/storefront-nextjs-shadcn/components/ui/empty-state.tsx +23 -12
  106. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-button.tsx +7 -4
  107. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-icon.tsx +1 -1
  108. package/templates/storefront-nextjs-shadcn/components/wishlist/wishlist-item.tsx +2 -10
  109. package/templates/storefront-nextjs-shadcn/generated/graphql.ts +1159 -551
  110. package/templates/storefront-nextjs-shadcn/hooks/index.ts +1 -0
  111. package/templates/storefront-nextjs-shadcn/hooks/use-cart-actions.ts +22 -249
  112. package/templates/storefront-nextjs-shadcn/hooks/use-cart-di.ts +67 -0
  113. package/templates/storefront-nextjs-shadcn/hooks/use-cart-sync.ts +3 -3
  114. package/templates/storefront-nextjs-shadcn/i18n/navigation.ts +12 -0
  115. package/templates/storefront-nextjs-shadcn/i18n/request.ts +17 -0
  116. package/templates/storefront-nextjs-shadcn/i18n/routing.ts +17 -0
  117. package/templates/storefront-nextjs-shadcn/lib/graphql/config.ts +1 -0
  118. package/templates/storefront-nextjs-shadcn/lib/graphql/hooks.ts +41 -8
  119. package/templates/storefront-nextjs-shadcn/lib/graphql/query-keys.ts +20 -18
  120. package/templates/storefront-nextjs-shadcn/lib/graphql/server.ts +2 -1
  121. package/templates/storefront-nextjs-shadcn/messages/en.json +869 -0
  122. package/templates/storefront-nextjs-shadcn/messages/pl.json +869 -0
  123. package/templates/storefront-nextjs-shadcn/next.config.ts +6 -5
  124. package/templates/storefront-nextjs-shadcn/package.json +3 -2
  125. package/templates/storefront-nextjs-shadcn/proxy.ts +115 -46
  126. package/templates/storefront-nextjs-shadcn/stores/cart-store.ts +24 -58
  127. package/templates/storefront-nextjs-shadcn/wrangler.toml +11 -0
  128. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/loading.tsx +0 -0
  129. /package/templates/storefront-nextjs-shadcn/app/{account → [locale]/account}/orders/[id]/loading.tsx +0 -0
  130. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/[slug]/loading.tsx +0 -0
  131. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/loading.tsx +0 -0
  132. /package/templates/storefront-nextjs-shadcn/app/{blog → [locale]/blog}/page.tsx +0 -0
  133. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/[slug]/page.tsx +0 -0
  134. /package/templates/storefront-nextjs-shadcn/app/{brands → [locale]/brands}/page.tsx +0 -0
  135. /package/templates/storefront-nextjs-shadcn/app/{cart → [locale]/cart}/loading.tsx +0 -0
  136. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/loading.tsx +0 -0
  137. /package/templates/storefront-nextjs-shadcn/app/{categories → [locale]/categories}/[slug]/page.tsx +0 -0
  138. /package/templates/storefront-nextjs-shadcn/app/{checkout → [locale]/checkout}/loading.tsx +0 -0
  139. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/[handle]/loading.tsx +0 -0
  140. /package/templates/storefront-nextjs-shadcn/app/{collections → [locale]/collections}/loading.tsx +0 -0
  141. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/loading.tsx +0 -0
  142. /package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/loading.tsx +0 -0
  143. /package/templates/storefront-nextjs-shadcn/app/{returns → [locale]/returns}/page.tsx +0 -0
  144. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/loading.tsx +0 -0
  145. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/page.tsx +0 -0
  146. /package/templates/storefront-nextjs-shadcn/app/{search → [locale]/search}/search-client.tsx +0 -0
  147. /package/templates/storefront-nextjs-shadcn/app/{shipping → [locale]/shipping}/page.tsx +0 -0
@@ -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
- <div className="flex min-h-[400px] items-center justify-center p-4">
80
- <Card className="w-full max-w-md">
81
- <CardHeader className="text-center">
82
- <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10">
83
- <AlertTriangle className="h-6 w-6 text-destructive" />
84
- </div>
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">Loading filters...</span>
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">Filters</h3>
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
- Clear all
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">Price</h4>
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} products
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
- label: "Active",
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
- label: "Fully Redeemed",
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
- label: "Expired",
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
- label: "Disabled",
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
- label: "Unknown",
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
- Gift Card
157
- <Badge variant={statusConfig.variant}>{statusConfig.label}</Badge>
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">Available Balance</span>
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
- Used:{" "}
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
- Initial:{" "}
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 ? "Expires soon: " : "Expires: "}
218
- {formatDate(giftCard.expiresAt)}
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>No expiration date</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">To: </span>
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.label}
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("Please enter a valid gift card code");
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("This gift card has already been applied");
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 : "Failed to validate gift card");
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="Gift card code (e.g., ABCD-EFGH-IJKL-MNOP)"
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
- Apply
213
+ {tc("apply")}
211
214
  </>
212
215
  ) : (
213
- "Check"
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>Gift card valid!</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 || "Invalid gift card"}</span>
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
- Applied gift cards:
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 Link from "next/link";
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">Shop by Category</h2>
28
+ <h2 className="text-3xl font-bold text-foreground">{t("shopByCategory")}</h2>
26
29
  <p className="mt-2 text-muted-foreground">
27
- Find exactly what you're looking for
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">Shop by Category</h2>
52
+ <h2 className="text-3xl font-bold text-foreground">{t("shopByCategory")}</h2>
50
53
  <p className="mt-2 text-muted-foreground">
51
- Find exactly what you're looking for
54
+ {t("shopByCategoryDescription")}
52
55
  </p>
53
56
  </div>
54
57
 
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
3
+ import { Link } from "@/i18n/navigation";
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { Card, CardContent } from "@/components/ui/card";
6
6
  import { useCollections } from "@/lib/graphql/hooks";
@@ -1,11 +1,15 @@
1
1
  "use client";
2
2
 
3
- import Link from "next/link";
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">Featured Products</h2>
26
+ <h2 className="text-3xl font-bold text-foreground">{t("featuredProducts")}</h2>
23
27
  <p className="mt-2 text-muted-foreground">
24
- Handpicked favorites just for you
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">Featured Products</h2>
60
+ <h2 className="text-3xl font-bold text-foreground">{t("featuredProducts")}</h2>
57
61
  <p className="mt-2 text-muted-foreground">
58
- Handpicked favorites just for you
62
+ {t("featuredDescription")}
59
63
  </p>
60
64
  </div>
61
65
  <Button variant="outline" asChild>
62
- <Link href="/products">View All</Link>
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
- No featured products available at the moment.
80
+ {t("noFeaturedProducts")}
77
81
  </p>
78
82
  <Button className="mt-4" asChild>
79
- <Link href="/products">Browse All Products</Link>
83
+ <Link href="/products">{t("browseAllProducts")}</Link>
80
84
  </Button>
81
85
  </div>
82
86
  )}
@@ -1,26 +1,31 @@
1
- import Link from "next/link";
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
- Welcome to{" "}
11
- <span className="text-primary">
12
- {process.env.NEXT_PUBLIC_SITE_NAME || "My Store"}
13
- </span>
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
- Discover our curated collection of quality products. Shop with confidence and enjoy exceptional service.
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">Shop Now</Link>
25
+ <Link href="/products">{t("shopNow")}</Link>
21
26
  </Button>
22
27
  <Button size="lg" variant="outline" asChild>
23
- <Link href="/products">Browse Products</Link>
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("Please enter a valid email address");
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("Failed to subscribe. Please try again.");
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
- Stay in the Loop
62
+ {t("stayInLoop")}
61
63
  </h2>
62
64
  <p className="mb-6 text-muted-foreground">
63
- Subscribe to our newsletter for exclusive offers, new arrivals, and insider news.
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
- Thanks for subscribing! Check your email for confirmation.
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="Enter your email"
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 ? "Subscribing..." : "Subscribe"}
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
- By subscribing, you agree to our Privacy Policy and consent to receive updates.
102
+ {t("privacyConsent")}
101
103
  </p>
102
104
  </form>
103
105
  )}