@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
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useTranslations } from "next-intl";
|
|
2
|
+
import { Link } from "@/i18n/navigation";
|
|
2
3
|
import { FileQuestion, Home, ShoppingBag, Search } from "lucide-react";
|
|
3
4
|
import { Button } from "@/components/ui/button";
|
|
4
5
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* 404 Not Found Page
|
|
8
|
-
*
|
|
9
|
-
* Displayed when a page or resource is not found.
|
|
10
|
-
* Provides helpful navigation back to the site.
|
|
11
|
-
*
|
|
12
|
-
* Requirements: 11.1
|
|
8
|
+
* 404 Not Found Page (locale-aware)
|
|
13
9
|
*/
|
|
14
10
|
export default function NotFound() {
|
|
11
|
+
const t = useTranslations("notFound");
|
|
12
|
+
|
|
15
13
|
return (
|
|
16
14
|
<div className="container mx-auto flex min-h-[60vh] items-center justify-center px-4 py-16">
|
|
17
15
|
<Card className="w-full max-w-md text-center">
|
|
@@ -19,46 +17,43 @@ export default function NotFound() {
|
|
|
19
17
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
|
20
18
|
<FileQuestion className="h-8 w-8 text-muted-foreground" />
|
|
21
19
|
</div>
|
|
22
|
-
<CardTitle className="text-6xl font-bold">
|
|
20
|
+
<CardTitle className="text-6xl font-bold">{t("title")}</CardTitle>
|
|
23
21
|
<CardDescription className="text-lg">
|
|
24
|
-
|
|
22
|
+
{t("description")}
|
|
25
23
|
</CardDescription>
|
|
26
24
|
</CardHeader>
|
|
27
25
|
<CardContent className="space-y-6">
|
|
28
26
|
<p className="text-muted-foreground">
|
|
29
|
-
|
|
30
|
-
Mogła zostać przeniesiona lub usunięta.
|
|
27
|
+
{t("message")}
|
|
31
28
|
</p>
|
|
32
29
|
|
|
33
|
-
{/* Navigation options */}
|
|
34
30
|
<div className="flex flex-col gap-3">
|
|
35
31
|
<Button asChild size="lg" className="w-full">
|
|
36
32
|
<Link href="/">
|
|
37
33
|
<Home className="mr-2 h-4 w-4" />
|
|
38
|
-
|
|
34
|
+
{t("home")}
|
|
39
35
|
</Link>
|
|
40
36
|
</Button>
|
|
41
37
|
<div className="flex gap-3">
|
|
42
38
|
<Button asChild variant="outline" size="lg" className="flex-1">
|
|
43
39
|
<Link href="/products">
|
|
44
40
|
<ShoppingBag className="mr-2 h-4 w-4" />
|
|
45
|
-
|
|
41
|
+
{t("products")}
|
|
46
42
|
</Link>
|
|
47
43
|
</Button>
|
|
48
44
|
<Button asChild variant="outline" size="lg" className="flex-1">
|
|
49
45
|
<Link href="/collections">
|
|
50
46
|
<Search className="mr-2 h-4 w-4" />
|
|
51
|
-
|
|
47
|
+
{t("collections")}
|
|
52
48
|
</Link>
|
|
53
49
|
</Button>
|
|
54
50
|
</div>
|
|
55
51
|
</div>
|
|
56
52
|
|
|
57
|
-
{/* Help text */}
|
|
58
53
|
<p className="text-sm text-muted-foreground">
|
|
59
|
-
|
|
54
|
+
{t("needHelp")}{" "}
|
|
60
55
|
<Link href="/contact" className="text-primary underline underline-offset-4">
|
|
61
|
-
|
|
56
|
+
{t("contactUs")}
|
|
62
57
|
</Link>
|
|
63
58
|
</p>
|
|
64
59
|
</CardContent>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { getTranslations } from "next-intl/server";
|
|
1
2
|
import { HeroSection } from "@/components/home/hero-section";
|
|
2
3
|
import { FeaturedProducts } from "@/components/home/featured-products";
|
|
3
4
|
import { CategoryGrid } from "@/components/home/category-grid";
|
|
4
5
|
import { NewsletterSignup } from "@/components/home/newsletter-signup";
|
|
5
6
|
import type { Metadata } from "next";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
9
|
+
const t = await getTranslations("home");
|
|
10
|
+
return {
|
|
11
|
+
title: "Home | " + (process.env.NEXT_PUBLIC_SITE_NAME || "My Store"),
|
|
12
|
+
description: t("metaDescription"),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
export default function HomePage() {
|
|
13
17
|
return (
|
package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/error.tsx
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
-
import Link from "
|
|
4
|
+
import { Link } from "@/i18n/navigation";
|
|
5
5
|
import { AlertCircle } from "lucide-react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/[slug]/page.tsx
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Metadata } from "next";
|
|
2
2
|
import { notFound } from "next/navigation";
|
|
3
|
+
import { getTranslations } from "next-intl/server";
|
|
3
4
|
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
|
4
5
|
import { ProductClient } from "./product-client";
|
|
5
6
|
import { fetchProduct, fetchProducts } from "@/lib/graphql/server";
|
|
@@ -26,22 +27,23 @@ export async function generateMetadata({
|
|
|
26
27
|
params: Promise<{ slug: string }>;
|
|
27
28
|
}): Promise<Metadata> {
|
|
28
29
|
try {
|
|
30
|
+
const t = await getTranslations("productPage");
|
|
29
31
|
const resolvedParams = await params;
|
|
30
|
-
|
|
32
|
+
|
|
31
33
|
if (!resolvedParams?.slug) {
|
|
32
34
|
return {
|
|
33
|
-
title: "
|
|
34
|
-
description: "
|
|
35
|
+
title: t("title"),
|
|
36
|
+
description: t("viewDetails"),
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
|
-
|
|
39
|
+
|
|
38
40
|
const data = await fetchProduct(resolvedParams.slug);
|
|
39
41
|
const product = data?.product;
|
|
40
42
|
|
|
41
43
|
if (!product) {
|
|
42
44
|
return {
|
|
43
|
-
title: "
|
|
44
|
-
description:
|
|
45
|
+
title: t("notFound"),
|
|
46
|
+
description: t("notFoundDescription"),
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -88,9 +90,10 @@ export async function generateMetadata({
|
|
|
88
90
|
};
|
|
89
91
|
} catch (error) {
|
|
90
92
|
console.error("Error generating metadata:", error);
|
|
93
|
+
const t = await getTranslations("productPage");
|
|
91
94
|
return {
|
|
92
|
-
title: "
|
|
93
|
-
description: "
|
|
95
|
+
title: t("title"),
|
|
96
|
+
description: t("viewDetails"),
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { useProduct } from "@/lib/graphql/hooks";
|
|
5
6
|
import { useCurrencyStore } from "@doswiftly/storefront-sdk/react";
|
|
6
7
|
import { ProductGallery } from "@/components/product/product-gallery";
|
|
@@ -35,6 +36,7 @@ export interface ProductClientProps {
|
|
|
35
36
|
* Requirements: 2.2, 3.2, 6.4
|
|
36
37
|
*/
|
|
37
38
|
export function ProductClient({ product: initialProduct, similarProducts = [] }: ProductClientProps) {
|
|
39
|
+
const t = useTranslations("productPage");
|
|
38
40
|
const [selectedVariant, setSelectedVariant] = useState(initialProduct.variants[0]);
|
|
39
41
|
const [quantity, setQuantity] = useState(1);
|
|
40
42
|
|
|
@@ -288,7 +290,7 @@ export function ProductClient({ product: initialProduct, similarProducts = [] }:
|
|
|
288
290
|
{similarProducts.length > 0 && (
|
|
289
291
|
<SimilarProducts
|
|
290
292
|
products={similarProducts}
|
|
291
|
-
title="
|
|
293
|
+
title={t("youMightAlsoLike")}
|
|
292
294
|
columns={4}
|
|
293
295
|
/>
|
|
294
296
|
)}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Suspense } from "react";
|
|
2
|
+
import { getTranslations } from "next-intl/server";
|
|
2
3
|
import { Breadcrumbs } from "@/components/layout/breadcrumbs";
|
|
3
4
|
import { Spinner } from "@/components/ui/spinner";
|
|
4
5
|
import { ProductsClient } from "./products-client";
|
|
5
6
|
|
|
6
|
-
export default function ProductsPage() {
|
|
7
|
+
export default async function ProductsPage() {
|
|
8
|
+
const t = await getTranslations("product");
|
|
9
|
+
|
|
7
10
|
return (
|
|
8
11
|
<div className="container mx-auto px-4 py-8">
|
|
9
12
|
{/* Breadcrumbs */}
|
|
@@ -11,9 +14,9 @@ export default function ProductsPage() {
|
|
|
11
14
|
|
|
12
15
|
{/* Page Header */}
|
|
13
16
|
<div className="mb-8">
|
|
14
|
-
<h1 className="text-3xl font-bold text-foreground">
|
|
17
|
+
<h1 className="text-3xl font-bold text-foreground">{t("allProducts")}</h1>
|
|
15
18
|
<p className="mt-2 text-muted-foreground">
|
|
16
|
-
|
|
19
|
+
{t("browseCollection")}
|
|
17
20
|
</p>
|
|
18
21
|
</div>
|
|
19
22
|
|
package/templates/storefront-nextjs-shadcn/app/{products → [locale]/products}/products-client.tsx
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useCallback, useMemo, useTransition } from "react";
|
|
4
|
-
import { useSearchParams
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
import { useRouter, usePathname } from "@/i18n/navigation";
|
|
5
7
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
6
8
|
import { ProductGrid } from "@/components/product/product-grid";
|
|
7
9
|
import { ProductFilters, type FilterGroup } from "@/components/product/product-filters";
|
|
@@ -42,6 +44,8 @@ export function ProductsClient() {
|
|
|
42
44
|
const router = useRouter();
|
|
43
45
|
const pathname = usePathname();
|
|
44
46
|
const [isPending, startTransition] = useTransition();
|
|
47
|
+
const t = useTranslations("product");
|
|
48
|
+
const tFilters = useTranslations("filters");
|
|
45
49
|
|
|
46
50
|
// ========== Parse URL State ==========
|
|
47
51
|
const page = parseInt(searchParams.get("page") || "1", 10);
|
|
@@ -151,7 +155,7 @@ export function ProductsClient() {
|
|
|
151
155
|
if (availableFilters?.categories && availableFilters.categories.length > 0) {
|
|
152
156
|
groups.push({
|
|
153
157
|
id: "category",
|
|
154
|
-
label: "
|
|
158
|
+
label: t("category"),
|
|
155
159
|
type: "checkbox",
|
|
156
160
|
options: availableFilters.categories.map((cat) => ({
|
|
157
161
|
label: cat.name,
|
|
@@ -168,7 +172,7 @@ export function ProductsClient() {
|
|
|
168
172
|
if (minPrice !== maxPrice) {
|
|
169
173
|
groups.push({
|
|
170
174
|
id: "price",
|
|
171
|
-
label: "
|
|
175
|
+
label: t("price"),
|
|
172
176
|
type: "range",
|
|
173
177
|
min: Math.floor(minPrice),
|
|
174
178
|
max: Math.ceil(maxPrice),
|
|
@@ -210,7 +214,7 @@ export function ProductsClient() {
|
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
return groups;
|
|
213
|
-
}, [availableFilters]);
|
|
217
|
+
}, [availableFilters, t]);
|
|
214
218
|
|
|
215
219
|
// ========== Selected Filters (from URL) ==========
|
|
216
220
|
const selectedFilters: Record<string, string[]> = useMemo(() => {
|
|
@@ -239,7 +243,7 @@ export function ProductsClient() {
|
|
|
239
243
|
const cat = availableFilters.categories.find((c) => c.id === catId);
|
|
240
244
|
pills.push({
|
|
241
245
|
filterId: "category",
|
|
242
|
-
label: "
|
|
246
|
+
label: t("category"),
|
|
243
247
|
value: catId,
|
|
244
248
|
displayValue: cat?.name || catId,
|
|
245
249
|
});
|
|
@@ -252,7 +256,7 @@ export function ProductsClient() {
|
|
|
252
256
|
availableFilters?.priceRange?.min.currencyCode || "PLN";
|
|
253
257
|
pills.push({
|
|
254
258
|
filterId: "price",
|
|
255
|
-
label: "
|
|
259
|
+
label: t("price"),
|
|
256
260
|
value: `${priceMin || 0}-${priceMax || "∞"}`,
|
|
257
261
|
displayValue: `${priceMin || 0} — ${priceMax || "∞"} ${currency}`,
|
|
258
262
|
});
|
|
@@ -275,7 +279,7 @@ export function ProductsClient() {
|
|
|
275
279
|
}
|
|
276
280
|
|
|
277
281
|
return pills;
|
|
278
|
-
}, [categoryId, priceMin, priceMax, attributeFilters, availableFilters]);
|
|
282
|
+
}, [categoryId, priceMin, priceMax, attributeFilters, availableFilters, t]);
|
|
279
283
|
|
|
280
284
|
const activeFilterCount = activePills.length;
|
|
281
285
|
|
|
@@ -368,8 +372,8 @@ export function ProductsClient() {
|
|
|
368
372
|
/>
|
|
369
373
|
<p className="text-sm text-muted-foreground">
|
|
370
374
|
{totalCount > 0
|
|
371
|
-
?
|
|
372
|
-
: "
|
|
375
|
+
? tFilters("productCount", { count: totalCount })
|
|
376
|
+
: t("noProducts")}
|
|
373
377
|
</p>
|
|
374
378
|
</div>
|
|
375
379
|
<ProductSort value={sort} onChange={handleSortChange} />
|
|
@@ -423,7 +427,7 @@ export function ProductsClient() {
|
|
|
423
427
|
columns={3}
|
|
424
428
|
priorityCount={6}
|
|
425
429
|
showBadges
|
|
426
|
-
emptyMessage="
|
|
430
|
+
emptyMessage={t("noProductsMatch")}
|
|
427
431
|
onResetFilters={handleClearAll}
|
|
428
432
|
/>
|
|
429
433
|
)}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* and quick add to cart functionality.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import { useTranslations } from 'next-intl';
|
|
11
|
+
import { Link } from '@/i18n/navigation';
|
|
11
12
|
import { Heart, ShoppingCart, Trash2 } from 'lucide-react';
|
|
12
13
|
import { Button } from '@/components/ui/button';
|
|
13
14
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
@@ -20,6 +21,7 @@ import { useCartActions } from '@/hooks/use-cart-actions';
|
|
|
20
21
|
import { toast } from 'sonner';
|
|
21
22
|
|
|
22
23
|
export default function WishlistPage() {
|
|
24
|
+
const t = useTranslations('wishlist');
|
|
23
25
|
const {
|
|
24
26
|
wishlists,
|
|
25
27
|
getActiveWishlist,
|
|
@@ -35,7 +37,7 @@ export default function WishlistPage() {
|
|
|
35
37
|
const handleClearWishlist = () => {
|
|
36
38
|
if (activeWishlist) {
|
|
37
39
|
clearWishlist(activeWishlist.id);
|
|
38
|
-
toast.success('
|
|
40
|
+
toast.success(t('cleared'));
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
43
|
|
|
@@ -44,21 +46,13 @@ export default function WishlistPage() {
|
|
|
44
46
|
|
|
45
47
|
for (const item of items) {
|
|
46
48
|
try {
|
|
47
|
-
await addToCart(
|
|
48
|
-
variantId: item.variantId || item.productId,
|
|
49
|
-
productId: item.productId,
|
|
50
|
-
productTitle: item.productTitle,
|
|
51
|
-
variantTitle: item.variantTitle || '',
|
|
52
|
-
price: item.price,
|
|
53
|
-
image: item.image,
|
|
54
|
-
available: true,
|
|
55
|
-
});
|
|
49
|
+
await addToCart(item.variantId || item.productId);
|
|
56
50
|
} catch {
|
|
57
51
|
// Individual item errors are handled by addToCart (shows toast)
|
|
58
52
|
}
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
toast.success(
|
|
55
|
+
toast.success(t('addedToCart', { count: items.length }));
|
|
62
56
|
};
|
|
63
57
|
|
|
64
58
|
// Show loading state while hydrating from localStorage
|
|
@@ -78,8 +72,8 @@ export default function WishlistPage() {
|
|
|
78
72
|
<div className="container max-w-4xl py-8">
|
|
79
73
|
<Breadcrumbs
|
|
80
74
|
items={[
|
|
81
|
-
{ label: '
|
|
82
|
-
{ label: '
|
|
75
|
+
{ label: t('home'), href: '/' },
|
|
76
|
+
{ label: t('title') },
|
|
83
77
|
]}
|
|
84
78
|
/>
|
|
85
79
|
|
|
@@ -87,12 +81,12 @@ export default function WishlistPage() {
|
|
|
87
81
|
<div>
|
|
88
82
|
<h1 className="text-3xl font-bold tracking-tight flex items-center gap-3">
|
|
89
83
|
<Heart className="h-8 w-8 text-red-500" />
|
|
90
|
-
|
|
84
|
+
{t('title')}
|
|
91
85
|
</h1>
|
|
92
86
|
<p className="text-muted-foreground mt-1">
|
|
93
87
|
{items.length === 0
|
|
94
|
-
? '
|
|
95
|
-
:
|
|
88
|
+
? t('empty')
|
|
89
|
+
: t('itemCount', { count: items.length })}
|
|
96
90
|
</p>
|
|
97
91
|
</div>
|
|
98
92
|
|
|
@@ -100,11 +94,11 @@ export default function WishlistPage() {
|
|
|
100
94
|
<div className="flex gap-2">
|
|
101
95
|
<Button variant="outline" size="sm" onClick={handleClearWishlist}>
|
|
102
96
|
<Trash2 className="h-4 w-4 mr-2" />
|
|
103
|
-
|
|
97
|
+
{t('clearList')}
|
|
104
98
|
</Button>
|
|
105
99
|
<Button size="sm" onClick={handleAddAllToCart}>
|
|
106
100
|
<ShoppingCart className="h-4 w-4 mr-2" />
|
|
107
|
-
|
|
101
|
+
{t('addAllToCart')}
|
|
108
102
|
</Button>
|
|
109
103
|
</div>
|
|
110
104
|
)}
|
|
@@ -113,11 +107,11 @@ export default function WishlistPage() {
|
|
|
113
107
|
{items.length === 0 ? (
|
|
114
108
|
<EmptyState
|
|
115
109
|
icon={<Heart className="h-12 w-12" />}
|
|
116
|
-
title=
|
|
117
|
-
description=
|
|
110
|
+
title={t('empty')}
|
|
111
|
+
description={t('emptyDescription')}
|
|
118
112
|
action={
|
|
119
113
|
<Link href="/products">
|
|
120
|
-
<Button>
|
|
114
|
+
<Button>{t('browseProducts')}</Button>
|
|
121
115
|
</Link>
|
|
122
116
|
}
|
|
123
117
|
/>
|
|
@@ -144,6 +138,8 @@ export default function WishlistPage() {
|
|
|
144
138
|
* Shows summary of price changes for all items
|
|
145
139
|
*/
|
|
146
140
|
function PriceChangeSummary({ items }: { items: Array<{ price: { amount: string }; priceAtAdd: { amount: string } }> }) {
|
|
141
|
+
const t = useTranslations('wishlist');
|
|
142
|
+
|
|
147
143
|
const itemsWithPriceDrops = items.filter((item) => {
|
|
148
144
|
const current = parseFloat(item.price.amount);
|
|
149
145
|
const original = parseFloat(item.priceAtAdd.amount);
|
|
@@ -162,13 +158,13 @@ function PriceChangeSummary({ items }: { items: Array<{ price: { amount: string
|
|
|
162
158
|
<Card className="border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950">
|
|
163
159
|
<CardHeader className="pb-2">
|
|
164
160
|
<CardTitle className="text-green-700 dark:text-green-400 text-lg flex items-center gap-2">
|
|
165
|
-
|
|
161
|
+
{t('goodNews')}
|
|
166
162
|
</CardTitle>
|
|
167
163
|
</CardHeader>
|
|
168
164
|
<CardContent>
|
|
169
165
|
<p className="text-green-700 dark:text-green-400">
|
|
170
|
-
{
|
|
171
|
-
|
|
166
|
+
{t('priceDropped', { count: itemsWithPriceDrops.length })}{' '}
|
|
167
|
+
{t('totalSavings')}{' '}
|
|
172
168
|
<span className="font-semibold">
|
|
173
169
|
{new Intl.NumberFormat('pl-PL', {
|
|
174
170
|
style: 'currency',
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { Inter } from "next/font/google";
|
|
3
|
-
import { cookies } from "next/headers";
|
|
4
3
|
import "./globals.css";
|
|
5
|
-
import {
|
|
6
|
-
import { Footer } from "@/components/layout/footer";
|
|
7
|
-
import { QueryProvider } from "@/components/providers/query-provider";
|
|
8
|
-
import { StorefrontProvider } from "@doswiftly/storefront-sdk/react";
|
|
9
|
-
import { ThemeProvider } from "@/components/providers/theme-provider";
|
|
10
|
-
import { StoresProvider } from "@/components/providers/stores-provider";
|
|
11
|
-
import { Toaster } from "sonner";
|
|
12
|
-
import { fetchShop } from "@/lib/graphql/server";
|
|
13
|
-
import type { ShopQuery } from "@/generated/graphql";
|
|
14
|
-
import { themeConfig } from "@/lib/theme/theme-config";
|
|
15
|
-
import { graphqlConfig } from "@/lib/graphql/config";
|
|
16
|
-
import { AUTH_COOKIE_NAME } from "@doswiftly/storefront-sdk";
|
|
4
|
+
import { defaultLocale } from "@/i18n/routing";
|
|
17
5
|
|
|
18
6
|
const inter = Inter({ subsets: ["latin"] });
|
|
19
7
|
|
|
@@ -26,67 +14,17 @@ export const metadata: Metadata = {
|
|
|
26
14
|
description: "Welcome to our online store powered by DoSwiftly Commerce",
|
|
27
15
|
};
|
|
28
16
|
|
|
29
|
-
// Enable ISR with 60 second revalidation for shop data (currencies)
|
|
30
|
-
export const revalidate = 60;
|
|
31
|
-
|
|
32
|
-
const FALLBACK_SHOP: ShopQuery = {
|
|
33
|
-
shop: {
|
|
34
|
-
id: 'fallback',
|
|
35
|
-
name: process.env.NEXT_PUBLIC_SITE_NAME || 'Store',
|
|
36
|
-
currencyCode: 'PLN',
|
|
37
|
-
supportedCurrencies: ['PLN'],
|
|
38
|
-
paymentCurrencies: ['PLN'],
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
17
|
export default async function RootLayout({
|
|
43
18
|
children,
|
|
19
|
+
params,
|
|
44
20
|
}: {
|
|
45
21
|
children: React.ReactNode;
|
|
22
|
+
params: Promise<{ locale?: string }>;
|
|
46
23
|
}) {
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
shopData = await fetchShop();
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error('[RootLayout] Failed to fetch shop data, using fallback:', error instanceof Error ? error.message : error);
|
|
52
|
-
shopData = FALLBACK_SHOP;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Read httpOnly auth cookie — invisible to JS, so we pass it as a prop
|
|
56
|
-
const cookieStore = await cookies();
|
|
57
|
-
const hasAuthToken = !!cookieStore.get(AUTH_COOKIE_NAME)?.value;
|
|
58
|
-
|
|
24
|
+
const { locale } = await params;
|
|
59
25
|
return (
|
|
60
|
-
<html lang=
|
|
61
|
-
<body className={inter.className}>
|
|
62
|
-
<ThemeProvider
|
|
63
|
-
attribute={themeConfig.attribute}
|
|
64
|
-
defaultTheme={themeConfig.defaultTheme}
|
|
65
|
-
enableSystem={themeConfig.enableSystem}
|
|
66
|
-
disableTransitionOnChange={themeConfig.disableTransitionOnChange}
|
|
67
|
-
storageKey={themeConfig.storageKey}
|
|
68
|
-
>
|
|
69
|
-
<QueryProvider>
|
|
70
|
-
<StorefrontProvider
|
|
71
|
-
config={{
|
|
72
|
-
apiUrl: graphqlConfig.apiUrl,
|
|
73
|
-
shopSlug: graphqlConfig.shopSlug,
|
|
74
|
-
}}
|
|
75
|
-
shopData={shopData.shop}
|
|
76
|
-
initialIsAuthenticated={hasAuthToken}
|
|
77
|
-
>
|
|
78
|
-
<StoresProvider>
|
|
79
|
-
<div className="flex min-h-screen flex-col">
|
|
80
|
-
<Header />
|
|
81
|
-
<main className="flex-1">{children}</main>
|
|
82
|
-
<Footer />
|
|
83
|
-
</div>
|
|
84
|
-
<Toaster position="bottom-right" richColors />
|
|
85
|
-
</StoresProvider>
|
|
86
|
-
</StorefrontProvider>
|
|
87
|
-
</QueryProvider>
|
|
88
|
-
</ThemeProvider>
|
|
89
|
-
</body>
|
|
26
|
+
<html lang={locale || defaultLocale} suppressHydrationWarning>
|
|
27
|
+
<body className={inter.className}>{children}</body>
|
|
90
28
|
</html>
|
|
91
29
|
);
|
|
92
30
|
}
|