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.
- package/dist/index.js +1 -1
- 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 +6 -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 +2 -1
- 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/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,257 +1,273 @@
|
|
|
1
|
-
<% if (i18nEnabled) { %>
|
|
2
|
-
import type { Metadata } from 'next';
|
|
3
|
-
<%- fontImport %>
|
|
4
|
-
import { StoreProvider } from '@/core/providers/store-provider';
|
|
5
|
-
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
6
|
-
import { SiteHeader } from '@/ui/layout/site-header';
|
|
7
|
-
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
8
|
-
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
9
|
-
import { TrackingBootstrap } from '@/components/tracking-bootstrap';
|
|
10
|
-
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce';
|
|
11
|
-
import { getDirection, getMessages, supportedLocales } from '@/i18n';
|
|
12
|
-
import { getNonce } from '@/core/lib/nonce';
|
|
13
|
-
import '
|
|
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
|
-
const
|
|
66
|
-
const
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
import {
|
|
145
|
-
|
|
146
|
-
import '
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
1
|
+
<% if (i18nEnabled) { %>
|
|
2
|
+
import type { Metadata } from 'next';
|
|
3
|
+
<%- fontImport %>
|
|
4
|
+
import { StoreProvider } from '@/core/providers/store-provider';
|
|
5
|
+
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
6
|
+
import { SiteHeader } from '@/ui/layout/site-header';
|
|
7
|
+
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
8
|
+
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
9
|
+
import { TrackingBootstrap } from '@/components/tracking-bootstrap';
|
|
10
|
+
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce.server';
|
|
11
|
+
import { getDirection, getMessages, supportedLocales } from '@/i18n';
|
|
12
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
13
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
14
|
+
import '../globals.css';
|
|
15
|
+
|
|
16
|
+
<%- fontVariable %>
|
|
17
|
+
|
|
18
|
+
// Resolved per request rather than read from a hardcoded env default: the
|
|
19
|
+
// scaffolder cannot know the deploy domain, so `metadataBase` is the one
|
|
20
|
+
// value that must never be guessed. See core/lib/site-url.ts.
|
|
21
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
22
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
23
|
+
return {
|
|
24
|
+
metadataBase: new URL(baseUrl),
|
|
25
|
+
title: {
|
|
26
|
+
default: <%- storeNameJs %>,
|
|
27
|
+
template: <%- titleTemplateJs %>,
|
|
28
|
+
},
|
|
29
|
+
description: <%- storeNameJs %>,
|
|
30
|
+
alternates: {
|
|
31
|
+
canonical: '/',
|
|
32
|
+
},
|
|
33
|
+
openGraph: {
|
|
34
|
+
siteName: <%- storeNameJs %>,
|
|
35
|
+
type: 'website',
|
|
36
|
+
},
|
|
37
|
+
robots: {
|
|
38
|
+
index: true,
|
|
39
|
+
follow: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildOrganizationJsonLd(baseUrl: string) {
|
|
45
|
+
return {
|
|
46
|
+
'@context': 'https://schema.org',
|
|
47
|
+
'@type': 'Organization',
|
|
48
|
+
name: <%- storeNameJs %>,
|
|
49
|
+
url: baseUrl,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function generateStaticParams() {
|
|
54
|
+
return supportedLocales.map((locale) => ({ locale }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default async function RootLayout({
|
|
58
|
+
children,
|
|
59
|
+
params,
|
|
60
|
+
}: {
|
|
61
|
+
children: React.ReactNode;
|
|
62
|
+
params: Promise<{ locale: string }>;
|
|
63
|
+
}) {
|
|
64
|
+
const { locale } = await params;
|
|
65
|
+
const dir = getDirection(locale);
|
|
66
|
+
const nonce = await getNonce();
|
|
67
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
68
|
+
|
|
69
|
+
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
70
|
+
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
71
|
+
// seeded a particular content type yet. New stores ship with default rows
|
|
72
|
+
// seeded by the backend (StoresService.seedDefaultContent).
|
|
73
|
+
const client = await getServerClient(locale);
|
|
74
|
+
const [announcements, siteHeader, siteFooter, storeInfo, messages] = await Promise.all([
|
|
75
|
+
client.content.announcement.list(locale).catch(() => []),
|
|
76
|
+
client.content.header.get('main', locale).catch(() => null),
|
|
77
|
+
client.content.footer.get('main', locale).catch(() => null),
|
|
78
|
+
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
79
|
+
// render with the real currency, feature flags, and i18n config at frame 0
|
|
80
|
+
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
81
|
+
fetchStoreInfo(locale),
|
|
82
|
+
// SSR-fetch UI message strings so translated text renders at frame 0 —
|
|
83
|
+
// without this, every page (and every locale switch) shows raw
|
|
84
|
+
// "namespace.key" strings until the client-only fetch resolves.
|
|
85
|
+
getMessages(locale),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<html lang={locale} dir={dir}>
|
|
90
|
+
<head>
|
|
91
|
+
<script
|
|
92
|
+
type="application/ld+json"
|
|
93
|
+
nonce={nonce}
|
|
94
|
+
suppressHydrationWarning
|
|
95
|
+
dangerouslySetInnerHTML={{
|
|
96
|
+
__html: JSON.stringify(buildOrganizationJsonLd(baseUrl))
|
|
97
|
+
.replace(/</g, '\\u003c')
|
|
98
|
+
.replace(/>/g, '\\u003e')
|
|
99
|
+
.replace(/&/g, '\\u0026'),
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
{/* Google Search Console verification — token set by the merchant in
|
|
103
|
+
the dashboard (channel settings → Google site verification). Also
|
|
104
|
+
what the Merchant Center website claim checks for. Renders nothing
|
|
105
|
+
until a token is configured. */}
|
|
106
|
+
{storeInfo?.seo?.googleSiteVerification ? (
|
|
107
|
+
<meta name="google-site-verification" content={storeInfo.seo.googleSiteVerification} />
|
|
108
|
+
) : null}
|
|
109
|
+
{/* RSS autodiscovery — feed readers and aggregators find the blog feed
|
|
110
|
+
from any page. The route itself lives at /blog/rss.xml. */}
|
|
111
|
+
<link rel="alternate" type="application/rss+xml" title="Blog" href="/blog/rss.xml" />
|
|
112
|
+
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
113
|
+
SPA route changes. Cookieless (no consent banner needed); the
|
|
114
|
+
merchant can toggle it per sales channel in the dashboard. */}
|
|
115
|
+
<script
|
|
116
|
+
defer
|
|
117
|
+
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
118
|
+
data-channel={
|
|
119
|
+
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
120
|
+
}
|
|
121
|
+
nonce={nonce}
|
|
122
|
+
suppressHydrationWarning
|
|
123
|
+
/>
|
|
124
|
+
</head>
|
|
125
|
+
<body className={font.className}>
|
|
126
|
+
<StoreProvider locale={locale} initialStoreInfo={storeInfo} initialMessages={messages}>
|
|
127
|
+
{/* Mounted ahead of the page tree on purpose: React runs effects in
|
|
128
|
+
tree order, so booting the tags here guarantees fbq/ttq exist by
|
|
129
|
+
the time a page component fires its first e-commerce event. */}
|
|
130
|
+
<TrackingBootstrap />
|
|
131
|
+
<div className="min-h-screen flex flex-col">
|
|
132
|
+
<AnnouncementBar announcements={announcements} />
|
|
133
|
+
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
134
|
+
<main className="flex-1">{children}</main>
|
|
135
|
+
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
136
|
+
</div>
|
|
137
|
+
<BrainerceBotWidget />
|
|
138
|
+
</StoreProvider>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
<% } else { %>
|
|
144
|
+
import type { Metadata } from 'next';
|
|
145
|
+
<%- fontImport %>
|
|
146
|
+
import { StoreProvider } from '@/core/providers/store-provider';
|
|
147
|
+
import { AnnouncementBar } from '@/ui/layout/announcement-bar';
|
|
148
|
+
import { SiteHeader } from '@/ui/layout/site-header';
|
|
149
|
+
import { SiteFooter } from '@/ui/layout/site-footer';
|
|
150
|
+
import { BrainerceBotWidget } from '@/components/brainerce-bot';
|
|
151
|
+
import { TrackingBootstrap } from '@/components/tracking-bootstrap';
|
|
152
|
+
import { getServerClient, fetchStoreInfo } from '@/core/lib/brainerce.server';
|
|
153
|
+
import { getNonce } from '@/core/lib/nonce';
|
|
154
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
155
|
+
import './globals.css';
|
|
156
|
+
|
|
157
|
+
<%- fontVariable %>
|
|
158
|
+
|
|
159
|
+
// Resolved per request rather than read from a hardcoded env default: the
|
|
160
|
+
// scaffolder cannot know the deploy domain, so `metadataBase` is the one
|
|
161
|
+
// value that must never be guessed. See core/lib/site-url.ts.
|
|
162
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
163
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
164
|
+
return {
|
|
165
|
+
metadataBase: new URL(baseUrl),
|
|
166
|
+
title: {
|
|
167
|
+
default: <%- storeNameJs %>,
|
|
168
|
+
template: <%- titleTemplateJs %>,
|
|
169
|
+
},
|
|
170
|
+
description: <%- storeNameJs %>,
|
|
171
|
+
alternates: {
|
|
172
|
+
canonical: '/',
|
|
173
|
+
},
|
|
174
|
+
openGraph: {
|
|
175
|
+
siteName: <%- storeNameJs %>,
|
|
176
|
+
locale: '<%= ogLocale %>',
|
|
177
|
+
type: 'website',
|
|
178
|
+
},
|
|
179
|
+
robots: {
|
|
180
|
+
index: true,
|
|
181
|
+
follow: true,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildOrganizationJsonLd(baseUrl: string) {
|
|
187
|
+
return {
|
|
188
|
+
'@context': 'https://schema.org',
|
|
189
|
+
'@type': 'Organization',
|
|
190
|
+
name: <%- storeNameJs %>,
|
|
191
|
+
url: baseUrl,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default async function RootLayout({
|
|
196
|
+
children,
|
|
197
|
+
}: {
|
|
198
|
+
children: React.ReactNode;
|
|
199
|
+
}) {
|
|
200
|
+
const nonce = await getNonce();
|
|
201
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
202
|
+
|
|
203
|
+
// Merchant-driven layout chrome — fetched server-side. Each call falls back
|
|
204
|
+
// to null/[] on 404 so the layout never crashes when the merchant hasn't
|
|
205
|
+
// seeded a particular content type yet. New stores ship with default rows
|
|
206
|
+
// seeded by the backend (StoresService.seedDefaultContent).
|
|
207
|
+
const client = await getServerClient();
|
|
208
|
+
const [announcements, siteHeader, siteFooter, storeInfo] = await Promise.all([
|
|
209
|
+
client.content.announcement.list().catch(() => []),
|
|
210
|
+
client.content.header.get('main').catch(() => null),
|
|
211
|
+
client.content.footer.get('main').catch(() => null),
|
|
212
|
+
// SSR-fetch store config so PriceDisplay / FreeShippingBar / upsell UI
|
|
213
|
+
// render with the real currency, feature flags, and i18n config at frame 0
|
|
214
|
+
// — without this, Googlebot sees USD/defaults baked into the HTML.
|
|
215
|
+
fetchStoreInfo(),
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<html lang="<%= language %>" dir="<%= direction %>">
|
|
220
|
+
<head>
|
|
221
|
+
<script
|
|
222
|
+
type="application/ld+json"
|
|
223
|
+
nonce={nonce}
|
|
224
|
+
suppressHydrationWarning
|
|
225
|
+
dangerouslySetInnerHTML={{
|
|
226
|
+
__html: JSON.stringify(buildOrganizationJsonLd(baseUrl))
|
|
227
|
+
.replace(/</g, '\\u003c')
|
|
228
|
+
.replace(/>/g, '\\u003e')
|
|
229
|
+
.replace(/&/g, '\\u0026'),
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
{/* Google Search Console verification — token set by the merchant in
|
|
233
|
+
the dashboard (channel settings → Google site verification). Also
|
|
234
|
+
what the Merchant Center website claim checks for. Renders nothing
|
|
235
|
+
until a token is configured. */}
|
|
236
|
+
{storeInfo?.seo?.googleSiteVerification ? (
|
|
237
|
+
<meta name="google-site-verification" content={storeInfo.seo.googleSiteVerification} />
|
|
238
|
+
) : null}
|
|
239
|
+
{/* RSS autodiscovery — feed readers and aggregators find the blog feed
|
|
240
|
+
from any page. The route itself lives at /blog/rss.xml. */}
|
|
241
|
+
<link rel="alternate" type="application/rss+xml" title="Blog" href="/blog/rss.xml" />
|
|
242
|
+
{/* Brainerce cookieless traffic analytics — auto-tracks pageviews +
|
|
243
|
+
SPA route changes. Cookieless (no consent banner needed); the
|
|
244
|
+
merchant can toggle it per sales channel in the dashboard. */}
|
|
245
|
+
<script
|
|
246
|
+
defer
|
|
247
|
+
src={`${process.env.NEXT_PUBLIC_BRAINERCE_PIXEL_URL || 'https://api.brainerce.com'}/t.js`}
|
|
248
|
+
data-channel={
|
|
249
|
+
process.env.NEXT_PUBLIC_BRAINERCE_SALES_CHANNEL_ID || '<%= connectionId %>'
|
|
250
|
+
}
|
|
251
|
+
nonce={nonce}
|
|
252
|
+
suppressHydrationWarning
|
|
253
|
+
/>
|
|
254
|
+
</head>
|
|
255
|
+
<body className={font.className}>
|
|
256
|
+
<StoreProvider initialStoreInfo={storeInfo}>
|
|
257
|
+
{/* Mounted ahead of the page tree on purpose: React runs effects in
|
|
258
|
+
tree order, so booting the tags here guarantees fbq/ttq exist by
|
|
259
|
+
the time a page component fires its first e-commerce event. */}
|
|
260
|
+
<TrackingBootstrap />
|
|
261
|
+
<div className="min-h-screen flex flex-col">
|
|
262
|
+
<AnnouncementBar announcements={announcements} />
|
|
263
|
+
<SiteHeader header={siteHeader} storeName={<%- storeNameJs %>} />
|
|
264
|
+
<main className="flex-1">{children}</main>
|
|
265
|
+
<SiteFooter footer={siteFooter} storeName={<%- storeNameJs %>} />
|
|
266
|
+
</div>
|
|
267
|
+
<BrainerceBotWidget />
|
|
268
|
+
</StoreProvider>
|
|
269
|
+
</body>
|
|
270
|
+
</html>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
<% } %>
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* right pages. /agents.md is the richer agent-facing companion (the newer
|
|
8
8
|
* convention) — keep the two consistent.
|
|
9
9
|
*/
|
|
10
|
-
import { getServerClient } from '@/core/lib/brainerce';
|
|
10
|
+
import { getServerClient } from '@/core/lib/brainerce.server';
|
|
11
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
11
12
|
|
|
12
13
|
export const revalidate = 3600;
|
|
13
14
|
|
|
@@ -18,8 +19,8 @@ interface CategoryNodeLike {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function GET() {
|
|
21
|
-
const baseUrl =
|
|
22
|
-
const client = getServerClient();
|
|
22
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
23
|
+
const client = await getServerClient();
|
|
23
24
|
|
|
24
25
|
const [info, posts, categoriesRes] = await Promise.all([
|
|
25
26
|
client.getStoreInfo().catch(() => null),
|