create-brainerce-store 1.51.0 → 1.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/index.js +9 -68
- package/messages/en.json +32 -0
- package/messages/he.json +32 -0
- package/package.json +1 -1
- package/templates/nextjs/base/.claude/commands/design.md +9 -0
- package/templates/nextjs/base/AI-GUIDE.md +52 -3
- package/templates/nextjs/base/CLAUDE.md +10 -0
- package/templates/nextjs/base/package.json.ejs +52 -39
- package/templates/nextjs/base/src/app/globals.css +36 -30
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +14 -10
- package/templates/nextjs/base/src/components/ui/accordion.tsx +53 -0
- package/templates/nextjs/base/src/components/ui/badge.tsx +32 -0
- package/templates/nextjs/base/src/components/ui/button.tsx +49 -0
- package/templates/nextjs/base/src/components/ui/card.tsx +55 -0
- package/templates/nextjs/base/src/components/ui/checkbox.tsx +28 -0
- package/templates/nextjs/base/src/components/ui/dialog.tsx +107 -0
- package/templates/nextjs/base/src/components/ui/input.tsx +22 -0
- package/templates/nextjs/base/src/components/ui/label.tsx +21 -0
- package/templates/nextjs/base/src/components/ui/radio-group.tsx +38 -0
- package/templates/nextjs/base/src/components/ui/select.tsx +153 -0
- package/templates/nextjs/base/src/components/ui/separator.tsx +26 -0
- package/templates/nextjs/base/src/components/ui/sheet.tsx +125 -0
- package/templates/nextjs/base/src/components/ui/skeleton.tsx +7 -0
- package/templates/nextjs/base/src/components/ui/tabs.tsx +55 -0
- package/templates/nextjs/base/src/components/ui/textarea.tsx +21 -0
- package/templates/nextjs/base/src/components/ui/tooltip.tsx +32 -0
- package/templates/nextjs/base/src/ui/cart/cart-bundle-offer.tsx +17 -11
- package/templates/nextjs/base/src/ui/cart/cart-item.tsx +2 -8
- package/templates/nextjs/base/src/ui/cart/cart-nudges.tsx +28 -39
- package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +3 -1
- package/templates/nextjs/base/src/ui/cart/cart-upgrade-banner.tsx +8 -23
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +18 -27
- package/templates/nextjs/base/src/ui/cart/coupon-input.tsx +10 -21
- package/templates/nextjs/base/src/ui/cart/free-shipping-bar.tsx +2 -3
- package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +95 -103
- package/templates/nextjs/base/src/ui/home/hero-section.tsx +4 -6
- package/templates/nextjs/base/src/ui/layout/announcement-bar.tsx.ejs +116 -125
- package/templates/nextjs/base/src/ui/layout/faq-section.tsx.ejs +90 -103
- package/templates/nextjs/base/src/ui/layout/header-account.tsx +2 -17
- package/templates/nextjs/base/src/ui/layout/language-switcher.tsx.ejs +80 -70
- package/templates/nextjs/base/src/ui/layout/site-footer.tsx.ejs +144 -164
- package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +152 -182
- package/templates/nextjs/base/src/ui/product/customization-fields.tsx +456 -444
- package/templates/nextjs/base/src/ui/product/discount-badge.tsx +18 -22
- package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +15 -27
- package/templates/nextjs/base/src/ui/product/modifier-group-selector.tsx +206 -175
- package/templates/nextjs/base/src/ui/product/price-display.tsx +3 -2
- package/templates/nextjs/base/src/ui/product/product-card.tsx +238 -271
- package/templates/nextjs/base/src/ui/product/product-client-section.tsx +455 -470
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +96 -79
- package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +102 -107
- package/templates/nextjs/base/src/ui/product/review-form.tsx +44 -43
- package/templates/nextjs/{designs/passepartout → base/src}/ui/product/reviews-section.tsx.ejs +108 -96
- package/templates/nextjs/base/src/ui/product/stock-badge.tsx +61 -63
- package/templates/nextjs/base/tailwind.config.ts +69 -42
- package/templates/nextjs/designs/{passepartout → atelier}/app-overlay/layout.tsx.ejs +23 -25
- package/templates/nextjs/designs/atelier/globals.css +396 -0
- package/templates/nextjs/designs/atelier/messages-patch/en.json +99 -0
- package/templates/nextjs/designs/atelier/messages-patch/he.json +99 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-bundle-offer.tsx +110 -111
- package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +163 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +140 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-nudges.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +107 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-upgrade-banner.tsx +123 -141
- package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +147 -0
- package/templates/nextjs/designs/atelier/ui/cart/coupon-input.tsx +142 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/free-shipping-bar.tsx +77 -62
- package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/reservation-countdown.tsx +89 -103
- package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/home/category-tiles.tsx +148 -0
- package/templates/nextjs/designs/atelier/ui/home/discount-banner-strip.tsx +27 -0
- package/templates/nextjs/designs/atelier/ui/home/editorial-band.tsx +73 -0
- package/templates/nextjs/designs/atelier/ui/home/featured-products-section.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +148 -0
- package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +54 -0
- package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +78 -0
- package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +46 -0
- package/templates/nextjs/designs/atelier/ui/layout/announcement-bar.tsx +118 -0
- package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +96 -0
- package/templates/nextjs/designs/atelier/ui/layout/header-account.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/layout/header-cart.tsx +36 -0
- package/templates/nextjs/designs/atelier/ui/layout/language-switcher.tsx.ejs +137 -0
- package/templates/nextjs/designs/atelier/ui/layout/nav-url.ts +14 -0
- package/templates/nextjs/designs/atelier/ui/layout/rich-text-block.tsx +34 -0
- package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +141 -0
- package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +138 -0
- package/templates/nextjs/designs/atelier/ui/product/customization-fields.tsx +466 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/discount-badge.tsx +17 -22
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/frequently-bought-together.tsx +196 -205
- package/templates/nextjs/designs/atelier/ui/product/modifier-group-selector.tsx +193 -0
- package/templates/nextjs/designs/atelier/ui/product/price-display.tsx +69 -0
- package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +260 -0
- package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +471 -0
- package/templates/nextjs/designs/atelier/ui/product/product-grid.tsx +47 -0
- package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +260 -0
- package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +95 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/product/review-form.tsx +267 -294
- package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -0
- package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +66 -0
- package/templates/nextjs/designs/atelier/ui/product/variant-selector.tsx +206 -0
- package/templates/nextjs/designs/atelier/ui/shared/cdn-image.tsx +29 -0
- package/templates/nextjs/designs/{passepartout → atelier}/ui/shared/fly-to-cart.ts +86 -85
- package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -0
- package/templates/nextjs/designs/atelier/ui/shared/loading-spinner.tsx +64 -0
- package/templates/nextjs/designs/atelier/ui/shared/reveal.tsx +71 -0
- package/templates/nextjs/designs/atelier/ui/shared/select-menu.tsx +125 -0
- package/templates/nextjs/themes/luxury/globals.css +405 -399
- package/templates/nextjs/themes/minimal/globals.css +36 -30
- package/templates/nextjs/themes/playful/globals.css +406 -400
- package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +2 -2
- package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +2 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +2 -0
- package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/hero-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/home/home-client.tsx +1 -1
- package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +1 -1
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/layout/header-account.tsx +1 -1
- package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +2 -0
- package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +2 -0
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +2 -2
- package/templates/nextjs/ui-canvas/product/customization-fields.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/discount-badge.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/price-display.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-grid.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/product-listing.tsx +1 -1
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +4 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +29 -32
- package/templates/nextjs/ui-canvas/product/{reviews-section.tsx → reviews-section.tsx.ejs} +26 -8
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +2 -0
- package/templates/nextjs/ui-canvas/product/variant-selector.tsx +2 -0
- package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +2 -0
- package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +2 -0
- package/templates/nextjs/base/src/ui/product/reviews-section.tsx +0 -85
- package/templates/nextjs/designs/passepartout/globals.css +0 -412
- package/templates/nextjs/designs/passepartout/messages-patch/en.json +0 -87
- package/templates/nextjs/designs/passepartout/messages-patch/he.json +0 -87
- package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +0 -39
- package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +0 -149
- package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +0 -401
- package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +0 -139
- package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +0 -45
- package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +0 -103
- package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +0 -159
- package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +0 -155
- package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +0 -60
- package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +0 -34
- package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +0 -111
- package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +0 -265
- package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +0 -667
- package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +0 -35
- package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +0 -427
- package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +0 -113
- package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +0 -52
- package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +0 -307
- package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +0 -33
|
@@ -1,103 +1,90 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FAQ accordion — fully driven by the merchant's dashboard configuration.
|
|
3
|
-
*
|
|
4
|
-
* The merchant configures FAQs in the Brainerce dashboard under
|
|
5
|
-
* Sell → Content → FAQ
|
|
6
|
-
*
|
|
7
|
-
* Each FAQ row has a `key` (e.g. 'main', 'shipping', 'returns'). Pass the
|
|
8
|
-
* pre-fetched payload as `faq`; this component knows nothing about which
|
|
9
|
-
* key it came from.
|
|
10
|
-
*
|
|
11
|
-
* Two render branches:
|
|
12
|
-
* 1. `faq` has items →
|
|
13
|
-
* 2. `faq` is null OR has no items → friendly empty state with a contact
|
|
14
|
-
* CTA, so the /faq page never looks broken before the merchant seeds.
|
|
15
|
-
*
|
|
16
|
-
* Security: FAQ answers may contain merchant-authored HTML — we sanitize
|
|
17
|
-
* via `sanitizeHtml` before injecting via dangerouslySetInnerHTML.
|
|
18
|
-
*/
|
|
19
|
-
'use client';
|
|
20
|
-
|
|
21
|
-
import * as React from 'react';
|
|
22
|
-
import type { Content } from 'brainerce';
|
|
23
|
-
import { useTranslations } from '@/core/lib/translations';
|
|
24
|
-
import { sanitizeHtml } from '@/core/lib/sanitize';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{open ? (
|
|
92
|
-
<div
|
|
93
|
-
className="text-muted-foreground prose prose-sm dark:prose-invert max-w-none px-4 pb-4 text-sm leading-relaxed"
|
|
94
|
-
dangerouslySetInnerHTML={{ __html: sanitizeHtml(item.answer) }}
|
|
95
|
-
/>
|
|
96
|
-
) : null}
|
|
97
|
-
</li>
|
|
98
|
-
);
|
|
99
|
-
})}
|
|
100
|
-
</ul>
|
|
101
|
-
</section>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* FAQ accordion — fully driven by the merchant's dashboard configuration.
|
|
3
|
+
*
|
|
4
|
+
* The merchant configures FAQs in the Brainerce dashboard under
|
|
5
|
+
* Sell → Content → FAQ
|
|
6
|
+
*
|
|
7
|
+
* Each FAQ row has a `key` (e.g. 'main', 'shipping', 'returns'). Pass the
|
|
8
|
+
* pre-fetched payload as `faq`; this component knows nothing about which
|
|
9
|
+
* key it came from.
|
|
10
|
+
*
|
|
11
|
+
* Two render branches:
|
|
12
|
+
* 1. `faq` has items → shadcn Accordion of question/answer pairs
|
|
13
|
+
* 2. `faq` is null OR has no items → friendly empty state with a contact
|
|
14
|
+
* CTA, so the /faq page never looks broken before the merchant seeds.
|
|
15
|
+
*
|
|
16
|
+
* Security: FAQ answers may contain merchant-authored HTML — we sanitize
|
|
17
|
+
* via `sanitizeHtml` before injecting via dangerouslySetInnerHTML.
|
|
18
|
+
*/
|
|
19
|
+
'use client';
|
|
20
|
+
|
|
21
|
+
import * as React from 'react';
|
|
22
|
+
import type { Content } from 'brainerce';
|
|
23
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
24
|
+
import { sanitizeHtml } from '@/core/lib/sanitize';
|
|
25
|
+
import { Link } from '@/core/lib/navigation';
|
|
26
|
+
import {
|
|
27
|
+
Accordion,
|
|
28
|
+
AccordionContent,
|
|
29
|
+
AccordionItem,
|
|
30
|
+
AccordionTrigger,
|
|
31
|
+
} from '@/components/ui/accordion';
|
|
32
|
+
import { Button } from '@/components/ui/button';
|
|
33
|
+
|
|
34
|
+
interface FaqSectionProps {
|
|
35
|
+
/** Pre-fetched FAQ row. `null` renders the empty-state fallback. */
|
|
36
|
+
faq: Content<'FAQ'> | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function FaqSection({ faq }: FaqSectionProps) {
|
|
40
|
+
const t = useTranslations('content');
|
|
41
|
+
|
|
42
|
+
const items = faq?.data?.items ?? [];
|
|
43
|
+
const heading = faq?.name || t('faqTitle');
|
|
44
|
+
|
|
45
|
+
// Empty state — runs when the merchant hasn't created a FAQ row yet,
|
|
46
|
+
// or the row exists but contains no items. Keep the page non-empty and
|
|
47
|
+
// give the visitor a path to contact instead of a blank screen.
|
|
48
|
+
if (!faq || items.length === 0) {
|
|
49
|
+
return (
|
|
50
|
+
<section className="mx-auto max-w-3xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
51
|
+
<h1 className="text-foreground mb-3 text-2xl font-semibold sm:text-3xl">{heading}</h1>
|
|
52
|
+
<p className="text-muted-foreground mb-6 text-sm sm:text-base">
|
|
53
|
+
<strong className="text-foreground">{t('faqEmptyTitle')}</strong>
|
|
54
|
+
<br />
|
|
55
|
+
{t('faqEmptyBody')}
|
|
56
|
+
</p>
|
|
57
|
+
<Button asChild>
|
|
58
|
+
<Link href="/contact">{t('faqContactCta')}</Link>
|
|
59
|
+
</Button>
|
|
60
|
+
</section>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<section className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8">
|
|
66
|
+
<h1 className="text-foreground mb-6 text-2xl font-semibold sm:text-3xl">{heading}</h1>
|
|
67
|
+
<Accordion
|
|
68
|
+
type="single"
|
|
69
|
+
collapsible
|
|
70
|
+
defaultValue="faq-0"
|
|
71
|
+
className="border-border rounded-lg border"
|
|
72
|
+
>
|
|
73
|
+
{items.map((item, idx) => (
|
|
74
|
+
<AccordionItem
|
|
75
|
+
key={idx}
|
|
76
|
+
value={`faq-${idx}`}
|
|
77
|
+
className="border-border last:border-b-0"
|
|
78
|
+
>
|
|
79
|
+
<AccordionTrigger className="hover:bg-muted/40 p-4 text-sm hover:no-underline sm:text-base">
|
|
80
|
+
{item.question}
|
|
81
|
+
</AccordionTrigger>
|
|
82
|
+
<AccordionContent className="text-muted-foreground prose prose-sm dark:prose-invert max-w-none px-4 pb-4 text-sm leading-relaxed">
|
|
83
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizeHtml(item.answer) }} />
|
|
84
|
+
</AccordionContent>
|
|
85
|
+
</AccordionItem>
|
|
86
|
+
))}
|
|
87
|
+
</Accordion>
|
|
88
|
+
</section>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -13,26 +13,11 @@
|
|
|
13
13
|
* the cart icon's icon-only treatment; the icon + aria-label keep it accessible.
|
|
14
14
|
*/
|
|
15
15
|
import * as React from 'react';
|
|
16
|
+
import { User } from 'lucide-react';
|
|
16
17
|
import { Link } from '@/core/lib/navigation';
|
|
17
18
|
import { useAuth } from '@/core/providers/store-provider';
|
|
18
19
|
import { useTranslations } from '@/core/lib/translations';
|
|
19
20
|
|
|
20
|
-
const UserIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
21
|
-
<svg
|
|
22
|
-
viewBox="0 0 24 24"
|
|
23
|
-
fill="none"
|
|
24
|
-
stroke="currentColor"
|
|
25
|
-
strokeWidth={2}
|
|
26
|
-
strokeLinecap="round"
|
|
27
|
-
strokeLinejoin="round"
|
|
28
|
-
aria-hidden="true"
|
|
29
|
-
{...props}
|
|
30
|
-
>
|
|
31
|
-
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
32
|
-
<circle cx="12" cy="7" r="4" />
|
|
33
|
-
</svg>
|
|
34
|
-
);
|
|
35
|
-
|
|
36
21
|
export function HeaderAccount() {
|
|
37
22
|
const { isLoggedIn } = useAuth();
|
|
38
23
|
const t = useTranslations('nav');
|
|
@@ -48,7 +33,7 @@ export function HeaderAccount() {
|
|
|
48
33
|
aria-label={label}
|
|
49
34
|
className="text-foreground/80 hover:text-foreground hover:bg-muted/60 inline-flex h-9 items-center justify-center gap-1.5 rounded-md px-2 transition-colors"
|
|
50
35
|
>
|
|
51
|
-
<
|
|
36
|
+
<User className="h-5 w-5" aria-hidden="true" />
|
|
52
37
|
<span className="hidden text-sm font-medium sm:inline">{label}</span>
|
|
53
38
|
</Link>
|
|
54
39
|
);
|
|
@@ -1,70 +1,80 @@
|
|
|
1
|
-
<% if (i18nEnabled) { %>
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import { usePathname } from 'next/navigation';
|
|
5
|
-
import { supportedLocales } from '@/i18n';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
1
|
+
<% if (i18nEnabled) { %>
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import { supportedLocales } from '@/i18n';
|
|
6
|
+
import {
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from '@/components/ui/select';
|
|
13
|
+
|
|
14
|
+
const LOCALE_NAMES: Record<string, string> = {
|
|
15
|
+
en: 'English',
|
|
16
|
+
he: 'עברית',
|
|
17
|
+
ar: 'العربية',
|
|
18
|
+
es: 'Español',
|
|
19
|
+
fr: 'Français',
|
|
20
|
+
de: 'Deutsch',
|
|
21
|
+
it: 'Italiano',
|
|
22
|
+
pt: 'Português',
|
|
23
|
+
nl: 'Nederlands',
|
|
24
|
+
ja: '日本語',
|
|
25
|
+
ko: '한국어',
|
|
26
|
+
zh: '中文',
|
|
27
|
+
ru: 'Русский',
|
|
28
|
+
tr: 'Türkçe',
|
|
29
|
+
pl: 'Polski',
|
|
30
|
+
sv: 'Svenska',
|
|
31
|
+
da: 'Dansk',
|
|
32
|
+
no: 'Norsk',
|
|
33
|
+
fi: 'Suomi',
|
|
34
|
+
cs: 'Čeština',
|
|
35
|
+
th: 'ไทย',
|
|
36
|
+
vi: 'Tiếng Việt',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const defaultLocale = '<%= defaultLocale %>';
|
|
40
|
+
|
|
41
|
+
export function LanguageSwitcher() {
|
|
42
|
+
const pathname = usePathname();
|
|
43
|
+
const firstSeg = pathname.split('/')[1];
|
|
44
|
+
const currentLocale = supportedLocales.includes(firstSeg as (typeof supportedLocales)[number])
|
|
45
|
+
? firstSeg
|
|
46
|
+
: defaultLocale;
|
|
47
|
+
|
|
48
|
+
function switchLocale(newLocale: string) {
|
|
49
|
+
// Strip any existing locale segment, then re-add only if the new one
|
|
50
|
+
// isn't the default — matches the as-needed prefix scheme in middleware.
|
|
51
|
+
const segments = pathname.split('/');
|
|
52
|
+
if (supportedLocales.includes(segments[1] as (typeof supportedLocales)[number])) {
|
|
53
|
+
segments.splice(1, 1);
|
|
54
|
+
}
|
|
55
|
+
const bare = segments.join('/') || '/';
|
|
56
|
+
window.location.href =
|
|
57
|
+
newLocale === defaultLocale ? bare : `/${newLocale}${bare === '/' ? '' : bare}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (supportedLocales.length <= 1) return null;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Select value={currentLocale} onValueChange={switchLocale}>
|
|
64
|
+
<SelectTrigger
|
|
65
|
+
className="h-8 w-auto gap-1 rounded border bg-transparent px-2 py-1 text-sm"
|
|
66
|
+
aria-label="Select language"
|
|
67
|
+
>
|
|
68
|
+
<SelectValue />
|
|
69
|
+
</SelectTrigger>
|
|
70
|
+
<SelectContent>
|
|
71
|
+
{supportedLocales.map((locale) => (
|
|
72
|
+
<SelectItem key={locale} value={locale}>
|
|
73
|
+
{LOCALE_NAMES[locale] || locale}
|
|
74
|
+
</SelectItem>
|
|
75
|
+
))}
|
|
76
|
+
</SelectContent>
|
|
77
|
+
</Select>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
<% } %>
|