import type { StoreInfo } from 'brainerce'; /** * The subset of {@link StoreInfo} that is safe to serialize into the * server-rendered HTML / RSC payload. Sales-channel-only operational fields * (channel name, connection status, allowed API scopes, sandbox flags, * internal cuids) are intentionally omitted — they leak operational state to * crawlers and have no role in storefront rendering. * * Uses indexed access on `StoreInfo` for nested types (`upsell`, `i18n`) so * this stays automatically in sync with the SDK without depending on those * sub-types being exported individually. */ export interface PublicStoreInfo { name: string; currency: string; language: string; metaDescription?: string | null; logo?: string | null; contactEmail?: string | null; contactPhone?: string | null; socialLinks?: Record | null; requireEmailVerification?: boolean; /** * Whether the merchant made the birthday mandatory at registration on this * sales channel. Render the signup form's month and day fields as required * when true and block the submit while either is empty, because the backend * rejects a register call without both with HTTP 400. Absent means optional. * Nothing else in the storefront enforces it, so treat it purely as a * rendering hint. */ requireBirthday?: boolean; upsell?: StoreInfo['upsell']; i18n?: StoreInfo['i18n']; /** Real flat-rate/free shipping zones — feeds Product JSON-LD `shippingDetails`. Public by design (merchants display shipping rates openly). */ shipping?: StoreInfo['shipping']; /** * Marketing tag ids (GA4 / GTM / Meta / TikTok) resolved from the apps the * merchant connected in the Brainerce dashboard. Public by design — these * ids appear in the page source of every storefront that runs a tag, and the * backend format-validates them before serving. Consumed by * ``; nothing else should read them. */ tracking?: StoreInfo['tracking']; /** * SEO surface: IndexNow key (served at /indexnow-key.txt) and the Google * site-verification token (rendered as a meta tag in the root layout so the * merchant can verify the domain in Search Console / claim it in Merchant * Center). Both public by protocol design. */ seo?: StoreInfo['seo']; /** * Whether this sales channel offers a back-in-stock alert on a sold-out * product. Read by `/products/[slug]` and passed into * ``. * * ⛔ It was READ before it was PICKED, so it arrived as `undefined` on every * storefront and the page also failed `tsc --noEmit` out of the box. If you * add a consumer of a StoreInfo field, add it to the projection below in the * same change or it silently resolves to undefined. */ stockAlertsEnabled?: boolean; /** * IANA timezone the store operates in (e.g. "Asia/Jerusalem"). The checkout * date/time custom fields evaluate availability against THIS, never the * shopper's browser timezone. Same story as above: read but not picked. */ timezone?: string; } /** * Project a raw {@link StoreInfo} response from the backend onto the * {@link PublicStoreInfo} shape. Anything not listed here never reaches the * browser. Add a field here only after confirming it is non-sensitive and * required by a storefront-side consumer. */ export function pickPublicStoreInfo(raw: StoreInfo): PublicStoreInfo { return { name: raw.name, currency: raw.currency, language: raw.language, metaDescription: raw.metaDescription ?? null, logo: raw.logo ?? null, contactEmail: raw.contactEmail ?? null, contactPhone: raw.contactPhone ?? null, socialLinks: raw.socialLinks ?? null, requireEmailVerification: raw.requireEmailVerification, requireBirthday: raw.requireBirthday, upsell: raw.upsell, i18n: raw.i18n, shipping: raw.shipping, tracking: raw.tracking, seo: raw.seo, stockAlertsEnabled: raw.stockAlertsEnabled, timezone: raw.timezone, }; }