create-brainerce-store 1.74.0 → 1.76.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 +44 -7
- package/messages/en.json +10 -1
- package/messages/he.json +10 -1
- package/package.json +4 -2
- package/templates/nextjs/base/.env.local.ejs +62 -52
- package/templates/nextjs/base/AGENTS.md.ejs +189 -161
- package/templates/nextjs/base/CLAUDE.md.ejs +200 -172
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +37 -0
- package/templates/nextjs/base/src/app/page.tsx +75 -65
- package/templates/nextjs/base/src/app/register/page.tsx +74 -67
- package/templates/nextjs/base/src/components/account/loyalty-panel.tsx +79 -7
- package/templates/nextjs/base/src/components/account/order-history.tsx +34 -0
- package/templates/nextjs/base/src/components/auth/register-form.tsx +358 -326
- package/templates/nextjs/base/src/components/referral/referral-greeting.tsx +120 -0
- package/templates/nextjs/base/src/core/lib/auth.ts +162 -155
- package/templates/nextjs/base/src/core/lib/referral.ts +95 -0
- package/templates/nextjs/base/src/ui/product/product-card.tsx +5 -0
- package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +5 -0
- package/templates/nextjs/ui-canvas/layout/header-search.tsx +222 -0
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +6 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +5 -0
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +382 -375
|
@@ -1,172 +1,200 @@
|
|
|
1
|
-
# CLAUDE.md — this is a LIVE Brainerce storefront
|
|
2
|
-
|
|
3
|
-
**Store: "<%- storeName %>" · sales channel `<%- connectionId %>` — already
|
|
4
|
-
connected.** Products, cart, checkout, coupons, discounts, orders and content
|
|
5
|
-
flow in real time from the Brainerce dashboard. There is nothing to hook up.
|
|
6
|
-
|
|
7
|
-
- **NEVER suggest connecting this store to Shopify, WooCommerce, or "a real
|
|
8
|
-
system"** — Brainerce IS the commerce backend, and this store is wired to
|
|
9
|
-
it end-to-end.
|
|
10
|
-
- **NEVER build standalone HTML mockups or demo pages** — design THIS Next.js
|
|
11
|
-
app. Run `pnpm dev` and you are working against live data.
|
|
12
|
-
- **NEVER hardcode products, prices, or currency** — the catalog is live.
|
|
13
|
-
- **No server side is needed** — the backend is Brainerce's cloud. This repo
|
|
14
|
-
is a frontend (plus thin, already-included API proxy routes under
|
|
15
|
-
`src/app/api/`). Do not scaffold databases, auth servers, or admin panels —
|
|
16
|
-
the merchant manages everything in the Brainerce dashboard.
|
|
17
|
-
|
|
18
|
-
Platform docs (endpoints, SDK, integration recipes): https://brainerce.com/docs
|
|
19
|
-
— AI-readable index: https://brainerce.com/llms.txt
|
|
20
|
-
|
|
21
|
-
## MCP servers
|
|
22
|
-
|
|
23
|
-
- **`brainerce-docs`** (already connected via `.mcp.json`, no auth needed).
|
|
24
|
-
Treat it as the build spec, not a lookup desk. `get-required-features` is
|
|
25
|
-
the functional checklist this store is measured against, and it is longer
|
|
26
|
-
than what any art-direction brief would make you think to build.
|
|
27
|
-
`get-critical-rules` and `get-business-flows` carry the sequences that cause
|
|
28
|
-
incidents when reordered. `get-sdk-docs`, `get-type-definitions` and
|
|
29
|
-
`get-code-example` give current method shapes instead of training-data
|
|
30
|
-
guesses, and `get-store-capabilities` reports what is actually toggled on
|
|
31
|
-
for `<%- connectionId %>` right now. Read the checklist before you start,
|
|
32
|
-
and again before you call the work done.
|
|
33
|
-
- **Brainerce Admin MCP** — opt-in, not wired up by default. Lets an agent
|
|
34
|
-
directly manage this store's *live* data (products, orders, discounts,
|
|
35
|
-
shipping, …) instead of just reading docs. Only add it if the merchant
|
|
36
|
-
wants that: connect `https://api.brainerce.com/api/mcp` as a remote HTTP
|
|
37
|
-
MCP server (e.g. `claude mcp add --transport http brainerce-admin
|
|
38
|
-
https://api.brainerce.com/api/mcp`) — it opens a browser to log into the
|
|
39
|
-
Brainerce dashboard, pick this store, and grant scoped OAuth permissions.
|
|
40
|
-
Treat it like handing the agent write access to production commerce data.
|
|
41
|
-
|
|
42
|
-
**Before building any feature the merchant asks for** (loyalty points,
|
|
43
|
-
shipping zones, subscriptions, gift cards, donations, multi-currency, reviews,
|
|
44
|
-
abandoned-cart recovery, etc.) — check the docs first. Brainerce likely
|
|
45
|
-
already has it as a platform capability (dashboard toggle + hook/SDK
|
|
46
|
-
method) that only needs a UI in `src/ui/`, not a feature built from scratch.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`src/
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
1
|
+
# CLAUDE.md — this is a LIVE Brainerce storefront
|
|
2
|
+
|
|
3
|
+
**Store: "<%- storeName %>" · sales channel `<%- connectionId %>` — already
|
|
4
|
+
connected.** Products, cart, checkout, coupons, discounts, orders and content
|
|
5
|
+
flow in real time from the Brainerce dashboard. There is nothing to hook up.
|
|
6
|
+
|
|
7
|
+
- **NEVER suggest connecting this store to Shopify, WooCommerce, or "a real
|
|
8
|
+
system"** — Brainerce IS the commerce backend, and this store is wired to
|
|
9
|
+
it end-to-end.
|
|
10
|
+
- **NEVER build standalone HTML mockups or demo pages** — design THIS Next.js
|
|
11
|
+
app. Run `pnpm dev` and you are working against live data.
|
|
12
|
+
- **NEVER hardcode products, prices, or currency** — the catalog is live.
|
|
13
|
+
- **No server side is needed** — the backend is Brainerce's cloud. This repo
|
|
14
|
+
is a frontend (plus thin, already-included API proxy routes under
|
|
15
|
+
`src/app/api/`). Do not scaffold databases, auth servers, or admin panels —
|
|
16
|
+
the merchant manages everything in the Brainerce dashboard.
|
|
17
|
+
|
|
18
|
+
Platform docs (endpoints, SDK, integration recipes): https://brainerce.com/docs
|
|
19
|
+
— AI-readable index: https://brainerce.com/llms.txt
|
|
20
|
+
|
|
21
|
+
## MCP servers
|
|
22
|
+
|
|
23
|
+
- **`brainerce-docs`** (already connected via `.mcp.json`, no auth needed).
|
|
24
|
+
Treat it as the build spec, not a lookup desk. `get-required-features` is
|
|
25
|
+
the functional checklist this store is measured against, and it is longer
|
|
26
|
+
than what any art-direction brief would make you think to build.
|
|
27
|
+
`get-critical-rules` and `get-business-flows` carry the sequences that cause
|
|
28
|
+
incidents when reordered. `get-sdk-docs`, `get-type-definitions` and
|
|
29
|
+
`get-code-example` give current method shapes instead of training-data
|
|
30
|
+
guesses, and `get-store-capabilities` reports what is actually toggled on
|
|
31
|
+
for `<%- connectionId %>` right now. Read the checklist before you start,
|
|
32
|
+
and again before you call the work done.
|
|
33
|
+
- **Brainerce Admin MCP** — opt-in, not wired up by default. Lets an agent
|
|
34
|
+
directly manage this store's *live* data (products, orders, discounts,
|
|
35
|
+
shipping, …) instead of just reading docs. Only add it if the merchant
|
|
36
|
+
wants that: connect `https://api.brainerce.com/api/mcp` as a remote HTTP
|
|
37
|
+
MCP server (e.g. `claude mcp add --transport http brainerce-admin
|
|
38
|
+
https://api.brainerce.com/api/mcp`) — it opens a browser to log into the
|
|
39
|
+
Brainerce dashboard, pick this store, and grant scoped OAuth permissions.
|
|
40
|
+
Treat it like handing the agent write access to production commerce data.
|
|
41
|
+
|
|
42
|
+
**Before building any feature the merchant asks for** (loyalty points,
|
|
43
|
+
shipping zones, subscriptions, gift cards, donations, multi-currency, reviews,
|
|
44
|
+
abandoned-cart recovery, etc.) — check the docs first. Brainerce likely
|
|
45
|
+
already has it as a platform capability (dashboard toggle + hook/SDK
|
|
46
|
+
method) that only needs a UI in `src/ui/`, not a feature built from scratch.
|
|
47
|
+
|
|
48
|
+
### ⛔ This storefront never holds an admin API key
|
|
49
|
+
|
|
50
|
+
Everything here runs on the sales channel's PUBLIC credentials, and that is the
|
|
51
|
+
whole security model. `.env.local` ships no secret on purpose. Anything you put
|
|
52
|
+
in a browser-reachable app is readable by anyone who opens devtools, so an
|
|
53
|
+
admin API key (`brainerce_*`) in this project hands the merchant's entire live
|
|
54
|
+
store to the first person who looks. Never add one to `.env.local`, to a
|
|
55
|
+
hosting environment variable this app reads, or to any file under `src/`.
|
|
56
|
+
|
|
57
|
+
**Gift cards are where this goes wrong**, because one feature name covers two
|
|
58
|
+
very different powers:
|
|
59
|
+
|
|
60
|
+
- **Redemption is yours to build.** `applyGiftCard()`, `removeGiftCard()` and
|
|
61
|
+
`checkGiftCardBalance()` are public storefront calls needing no key beyond
|
|
62
|
+
the sales channel. `src/ui/cart/gift-card-input.tsx` already wires the first
|
|
63
|
+
two into checkout. Restyle it freely.
|
|
64
|
+
- **⛔ Issuing is not.** The `gift_cards:issue` scope mints stored value, which
|
|
65
|
+
is real money the merchant is liable for. It belongs to the dashboard, or to
|
|
66
|
+
server-side code on infrastructure the merchant controls. Never request that
|
|
67
|
+
scope for this storefront, never hold a key that carries it, and never add an
|
|
68
|
+
"issue a gift card" control to these pages. A merchant who wants to SELL gift
|
|
69
|
+
cards sells them as an ordinary product through normal checkout, and the
|
|
70
|
+
platform issues the card once payment clears.
|
|
71
|
+
|
|
72
|
+
The same split governs every admin capability you may be tempted to reach for
|
|
73
|
+
(creating discounts, editing inventory, refunding an order): this storefront
|
|
74
|
+
reads and transacts as a shopper, the dashboard administers.
|
|
75
|
+
|
|
76
|
+
Your job here is almost always **design**. It is never *only* design: the
|
|
77
|
+
coverage checklist in `get-required-features` applies to a redesign exactly as
|
|
78
|
+
it applies to a build from scratch.
|
|
79
|
+
|
|
80
|
+
## The one rule
|
|
81
|
+
|
|
82
|
+
**`src/core/` is the platform's. `src/ui/` is yours.**
|
|
83
|
+
|
|
84
|
+
The shipped `src/ui/` is a working reference, not a design to preserve: a full
|
|
85
|
+
delete-and-rebuild of the *look* is encouraged and expected, and you can
|
|
86
|
+
rewrite anything under `src/ui/` and `src/app/globals.css` as boldly as you
|
|
87
|
+
like. The store keeps working. (Stores scaffolded with `--canvas` ship
|
|
88
|
+
`src/ui/` as bare unstyled skeletons, so there is no reference look at all and
|
|
89
|
+
the design is entirely yours to create.) Never modify `src/core/`,
|
|
90
|
+
`src/app/api/`, or the checkout/auth/account components. Data and behavior come
|
|
91
|
+
exclusively from `@/core/hooks/*` and `@/core/providers/store-provider`: hooks
|
|
92
|
+
return state and handlers, never JSX. Never hardcode catalog content.
|
|
93
|
+
|
|
94
|
+
### Rebuilding the look is free. Dropping a feature is not.
|
|
95
|
+
|
|
96
|
+
Some files under `src/ui/` are the ONLY place a mandatory checklist entry
|
|
97
|
+
exists in this project. Delete one and the capability leaves the store with
|
|
98
|
+
nothing to notice it by: no type error, no console warning, and no visual hole
|
|
99
|
+
either, because these components auto-hide while the merchant has the feature
|
|
100
|
+
switched off, so a deleted one and an idle one look identical on the page.
|
|
101
|
+
These are the usual casualties of a redesign, because no art-direction brief
|
|
102
|
+
asks for them by name:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
product/ back-in-stock-form · customization-fields · modifier-group-selector
|
|
106
|
+
review-form · reviews-section · frequently-bought-together
|
|
107
|
+
discount-badge · stock-badge
|
|
108
|
+
cart/ reservation-countdown · coupon-input · cart-upgrade-banner
|
|
109
|
+
cart-bundle-offer · tax-estimate-line
|
|
110
|
+
home/ discount-banner-strip
|
|
111
|
+
layout/ newsletter-signup · announcement-bar · region-switcher<% if (i18nEnabled) { %>
|
|
112
|
+
language-switcher<% } %> · faq-section · rich-text-block
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Restyle them, re-lay them out, rename them, fold them into other components,
|
|
116
|
+
split them in half: all fine. What has to survive a rebuild is the SDK call
|
|
117
|
+
each one makes and the states it handles (loading, empty, failed, and the
|
|
118
|
+
merchant-has-it-off state that renders nothing).
|
|
119
|
+
|
|
120
|
+
The list is short on purpose and it is NOT the specification. It names the
|
|
121
|
+
files people lose, not every mandatory entry. `get-required-features` is the
|
|
122
|
+
specification, and step 1 of "Verify before declaring done" is what actually
|
|
123
|
+
catches a loss. `git show HEAD:src/ui/<path>` brings back anything you already
|
|
124
|
+
deleted: the scaffolder committed the tree before you touched it.
|
|
125
|
+
|
|
126
|
+
**Read `AI-GUIDE.md` before any redesign** — it has the full file map, hook
|
|
127
|
+
contracts, motion language, and hard-won RTL/i18n gotchas that will save you
|
|
128
|
+
real debugging time.
|
|
129
|
+
|
|
130
|
+
## The dialect
|
|
131
|
+
|
|
132
|
+
- `src/components/ui/` = official **shadcn/ui primitives** (Button, Card,
|
|
133
|
+
Badge, Input, Select, Accordion, Dialog, Sheet, Skeleton, …). Compose them;
|
|
134
|
+
extend looks via CVA **variants**, never repeated inline overrides.
|
|
135
|
+
- Icons: **lucide-react only** — no inline `<svg>`, no emoji-as-icons.
|
|
136
|
+
- Class merging: **`cn()`** from `@/core/lib/utils`.
|
|
137
|
+
- Colors/radius come from the semantic HSL tokens in `globals.css`
|
|
138
|
+
(`--primary`, `--card`, `--ring`, `--radius`, …) — restyle via tokens.
|
|
139
|
+
|
|
140
|
+
## Redesigning? Use the ready-made flow
|
|
141
|
+
|
|
142
|
+
Run `/design <your art-direction brief>` — it walks the whole process:
|
|
143
|
+
concept → implementation in `ui/` → verification.
|
|
144
|
+
|
|
145
|
+
## The store's web address
|
|
146
|
+
|
|
147
|
+
This project has no web address written into it, and that is deliberate — the
|
|
148
|
+
scaffolder cannot know where you will deploy. `src/core/lib/site-url.ts`
|
|
149
|
+
resolves it per request (hosting-platform variables, then the forwarded host),
|
|
150
|
+
so canonical tags, `sitemap.xml`, `robots.txt` and JSON-LD are correct in local
|
|
151
|
+
development and on any host with nothing configured.
|
|
152
|
+
|
|
153
|
+
- **Never invent an address.** Do not write `NEXT_PUBLIC_SITE_URL`,
|
|
154
|
+
`SITE_URL`, `localhost`, or a placeholder domain into `.env.local`. A wrong
|
|
155
|
+
absolute URL is indistinguishable from a right one to a search crawler, so a
|
|
156
|
+
guess is worse than an empty value.
|
|
157
|
+
- **Never hand-roll an origin** from `host` / `x-forwarded-proto` in a route
|
|
158
|
+
handler. Call `getCanonicalSiteUrl()` (for canonical/SEO URLs) or
|
|
159
|
+
`getRequestOrigin()` (for the `Origin` header sent to Brainerce). Hand-rolled
|
|
160
|
+
versions have shipped `http://` on HTTPS hosts and internal container
|
|
161
|
+
hostnames.
|
|
162
|
+
- **Some hosts hide the real hostname from the app.** Platforms whose proxy
|
|
163
|
+
forwards requests as `Host: localhost:<port>` with no `x-forwarded-host`
|
|
164
|
+
(OpenAI Sites / `*.chatgpt.site` does this) leave the resolver blind — the
|
|
165
|
+
request looks like it arrived on localhost. On such platforms `SITE_URL` is
|
|
166
|
+
not optional: set it, and the resolver prefers it over any internal host it
|
|
167
|
+
sees. Without it, server-side API calls send `Origin: http://localhost:3000`
|
|
168
|
+
and a channel with a configured Domain rejects them with 403.
|
|
169
|
+
- **When the merchant gives you a real domain**, set `SITE_URL` in the hosting
|
|
170
|
+
provider's environment variables — not in `.env.local`, which is gitignored
|
|
171
|
+
and never travels with a deploy.
|
|
172
|
+
- **Tell the merchant the other half.** A sales channel in **Live** mode only
|
|
173
|
+
accepts requests whose Origin matches the Domain configured on the channel in
|
|
174
|
+
the Brainerce dashboard. That check is server-side; nothing in this project
|
|
175
|
+
can satisfy it. If they skip it, every storefront API call returns 403 and the
|
|
176
|
+
store loads empty — no matter what `SITE_URL` says.
|
|
177
|
+
|
|
178
|
+
## Verify before declaring done
|
|
179
|
+
|
|
180
|
+
1. **Feature coverage, first and always.** Call `get-required-features` on the
|
|
181
|
+
`brainerce-docs` MCP server (already wired, nothing to set up) and confirm
|
|
182
|
+
every mandatory entry is still reachable in the running store. Do this
|
|
183
|
+
before the steps below, because if you rebuilt `src/ui/` a feature that
|
|
184
|
+
lived only in the shipped reference is now gone, and no other check can see
|
|
185
|
+
it: `tsc` cannot see a missing feature, and the component that used to
|
|
186
|
+
carry it auto-hid when the merchant had not switched it on, so the page
|
|
187
|
+
looks right either way.
|
|
188
|
+
2. `pnpm exec tsc --noEmit` → 0 errors
|
|
189
|
+
3. `pnpm dev` → drive the changed flow in a real browser (home → product →
|
|
190
|
+
add to cart → cart)
|
|
191
|
+
4. Screenshot desktop (1440px) and mobile (390px)
|
|
192
|
+
5. RTL stores: check anchoring and arrow directions
|
|
193
|
+
|
|
194
|
+
## i18n
|
|
195
|
+
|
|
196
|
+
Every user-facing string goes through `useTranslations()` with keys in **all**
|
|
197
|
+
files under `messages/`. The shipped copy is example boutique content — a
|
|
198
|
+
starting point meant to be rewritten in the store's real voice. Hebrew: no
|
|
199
|
+
uppercase transforms, no wide letter-spacing on headings, logical CSS
|
|
200
|
+
properties only (`ms-/me-`, `ps-/pe-`, `start-/end-`).
|
|
@@ -333,6 +333,43 @@ function ConfirmationLineItems({ order }: { order: Order | null }) {
|
|
|
333
333
|
{formatPrice(parseFloat(totalAmount), { currency }) as string}
|
|
334
334
|
</span>
|
|
335
335
|
</div>
|
|
336
|
+
|
|
337
|
+
{/* What actually paid.
|
|
338
|
+
This is the first screen a customer sees after handing over money, and
|
|
339
|
+
a total with no gift-card line tells them they paid an amount they did
|
|
340
|
+
not. The total above is what the order was WORTH and stays unreduced —
|
|
341
|
+
tax was calculated on it — so the cards sit below it, followed by what
|
|
342
|
+
the card or wallet was really charged. */}
|
|
343
|
+
{order.tenders && order.tenders.length > 0 && (
|
|
344
|
+
<>
|
|
345
|
+
{order.tenders.map((tender) => (
|
|
346
|
+
<div key={tender.id} className="mt-2 flex items-center justify-between text-sm">
|
|
347
|
+
<span className="text-muted-foreground">
|
|
348
|
+
{tender.giftCard?.codeLast4
|
|
349
|
+
? `${tc('giftCard')} ····${tender.giftCard.codeLast4}`
|
|
350
|
+
: tc('giftCard')}
|
|
351
|
+
</span>
|
|
352
|
+
<span className="text-primary">
|
|
353
|
+
-{formatPrice(parseFloat(tender.amountBase), { currency }) as string}
|
|
354
|
+
</span>
|
|
355
|
+
</div>
|
|
356
|
+
))}
|
|
357
|
+
<div className="border-border mt-2 flex items-center justify-between border-t pt-2">
|
|
358
|
+
<span className="text-foreground text-sm font-medium">{tc('amountCharged')}</span>
|
|
359
|
+
<span className="text-foreground text-sm font-semibold">
|
|
360
|
+
{
|
|
361
|
+
formatPrice(
|
|
362
|
+
order.tenders.reduce(
|
|
363
|
+
(due, tn) => due - parseFloat(tn.amountBase),
|
|
364
|
+
parseFloat(totalAmount)
|
|
365
|
+
),
|
|
366
|
+
{ currency }
|
|
367
|
+
) as string
|
|
368
|
+
}
|
|
369
|
+
</span>
|
|
370
|
+
</div>
|
|
371
|
+
</>
|
|
372
|
+
)}
|
|
336
373
|
</div>
|
|
337
374
|
);
|
|
338
375
|
}
|
|
@@ -1,65 +1,75 @@
|
|
|
1
|
-
import type { Metadata } from 'next';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
}
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { getServerClient } from '@/core/lib/brainerce.server';
|
|
4
|
+
import { buildMetaDescription } from '@/core/lib/seo';
|
|
5
|
+
import { OrganizationJsonLd } from '@/components/seo/organization-json-ld';
|
|
6
|
+
import { getCanonicalSiteUrl } from '@/core/lib/site-url';
|
|
7
|
+
import { RichTextBlock } from '@/ui/layout/rich-text-block';
|
|
8
|
+
import { HomeClient } from '@/ui/home/home-client';
|
|
9
|
+
import { ReferralGreeting } from '@/components/referral/referral-greeting';
|
|
10
|
+
|
|
11
|
+
// Build homepage metadata server-side so Google, Facebook, WhatsApp etc.
|
|
12
|
+
// see a real <meta name="description"> on first request. Falls back to the
|
|
13
|
+
// store name when the merchant hasn't authored a meta description yet.
|
|
14
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
15
|
+
try {
|
|
16
|
+
const storeInfo = await (await getServerClient()).getStoreInfo();
|
|
17
|
+
const description = buildMetaDescription(storeInfo.metaDescription) || undefined;
|
|
18
|
+
return {
|
|
19
|
+
title: storeInfo.name,
|
|
20
|
+
description,
|
|
21
|
+
openGraph: {
|
|
22
|
+
title: storeInfo.name,
|
|
23
|
+
description,
|
|
24
|
+
type: 'website',
|
|
25
|
+
},
|
|
26
|
+
twitter: {
|
|
27
|
+
card: 'summary_large_image',
|
|
28
|
+
title: storeInfo.name,
|
|
29
|
+
description,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default async function HomePage() {
|
|
38
|
+
// Fetch store info server-side so the JSON-LD is in the initial HTML —
|
|
39
|
+
// crawlers and AI Overviews see it without needing to execute JavaScript.
|
|
40
|
+
// Failure here is non-fatal: the client component re-fetches via the
|
|
41
|
+
// StoreProvider so the page still renders.
|
|
42
|
+
//
|
|
43
|
+
// Also fetch the merchant's homepage intro (RICH_TEXT key='main'). When
|
|
44
|
+
// present, it renders as a sanitized HTML block above the product grid —
|
|
45
|
+
// typical use is brand introduction, value-prop copy, or a seasonal
|
|
46
|
+
// announcement that needs more formatting than the AnnouncementBar.
|
|
47
|
+
// When absent, RichTextBlock returns null cleanly so the layout collapses
|
|
48
|
+
// to just the product grid.
|
|
49
|
+
const client = await getServerClient();
|
|
50
|
+
const [storeInfo, intro] = await Promise.all([
|
|
51
|
+
client.getStoreInfo().catch(() => null),
|
|
52
|
+
client.content.richText.get('main').catch(() => null),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const baseUrl = await getCanonicalSiteUrl();
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
{storeInfo && baseUrl ? <OrganizationJsonLd storeInfo={storeInfo} baseUrl={baseUrl} /> : null}
|
|
60
|
+
{/* Referral landing. A referral link is `/?ref=<code>`, so it lands here.
|
|
61
|
+
Renders nothing without a valid `?ref=`, and nothing at all on a store
|
|
62
|
+
with referrals switched off. The Suspense boundary is required: the
|
|
63
|
+
component reads `useSearchParams()`, which would otherwise opt this
|
|
64
|
+
whole page out of static rendering. */}
|
|
65
|
+
<Suspense fallback={null}>
|
|
66
|
+
<ReferralGreeting className="mt-6" />
|
|
67
|
+
</Suspense>
|
|
68
|
+
<RichTextBlock
|
|
69
|
+
block={intro}
|
|
70
|
+
className="prose prose-sm dark:prose-invert mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8"
|
|
71
|
+
/>
|
|
72
|
+
<HomeClient />
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|