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.
Files changed (49) hide show
  1. package/dist/index.js +2 -2
  2. package/messages/en.json +505 -502
  3. package/messages/he.json +505 -502
  4. package/package.json +1 -1
  5. package/templates/nextjs/base/.env.local.ejs +45 -24
  6. package/templates/nextjs/base/.eslintrc.json +51 -57
  7. package/templates/nextjs/base/AGENTS.md.ejs +107 -81
  8. package/templates/nextjs/base/CLAUDE.md.ejs +118 -92
  9. package/templates/nextjs/base/next.config.ts +103 -86
  10. package/templates/nextjs/base/scripts/fetch-store-info.mjs +104 -97
  11. package/templates/nextjs/base/src/app/agents.md/route.ts +4 -3
  12. package/templates/nextjs/base/src/app/api/auth/me/route.ts +65 -59
  13. package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +255 -242
  14. package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +311 -308
  15. package/templates/nextjs/base/src/app/blog/page.tsx.ejs +277 -276
  16. package/templates/nextjs/base/src/app/blog/rss.xml/route.ts +4 -3
  17. package/templates/nextjs/base/src/app/category/[slug]/page.tsx +6 -5
  18. package/templates/nextjs/base/src/app/faq/page.tsx.ejs +46 -46
  19. package/templates/nextjs/base/src/app/indexnow-key.txt/route.ts +25 -26
  20. package/templates/nextjs/base/src/app/layout.tsx.ejs +273 -257
  21. package/templates/nextjs/base/src/app/llms.txt/route.ts +4 -3
  22. package/templates/nextjs/base/src/app/opengraph-image.tsx +1 -1
  23. package/templates/nextjs/base/src/app/page.tsx +65 -64
  24. package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +92 -92
  25. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +10 -5
  26. package/templates/nextjs/base/src/app/robots.ts +3 -2
  27. package/templates/nextjs/base/src/app/sitemap.ts +4 -3
  28. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +294 -292
  29. package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +60 -59
  30. package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +60 -61
  31. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +16 -2
  32. package/templates/nextjs/base/src/core/lib/brainerce.server.ts +81 -0
  33. package/templates/nextjs/base/src/core/lib/brainerce.ts.ejs +60 -110
  34. package/templates/nextjs/base/src/core/lib/site-url.ts +197 -0
  35. package/templates/nextjs/base/src/ui/product/faq-section.tsx.ejs +46 -0
  36. package/templates/nextjs/base/src/ui/product/review-form.tsx +294 -294
  37. package/templates/nextjs/base/src/ui/product/reviews-section.tsx.ejs +108 -108
  38. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +301 -285
  39. package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +97 -96
  40. package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +142 -141
  41. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +139 -138
  42. package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +295 -267
  43. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -148
  44. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +72 -71
  45. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +83 -82
  46. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +120 -119
  47. package/templates/nextjs/ui-canvas/product/faq-section.tsx.ejs +54 -0
  48. package/templates/nextjs/ui-canvas/product/review-form.tsx +267 -266
  49. 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 type { Content } from 'brainerce';
19
- import { HeaderAccount } from './header-account';
20
- <% if (i18nEnabled) { %>
21
- import { LanguageSwitcher } from '@/ui/layout/language-switcher';
22
- <% } %>
23
-
24
- interface SiteHeaderProps {
25
- /** Pre-fetched header payload (server-side). `null` triggers static fallback. */
26
- header: Content<'HEADER'> | null;
27
- /** Fallback brand label when the merchant hasn't uploaded a logo yet. */
28
- storeName?: string;
29
- }
30
-
31
- export function SiteHeader({ header, storeName }: SiteHeaderProps) {
32
- const data = header?.data;
33
- const logo = data?.logo;
34
- const navItems = data?.navItems ?? [];
35
- const cta = data?.cta;
36
- const brandLabel = logo?.alt || storeName || 'Store';
37
-
38
- // Static fallback — runs when the Content API returned null (no HEADER row
39
- // for this store yet, or fetch failed). Keep the brand + cart so the page
40
- // always has a clickable home link and entry to checkout.
41
- if (!header) {
42
- return (
43
- <header>
44
- {/* 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. */}
45
- <div className="flex items-center justify-between gap-4">
46
- <a href="/" aria-label={brandLabel}>
47
- {brandLabel}
48
- </a>
49
- <div className="flex items-center gap-2">
50
- <% if (i18nEnabled) { %>
51
- <LanguageSwitcher />
52
- <% } %>
53
- <HeaderAccount />
54
- <a href="/cart" aria-label="Cart">
55
- Cart
56
- </a>
57
- </div>
58
- </div>
59
- </header>
60
- );
61
- }
62
-
63
- return (
64
- <header>
65
- {/* 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. */}
66
- <div className="flex items-center justify-between gap-4">
67
- <a href="/" className="flex items-center gap-2" aria-label={brandLabel}>
68
- {logo ? (
69
- // eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
70
- <img src={logo.src} alt={logo.alt} />
71
- ) : (
72
- <span>{brandLabel}</span>
73
- )}
74
- </a>
75
-
76
- {navItems.length > 0 ? (
77
- <nav aria-label="Main">
78
- <ul className="flex items-center gap-4">
79
- {navItems.map((item, idx) => (
80
- <li key={`${item.url}-${idx}`}>
81
- <a href={item.url}>{item.label}</a>
82
- </li>
83
- ))}
84
- </ul>
85
- </nav>
86
- ) : null}
87
-
88
- <div className="flex items-center gap-2">
89
- {cta ? <a href={cta.url}>{cta.label}</a> : null}
90
-
91
- <% if (i18nEnabled) { %>
92
- <LanguageSwitcher />
93
- <% } %>
94
-
95
- <HeaderAccount />
96
-
97
- <a href="/cart" aria-label="Cart">
98
- Cart
99
- </a>
100
-
101
- {navItems.length > 0 ? (
102
- <details>
103
- <summary>Menu</summary>
104
- <nav aria-label="Mobile">
105
- <ul>
106
- {navItems.map((item, idx) => (
107
- <li key={`m-${item.url}-${idx}`}>
108
- <a href={item.url}>{item.label}</a>
109
- </li>
110
- ))}
111
- </ul>
112
- </nav>
113
- </details>
114
- ) : null}
115
- </div>
116
- </div>
117
- </header>
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
+ }