create-cartbase 0.1.13 → 0.1.15

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.
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@cartbase/storefront": "^0.14.0",
12
+ "@cartbase/storefront": "^0.16.0",
13
13
  "next": "16.2.4",
14
14
  "react": "19.2.4",
15
15
  "react-dom": "19.2.4"
@@ -9,7 +9,7 @@ import { StorefrontTags } from "@cartbase/storefront/tracking/storefront-tags"
9
9
  import { TrackInit } from "@cartbase/storefront/tracking/track-init"
10
10
  import { CartButtonClient } from "@cartbase/storefront/common/cart-button-client"
11
11
  import { createStorefrontMetadata, PlatformInit } from "@cartbase/storefront/platform"
12
- import { BARTER_CLIENT_ID, readCartCookie } from "@/lib/config"
12
+ import { BARTER_CLIENT_ID, readCartCookie, STORE_LOCALE } from "@/lib/config"
13
13
  import { getServerClient } from "@/lib/server-client"
14
14
  import { Providers } from "./providers"
15
15
  import "./globals.css"
@@ -126,14 +126,18 @@ export default async function RootLayout({
126
126
  ])
127
127
 
128
128
  return (
129
- <html lang="en">
129
+ <html lang={STORE_LOCALE.code}>
130
130
  <body>
131
- <ConsentInit />
131
+ {/* The store's own consent switch decides the default: a store
132
+ that collects consent starts every visitor denied until they
133
+ choose, a store with the banner off starts them granted. The
134
+ prop is required, so a layout cannot leave it out. */}
135
+ <ConsentInit required={consentRes.consent.enabled} />
132
136
  {/* Every marketing tag the store configured in the admin, mounted
133
- from its own config: Meta, TikTok, Google (GA4 + Ads on one
134
- tag), GTM. Nothing to wire per vendor — saving the ids in
135
- Settings → Integrations is the whole merchant-side act. Order
136
- matters: ConsentInit sets the Consent Mode defaults
137
+ from its own config: Meta, TikTok, ChatGPT, Google (GA4 + Ads
138
+ on one tag), GTM. Nothing to wire per vendor — saving the ids
139
+ in Settings → Integrations is the whole merchant-side act.
140
+ Order matters: ConsentInit sets the Consent Mode defaults
137
141
  synchronously ABOVE this, so every tag below inherits the
138
142
  gate. */}
139
143
  <StorefrontTags client={client} />
@@ -15,6 +15,7 @@ import {
15
15
  import { TrackOrderPurchase } from "@cartbase/storefront/tracking/track-order-purchase"
16
16
  import { useTrackingConfig } from "@cartbase/storefront/tracking/use-tracking-config"
17
17
  import { browserClient } from "@/lib/browser-client"
18
+ import { STORE_LOCALE } from "@/lib/config"
18
19
  import { LAST_ORDER_STORAGE_KEY } from "../../../checkout/checkout-page-client"
19
20
 
20
21
  /**
@@ -86,6 +87,9 @@ export default function OrderConfirmedPage() {
86
87
  // the order.
87
88
  paymentMethodName={"Cash on delivery"}
88
89
  storeHref="/"
90
+ // The order pack, as a prop: this template reads its copy from props,
91
+ // not from the locale provider, and the prop is required.
92
+ labels={STORE_LOCALE.order}
89
93
  />
90
94
  </>
91
95
  )
@@ -1,6 +1,6 @@
1
1
  import { StoreTemplate } from "@cartbase/storefront/store/store-template"
2
2
  import type { SortOptions } from "@cartbase/storefront/lib/sort-products"
3
- import { PRICING_CONTEXT } from "@/lib/config"
3
+ import { PRICING_CONTEXT, STORE_LOCALE } from "@/lib/config"
4
4
  import { getServerClient } from "@/lib/server-client"
5
5
 
6
6
  /**
@@ -20,6 +20,9 @@ export default async function HomePage({
20
20
  sortBy={sortBy as SortOptions | undefined}
21
21
  page={page}
22
22
  pricingContext={PRICING_CONTEXT}
23
+ // Server-rendered: the locale provider cannot reach it, so the pack
24
+ // comes as a prop, and the prop is required.
25
+ labels={STORE_LOCALE.store}
23
26
  />
24
27
  )
25
28
  }
@@ -3,7 +3,7 @@ import { retrieveProduct } from "@cartbase/storefront/api/products"
3
3
  import { StoreApiError } from "@cartbase/storefront/api/types"
4
4
  import { ProductTemplate } from "@cartbase/storefront/products/product-template"
5
5
  import { addToCartAction } from "@/lib/cart-actions"
6
- import { PRICING_CONTEXT } from "@/lib/config"
6
+ import { PRICING_CONTEXT, STORE_LOCALE } from "@/lib/config"
7
7
  import { getServerClient } from "@/lib/server-client"
8
8
 
9
9
  /**
@@ -13,10 +13,12 @@ import { getServerClient } from "@/lib/server-client"
13
13
  */
14
14
  export default async function ProductPage({
15
15
  params,
16
+ searchParams,
16
17
  }: {
17
18
  params: Promise<{ handle: string }>
19
+ searchParams: Promise<{ variant?: string }>
18
20
  }) {
19
- const { handle } = await params
21
+ const [{ handle }, { variant }] = await Promise.all([params, searchParams])
20
22
  const client = await getServerClient()
21
23
 
22
24
  let product
@@ -34,6 +36,12 @@ export default async function ProductPage({
34
36
  product={product}
35
37
  pricingContext={PRICING_CONTEXT}
36
38
  addToCart={addToCartAction}
39
+ // The product page contract: the address names the variant
40
+ // (`?variant=<digits>`, Shopify's parameter), and the page hands it to
41
+ // the template so the FIRST paint is that variant's price, code and
42
+ // stock. Switching a variant rewrites the address in the browser
43
+ // without asking the server.
44
+ initialVariantId={variant}
37
45
  // NO `promises` here, deliberately. Until 0.13.0 the library carried
38
46
  // delivery, exchange and return promises as label DEFAULTS, so every
39
47
  // store scaffolded from this file told shoppers "your package will
@@ -50,6 +58,11 @@ export default async function ProductPage({
50
58
  // default for a store that has not decided yet. The physical-facts
51
59
  // section appears on its own for products that HAVE facts, and is
52
60
  // skipped for those that do not.
61
+ //
62
+ // The pack has to be handed over here, not just mounted at the root:
63
+ // the related-products strip is a server component and cannot read
64
+ // the locale provider, so its heading would stay English. Required.
65
+ labels={STORE_LOCALE.products}
53
66
  />
54
67
  )
55
68
  }
@@ -9,14 +9,20 @@ import {
9
9
  shouldRenderBanner,
10
10
  type ConsentSettings,
11
11
  } from "@cartbase/storefront/tracking/consent"
12
- import { CART_COOKIE, CART_COOKIE_MAX_AGE } from "@/lib/config"
12
+ import { StorefrontLocaleProvider } from "@cartbase/storefront/locales"
13
+ import { CART_COOKIE, CART_COOKIE_MAX_AGE, STORE_LOCALE } from "@/lib/config"
13
14
  import { browserClient } from "@/lib/browser-client"
14
15
 
15
16
  /**
16
- * Client-side shell: ONE CartDrawerProvider per app (cart-drawer family
17
- * mount rule) + the consent banner (consent.md: render the builtin banner
18
- * only when `enabled && mode === "builtin"`; ConsentInit stays in the
19
- * server layout as the first child of <body>).
17
+ * Client-side shell: the store's language, ONE CartDrawerProvider per app
18
+ * (cart-drawer family mount rule) + the consent banner (consent.md: render
19
+ * the builtin banner only when `enabled && mode === "builtin"`; ConsentInit
20
+ * stays in the server layout as the first child of <body>).
21
+ *
22
+ * The language is mounted once, here. Every client component inside reads
23
+ * it, the cart drawer included, so nothing is handed over below. The
24
+ * server-rendered templates cannot read it, so each page hands them their
25
+ * area (`labels={STORE_LOCALE.store}`), and those props are required.
20
26
  */
21
27
  export function Providers({
22
28
  cart,
@@ -27,28 +33,32 @@ export function Providers({
27
33
  consent: ConsentSettings
28
34
  children: React.ReactNode
29
35
  }) {
30
- const copy = pickConsentCopy(consent.copy, "en")
36
+ // The store's own consent copy, in the store's language where it has it;
37
+ // pickConsentCopy falls back to English and then to whatever exists.
38
+ const copy = pickConsentCopy(consent.copy, STORE_LOCALE.code)
31
39
  return (
32
- <CartDrawerProvider
33
- cart={cart}
34
- client={browserClient}
35
- onCartChange={(next) => {
36
- // Persist the cart id (carts.md: the app owns the cart cookie).
37
- document.cookie = `${CART_COOKIE}=${encodeURIComponent(
38
- next.id
39
- )};path=/;max-age=${CART_COOKIE_MAX_AGE};samesite=lax`
40
- }}
41
- >
42
- {children}
43
- <CartDrawerTemplate />
44
- {shouldRenderBanner(consent) && copy && (
45
- <ConsentBanner
46
- copy={copy}
47
- layout={consent.layout}
48
- privacyHref={consent.privacy_href}
49
- rejectOnFirstLayer={consent.reject_on_first_layer}
50
- />
51
- )}
52
- </CartDrawerProvider>
40
+ <StorefrontLocaleProvider locale={STORE_LOCALE}>
41
+ <CartDrawerProvider
42
+ cart={cart}
43
+ client={browserClient}
44
+ onCartChange={(next) => {
45
+ // Persist the cart id (carts.md: the app owns the cart cookie).
46
+ document.cookie = `${CART_COOKIE}=${encodeURIComponent(
47
+ next.id
48
+ )};path=/;max-age=${CART_COOKIE_MAX_AGE};samesite=lax`
49
+ }}
50
+ >
51
+ {children}
52
+ <CartDrawerTemplate />
53
+ {shouldRenderBanner(consent) && copy && (
54
+ <ConsentBanner
55
+ copy={copy}
56
+ layout={consent.layout}
57
+ privacyHref={consent.privacy_href}
58
+ rejectOnFirstLayer={consent.reject_on_first_layer}
59
+ />
60
+ )}
61
+ </CartDrawerProvider>
62
+ </StorefrontLocaleProvider>
53
63
  )
54
64
  }
@@ -1,5 +1,5 @@
1
1
  import { SearchTemplate } from "@cartbase/storefront/store/search-template"
2
- import { PRICING_CONTEXT } from "@/lib/config"
2
+ import { PRICING_CONTEXT, STORE_LOCALE } from "@/lib/config"
3
3
  import { getServerClient } from "@/lib/server-client"
4
4
 
5
5
  /** Search page (runbook step 5) — fully URL-state driven search-template. */
@@ -15,6 +15,9 @@ export default async function SearchPage({
15
15
  client={client}
16
16
  searchParams={params}
17
17
  pricingContext={PRICING_CONTEXT}
18
+ // Server-rendered: the locale provider cannot reach it, so the pack
19
+ // comes as a prop, and the prop is required.
20
+ labels={STORE_LOCALE.store}
18
21
  />
19
22
  )
20
23
  }
@@ -26,5 +26,16 @@ export const CART_COOKIE_MAX_AGE = 60 * 60 * 24 * 30 // 30 days
26
26
  /** Locale cookie read by the clients' `getLocale`. */
27
27
  export const LOCALE_COOKIE = "_barter_locale"
28
28
 
29
+ /**
30
+ * THE STORE'S LANGUAGE, declared once. The library speaks international
31
+ * English and knows no other language; a language is a typed pack the store
32
+ * imports. Swap `en` for `bg` or `es` here (`@cartbase/storefront/locales/bg`)
33
+ * and every screen follows: providers.tsx mounts it for the client
34
+ * components, and each page hands the server-rendered templates their area
35
+ * from it (`labels={STORE_LOCALE.store}`), because a server component cannot
36
+ * read a client context. Those props are required, so a page cannot forget.
37
+ */
38
+ export { en as STORE_LOCALE } from "@cartbase/storefront/locales/en"
39
+
29
40
  /** Pricing context for every catalog surface (runbook step 5). EUR only. */
30
41
  export const PRICING_CONTEXT = { currency_code: "eur" } as const