@doswiftly/cli 0.1.19 → 0.1.21
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 +225 -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,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { usePathname } from "
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Link, usePathname } from "@/i18n/navigation";
|
|
5
5
|
import { ChevronRight, Home } from "lucide-react";
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
|
|
@@ -31,15 +31,33 @@ export interface BreadcrumbsProps {
|
|
|
31
31
|
* { label: "Laptops", href: "/products/electronics/laptops" }
|
|
32
32
|
* ]} />
|
|
33
33
|
*/
|
|
34
|
+
// Known segment keys that exist in nav translations
|
|
35
|
+
const SEGMENT_TRANSLATION_KEYS: Record<string, string> = {
|
|
36
|
+
products: "products",
|
|
37
|
+
collections: "collections",
|
|
38
|
+
categories: "categories",
|
|
39
|
+
about: "about",
|
|
40
|
+
brands: "brands",
|
|
41
|
+
blog: "blog",
|
|
42
|
+
cart: "cart",
|
|
43
|
+
account: "account",
|
|
44
|
+
contact: "contact",
|
|
45
|
+
shipping: "shipping",
|
|
46
|
+
returns: "returns",
|
|
47
|
+
search: "searchBreadcrumb",
|
|
48
|
+
wishlist: "wishlist",
|
|
49
|
+
};
|
|
50
|
+
|
|
34
51
|
export function Breadcrumbs({
|
|
35
52
|
items,
|
|
36
53
|
className,
|
|
37
54
|
showHome = true,
|
|
38
55
|
}: BreadcrumbsProps) {
|
|
56
|
+
const t = useTranslations("nav");
|
|
39
57
|
const pathname = usePathname();
|
|
40
58
|
|
|
41
59
|
// Auto-generate breadcrumbs from pathname if items not provided
|
|
42
|
-
const breadcrumbItems = items || generateBreadcrumbs(pathname);
|
|
60
|
+
const breadcrumbItems = items || generateBreadcrumbs(pathname, t);
|
|
43
61
|
|
|
44
62
|
// Don't show breadcrumbs on homepage
|
|
45
63
|
if (pathname === "/" || breadcrumbItems.length === 0) {
|
|
@@ -48,7 +66,7 @@ export function Breadcrumbs({
|
|
|
48
66
|
|
|
49
67
|
return (
|
|
50
68
|
<nav
|
|
51
|
-
aria-label="
|
|
69
|
+
aria-label={t("breadcrumb")}
|
|
52
70
|
className={cn("flex items-center gap-2 text-sm", className)}
|
|
53
71
|
>
|
|
54
72
|
<ol className="flex items-center gap-2">
|
|
@@ -57,7 +75,7 @@ export function Breadcrumbs({
|
|
|
57
75
|
<Link
|
|
58
76
|
href="/"
|
|
59
77
|
className="flex items-center text-muted-foreground hover:text-foreground transition-colors"
|
|
60
|
-
aria-label="
|
|
78
|
+
aria-label={t("home")}
|
|
61
79
|
>
|
|
62
80
|
<Home className="h-4 w-4" />
|
|
63
81
|
</Link>
|
|
@@ -101,20 +119,27 @@ export function Breadcrumbs({
|
|
|
101
119
|
|
|
102
120
|
/**
|
|
103
121
|
* Generate breadcrumb items from pathname
|
|
122
|
+
* Uses translations for known segments, falls back to capitalized segment name
|
|
104
123
|
*/
|
|
105
|
-
function generateBreadcrumbs(
|
|
124
|
+
function generateBreadcrumbs(
|
|
125
|
+
pathname: string,
|
|
126
|
+
t: (key: string) => string,
|
|
127
|
+
): BreadcrumbItem[] {
|
|
106
128
|
const segments = pathname.split("/").filter(Boolean);
|
|
107
129
|
const breadcrumbs: BreadcrumbItem[] = [];
|
|
108
130
|
|
|
109
131
|
let currentPath = "";
|
|
110
132
|
for (const segment of segments) {
|
|
111
133
|
currentPath += `/${segment}`;
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
|
|
135
|
+
// Use translation for known segments, fallback to capitalized segment
|
|
136
|
+
const translationKey = SEGMENT_TRANSLATION_KEYS[segment];
|
|
137
|
+
const label = translationKey
|
|
138
|
+
? t(translationKey)
|
|
139
|
+
: segment
|
|
140
|
+
.split("-")
|
|
141
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
142
|
+
.join(" ");
|
|
118
143
|
|
|
119
144
|
breadcrumbs.push({
|
|
120
145
|
label,
|
|
@@ -21,6 +21,7 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
21
21
|
import { useCurrencyStore } from '@doswiftly/storefront-sdk/react';
|
|
22
22
|
import { useQueryClient } from '@tanstack/react-query';
|
|
23
23
|
import { ChevronDown, Check, Globe } from 'lucide-react';
|
|
24
|
+
import { useTranslations } from 'next-intl';
|
|
24
25
|
|
|
25
26
|
// ============================================================================
|
|
26
27
|
// CURRENCY DATA
|
|
@@ -149,6 +150,8 @@ export function CurrencySelector({
|
|
|
149
150
|
showIcon = true,
|
|
150
151
|
variant = 'default',
|
|
151
152
|
}: CurrencySelectorProps) {
|
|
153
|
+
const t = useTranslations("currency");
|
|
154
|
+
|
|
152
155
|
// Currency store state
|
|
153
156
|
const currency = useCurrencyStore((state) => state.currency);
|
|
154
157
|
const supportedCurrencies = useCurrencyStore((state) => state.supportedCurrencies);
|
|
@@ -261,7 +264,7 @@ export function CurrencySelector({
|
|
|
261
264
|
`}
|
|
262
265
|
aria-expanded={isOpen}
|
|
263
266
|
aria-haspopup="listbox"
|
|
264
|
-
aria-label="
|
|
267
|
+
aria-label={t("selectCurrency")}
|
|
265
268
|
>
|
|
266
269
|
{showIcon && variant !== 'minimal' && (
|
|
267
270
|
<Globe className="w-4 h-4 text-gray-400" aria-hidden="true" />
|
|
@@ -287,7 +290,7 @@ export function CurrencySelector({
|
|
|
287
290
|
${dropdownStyles[variant]}
|
|
288
291
|
`}
|
|
289
292
|
role="listbox"
|
|
290
|
-
aria-label="
|
|
293
|
+
aria-label={t("currencyOptions")}
|
|
291
294
|
>
|
|
292
295
|
<div className="py-1 max-h-64 overflow-y-auto">
|
|
293
296
|
{supportedCurrencies.map((code) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import Link from "
|
|
1
|
+
import { Link } from "@/i18n/navigation";
|
|
2
|
+
import { getTranslations } from "next-intl/server";
|
|
2
3
|
|
|
3
|
-
export function Footer() {
|
|
4
|
+
export async function Footer() {
|
|
4
5
|
const currentYear = new Date().getFullYear();
|
|
5
6
|
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "My Store";
|
|
7
|
+
const t = await getTranslations("footer");
|
|
8
|
+
const tc = await getTranslations("common");
|
|
6
9
|
|
|
7
10
|
return (
|
|
8
11
|
<footer className="border-t border-border bg-muted/50">
|
|
@@ -14,33 +17,32 @@ export function Footer() {
|
|
|
14
17
|
{siteName}
|
|
15
18
|
</Link>
|
|
16
19
|
<p className="mt-4 text-sm text-muted-foreground">
|
|
17
|
-
|
|
18
|
-
Commerce.
|
|
20
|
+
{t("description")}
|
|
19
21
|
</p>
|
|
20
22
|
</div>
|
|
21
23
|
|
|
22
24
|
{/* Shop */}
|
|
23
25
|
<div>
|
|
24
|
-
<h3 className="mb-4 font-semibold text-foreground">
|
|
26
|
+
<h3 className="mb-4 font-semibold text-foreground">{t("shop")}</h3>
|
|
25
27
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
26
28
|
<li>
|
|
27
29
|
<Link href="/products" className="hover:text-primary transition-colors">
|
|
28
|
-
|
|
30
|
+
{t("allProducts")}
|
|
29
31
|
</Link>
|
|
30
32
|
</li>
|
|
31
33
|
<li>
|
|
32
34
|
<Link href="/collections" className="hover:text-primary transition-colors">
|
|
33
|
-
|
|
35
|
+
{t("collections")}
|
|
34
36
|
</Link>
|
|
35
37
|
</li>
|
|
36
38
|
<li>
|
|
37
39
|
<Link href="/categories" className="hover:text-primary transition-colors">
|
|
38
|
-
|
|
40
|
+
{t("categories")}
|
|
39
41
|
</Link>
|
|
40
42
|
</li>
|
|
41
43
|
<li>
|
|
42
44
|
<Link href="/search" className="hover:text-primary transition-colors">
|
|
43
|
-
|
|
45
|
+
{tc("search")}
|
|
44
46
|
</Link>
|
|
45
47
|
</li>
|
|
46
48
|
</ul>
|
|
@@ -48,31 +50,31 @@ export function Footer() {
|
|
|
48
50
|
|
|
49
51
|
{/* Account */}
|
|
50
52
|
<div>
|
|
51
|
-
<h3 className="mb-4 font-semibold text-foreground">
|
|
53
|
+
<h3 className="mb-4 font-semibold text-foreground">{t("myAccount")}</h3>
|
|
52
54
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
53
55
|
<li>
|
|
54
56
|
<Link href="/auth/login" className="hover:text-primary transition-colors">
|
|
55
|
-
|
|
57
|
+
{t("signIn")}
|
|
56
58
|
</Link>
|
|
57
59
|
</li>
|
|
58
60
|
<li>
|
|
59
61
|
<Link href="/auth/register" className="hover:text-primary transition-colors">
|
|
60
|
-
|
|
62
|
+
{t("createAccount")}
|
|
61
63
|
</Link>
|
|
62
64
|
</li>
|
|
63
65
|
<li>
|
|
64
66
|
<Link href="/account" className="hover:text-primary transition-colors">
|
|
65
|
-
|
|
67
|
+
{t("myAccount")}
|
|
66
68
|
</Link>
|
|
67
69
|
</li>
|
|
68
70
|
<li>
|
|
69
71
|
<Link href="/account/orders" className="hover:text-primary transition-colors">
|
|
70
|
-
|
|
72
|
+
{t("orderHistory")}
|
|
71
73
|
</Link>
|
|
72
74
|
</li>
|
|
73
75
|
<li>
|
|
74
76
|
<Link href="/cart" className="hover:text-primary transition-colors">
|
|
75
|
-
|
|
77
|
+
{t("shoppingCart")}
|
|
76
78
|
</Link>
|
|
77
79
|
</li>
|
|
78
80
|
</ul>
|
|
@@ -80,26 +82,26 @@ export function Footer() {
|
|
|
80
82
|
|
|
81
83
|
{/* Support */}
|
|
82
84
|
<div>
|
|
83
|
-
<h3 className="mb-4 font-semibold text-foreground">
|
|
85
|
+
<h3 className="mb-4 font-semibold text-foreground">{t("support")}</h3>
|
|
84
86
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
85
87
|
<li>
|
|
86
88
|
<Link href="/contact" className="hover:text-primary transition-colors">
|
|
87
|
-
|
|
89
|
+
{t("contactUs")}
|
|
88
90
|
</Link>
|
|
89
91
|
</li>
|
|
90
92
|
<li>
|
|
91
93
|
<Link href="/about" className="hover:text-primary transition-colors">
|
|
92
|
-
|
|
94
|
+
{t("aboutUs")}
|
|
93
95
|
</Link>
|
|
94
96
|
</li>
|
|
95
97
|
<li>
|
|
96
98
|
<Link href="/shipping" className="hover:text-primary transition-colors">
|
|
97
|
-
|
|
99
|
+
{t("shippingInfo")}
|
|
98
100
|
</Link>
|
|
99
101
|
</li>
|
|
100
102
|
<li>
|
|
101
103
|
<Link href="/returns" className="hover:text-primary transition-colors">
|
|
102
|
-
|
|
104
|
+
{t("returnsRefunds")}
|
|
103
105
|
</Link>
|
|
104
106
|
</li>
|
|
105
107
|
</ul>
|
|
@@ -108,17 +110,16 @@ export function Footer() {
|
|
|
108
110
|
|
|
109
111
|
<div className="mt-12 border-t border-border pt-8 text-center text-sm text-muted-foreground">
|
|
110
112
|
<p>
|
|
111
|
-
© {currentYear} {siteName}.
|
|
113
|
+
© {currentYear} {siteName}. {t("allRightsReserved")}.
|
|
112
114
|
</p>
|
|
113
115
|
<p className="mt-2">
|
|
114
|
-
Powered by{" "}
|
|
115
116
|
<a
|
|
116
117
|
href="https://doswiftly.pl"
|
|
117
118
|
target="_blank"
|
|
118
119
|
rel="noopener noreferrer"
|
|
119
120
|
className="text-primary hover:underline transition-colors"
|
|
120
121
|
>
|
|
121
|
-
|
|
122
|
+
{t("poweredBy")}
|
|
122
123
|
</a>
|
|
123
124
|
</p>
|
|
124
125
|
</div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import Link from "next/link";
|
|
4
3
|
import { useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { Link } from "@/i18n/navigation";
|
|
5
6
|
import { Menu, X } from "lucide-react";
|
|
6
7
|
import { CurrencySelector } from "@/components/commerce/currency-selector";
|
|
7
8
|
import { CartIcon } from "@/components/cart/cart-icon";
|
|
@@ -9,11 +10,13 @@ import { SearchInput } from "@/components/commerce/search-input";
|
|
|
9
10
|
import { Navigation } from "@/components/layout/navigation";
|
|
10
11
|
import { AccountMenu } from "@/components/auth/account-menu";
|
|
11
12
|
import { ThemeSwitcher } from "@/components/layout/theme-switcher";
|
|
13
|
+
import { LanguageSwitcher } from "@/components/layout/language-switcher";
|
|
12
14
|
import { useAuthStore, useAuthHydrated } from "@doswiftly/storefront-sdk/react";
|
|
13
15
|
import { useHydrated } from "@doswiftly/storefront-sdk/react";
|
|
14
16
|
import { useAuthSync } from "@/hooks/use-auth-sync";
|
|
15
17
|
|
|
16
18
|
export function Header() {
|
|
19
|
+
const t = useTranslations("nav");
|
|
17
20
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
18
21
|
|
|
19
22
|
// Auto-fix cookie/store desync (e.g., localStorage cleared but cookie persists)
|
|
@@ -30,19 +33,19 @@ export function Header() {
|
|
|
30
33
|
|
|
31
34
|
const navigationItems = [
|
|
32
35
|
{
|
|
33
|
-
label: "
|
|
36
|
+
label: t("products"),
|
|
34
37
|
href: "/products",
|
|
35
38
|
},
|
|
36
39
|
{
|
|
37
|
-
label: "
|
|
40
|
+
label: t("collections"),
|
|
38
41
|
href: "/collections",
|
|
39
42
|
},
|
|
40
43
|
{
|
|
41
|
-
label: "
|
|
44
|
+
label: t("categories"),
|
|
42
45
|
href: "/categories",
|
|
43
46
|
},
|
|
44
47
|
{
|
|
45
|
-
label: "
|
|
48
|
+
label: t("about"),
|
|
46
49
|
href: "/about",
|
|
47
50
|
},
|
|
48
51
|
];
|
|
@@ -65,7 +68,12 @@ export function Header() {
|
|
|
65
68
|
<div className="flex items-center gap-3">
|
|
66
69
|
{/* Search */}
|
|
67
70
|
<div className="hidden w-64 lg:block">
|
|
68
|
-
<SearchInput placeholder="
|
|
71
|
+
<SearchInput placeholder={t("search")} />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Language Switcher */}
|
|
75
|
+
<div className="hidden md:block">
|
|
76
|
+
<LanguageSwitcher />
|
|
69
77
|
</div>
|
|
70
78
|
|
|
71
79
|
{/* Currency Selector */}
|
|
@@ -87,7 +95,7 @@ export function Header() {
|
|
|
87
95
|
href="/auth/login"
|
|
88
96
|
className="hidden md:inline-flex items-center rounded-md px-3 py-2 text-sm font-medium text-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
89
97
|
>
|
|
90
|
-
|
|
98
|
+
{t("signIn")}
|
|
91
99
|
</Link>
|
|
92
100
|
)
|
|
93
101
|
)}
|
|
@@ -99,7 +107,7 @@ export function Header() {
|
|
|
99
107
|
<button
|
|
100
108
|
className="p-2 text-foreground hover:text-primary md:hidden"
|
|
101
109
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
102
|
-
aria-label="
|
|
110
|
+
aria-label={t("menu")}
|
|
103
111
|
aria-expanded={isMenuOpen}
|
|
104
112
|
>
|
|
105
113
|
{isMenuOpen ? (
|
|
@@ -117,12 +125,12 @@ export function Header() {
|
|
|
117
125
|
<div className="flex flex-col gap-4">
|
|
118
126
|
{/* Mobile Search */}
|
|
119
127
|
<div className="lg:hidden">
|
|
120
|
-
<SearchInput placeholder="
|
|
128
|
+
<SearchInput placeholder={t("search")} />
|
|
121
129
|
</div>
|
|
122
130
|
|
|
123
|
-
{/* Mobile Currency
|
|
131
|
+
{/* Mobile Language & Currency */}
|
|
124
132
|
<div className="flex items-center justify-between">
|
|
125
|
-
<
|
|
133
|
+
<LanguageSwitcher />
|
|
126
134
|
<CurrencySelector variant="compact" />
|
|
127
135
|
</div>
|
|
128
136
|
|
|
@@ -145,7 +153,7 @@ export function Header() {
|
|
|
145
153
|
className="text-foreground hover:text-primary transition-colors font-medium"
|
|
146
154
|
onClick={() => setIsMenuOpen(false)}
|
|
147
155
|
>
|
|
148
|
-
|
|
156
|
+
{t("signIn")}
|
|
149
157
|
</Link>
|
|
150
158
|
)}
|
|
151
159
|
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useLanguageStore } from "@doswiftly/storefront-sdk/react";
|
|
4
|
+
import { useRouter, usePathname } from "@/i18n/navigation";
|
|
5
|
+
import { useLocale, useTranslations } from "next-intl";
|
|
6
|
+
import {
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from "@/components/ui/select";
|
|
13
|
+
|
|
14
|
+
const LANGUAGE_NAMES: Record<string, string> = {
|
|
15
|
+
pl: "Polski",
|
|
16
|
+
en: "English",
|
|
17
|
+
de: "Deutsch",
|
|
18
|
+
fr: "Français",
|
|
19
|
+
es: "Español",
|
|
20
|
+
cs: "Čeština",
|
|
21
|
+
it: "Italiano",
|
|
22
|
+
nl: "Nederlands",
|
|
23
|
+
pt: "Português",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function LanguageSwitcher() {
|
|
27
|
+
const t = useTranslations("language");
|
|
28
|
+
const locale = useLocale();
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const pathname = usePathname();
|
|
31
|
+
const supportedLanguages = useLanguageStore((s) => s.supportedLanguages);
|
|
32
|
+
|
|
33
|
+
if (supportedLanguages.length < 2) return null;
|
|
34
|
+
|
|
35
|
+
const handleLocaleChange = (newLocale: string) => {
|
|
36
|
+
if (newLocale === locale) return;
|
|
37
|
+
router.replace(pathname, { locale: newLocale });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Select value={locale} onValueChange={handleLocaleChange}>
|
|
42
|
+
<SelectTrigger className="h-8 w-auto gap-1 border-0 bg-transparent px-2 text-xs font-medium shadow-none" aria-label={t("switchLanguage")}>
|
|
43
|
+
<SelectValue>{locale.toUpperCase()}</SelectValue>
|
|
44
|
+
</SelectTrigger>
|
|
45
|
+
<SelectContent align="end">
|
|
46
|
+
{supportedLanguages.map((lang) => (
|
|
47
|
+
<SelectItem key={lang} value={lang}>
|
|
48
|
+
{lang.toUpperCase()} — {LANGUAGE_NAMES[lang] || lang}
|
|
49
|
+
</SelectItem>
|
|
50
|
+
))}
|
|
51
|
+
</SelectContent>
|
|
52
|
+
</Select>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import
|
|
5
|
-
import { usePathname } from "
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { Link, usePathname } from "@/i18n/navigation";
|
|
6
6
|
import { Menu, X, ChevronDown, ChevronRight } from "lucide-react";
|
|
7
7
|
import { cn } from "@/lib/utils";
|
|
8
8
|
|
|
@@ -17,26 +17,29 @@ export interface MobileMenuProps {
|
|
|
17
17
|
className?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
export function MobileMenu({ items, className }: MobileMenuProps) {
|
|
21
|
+
const t = useTranslations("nav");
|
|
22
|
+
|
|
23
|
+
const defaultItems: MobileMenuItem[] = [
|
|
24
|
+
{
|
|
25
|
+
label: t("products"),
|
|
26
|
+
href: "/products",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: t("collections"),
|
|
30
|
+
href: "/collections",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: t("categories"),
|
|
34
|
+
href: "/categories",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: t("about"),
|
|
38
|
+
href: "/about",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const resolvedItems = items ?? defaultItems;
|
|
40
43
|
const [isOpen, setIsOpen] = useState(false);
|
|
41
44
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
|
42
45
|
const pathname = usePathname();
|
|
@@ -108,7 +111,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
|
|
|
108
111
|
toggleExpanded(item.href);
|
|
109
112
|
}}
|
|
110
113
|
className="p-3 text-muted-foreground hover:text-foreground"
|
|
111
|
-
aria-label={
|
|
114
|
+
aria-label={t("toggleSubmenu", { label: item.label })}
|
|
112
115
|
aria-expanded={isExpanded}
|
|
113
116
|
>
|
|
114
117
|
{isExpanded ? (
|
|
@@ -140,7 +143,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
|
|
|
140
143
|
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
141
144
|
className
|
|
142
145
|
)}
|
|
143
|
-
aria-label="
|
|
146
|
+
aria-label={t("toggleMobileMenu")}
|
|
144
147
|
aria-expanded={isOpen}
|
|
145
148
|
aria-controls="mobile-menu"
|
|
146
149
|
>
|
|
@@ -163,16 +166,16 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
|
|
|
163
166
|
className="fixed inset-y-0 left-0 z-50 w-full max-w-sm bg-background shadow-xl"
|
|
164
167
|
role="dialog"
|
|
165
168
|
aria-modal="true"
|
|
166
|
-
aria-label="
|
|
169
|
+
aria-label={t("mobileNavigation")}
|
|
167
170
|
>
|
|
168
171
|
<div className="flex h-full flex-col">
|
|
169
172
|
{/* Header */}
|
|
170
173
|
<div className="flex items-center justify-between border-b border-border p-4">
|
|
171
|
-
<h2 className="text-lg font-semibold">
|
|
174
|
+
<h2 className="text-lg font-semibold">{t("menu")}</h2>
|
|
172
175
|
<button
|
|
173
176
|
onClick={() => setIsOpen(false)}
|
|
174
177
|
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
175
|
-
aria-label="
|
|
178
|
+
aria-label={t("closeMobileMenu")}
|
|
176
179
|
>
|
|
177
180
|
<X className="h-5 w-5" />
|
|
178
181
|
</button>
|
|
@@ -181,7 +184,7 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
|
|
|
181
184
|
{/* Menu Items */}
|
|
182
185
|
<nav className="flex-1 overflow-y-auto p-4">
|
|
183
186
|
<div className="space-y-1">
|
|
184
|
-
{
|
|
187
|
+
{resolvedItems.map((item) => renderMenuItem(item))}
|
|
185
188
|
</div>
|
|
186
189
|
</nav>
|
|
187
190
|
|
|
@@ -192,13 +195,13 @@ export function MobileMenu({ items = defaultItems, className }: MobileMenuProps)
|
|
|
192
195
|
href="/account"
|
|
193
196
|
className="rounded-md bg-primary px-4 py-2 text-center text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
|
194
197
|
>
|
|
195
|
-
|
|
198
|
+
{t("account")}
|
|
196
199
|
</Link>
|
|
197
200
|
<Link
|
|
198
201
|
href="/cart"
|
|
199
202
|
className="rounded-md border border-border px-4 py-2 text-center text-sm font-medium hover:bg-accent"
|
|
200
203
|
>
|
|
201
|
-
|
|
204
|
+
{t("viewCart")}
|
|
202
205
|
</Link>
|
|
203
206
|
</div>
|
|
204
207
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { usePathname } from "
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
import { Link, usePathname } from "@/i18n/navigation";
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
6
6
|
|
|
7
7
|
export interface NavigationItem {
|
|
@@ -15,28 +15,31 @@ export interface NavigationProps {
|
|
|
15
15
|
className?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
label: "Products",
|
|
21
|
-
href: "/products",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
label: "Collections",
|
|
25
|
-
href: "/collections",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
label: "Categories",
|
|
29
|
-
href: "/categories",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
label: "About",
|
|
33
|
-
href: "/about",
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
export function Navigation({ items = defaultItems, className }: NavigationProps) {
|
|
18
|
+
export function Navigation({ items, className }: NavigationProps) {
|
|
19
|
+
const t = useTranslations("nav");
|
|
38
20
|
const pathname = usePathname();
|
|
39
21
|
|
|
22
|
+
const defaultItems: NavigationItem[] = [
|
|
23
|
+
{
|
|
24
|
+
label: t("products"),
|
|
25
|
+
href: "/products",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: t("collections"),
|
|
29
|
+
href: "/collections",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: t("categories"),
|
|
33
|
+
href: "/categories",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: t("about"),
|
|
37
|
+
href: "/about",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const resolvedItems = items ?? defaultItems;
|
|
42
|
+
|
|
40
43
|
const isActive = (href: string) => {
|
|
41
44
|
if (href === "/") {
|
|
42
45
|
return pathname === "/";
|
|
@@ -48,9 +51,9 @@ export function Navigation({ items = defaultItems, className }: NavigationProps)
|
|
|
48
51
|
<nav
|
|
49
52
|
className={cn("flex items-center gap-6", className)}
|
|
50
53
|
role="navigation"
|
|
51
|
-
aria-label="
|
|
54
|
+
aria-label={t("mainNavigation")}
|
|
52
55
|
>
|
|
53
|
-
{
|
|
56
|
+
{resolvedItems.map((item) => (
|
|
54
57
|
<div key={item.href} className="relative group">
|
|
55
58
|
<Link
|
|
56
59
|
href={item.href}
|