create-brainerce-store 1.66.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 (46) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/nextjs/base/.env.local.ejs +45 -24
  4. package/templates/nextjs/base/.eslintrc.json +51 -57
  5. package/templates/nextjs/base/AGENTS.md.ejs +107 -81
  6. package/templates/nextjs/base/CLAUDE.md.ejs +118 -92
  7. package/templates/nextjs/base/next.config.ts +103 -86
  8. package/templates/nextjs/base/scripts/fetch-store-info.mjs +104 -97
  9. package/templates/nextjs/base/src/app/agents.md/route.ts +4 -3
  10. package/templates/nextjs/base/src/app/api/auth/me/route.ts +65 -59
  11. package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +255 -242
  12. package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +311 -308
  13. package/templates/nextjs/base/src/app/blog/page.tsx.ejs +277 -276
  14. package/templates/nextjs/base/src/app/blog/rss.xml/route.ts +4 -3
  15. package/templates/nextjs/base/src/app/category/[slug]/page.tsx +6 -5
  16. package/templates/nextjs/base/src/app/faq/page.tsx.ejs +46 -46
  17. package/templates/nextjs/base/src/app/indexnow-key.txt/route.ts +25 -26
  18. package/templates/nextjs/base/src/app/layout.tsx.ejs +273 -257
  19. package/templates/nextjs/base/src/app/llms.txt/route.ts +4 -3
  20. package/templates/nextjs/base/src/app/opengraph-image.tsx +1 -1
  21. package/templates/nextjs/base/src/app/page.tsx +65 -64
  22. package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +92 -92
  23. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -5
  24. package/templates/nextjs/base/src/app/robots.ts +3 -2
  25. package/templates/nextjs/base/src/app/sitemap.ts +4 -3
  26. package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +294 -292
  27. package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +60 -59
  28. package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +60 -61
  29. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +2 -1
  30. package/templates/nextjs/base/src/core/lib/brainerce.server.ts +81 -0
  31. package/templates/nextjs/base/src/core/lib/brainerce.ts.ejs +60 -110
  32. package/templates/nextjs/base/src/core/lib/site-url.ts +197 -0
  33. package/templates/nextjs/base/src/ui/product/review-form.tsx +294 -294
  34. package/templates/nextjs/base/src/ui/product/reviews-section.tsx.ejs +108 -108
  35. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +301 -285
  36. package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +97 -96
  37. package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +142 -141
  38. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +139 -138
  39. package/templates/nextjs/designs/atelier/ui/product/review-form.tsx +295 -267
  40. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -148
  41. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +72 -71
  42. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +83 -82
  43. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +120 -119
  44. package/templates/nextjs/ui-canvas/product/faq-section.tsx.ejs +54 -0
  45. package/templates/nextjs/ui-canvas/product/review-form.tsx +267 -266
  46. 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
+ }