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,138 +1,139 @@
1
- /**
2
- * Merchant-controlled site header.
3
- *
4
- * The merchant configures the header in the Brainerce dashboard under
5
- * Sell → Content → Header
6
- *
7
- * Sticky, translucent warm-white bar: brand (serif) at the start, nav in the
8
- * middle (desktop), actions at the end (language / account / cart / mobile
9
- * menu). Everything is generic — nav labels and logo come from the dashboard.
10
- * If `header` is null we render the same chrome with just brand + actions so
11
- * the layout never collapses.
12
- */
13
- import * as React from 'react';
14
- import type { Content } from 'brainerce';
15
- import { HeaderAccount } from './header-account';
16
- import { HeaderCart } from './header-cart';
17
- <% if (i18nEnabled) { %>
18
- import { LanguageSwitcher } from '@/ui/layout/language-switcher';
19
- <% } %>
20
- import { IconMenu } from '@/ui/shared/icons';
21
- import { resolveNavUrl } from '@/ui/layout/nav-url';
22
-
23
- interface SiteHeaderProps {
24
- /** Pre-fetched header payload (server-side). `null` triggers static fallback. */
25
- header: Content<'HEADER'> | null;
26
- /** Fallback brand label when the merchant hasn't uploaded a logo yet. */
27
- storeName?: string;
28
- }
29
-
30
- function Brand({
31
- logo,
32
- brandLabel,
33
- }: {
34
- logo?: { src: string; alt: string } | null;
35
- brandLabel: string;
36
- }) {
37
- return (
38
- <a
39
- href="/"
40
- aria-label={brandLabel}
41
- className="flex shrink-0 items-center gap-2 rounded-lg"
42
- >
43
- {logo ? (
44
- // eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
45
- <img src={logo.src} alt={logo.alt} className="h-9 w-auto object-contain" />
46
- ) : (
47
- <span className="font-display text-xl font-semibold leading-none text-foreground sm:text-2xl">
48
- {brandLabel}
49
- </span>
50
- )}
51
- </a>
52
- );
53
- }
54
-
55
- export function SiteHeader({ header, storeName }: SiteHeaderProps) {
56
- const data = header?.data;
57
- const logo = data?.logo;
58
- const navItems = data?.navItems ?? [];
59
- const cta = data?.cta;
60
- const brandLabel = logo?.alt || storeName || 'Store';
61
-
62
- return (
63
- <header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/80">
64
- <div className="container-page flex h-16 items-center justify-between gap-4 sm:h-[4.5rem]">
65
- <Brand logo={logo ?? null} brandLabel={brandLabel} />
66
-
67
- {navItems.length > 0 && (
68
- <nav aria-label="Main" className="hidden md:block">
69
- <ul className="flex items-center gap-1">
70
- {navItems.map((item, idx) => (
71
- <li key={`${item.url}-${idx}`}>
72
- <a
73
- href={resolveNavUrl(item.url)}
74
- className="rounded-full px-4 py-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-secondary hover:text-foreground"
75
- >
76
- {item.label}
77
- </a>
78
- </li>
79
- ))}
80
- </ul>
81
- </nav>
82
- )}
83
-
84
- <div className="flex items-center gap-1 sm:gap-2">
85
- {cta ? (
86
- <a href={resolveNavUrl(cta.url)} className="btn-primary btn-sm hidden lg:inline-flex">
87
- {cta.label}
88
- </a>
89
- ) : null}
90
- <% if (i18nEnabled) { %>
91
-
92
- <LanguageSwitcher />
93
- <% } %>
94
- <HeaderAccount />
95
- <HeaderCart />
96
-
97
- {navItems.length > 0 && (
98
- <details className="group relative md:hidden">
99
- <summary
100
- className="flex h-10 w-10 cursor-pointer list-none items-center justify-center rounded-full text-foreground transition-colors hover:bg-secondary [&::-webkit-details-marker]:hidden"
101
- aria-label="Menu"
102
- >
103
- <IconMenu size={20} />
104
- </summary>
105
- <nav
106
- aria-label="Mobile"
107
- className="absolute end-0 top-12 z-50 w-56 rounded-xl border bg-background p-2 shadow-lg"
108
- >
109
- <ul className="flex flex-col">
110
- {navItems.map((item, idx) => (
111
- <li key={`m-${item.url}-${idx}`}>
112
- <a
113
- href={resolveNavUrl(item.url)}
114
- className="block rounded-lg px-3 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary"
115
- >
116
- {item.label}
117
- </a>
118
- </li>
119
- ))}
120
- {cta ? (
121
- <li className="mt-1 border-t pt-2">
122
- <a
123
- href={resolveNavUrl(cta.url)}
124
- className="block rounded-lg px-3 py-2.5 text-sm font-semibold text-primary transition-colors hover:bg-secondary"
125
- >
126
- {cta.label}
127
- </a>
128
- </li>
129
- ) : null}
130
- </ul>
131
- </nav>
132
- </details>
133
- )}
134
- </div>
135
- </div>
136
- </header>
137
- );
138
- }
1
+ /**
2
+ * Merchant-controlled site header.
3
+ *
4
+ * The merchant configures the header in the Brainerce dashboard under
5
+ * Sell → Content → Header
6
+ *
7
+ * Sticky, translucent warm-white bar: brand (serif) at the start, nav in the
8
+ * middle (desktop), actions at the end (language / account / cart / mobile
9
+ * menu). Everything is generic — nav labels and logo come from the dashboard.
10
+ * If `header` is null we render the same chrome with just brand + actions so
11
+ * the layout never collapses.
12
+ */
13
+ import * as React from 'react';
14
+ import { Link } from '@/core/lib/navigation';
15
+ import type { Content } from 'brainerce';
16
+ import { HeaderAccount } from './header-account';
17
+ import { HeaderCart } from './header-cart';
18
+ <% if (i18nEnabled) { %>
19
+ import { LanguageSwitcher } from '@/ui/layout/language-switcher';
20
+ <% } %>
21
+ import { IconMenu } from '@/ui/shared/icons';
22
+ import { resolveNavUrl } from '@/ui/layout/nav-url';
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
+ function Brand({
32
+ logo,
33
+ brandLabel,
34
+ }: {
35
+ logo?: { src: string; alt: string } | null;
36
+ brandLabel: string;
37
+ }) {
38
+ return (
39
+ <Link
40
+ href="/"
41
+ aria-label={brandLabel}
42
+ className="flex shrink-0 items-center gap-2 rounded-lg"
43
+ >
44
+ {logo ? (
45
+ // eslint-disable-next-line @next/next/no-img-element -- merchant-supplied URL, dynamic optimization handled by CDN
46
+ <img src={logo.src} alt={logo.alt} className="h-9 w-auto object-contain" />
47
+ ) : (
48
+ <span className="font-display text-xl font-semibold leading-none text-foreground sm:text-2xl">
49
+ {brandLabel}
50
+ </span>
51
+ )}
52
+ </Link>
53
+ );
54
+ }
55
+
56
+ export function SiteHeader({ header, storeName }: SiteHeaderProps) {
57
+ const data = header?.data;
58
+ const logo = data?.logo;
59
+ const navItems = data?.navItems ?? [];
60
+ const cta = data?.cta;
61
+ const brandLabel = logo?.alt || storeName || 'Store';
62
+
63
+ return (
64
+ <header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur supports-[backdrop-filter]:bg-background/80">
65
+ <div className="container-page flex h-16 items-center justify-between gap-4 sm:h-[4.5rem]">
66
+ <Brand logo={logo ?? null} brandLabel={brandLabel} />
67
+
68
+ {navItems.length > 0 && (
69
+ <nav aria-label="Main" className="hidden md:block">
70
+ <ul className="flex items-center gap-1">
71
+ {navItems.map((item, idx) => (
72
+ <li key={`${item.url}-${idx}`}>
73
+ <a
74
+ href={resolveNavUrl(item.url)}
75
+ className="rounded-full px-4 py-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-secondary hover:text-foreground"
76
+ >
77
+ {item.label}
78
+ </a>
79
+ </li>
80
+ ))}
81
+ </ul>
82
+ </nav>
83
+ )}
84
+
85
+ <div className="flex items-center gap-1 sm:gap-2">
86
+ {cta ? (
87
+ <a href={resolveNavUrl(cta.url)} className="btn-primary btn-sm hidden lg:inline-flex">
88
+ {cta.label}
89
+ </a>
90
+ ) : null}
91
+ <% if (i18nEnabled) { %>
92
+
93
+ <LanguageSwitcher />
94
+ <% } %>
95
+ <HeaderAccount />
96
+ <HeaderCart />
97
+
98
+ {navItems.length > 0 && (
99
+ <details className="group relative md:hidden">
100
+ <summary
101
+ className="flex h-10 w-10 cursor-pointer list-none items-center justify-center rounded-full text-foreground transition-colors hover:bg-secondary [&::-webkit-details-marker]:hidden"
102
+ aria-label="Menu"
103
+ >
104
+ <IconMenu size={20} />
105
+ </summary>
106
+ <nav
107
+ aria-label="Mobile"
108
+ className="absolute end-0 top-12 z-50 w-56 rounded-xl border bg-background p-2 shadow-lg"
109
+ >
110
+ <ul className="flex flex-col">
111
+ {navItems.map((item, idx) => (
112
+ <li key={`m-${item.url}-${idx}`}>
113
+ <a
114
+ href={resolveNavUrl(item.url)}
115
+ className="block rounded-lg px-3 py-2.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary"
116
+ >
117
+ {item.label}
118
+ </a>
119
+ </li>
120
+ ))}
121
+ {cta ? (
122
+ <li className="mt-1 border-t pt-2">
123
+ <a
124
+ href={resolveNavUrl(cta.url)}
125
+ className="block rounded-lg px-3 py-2.5 text-sm font-semibold text-primary transition-colors hover:bg-secondary"
126
+ >
127
+ {cta.label}
128
+ </a>
129
+ </li>
130
+ ) : null}
131
+ </ul>
132
+ </nav>
133
+ </details>
134
+ )}
135
+ </div>
136
+ </div>
137
+ </header>
138
+ );
139
+ }