create-brainerce-store 1.65.0 → 1.67.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/dist/index.js +2 -2
- package/messages/en.json +505 -502
- package/messages/he.json +505 -502
- package/package.json +1 -1
- package/templates/nextjs/base/.env.local.ejs +45 -24
- package/templates/nextjs/base/.eslintrc.json +51 -57
- package/templates/nextjs/base/AGENTS.md.ejs +107 -81
- package/templates/nextjs/base/CLAUDE.md.ejs +118 -92
- package/templates/nextjs/base/next.config.ts +103 -86
- package/templates/nextjs/base/scripts/fetch-store-info.mjs +104 -97
- package/templates/nextjs/base/src/app/agents.md/route.ts +4 -3
- package/templates/nextjs/base/src/app/api/auth/me/route.ts +65 -59
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +255 -242
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +311 -308
- package/templates/nextjs/base/src/app/blog/page.tsx.ejs +277 -276
- package/templates/nextjs/base/src/app/blog/rss.xml/route.ts +4 -3
- package/templates/nextjs/base/src/app/category/[slug]/page.tsx +6 -5
- package/templates/nextjs/base/src/app/faq/page.tsx.ejs +46 -46
- package/templates/nextjs/base/src/app/indexnow-key.txt/route.ts +25 -26
- package/templates/nextjs/base/src/app/layout.tsx.ejs +273 -257
- package/templates/nextjs/base/src/app/llms.txt/route.ts +4 -3
- package/templates/nextjs/base/src/app/opengraph-image.tsx +1 -1
- package/templates/nextjs/base/src/app/page.tsx +65 -64
- package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +92 -92
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +10 -5
- package/templates/nextjs/base/src/app/robots.ts +3 -2
- package/templates/nextjs/base/src/app/sitemap.ts +4 -3
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +294 -292
- package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +60 -59
- package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +60 -61
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +16 -2
- package/templates/nextjs/base/src/core/lib/brainerce.server.ts +81 -0
- package/templates/nextjs/base/src/core/lib/brainerce.ts.ejs +60 -110
- package/templates/nextjs/base/src/core/lib/site-url.ts +197 -0
- package/templates/nextjs/base/src/ui/product/faq-section.tsx.ejs +46 -0
- package/templates/nextjs/base/src/ui/product/review-form.tsx +294 -294
- package/templates/nextjs/base/src/ui/product/reviews-section.tsx.ejs +108 -108
- package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +301 -285
- package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +97 -96
- package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +142 -141
- package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +139 -138
- package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +295 -267
- package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -148
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +72 -71
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +83 -82
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +120 -119
- package/templates/nextjs/ui-canvas/product/faq-section.tsx.ejs +54 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +267 -266
- package/templates/nextjs/ui-canvas/product/reviews-section.tsx.ejs +96 -96
|
@@ -1,119 +1,120 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Merchant-controlled site header (bare skeleton).
|
|
3
|
-
*
|
|
4
|
-
* The merchant configures the header in the Brainerce dashboard under
|
|
5
|
-
* Sell → Content → Header
|
|
6
|
-
*
|
|
7
|
-
* Renders brand (logo or store-name fallback) + nav items + CTA + cart link,
|
|
8
|
-
* with a `<details>` mobile-friendly menu that works without client-side JS.
|
|
9
|
-
* Everything is generic — do NOT hardcode nav labels or logo paths here; the
|
|
10
|
-
* merchant edits them in the dashboard and the change propagates within ~5
|
|
11
|
-
* minutes.
|
|
12
|
-
*
|
|
13
|
-
* If `header` is null (404 from the API, or the merchant deleted the row),
|
|
14
|
-
* we render a minimal static fallback with just the brand label and cart link
|
|
15
|
-
* so the layout never collapses.
|
|
16
|
-
*/
|
|
17
|
-
import * as React from 'react';
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
header
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Merchant-controlled site header (bare skeleton).
|
|
3
|
+
*
|
|
4
|
+
* The merchant configures the header in the Brainerce dashboard under
|
|
5
|
+
* Sell → Content → Header
|
|
6
|
+
*
|
|
7
|
+
* Renders brand (logo or store-name fallback) + nav items + CTA + cart link,
|
|
8
|
+
* with a `<details>` mobile-friendly menu that works without client-side JS.
|
|
9
|
+
* Everything is generic — do NOT hardcode nav labels or logo paths here; the
|
|
10
|
+
* merchant edits them in the dashboard and the change propagates within ~5
|
|
11
|
+
* minutes.
|
|
12
|
+
*
|
|
13
|
+
* If `header` is null (404 from the API, or the merchant deleted the row),
|
|
14
|
+
* we render a minimal static fallback with just the brand label and cart link
|
|
15
|
+
* so the layout never collapses.
|
|
16
|
+
*/
|
|
17
|
+
import * as React from 'react';
|
|
18
|
+
import { Link } from '@/core/lib/navigation';
|
|
19
|
+
import type { Content } from 'brainerce';
|
|
20
|
+
import { HeaderAccount } from './header-account';
|
|
21
|
+
<% if (i18nEnabled) { %>
|
|
22
|
+
import { LanguageSwitcher } from '@/ui/layout/language-switcher';
|
|
23
|
+
<% } %>
|
|
24
|
+
|
|
25
|
+
interface SiteHeaderProps {
|
|
26
|
+
/** Pre-fetched header payload (server-side). `null` triggers static fallback. */
|
|
27
|
+
header: Content<'HEADER'> | null;
|
|
28
|
+
/** Fallback brand label when the merchant hasn't uploaded a logo yet. */
|
|
29
|
+
storeName?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function SiteHeader({ header, storeName }: SiteHeaderProps) {
|
|
33
|
+
const data = header?.data;
|
|
34
|
+
const logo = data?.logo;
|
|
35
|
+
const navItems = data?.navItems ?? [];
|
|
36
|
+
const cta = data?.cta;
|
|
37
|
+
const brandLabel = logo?.alt || storeName || 'Store';
|
|
38
|
+
|
|
39
|
+
// Static fallback — runs when the Content API returned null (no HEADER row
|
|
40
|
+
// for this store yet, or fetch failed). Keep the brand + cart so the page
|
|
41
|
+
// always has a clickable home link and entry to checkout.
|
|
42
|
+
if (!header) {
|
|
43
|
+
return (
|
|
44
|
+
<header>
|
|
45
|
+
{/* DESIGN ME — site header (fallback branch, no merchant nav yet): brand link + account + cart; data comes pre-fetched from the layout via the Content API. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
|
|
46
|
+
<div className="flex items-center justify-between gap-4">
|
|
47
|
+
<Link href="/" aria-label={brandLabel}>
|
|
48
|
+
{brandLabel}
|
|
49
|
+
</Link>
|
|
50
|
+
<div className="flex items-center gap-2">
|
|
51
|
+
<% if (i18nEnabled) { %>
|
|
52
|
+
<LanguageSwitcher />
|
|
53
|
+
<% } %>
|
|
54
|
+
<HeaderAccount />
|
|
55
|
+
<Link href="/cart" aria-label="Cart">
|
|
56
|
+
Cart
|
|
57
|
+
</Link>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</header>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<header>
|
|
66
|
+
{/* DESIGN ME — site header: merchant-configured logo/nav/CTA (Content<'HEADER'> pre-fetched in the layout) + account + cart links. Keep every link working. Compose with the shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons. */}
|
|
67
|
+
<div className="flex items-center justify-between gap-4">
|
|
68
|
+
<Link href="/" className="flex items-center gap-2" aria-label={brandLabel}>
|
|
69
|
+
{logo ? (
|
|
70
|
+
// eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
|
|
71
|
+
<img src={logo.src} alt={logo.alt} />
|
|
72
|
+
) : (
|
|
73
|
+
<span>{brandLabel}</span>
|
|
74
|
+
)}
|
|
75
|
+
</Link>
|
|
76
|
+
|
|
77
|
+
{navItems.length > 0 ? (
|
|
78
|
+
<nav aria-label="Main">
|
|
79
|
+
<ul className="flex items-center gap-4">
|
|
80
|
+
{navItems.map((item, idx) => (
|
|
81
|
+
<li key={`${item.url}-${idx}`}>
|
|
82
|
+
<a href={item.url}>{item.label}</a>
|
|
83
|
+
</li>
|
|
84
|
+
))}
|
|
85
|
+
</ul>
|
|
86
|
+
</nav>
|
|
87
|
+
) : null}
|
|
88
|
+
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
{cta ? <a href={cta.url}>{cta.label}</a> : null}
|
|
91
|
+
|
|
92
|
+
<% if (i18nEnabled) { %>
|
|
93
|
+
<LanguageSwitcher />
|
|
94
|
+
<% } %>
|
|
95
|
+
|
|
96
|
+
<HeaderAccount />
|
|
97
|
+
|
|
98
|
+
<Link href="/cart" aria-label="Cart">
|
|
99
|
+
Cart
|
|
100
|
+
</Link>
|
|
101
|
+
|
|
102
|
+
{navItems.length > 0 ? (
|
|
103
|
+
<details>
|
|
104
|
+
<summary>Menu</summary>
|
|
105
|
+
<nav aria-label="Mobile">
|
|
106
|
+
<ul>
|
|
107
|
+
{navItems.map((item, idx) => (
|
|
108
|
+
<li key={`m-${item.url}-${idx}`}>
|
|
109
|
+
<a href={item.url}>{item.label}</a>
|
|
110
|
+
</li>
|
|
111
|
+
))}
|
|
112
|
+
</ul>
|
|
113
|
+
</nav>
|
|
114
|
+
</details>
|
|
115
|
+
) : null}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</header>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Product } from 'brainerce';
|
|
2
|
+
<% if (i18nEnabled) { %>import { getMessages, defaultLocale } from '@/i18n';<% } else { %>import { defaultLocale, messages } from '@/i18n';<% } %>
|
|
3
|
+
|
|
4
|
+
type FaqStrings = Record<string, string>;
|
|
5
|
+
|
|
6
|
+
<% if (i18nEnabled) { %>async function getFaqStrings(locale: string): Promise<FaqStrings> {
|
|
7
|
+
return ((await getMessages(locale)).faq ?? {}) as FaqStrings;
|
|
8
|
+
}<% } else { %>async function getFaqStrings(_locale: string): Promise<FaqStrings> {
|
|
9
|
+
return ((messages as Record<string, unknown>).faq ?? {}) as FaqStrings;
|
|
10
|
+
}<% } %>
|
|
11
|
+
|
|
12
|
+
interface ProductFaqSectionProps {
|
|
13
|
+
product: Product;
|
|
14
|
+
locale?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DESIGN ME — visible product Q&A.
|
|
19
|
+
*
|
|
20
|
+
* Two things here are behaviour, not styling, and must survive a redesign:
|
|
21
|
+
*
|
|
22
|
+
* 1. Keep it a SERVER component (no 'use client'). The pairs have to be in the
|
|
23
|
+
* initial HTML payload — AI answer engines cite this extractable Q&A format
|
|
24
|
+
* and do not run JavaScript. Content inside a closed <details> is still in
|
|
25
|
+
* the HTML, so an accordion costs nothing for extraction.
|
|
26
|
+
* 2. Render the same pairs <ProductJsonLd> emits as FAQPage JSON-LD. Both read
|
|
27
|
+
* `product.faq`, so they cannot drift — unless you stop rendering them, at
|
|
28
|
+
* which point the structured data describes content that is not on the page.
|
|
29
|
+
*
|
|
30
|
+
* Style it however you like; drop the <details> for a custom accordion if you
|
|
31
|
+
* prefer, as long as the text ships in the server-rendered HTML.
|
|
32
|
+
*/
|
|
33
|
+
export async function ProductFaqSection({
|
|
34
|
+
product,
|
|
35
|
+
locale = defaultLocale,
|
|
36
|
+
}: ProductFaqSectionProps) {
|
|
37
|
+
const pairs = (product.faq ?? []).filter((item) => item?.q && item?.a);
|
|
38
|
+
if (pairs.length === 0) return null;
|
|
39
|
+
const t = await getFaqStrings(locale);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<section>
|
|
43
|
+
<h2>{t.title ?? 'Frequently asked questions'}</h2>
|
|
44
|
+
<div>
|
|
45
|
+
{pairs.map((item) => (
|
|
46
|
+
<details key={item.q}>
|
|
47
|
+
<summary>{item.q}</summary>
|
|
48
|
+
<p>{item.a}</p>
|
|
49
|
+
</details>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
53
|
+
);
|
|
54
|
+
}
|