@numueg/theme-sdk 0.3.1 → 0.3.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contexts/index.ts","../src/hooks/usePage.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/v2-compat.ts"],"names":["createContext","useContext","useMemo","useState","useEffect"],"mappings":"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","file":"v2-compat.cjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<{ cart: Cart; addItem: (productId: string, variantId?: string, quantity?: number) => Promise<void>; removeItem: (itemId: string) => Promise<void>; updateQuantity: (itemId: string, quantity: number) => Promise<void>; applyDiscount: (code: string) => Promise<void>; removeDiscount: () => Promise<void>; updateNote: (note: string) => Promise<void>; clearCart: () => Promise<void>; loading: boolean } | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","/**\n * V2 → V3 compatibility shim.\n *\n * V2 themes in `numu-egyptian-bazaar/src/themes/` consume data via the\n * bazaar's own context hooks (useStore, useProductsContext,\n * useAuth, useLanguage, useTheme). When porting a V2 theme to V3 via\n * `numu-theme migrate`, those hooks don't exist inside the BYOT\n * bundle — the bundle runs in its own React tree fed by the SDK's\n * NuMuProvider, which exposes a different (V3) API.\n *\n * Rather than rewrite every section by hand on the first pass, themes\n * can import these compat hooks (e.g. `useV2Products()`) which return\n * the OLD V2-shaped data the section already knows how to render.\n * Internally they thunk into the V3 SDK hooks. This buys \"the theme\n * renders\" same-day; the per-section idiomatic V3 rewrite then\n * happens incrementally without blocking the bundle build.\n *\n * Use cases:\n *\n * 1. `useV2Products()` — returns `Product[]` (flat, no pagination\n * shape). Internally calls `useProducts()` and exposes only\n * the first page's items. Themes that paginate manually keep\n * working; themes that assume \"all products are loaded\" keep\n * working with the first 50.\n *\n * 2. `useV2Categories()` — returns `Category[]` shaped like the\n * bazaar's `Category` (id, name, slug, image_url) rather than\n * the SDK's `Collection`. Adapter maps fields.\n *\n * 3. `useV2Auth()` — returns `{ user, isAuthenticated }`. Maps\n * from `useCustomer()`'s null-or-Customer return.\n *\n * 4. `useV2Language()` — returns `{ language, setLanguage }` with\n * 'en' | 'ar' shape. Maps to `useLocalization()`.\n *\n * 5. `useV2Theme()` — returns the bazaar's V2 theme shape\n * (`{ themeSettings.theme.primary_color }`) by reading from the\n * V3 `themeSettings.global_settings` (which is where the\n * migrated values end up).\n *\n * Subpath import: `import { useV2Products } from \"@numueg/theme-sdk/v2-compat\"`.\n * Keeping this off the main entrypoint signals \"this is a migration\n * helper, plan to remove it\" — themes that linger on the shim past\n * the V2 retirement deadline (Wave 8) will fail to resolve the\n * subpath once we drop the file.\n */\n\n\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useProducts } from \"./hooks/useProducts\";\nimport { useCollections } from \"./hooks/useCollections\";\nimport { useCustomer } from \"./hooks/useCustomer\";\nimport { useLocalization } from \"./hooks/useLocalization\";\nimport { useThemeSettings } from \"./hooks/useThemeSettings\";\nimport type { Product, Collection, Customer } from \"./types/entities\";\n\n// ─── useV2Products ──────────────────────────────────────────────────────────\n\n/**\n * V2's `useProductsContext().products` returned a flat array; V3's\n * useProducts() returns `{ items, loading, hasMore, ... }`. The shim\n * exposes `items` plus a `loading` flag so V2 sections that conditionally\n * render against a \"still loading\" placeholder don't break.\n */\nexport interface V2ProductsState {\n products: Product[];\n loading: boolean;\n}\n\nexport function useV2Products(): V2ProductsState {\n const { products, loading } = useProducts();\n return useMemo(\n () => ({\n products,\n loading,\n }),\n [products, loading],\n );\n}\n\n// ─── useV2Categories ────────────────────────────────────────────────────────\n\n/**\n * Bazaar `Category` shape. V2 sections destructure `cat.image_url`,\n * `cat.name`; the SDK's `Collection` calls the field `image`. Map.\n */\nexport interface V2Category {\n id: string;\n name: string;\n slug?: string;\n image_url?: string | null;\n description?: string | null;\n}\n\nexport interface V2CategoriesState {\n categories: V2Category[];\n loading: boolean;\n}\n\nfunction collectionToV2Category(c: Collection): V2Category {\n return {\n id: c.id,\n name: c.name,\n slug: c.slug,\n image_url: c.image_url ?? null,\n description: c.description ?? null,\n };\n}\n\nexport function useV2Categories(): V2CategoriesState {\n const { collections, loading } = useCollections();\n return useMemo(\n () => ({\n categories: collections.map(collectionToV2Category),\n loading,\n }),\n [collections, loading],\n );\n}\n\n// ─── useV2Auth ──────────────────────────────────────────────────────────────\n\nexport interface V2AuthState {\n user: Customer | null;\n isAuthenticated: boolean;\n}\n\nexport function useV2Auth(): V2AuthState {\n const customer = useCustomer();\n return useMemo(\n () => ({\n user: customer,\n isAuthenticated: customer !== null,\n }),\n [customer],\n );\n}\n\n// ─── useV2Language ──────────────────────────────────────────────────────────\n\nexport interface V2LanguageState {\n language: \"ar\" | \"en\";\n direction: \"rtl\" | \"ltr\";\n setLanguage: (next: \"ar\" | \"en\") => void;\n t: (key: string, fallback?: string) => string;\n}\n\nexport function useV2Language(): V2LanguageState {\n const { locale, direction, translations, setLocale } = useLocalization();\n const normalisedLocale = locale === \"ar\" ? \"ar\" : \"en\";\n return useMemo<V2LanguageState>(\n () => ({\n language: normalisedLocale,\n direction,\n setLanguage: (next) => setLocale(next),\n t: (key, fallback) => translations[key] ?? fallback ?? key,\n }),\n [normalisedLocale, direction, translations, setLocale],\n );\n}\n\n// ─── useV2Theme ─────────────────────────────────────────────────────────────\n\n/**\n * V2 themes destructure `useTheme().themeSettings.theme.primary_color`\n * etc. V3 stores the same values under `global_settings.<key>` (the\n * V2 → V3 normalizer flattens the nested object on first read).\n * This shim re-wraps the flat V3 shape into the V2 nested form so the\n * old destructure paths still resolve.\n *\n * Field map (V2 nested key → V3 flat key):\n *\n * theme.primary_color → global_settings.primary_color\n * theme.accent_color → global_settings.accent_color\n * theme.background_color → global_settings.background_color\n * theme.text_color → global_settings.text_color\n * theme.heading_font → global_settings.heading_font\n * identity.logo_url → global_settings.logo_url\n * identity.store_name → global_settings.store_name\n *\n * Themes that read keys not in this map fall through to undefined —\n * the consumer code should treat them as optional (V2 already did).\n */\nexport interface V2ThemeShape {\n themeSettings: {\n theme: {\n primary_color?: string;\n accent_color?: string;\n background_color?: string;\n text_color?: string;\n heading_font?: string;\n [extra: string]: unknown;\n };\n identity: {\n logo_url?: string;\n store_name?: string;\n [extra: string]: unknown;\n };\n [extra: string]: unknown;\n };\n}\n\nexport function useV2Theme(): V2ThemeShape {\n const settings = useThemeSettings();\n return useMemo<V2ThemeShape>(() => {\n const g = settings.global_settings ?? {};\n const get = (key: string): string | undefined => {\n const v = (g as Record<string, unknown>)[key];\n return typeof v === \"string\" ? v : undefined;\n };\n return {\n themeSettings: {\n theme: {\n primary_color: get(\"primary_color\"),\n accent_color: get(\"accent_color\"),\n background_color: get(\"background_color\"),\n text_color: get(\"text_color\"),\n heading_font: get(\"heading_font\"),\n ...g,\n },\n identity: {\n logo_url: get(\"logo_url\"),\n store_name: get(\"store_name\"),\n },\n },\n };\n }, [settings]);\n}\n"]}
1
+ {"version":3,"sources":["../src/contexts/index.ts","../src/hooks/usePage.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/v2-compat.ts"],"names":["createContext","useContext","useMemo","useState","useEffect"],"mappings":"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","file":"v2-compat.cjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<{ cart: Cart; addItem: (productId: string, variantId?: string, quantity?: number) => Promise<void>; removeItem: (itemId: string) => Promise<void>; updateQuantity: (itemId: string, quantity: number) => Promise<void>; applyDiscount: (code: string) => Promise<void>; removeDiscount: () => Promise<void>; updateNote: (note: string) => Promise<void>; clearCart: () => Promise<void>; loading: boolean } | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n /**\n * §5 hide-page → hide-nav-link. `false` when the item targets a CMS page\n * (`/pages/<handle>`) that is currently unpublished or deleted. The backend\n * menus resolver annotates it; absent/`true` means visible (back-compat).\n */\n target_visible?: boolean;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","/**\n * V2 → V3 compatibility shim.\n *\n * V2 themes in `numu-egyptian-bazaar/src/themes/` consume data via the\n * bazaar's own context hooks (useStore, useProductsContext,\n * useAuth, useLanguage, useTheme). When porting a V2 theme to V3 via\n * `numu-theme migrate`, those hooks don't exist inside the BYOT\n * bundle — the bundle runs in its own React tree fed by the SDK's\n * NuMuProvider, which exposes a different (V3) API.\n *\n * Rather than rewrite every section by hand on the first pass, themes\n * can import these compat hooks (e.g. `useV2Products()`) which return\n * the OLD V2-shaped data the section already knows how to render.\n * Internally they thunk into the V3 SDK hooks. This buys \"the theme\n * renders\" same-day; the per-section idiomatic V3 rewrite then\n * happens incrementally without blocking the bundle build.\n *\n * Use cases:\n *\n * 1. `useV2Products()` — returns `Product[]` (flat, no pagination\n * shape). Internally calls `useProducts()` and exposes only\n * the first page's items. Themes that paginate manually keep\n * working; themes that assume \"all products are loaded\" keep\n * working with the first 50.\n *\n * 2. `useV2Categories()` — returns `Category[]` shaped like the\n * bazaar's `Category` (id, name, slug, image_url) rather than\n * the SDK's `Collection`. Adapter maps fields.\n *\n * 3. `useV2Auth()` — returns `{ user, isAuthenticated }`. Maps\n * from `useCustomer()`'s null-or-Customer return.\n *\n * 4. `useV2Language()` — returns `{ language, setLanguage }` with\n * 'en' | 'ar' shape. Maps to `useLocalization()`.\n *\n * 5. `useV2Theme()` — returns the bazaar's V2 theme shape\n * (`{ themeSettings.theme.primary_color }`) by reading from the\n * V3 `themeSettings.global_settings` (which is where the\n * migrated values end up).\n *\n * Subpath import: `import { useV2Products } from \"@numueg/theme-sdk/v2-compat\"`.\n * Keeping this off the main entrypoint signals \"this is a migration\n * helper, plan to remove it\" — themes that linger on the shim past\n * the V2 retirement deadline (Wave 8) will fail to resolve the\n * subpath once we drop the file.\n */\n\n\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useProducts } from \"./hooks/useProducts\";\nimport { useCollections } from \"./hooks/useCollections\";\nimport { useCustomer } from \"./hooks/useCustomer\";\nimport { useLocalization } from \"./hooks/useLocalization\";\nimport { useThemeSettings } from \"./hooks/useThemeSettings\";\nimport type { Product, Collection, Customer } from \"./types/entities\";\n\n// ─── useV2Products ──────────────────────────────────────────────────────────\n\n/**\n * V2's `useProductsContext().products` returned a flat array; V3's\n * useProducts() returns `{ items, loading, hasMore, ... }`. The shim\n * exposes `items` plus a `loading` flag so V2 sections that conditionally\n * render against a \"still loading\" placeholder don't break.\n */\nexport interface V2ProductsState {\n products: Product[];\n loading: boolean;\n}\n\nexport function useV2Products(): V2ProductsState {\n const { products, loading } = useProducts();\n return useMemo(\n () => ({\n products,\n loading,\n }),\n [products, loading],\n );\n}\n\n// ─── useV2Categories ────────────────────────────────────────────────────────\n\n/**\n * Bazaar `Category` shape. V2 sections destructure `cat.image_url`,\n * `cat.name`; the SDK's `Collection` calls the field `image`. Map.\n */\nexport interface V2Category {\n id: string;\n name: string;\n slug?: string;\n image_url?: string | null;\n description?: string | null;\n}\n\nexport interface V2CategoriesState {\n categories: V2Category[];\n loading: boolean;\n}\n\nfunction collectionToV2Category(c: Collection): V2Category {\n return {\n id: c.id,\n name: c.name,\n slug: c.slug,\n image_url: c.image_url ?? null,\n description: c.description ?? null,\n };\n}\n\nexport function useV2Categories(): V2CategoriesState {\n const { collections, loading } = useCollections();\n return useMemo(\n () => ({\n categories: collections.map(collectionToV2Category),\n loading,\n }),\n [collections, loading],\n );\n}\n\n// ─── useV2Auth ──────────────────────────────────────────────────────────────\n\nexport interface V2AuthState {\n user: Customer | null;\n isAuthenticated: boolean;\n}\n\nexport function useV2Auth(): V2AuthState {\n const customer = useCustomer();\n return useMemo(\n () => ({\n user: customer,\n isAuthenticated: customer !== null,\n }),\n [customer],\n );\n}\n\n// ─── useV2Language ──────────────────────────────────────────────────────────\n\nexport interface V2LanguageState {\n language: \"ar\" | \"en\";\n direction: \"rtl\" | \"ltr\";\n setLanguage: (next: \"ar\" | \"en\") => void;\n t: (key: string, fallback?: string) => string;\n}\n\nexport function useV2Language(): V2LanguageState {\n const { locale, direction, translations, setLocale } = useLocalization();\n const normalisedLocale = locale === \"ar\" ? \"ar\" : \"en\";\n return useMemo<V2LanguageState>(\n () => ({\n language: normalisedLocale,\n direction,\n setLanguage: (next) => setLocale(next),\n t: (key, fallback) => translations[key] ?? fallback ?? key,\n }),\n [normalisedLocale, direction, translations, setLocale],\n );\n}\n\n// ─── useV2Theme ─────────────────────────────────────────────────────────────\n\n/**\n * V2 themes destructure `useTheme().themeSettings.theme.primary_color`\n * etc. V3 stores the same values under `global_settings.<key>` (the\n * V2 → V3 normalizer flattens the nested object on first read).\n * This shim re-wraps the flat V3 shape into the V2 nested form so the\n * old destructure paths still resolve.\n *\n * Field map (V2 nested key → V3 flat key):\n *\n * theme.primary_color → global_settings.primary_color\n * theme.accent_color → global_settings.accent_color\n * theme.background_color → global_settings.background_color\n * theme.text_color → global_settings.text_color\n * theme.heading_font → global_settings.heading_font\n * identity.logo_url → global_settings.logo_url\n * identity.store_name → global_settings.store_name\n *\n * Themes that read keys not in this map fall through to undefined —\n * the consumer code should treat them as optional (V2 already did).\n */\nexport interface V2ThemeShape {\n themeSettings: {\n theme: {\n primary_color?: string;\n accent_color?: string;\n background_color?: string;\n text_color?: string;\n heading_font?: string;\n [extra: string]: unknown;\n };\n identity: {\n logo_url?: string;\n store_name?: string;\n [extra: string]: unknown;\n };\n [extra: string]: unknown;\n };\n}\n\nexport function useV2Theme(): V2ThemeShape {\n const settings = useThemeSettings();\n return useMemo<V2ThemeShape>(() => {\n const g = settings.global_settings ?? {};\n const get = (key: string): string | undefined => {\n const v = (g as Record<string, unknown>)[key];\n return typeof v === \"string\" ? v : undefined;\n };\n return {\n themeSettings: {\n theme: {\n primary_color: get(\"primary_color\"),\n accent_color: get(\"accent_color\"),\n background_color: get(\"background_color\"),\n text_color: get(\"text_color\"),\n heading_font: get(\"heading_font\"),\n ...g,\n },\n identity: {\n logo_url: get(\"logo_url\"),\n store_name: get(\"store_name\"),\n },\n },\n };\n }, [settings]);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contexts/index.ts","../src/hooks/usePage.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/v2-compat.ts"],"names":["useContext","useState","useEffect","useMemo"],"mappings":";;AAoCO,IAAM,WAAA,GAAc,cAA4B,IAAI,CAAA;AAC7B,cAA8B,IAAI;AAC/B,cAAiC,IAAI;AAC3C,cAAma,IAAI;AAC3b,IAAM,eAAA,GAAkB,cAA+B,IAAI,CAAA;AAC3D,IAAM,oBAAA,GAAuB,cAAsC,IAAI,CAAA;AACvE,IAAM,mBAAA,GAAsB,cAAwC,IAAI,CAAA;AACxE,IAAM,WAAA,GAAc,cAA2B,IAAI,CAAA;AASpB,cAAsB,MAAM;AA+BjC,aAAA;AAAA,EAC/B;AACF;;;ACjFO,SAAS,OAAA,GAAuB;AACrC,EAAA,OAAO,WAAW,WAAW,CAAA;AAC/B;ACFO,SAAS,eAAA,GAAqC;AACnD,EAAA,MAAM,GAAA,GAAMA,WAAW,mBAAmB,CAAA;AAC1C,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAC5E,EAAA,OAAO,GAAA;AACT;AAOO,SAAS,SAAA,GAAoB;AAClC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,eAAA,EAAgB;AACnC,EAAA,OAAO,MAAA;AACT;;;ACeA,IAAM,gBAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GACd,MAAA,CAAO,SAAS,QAAA,CAAS,OAAA,CAAQ,GAAA,EAAK,EAAE,CAAA,GACxC,OAAA;AAEN,SAAS,cAAc,KAAA,EAAsB;AAI3C,EAAA,IAAI,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAA,CAAM,MAAA;AAC/B,EAAA,IAAI,MAAM,SAAA,EAAW;AAOnB,IAAA,MAAM,QAAA,GACH,OAAO,MAAA,KAAW,WAAA,IAChB,OACE,sBAAA,IACL,YAAA;AACF,IAAA,OAAO,CAAA,EAAG,KAAA,CAAM,SAAS,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,KAAA,CAAM,IAAA;AACf;AAEO,SAAS,OAAA,GAA2B;AACzC,EAAA,MAAM,GAAA,GAAMA,WAAW,WAAW,CAAA;AAClC,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,0CAA0C,CAAA;AACpE,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,MAAA,GAAS,cAAc,GAAG,CAAA;AAChC,IAAA,MAAM,WAAY,GAAA,CACf,QAAA;AACH,IAAA,MAAM,mBAAA,GAAsB,OAAA;AAAA,MAC1B,YAAa,QAAA,CAAqD;AAAA,KACpE;AACA,IAAA,MAAM,aAAA,GAAgB,IAAI,gBAAA,IAAoB,IAAA;AAK9C,IAAA,MAAM,eAAA,GACH,UACG,yBAAA,IAA6B,IAAA;AACnC,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAAuB;AAC3C,MAAA,IAAI,CAAC,qBAAqB,OAAO,KAAA;AACjC,MAAA,IAAI,eAAA,EAAiB,OAAO,eAAA,CAAgB,QAAA,CAAS,CAAC,CAAA;AACtD,MAAA,OAAO,CAAA,KAAM,aAAA;AAAA,IACf,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAyB;AAC1C,MAAA,IAAI,CAAC,IAAA,EAAM,OAAO,CAAA,EAAG,gBAAgB,MAAM,MAAM,CAAA,CAAA,CAAA;AAEjD,MAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,IAAA;AAEvC,MAAA,IAAI,IAAA,CAAK,WAAW,IAAI,CAAA,SAAU,CAAA,EAAG,gBAAgB,IAAI,IAAI,CAAA,CAAA;AAC7D,MAAA,IAAI,aAAa,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AACvD,MAAA,IAAI,MAAA,IAAU,YAAA,CAAa,MAAM,CAAA,EAAG;AAGlC,QAAA,MAAM,MAAA,GAAS,IAAI,MAAM,CAAA,CAAA,CAAA;AACzB,QAAA,MAAM,IAAA,GAAO,IAAI,MAAM,CAAA,CAAA;AACvB,QAAA,IAAI,eAAe,IAAA,IAAQ,CAAC,UAAA,CAAW,UAAA,CAAW,MAAM,CAAA,EAAG;AACzD,UAAA,UAAA,GAAa,CAAA,EAAG,IAAI,CAAA,EAAG,UAAU,CAAA,CAAA;AAAA,QACnC;AAAA,MACF;AACA,MAAA,OAAO,CAAA,EAAG,gBAAgB,CAAA,GAAA,EAAM,MAAM,GAAG,UAAU,CAAA,CAAA;AAAA,IACrD,CAAA;AACA,IAAA,OAAO,EAAE,GAAG,GAAA,EAAK,MAAA,EAAQ,SAAA,EAAU;AAAA,EACrC,CAAA,EAAG,CAAC,GAAA,EAAK,MAAM,CAAC,CAAA;AAClB;;;ACxEO,SAAS,WAAA,CACd,IAAA,GAA2B,EAAC,EACT;AACnB,EAAA,MAAM,EAAE,KAAA,EAAO,cAAA,GAAiB,KAAA,EAAM,GAAI,IAAA;AAC1C,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,OAAA,GAAW,IAAA,EAAM,IAAA,EAAM,QAAA,IAAsC,IAAA;AAEnE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,IAAI,QAAA,CAAoB,OAAA,IAAW,EAAE,CAAA;AACjE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA;AAAA,IAC5B,WAAW,IAAA,IAAQ;AAAA,GACrB;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,WAAW,IAAA,EAAM;AACrB,IAAA,IAAI,CAAC,cAAA,EAAgB;AACrB,IAAA,IAAI,CAAC,MAAM,EAAA,EAAI;AACf,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,CAAC,YAAY;AACX,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,EAAE,QAAA,EAAU,IAAA,CAAK,IAAI,CAAA;AACxD,QAAA,IAAI,OAAO,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,MAAA,CAAO,KAAK,CAAC,CAAA;AAC5C,QAAA,MAAM,MAAM,MAAM,KAAA,CAAM,iBAAiB,MAAA,CAAO,QAAA,EAAU,CAAA,CAAE,CAAA;AAC5D,QAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAmB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC5D,QAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,WAAA,CAAY,IAAA,CAAK,QAAA,IAAY,EAAE,CAAA;AAC/B,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA,GAAG;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,GAAG,CAAC,OAAA,EAAS,gBAAgB,KAAA,EAAO,IAAA,EAAM,EAAE,CAAC,CAAA;AAE7C,EAAA,MAAM,SAAS,KAAA,IAAS,IAAA,GAAO,SAAS,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,QAAA;AAC1D,EAAA,OAAO,EAAE,QAAA,EAAU,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAM;AAC5C;ACrDO,SAAS,cAAA,CACd,IAAA,GAA8B,EAAC,EACT;AACtB,EAAA,MAAM,EAAE,KAAA,EAAO,cAAA,GAAiB,KAAA,EAAM,GAAI,IAAA;AAC1C,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,OAAO,OAAA,EAAQ;AACrB,EAAA,MAAM,OAAA,GAAW,IAAA,EAAM,IAAA,EAAM,WAAA,IAA4C,IAAA;AAEzE,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,IAAIC,QAAAA,CAAuB,OAAA,IAAW,EAAE,CAAA;AAC1E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,QAAAA;AAAA,IAC5B,WAAW,IAAA,IAAQ;AAAA,GACrB;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,WAAW,IAAA,EAAM;AACrB,IAAA,IAAI,CAAC,cAAA,EAAgB;AACrB,IAAA,IAAI,CAAC,MAAM,EAAA,EAAI;AACf,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,CAAC,YAAY;AACX,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,EAAE,QAAA,EAAU,IAAA,CAAK,IAAI,CAAA;AACxD,QAAA,MAAM,MAAM,MAAM,KAAA,CAAM,oBAAoB,MAAA,CAAO,QAAA,EAAU,CAAA,CAAE,CAAA;AAC/D,QAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAAsB,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC/D,QAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,cAAA,CAAe,IAAA,CAAK,WAAA,IAAe,EAAE,CAAA;AACrC,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA,GAAG;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,GAAG,CAAC,OAAA,EAAS,cAAA,EAAgB,IAAA,EAAM,EAAE,CAAC,CAAA;AAEtC,EAAA,MAAM,SAAS,KAAA,IAAS,IAAA,GAAO,YAAY,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,WAAA;AAC7D,EAAA,OAAO,EAAE,WAAA,EAAa,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAM;AAC/C;AC5DO,SAAS,WAAA,GAA+B;AAC7C,EAAA,OAAOF,WAAW,eAAe,CAAA;AACnC;ACFO,SAAS,gBAAA,GAAoC;AAClD,EAAA,MAAM,GAAA,GAAMA,WAAW,oBAAoB,CAAA;AAC3C,EAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAC7E,EAAA,OAAO,GAAA;AACT;;;AC8DO,SAAS,aAAA,GAAiC;AAC/C,EAAA,MAAM,EAAE,QAAA,EAAU,OAAA,EAAQ,GAAI,WAAA,EAAY;AAC1C,EAAA,OAAOG,OAAAA;AAAA,IACL,OAAO;AAAA,MACL,QAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,UAAU,OAAO;AAAA,GACpB;AACF;AAqBA,SAAS,uBAAuB,CAAA,EAA2B;AACzD,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,SAAA,EAAW,EAAE,SAAA,IAAa,IAAA;AAAA,IAC1B,WAAA,EAAa,EAAE,WAAA,IAAe;AAAA,GAChC;AACF;AAEO,SAAS,eAAA,GAAqC;AACnD,EAAA,MAAM,EAAE,WAAA,EAAa,OAAA,EAAQ,GAAI,cAAA,EAAe;AAChD,EAAA,OAAOA,OAAAA;AAAA,IACL,OAAO;AAAA,MACL,UAAA,EAAY,WAAA,CAAY,GAAA,CAAI,sBAAsB,CAAA;AAAA,MAClD;AAAA,KACF,CAAA;AAAA,IACA,CAAC,aAAa,OAAO;AAAA,GACvB;AACF;AASO,SAAS,SAAA,GAAyB;AACvC,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,OAAOA,OAAAA;AAAA,IACL,OAAO;AAAA,MACL,IAAA,EAAM,QAAA;AAAA,MACN,iBAAiB,QAAA,KAAa;AAAA,KAChC,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AACF;AAWO,SAAS,aAAA,GAAiC;AAC/C,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAW,YAAA,EAAc,SAAA,KAAc,eAAA,EAAgB;AACvE,EAAA,MAAM,gBAAA,GAAmB,MAAA,KAAW,IAAA,GAAO,IAAA,GAAO,IAAA;AAClD,EAAA,OAAOA,OAAAA;AAAA,IACL,OAAO;AAAA,MACL,QAAA,EAAU,gBAAA;AAAA,MACV,SAAA;AAAA,MACA,WAAA,EAAa,CAAC,IAAA,KAAS,SAAA,CAAU,IAAI,CAAA;AAAA,MACrC,GAAG,CAAC,GAAA,EAAK,aAAa,YAAA,CAAa,GAAG,KAAK,QAAA,IAAY;AAAA,KACzD,CAAA;AAAA,IACA,CAAC,gBAAA,EAAkB,SAAA,EAAW,YAAA,EAAc,SAAS;AAAA,GACvD;AACF;AA2CO,SAAS,UAAA,GAA2B;AACzC,EAAA,MAAM,WAAW,gBAAA,EAAiB;AAClC,EAAA,OAAOA,QAAsB,MAAM;AACjC,IAAA,MAAM,CAAA,GAAI,QAAA,CAAS,eAAA,IAAmB,EAAC;AACvC,IAAA,MAAM,GAAA,GAAM,CAAC,GAAA,KAAoC;AAC/C,MAAA,MAAM,CAAA,GAAK,EAA8B,GAAG,CAAA;AAC5C,MAAA,OAAO,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,GAAI,MAAA;AAAA,IACrC,CAAA;AACA,IAAA,OAAO;AAAA,MACL,aAAA,EAAe;AAAA,QACb,KAAA,EAAO;AAAA,UACL,aAAA,EAAe,IAAI,eAAe,CAAA;AAAA,UAClC,YAAA,EAAc,IAAI,cAAc,CAAA;AAAA,UAChC,gBAAA,EAAkB,IAAI,kBAAkB,CAAA;AAAA,UACxC,UAAA,EAAY,IAAI,YAAY,CAAA;AAAA,UAC5B,YAAA,EAAc,IAAI,cAAc,CAAA;AAAA,UAChC,GAAG;AAAA,SACL;AAAA,QACA,QAAA,EAAU;AAAA,UACR,QAAA,EAAU,IAAI,UAAU,CAAA;AAAA,UACxB,UAAA,EAAY,IAAI,YAAY;AAAA;AAC9B;AACF,KACF;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACf","file":"v2-compat.mjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<{ cart: Cart; addItem: (productId: string, variantId?: string, quantity?: number) => Promise<void>; removeItem: (itemId: string) => Promise<void>; updateQuantity: (itemId: string, quantity: number) => Promise<void>; applyDiscount: (code: string) => Promise<void>; removeDiscount: () => Promise<void>; updateNote: (note: string) => Promise<void>; clearCart: () => Promise<void>; loading: boolean } | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","/**\n * V2 → V3 compatibility shim.\n *\n * V2 themes in `numu-egyptian-bazaar/src/themes/` consume data via the\n * bazaar's own context hooks (useStore, useProductsContext,\n * useAuth, useLanguage, useTheme). When porting a V2 theme to V3 via\n * `numu-theme migrate`, those hooks don't exist inside the BYOT\n * bundle — the bundle runs in its own React tree fed by the SDK's\n * NuMuProvider, which exposes a different (V3) API.\n *\n * Rather than rewrite every section by hand on the first pass, themes\n * can import these compat hooks (e.g. `useV2Products()`) which return\n * the OLD V2-shaped data the section already knows how to render.\n * Internally they thunk into the V3 SDK hooks. This buys \"the theme\n * renders\" same-day; the per-section idiomatic V3 rewrite then\n * happens incrementally without blocking the bundle build.\n *\n * Use cases:\n *\n * 1. `useV2Products()` — returns `Product[]` (flat, no pagination\n * shape). Internally calls `useProducts()` and exposes only\n * the first page's items. Themes that paginate manually keep\n * working; themes that assume \"all products are loaded\" keep\n * working with the first 50.\n *\n * 2. `useV2Categories()` — returns `Category[]` shaped like the\n * bazaar's `Category` (id, name, slug, image_url) rather than\n * the SDK's `Collection`. Adapter maps fields.\n *\n * 3. `useV2Auth()` — returns `{ user, isAuthenticated }`. Maps\n * from `useCustomer()`'s null-or-Customer return.\n *\n * 4. `useV2Language()` — returns `{ language, setLanguage }` with\n * 'en' | 'ar' shape. Maps to `useLocalization()`.\n *\n * 5. `useV2Theme()` — returns the bazaar's V2 theme shape\n * (`{ themeSettings.theme.primary_color }`) by reading from the\n * V3 `themeSettings.global_settings` (which is where the\n * migrated values end up).\n *\n * Subpath import: `import { useV2Products } from \"@numueg/theme-sdk/v2-compat\"`.\n * Keeping this off the main entrypoint signals \"this is a migration\n * helper, plan to remove it\" — themes that linger on the shim past\n * the V2 retirement deadline (Wave 8) will fail to resolve the\n * subpath once we drop the file.\n */\n\n\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useProducts } from \"./hooks/useProducts\";\nimport { useCollections } from \"./hooks/useCollections\";\nimport { useCustomer } from \"./hooks/useCustomer\";\nimport { useLocalization } from \"./hooks/useLocalization\";\nimport { useThemeSettings } from \"./hooks/useThemeSettings\";\nimport type { Product, Collection, Customer } from \"./types/entities\";\n\n// ─── useV2Products ──────────────────────────────────────────────────────────\n\n/**\n * V2's `useProductsContext().products` returned a flat array; V3's\n * useProducts() returns `{ items, loading, hasMore, ... }`. The shim\n * exposes `items` plus a `loading` flag so V2 sections that conditionally\n * render against a \"still loading\" placeholder don't break.\n */\nexport interface V2ProductsState {\n products: Product[];\n loading: boolean;\n}\n\nexport function useV2Products(): V2ProductsState {\n const { products, loading } = useProducts();\n return useMemo(\n () => ({\n products,\n loading,\n }),\n [products, loading],\n );\n}\n\n// ─── useV2Categories ────────────────────────────────────────────────────────\n\n/**\n * Bazaar `Category` shape. V2 sections destructure `cat.image_url`,\n * `cat.name`; the SDK's `Collection` calls the field `image`. Map.\n */\nexport interface V2Category {\n id: string;\n name: string;\n slug?: string;\n image_url?: string | null;\n description?: string | null;\n}\n\nexport interface V2CategoriesState {\n categories: V2Category[];\n loading: boolean;\n}\n\nfunction collectionToV2Category(c: Collection): V2Category {\n return {\n id: c.id,\n name: c.name,\n slug: c.slug,\n image_url: c.image_url ?? null,\n description: c.description ?? null,\n };\n}\n\nexport function useV2Categories(): V2CategoriesState {\n const { collections, loading } = useCollections();\n return useMemo(\n () => ({\n categories: collections.map(collectionToV2Category),\n loading,\n }),\n [collections, loading],\n );\n}\n\n// ─── useV2Auth ──────────────────────────────────────────────────────────────\n\nexport interface V2AuthState {\n user: Customer | null;\n isAuthenticated: boolean;\n}\n\nexport function useV2Auth(): V2AuthState {\n const customer = useCustomer();\n return useMemo(\n () => ({\n user: customer,\n isAuthenticated: customer !== null,\n }),\n [customer],\n );\n}\n\n// ─── useV2Language ──────────────────────────────────────────────────────────\n\nexport interface V2LanguageState {\n language: \"ar\" | \"en\";\n direction: \"rtl\" | \"ltr\";\n setLanguage: (next: \"ar\" | \"en\") => void;\n t: (key: string, fallback?: string) => string;\n}\n\nexport function useV2Language(): V2LanguageState {\n const { locale, direction, translations, setLocale } = useLocalization();\n const normalisedLocale = locale === \"ar\" ? \"ar\" : \"en\";\n return useMemo<V2LanguageState>(\n () => ({\n language: normalisedLocale,\n direction,\n setLanguage: (next) => setLocale(next),\n t: (key, fallback) => translations[key] ?? fallback ?? key,\n }),\n [normalisedLocale, direction, translations, setLocale],\n );\n}\n\n// ─── useV2Theme ─────────────────────────────────────────────────────────────\n\n/**\n * V2 themes destructure `useTheme().themeSettings.theme.primary_color`\n * etc. V3 stores the same values under `global_settings.<key>` (the\n * V2 → V3 normalizer flattens the nested object on first read).\n * This shim re-wraps the flat V3 shape into the V2 nested form so the\n * old destructure paths still resolve.\n *\n * Field map (V2 nested key → V3 flat key):\n *\n * theme.primary_color → global_settings.primary_color\n * theme.accent_color → global_settings.accent_color\n * theme.background_color → global_settings.background_color\n * theme.text_color → global_settings.text_color\n * theme.heading_font → global_settings.heading_font\n * identity.logo_url → global_settings.logo_url\n * identity.store_name → global_settings.store_name\n *\n * Themes that read keys not in this map fall through to undefined —\n * the consumer code should treat them as optional (V2 already did).\n */\nexport interface V2ThemeShape {\n themeSettings: {\n theme: {\n primary_color?: string;\n accent_color?: string;\n background_color?: string;\n text_color?: string;\n heading_font?: string;\n [extra: string]: unknown;\n };\n identity: {\n logo_url?: string;\n store_name?: string;\n [extra: string]: unknown;\n };\n [extra: string]: unknown;\n };\n}\n\nexport function useV2Theme(): V2ThemeShape {\n const settings = useThemeSettings();\n return useMemo<V2ThemeShape>(() => {\n const g = settings.global_settings ?? {};\n const get = (key: string): string | undefined => {\n const v = (g as Record<string, unknown>)[key];\n return typeof v === \"string\" ? v : undefined;\n };\n return {\n themeSettings: {\n theme: {\n primary_color: get(\"primary_color\"),\n accent_color: get(\"accent_color\"),\n background_color: get(\"background_color\"),\n text_color: get(\"text_color\"),\n heading_font: get(\"heading_font\"),\n ...g,\n },\n identity: {\n logo_url: get(\"logo_url\"),\n store_name: get(\"store_name\"),\n },\n },\n };\n }, [settings]);\n}\n"]}
1
+ {"version":3,"sources":["../src/contexts/index.ts","../src/hooks/usePage.ts","../src/hooks/useLocalization.ts","../src/hooks/useShop.ts","../src/hooks/useProducts.ts","../src/hooks/useCollections.ts","../src/hooks/useCustomer.ts","../src/hooks/useThemeSettings.ts","../src/v2-compat.ts"],"names":["useContext","useState","useEffect","useMemo"],"mappings":"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","file":"v2-compat.mjs","sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\nimport type { Store, Product, Collection, Cart, Customer, Page } from \"../types/entities\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport interface LocalizationState {\n locale: string;\n direction: \"ltr\" | \"rtl\";\n translations: Record<string, string>;\n formatMoney: (amount: number, currency?: string) => string;\n formatDate: (date: string | Date) => string;\n /**\n * Phase 3.7 — locale-aware number formatter. Routes to either\n * Western (1234) or Arab-Indic (١٢٣٤) digits depending on\n * `store.settings.numerals`. Themes calling formatMoney get the\n * same digit choice automatically; this is for raw counts (\"12 items\").\n */\n formatNumber: (n: number, options?: Intl.NumberFormatOptions) => string;\n /**\n * Phase 3.6 — switch the active locale.\n *\n * Sets the `numu_locale` cookie and triggers a full page reload so\n * the server-rendered layout picks up the new locale (the storefront\n * resolves locale at SSR time from cookie/query). Returns once the\n * cookie is written; the page navigation cancels any pending React\n * work so callers don't need to await.\n */\n setLocale: (next: string) => void;\n /**\n * Phase 3.6 — list of locales the store advertises. Empty when the\n * store hasn't configured a multi-locale catalog. Themes use this\n * to decide whether to render the LocaleSwitcher at all.\n */\n availableLocales: string[];\n}\n\nexport const ShopContext = createContext<Store | null>(null);\nexport const ProductContext = createContext<Product | null>(null);\nexport const CollectionContext = createContext<Collection | null>(null);\nexport const CartContext = createContext<{ cart: Cart; addItem: (productId: string, variantId?: string, quantity?: number) => Promise<void>; removeItem: (itemId: string) => Promise<void>; updateQuantity: (itemId: string, quantity: number) => Promise<void>; applyDiscount: (code: string) => Promise<void>; removeDiscount: () => Promise<void>; updateNote: (note: string) => Promise<void>; clearCart: () => Promise<void>; loading: boolean } | null>(null);\nexport const CustomerContext = createContext<Customer | null>(null);\nexport const ThemeSettingsContext = createContext<ThemeSettingsV3 | null>(null);\nexport const LocalizationContext = createContext<LocalizationState | null>(null);\nexport const PageContext = createContext<Page | null>(null);\n/**\n * Wave 5 — currently-active template identifier. Mirrors the key inside\n * `themeSettings.templates.<currentTemplate>` so themes can dispatch\n * which section list to render. Hosts set this via `NuMuProvider`'s\n * `currentTemplate` prop (which is passed in by the storefront page\n * component — `app/(store)/[subdomain]/product/[id]/page.tsx` passes\n * \"product\", `cart/page.tsx` passes \"cart\", etc.). Falls back to \"home\".\n */\nexport const CurrentTemplateContext = createContext<string>(\"home\");\n\n/**\n * A merchant-managed navigation menu item, exactly as the storefront\n * menus resolver returns it (`GET /storefront/store/{id}/menus`):\n * bilingual `label`, a pre-resolved `url`, and nested `children`.\n *\n * This is the RAW shape the host injects via `NuMuProvider`'s\n * `navigation` prop. `useNavigation(handle)` localizes it to the\n * display-ready `NavigationItem` (a single `title` string for the\n * active locale).\n */\nexport interface MenuItemData {\n id: string;\n label: Record<string, string>;\n url: string;\n type?: string | null;\n resource_id?: string | null;\n /**\n * §5 hide-page → hide-nav-link. `false` when the item targets a CMS page\n * (`/pages/<handle>`) that is currently unpublished or deleted. The backend\n * menus resolver annotates it; absent/`true` means visible (back-compat).\n */\n target_visible?: boolean;\n children?: MenuItemData[];\n}\n\n/**\n * Phase 2.4 — navigation menus keyed by handle (`main-menu`, `footer`,\n * plus custom), injected by the host from the storefront resolver so a\n * theme's `useNavigation(handle)` resolves without a client round-trip.\n *\n * Defaults to `{}` — an empty map signals \"host provided no menus\", at\n * which point `useNavigation` falls back to its own fetch / a theme's\n * `DEFAULT_NAV`. A present-but-handle-absent map means the menu simply\n * doesn't exist (render nothing / fallback), no fetch attempted.\n */\nexport const NavigationContext = createContext<Record<string, MenuItemData[]>>(\n {},\n);\n","import { useContext } from \"react\";\nimport { PageContext } from \"../contexts\";\nimport type { Page } from \"../types/entities\";\n\nexport function usePage(): Page | null {\n return useContext(PageContext);\n}\n","import { useContext } from \"react\";\nimport { LocalizationContext } from \"../contexts\";\nimport type { LocalizationState } from \"../contexts\";\n\nexport function useLocalization(): LocalizationState {\n const ctx = useContext(LocalizationContext);\n if (!ctx) throw new Error(\"useLocalization must be used within NuMuProvider\");\n return ctx;\n}\n\nexport function useDirection(): \"ltr\" | \"rtl\" {\n const { direction } = useLocalization();\n return direction;\n}\n\nexport function useLocale(): string {\n const { locale } = useLocalization();\n return locale;\n}\n\nexport function useTranslation() {\n const { translations, locale } = useLocalization();\n return {\n t: (key: string, fallback?: string) => translations[key] || fallback || key,\n locale,\n };\n}\n\n/**\n * Phase 3.6 — pull a translated field off a domain object.\n *\n * Convention: per-product translations live on `entity.attributes`\n * keyed as `<field>_<locale>` — e.g. `name_ar`, `description_ar`.\n * Backend writes these via the merchant hub when a merchant turns on\n * \"Translations\" for a product. Themes call:\n *\n * const productName = useFieldTranslation(product, \"name\");\n *\n * and get the Arabic name when the active locale is \"ar\", falling\n * back to the English `product.name` otherwise. Works for any object\n * that pairs a base field on the entity with an `attributes` JSONB\n * blob holding the translated variants.\n *\n * Why not put translations on the entity itself? Adding `name_ar`,\n * `description_ar`, `name_he`, etc. as first-class columns means a\n * schema migration every time a merchant enables a new locale. JSONB\n * lets the merchant flip locales on/off in settings without touching\n * the table.\n */\nexport function useFieldTranslation<\n T extends { attributes?: Record<string, unknown> },\n>(entity: T | null | undefined, field: string): string | undefined {\n const { locale } = useLocalization();\n if (!entity) return undefined;\n // English (or whichever default locale the merchant configured) is\n // stored as the base field; non-default locales live on attributes\n // with a `<field>_<locale>` key.\n const translated =\n entity.attributes && typeof entity.attributes === \"object\"\n ? (entity.attributes as Record<string, unknown>)[`${field}_${locale}`]\n : undefined;\n if (typeof translated === \"string\" && translated) return translated;\n // Base field — supports any value type that stringifies sensibly.\n const base = (entity as unknown as Record<string, unknown>)[field];\n if (typeof base === \"string\") return base;\n if (base == null) return undefined;\n return String(base);\n}\n\n/**\n * Phase 3.7 — formatted-number hook.\n *\n * Themes that need to render counts (\"12 items\", \"3 reviews\") should\n * route through this hook so Arab-Indic digits (٠١٢٣٤) vs Western\n * (01234) stay consistent with money + date elsewhere on the page.\n *\n * const fmtNum = useNumberFormat();\n * fmtNum(12) → \"12\" (or \"١٢\" for Arabic stores)\n * fmtNum(12.5, { minimumFractionDigits: 2 }) → \"12.50\" / \"١٢٫٥٠\"\n */\nexport function useNumberFormat() {\n const { formatNumber } = useLocalization();\n return formatNumber;\n}\n","\"use client\";\nimport { useContext, useMemo } from \"react\";\nimport { ShopContext } from \"../contexts\";\nimport { useLocale } from \"./useLocalization\";\nimport type { Store } from \"../types/entities\";\n\n/**\n * Augmented Store shape returned from useShop().\n *\n * Adds a couple of conveniences theme devs reach for constantly:\n * - `domain` — the active hostname (subdomain or custom). Resolved\n * from `store.domain ?? store.subdomain ?? store.slug`.\n * - `formatUrl(path)` — emits a fully-qualified URL using `domain`.\n * Theme code that needs to render absolute URLs (canonical tags,\n * Open Graph, JSON-LD) goes through this so dev/prod / subdomain\n * vs. custom-domain stays correct without each theme reinventing\n * the resolver.\n */\nexport interface ShopWithHelpers extends Store {\n /** Fully-qualified hostname this store currently serves on. */\n domain: string;\n /**\n * Format a path (relative or absolute) as a fully-qualified URL on\n * this store's domain. No-ops when given an already-absolute URL.\n *\n * Phase 6 — when the active locale is non-default and the store\n * has opted into locale URL prefixes, the path is prefixed with\n * `/{locale}/` (e.g. `/ar/products/foo`). Already-prefixed paths\n * are left alone so calling `formatUrl(formatUrl(...))` is safe.\n */\n formatUrl(path: string): string;\n}\n\nconst DEFAULT_PROTOCOL =\n typeof window !== \"undefined\"\n ? window.location.protocol.replace(\":\", \"\")\n : \"https\";\n\nfunction resolveDomain(store: Store): string {\n // Custom domain wins when set; otherwise fall back to the platform\n // subdomain. The slug is a last resort for stores that haven't been\n // assigned either yet (mostly demo stores in dev).\n if (store.domain) return store.domain;\n if (store.subdomain) {\n // The platform-domain default lives on the storefront; theme code\n // doesn't have access to env vars, so we read it off the runtime\n // window object when present and fall back to the well-known\n // production value. Themes can override by rendering with\n // `<meta name=\"numu:platform-domain\" content=\"...\">` if they're\n // hosted on a non-default platform.\n const platform =\n (typeof window !== \"undefined\" &&\n (window as unknown as { __NUMU_PLATFORM_DOMAIN?: string })\n .__NUMU_PLATFORM_DOMAIN) ||\n \"numueg.app\";\n return `${store.subdomain}.${platform}`;\n }\n return store.slug;\n}\n\nexport function useShop(): ShopWithHelpers {\n const ctx = useContext(ShopContext);\n if (!ctx) throw new Error(\"useShop must be used within NuMuProvider\");\n const locale = useLocale();\n\n return useMemo(() => {\n const domain = resolveDomain(ctx);\n const settings = (ctx as Store & { settings?: Record<string, unknown> })\n .settings;\n const localePrefixEnabled = Boolean(\n settings && (settings as { locale_url_prefix_enabled?: boolean }).locale_url_prefix_enabled,\n );\n const defaultLocale = ctx.default_language || \"en\";\n // List of locales that get a URL prefix. Defaults to \"everything\n // except the default\" — but a store can opt into `[\"ar\", \"en\"]`\n // (prefix even the default) if it wants a fully prefixed URL\n // scheme like Shopify does for markets.\n const prefixedLocales =\n (settings as { locale_url_prefix_locales?: string[] } | undefined)\n ?.locale_url_prefix_locales || null;\n const shouldPrefix = (l: string): boolean => {\n if (!localePrefixEnabled) return false;\n if (prefixedLocales) return prefixedLocales.includes(l);\n return l !== defaultLocale;\n };\n\n const formatUrl = (path: string): string => {\n if (!path) return `${DEFAULT_PROTOCOL}://${domain}/`;\n // Already absolute? Hand back as-is.\n if (/^https?:\\/\\//i.test(path)) return path;\n // Protocol-relative — assume same protocol as the host.\n if (path.startsWith(\"//\")) return `${DEFAULT_PROTOCOL}:${path}`;\n let normalized = path.startsWith(\"/\") ? path : `/${path}`;\n if (locale && shouldPrefix(locale)) {\n // Idempotent: don't double-prefix if the caller already\n // gave us a /{locale}/... path.\n const prefix = `/${locale}/`;\n const root = `/${locale}`;\n if (normalized !== root && !normalized.startsWith(prefix)) {\n normalized = `${root}${normalized}`;\n }\n }\n return `${DEFAULT_PROTOCOL}://${domain}${normalized}`;\n };\n return { ...ctx, domain, formatUrl };\n }, [ctx, locale]);\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Product } from \"../types/entities\";\n\ninterface UseProductsOptions {\n /** Limit the slice returned. Defaults to whatever the host gave us. */\n limit?: number;\n /**\n * When true, fetch from `/api/products` if the host didn't provide a\n * list via PageContext. Useful for sections rendered on routes that\n * don't pre-fetch products (e.g. a custom CMS page that wants a\n * \"featured\" rail). Default: false — themes typically prefer SSR data.\n */\n fetchIfMissing?: boolean;\n}\n\ninterface UseProductsResult {\n products: Product[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useProducts — read the storefront-pre-fetched product list from the\n * page context, optionally falling back to a client-side fetch.\n *\n * The SSR path passes `page.data.products: Product[]` from\n * `numu-storefront/src/app/[domain]/page.tsx`; sections on the home\n * route get them for free. For other routes that don't pre-fetch, set\n * `fetchIfMissing: true` and we'll hit `/api/products`.\n */\nexport function useProducts(\n opts: UseProductsOptions = {},\n): UseProductsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.products as Product[] | undefined) ?? null;\n\n const [products, setProducts] = useState<Product[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n if (limit) params.set(\"limit\", String(limit));\n const res = await fetch(`/api/products?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/products → ${res.status}`);\n const data = (await res.json()) as { products?: Product[] };\n if (cancelled) return;\n setProducts(data.products ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, limit, shop?.id]);\n\n const sliced = limit != null ? products.slice(0, limit) : products;\n return { products: sliced, loading, error };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { usePage } from \"./usePage\";\nimport { useShop } from \"./useShop\";\nimport type { Collection } from \"../types/entities\";\n\ninterface UseCollectionsOptions {\n limit?: number;\n fetchIfMissing?: boolean;\n}\n\ninterface UseCollectionsResult {\n collections: Collection[];\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * useCollections — analog to useProducts. Reads page.data.collections\n * pre-fetched by the storefront SSR; falls back to /api/collections\n * when fetchIfMissing is true.\n */\nexport function useCollections(\n opts: UseCollectionsOptions = {},\n): UseCollectionsResult {\n const { limit, fetchIfMissing = false } = opts;\n const page = usePage();\n const shop = useShop();\n const initial = (page?.data?.collections as Collection[] | undefined) ?? null;\n\n const [collections, setCollections] = useState<Collection[]>(initial ?? []);\n const [loading, setLoading] = useState<boolean>(\n initial == null && fetchIfMissing,\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (initial != null) return;\n if (!fetchIfMissing) return;\n if (!shop?.id) return;\n let cancelled = false;\n (async () => {\n try {\n const params = new URLSearchParams({ store_id: shop.id });\n const res = await fetch(`/api/collections?${params.toString()}`);\n if (!res.ok) throw new Error(`/api/collections → ${res.status}`);\n const data = (await res.json()) as { collections?: Collection[] };\n if (cancelled) return;\n setCollections(data.collections ?? []);\n setLoading(false);\n } catch (err) {\n if (cancelled) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [initial, fetchIfMissing, shop?.id]);\n\n const sliced = limit != null ? collections.slice(0, limit) : collections;\n return { collections: sliced, loading, error };\n}\n","import { useContext } from \"react\";\nimport { CustomerContext } from \"../contexts\";\nimport type { Customer } from \"../types/entities\";\n\nexport function useCustomer(): Customer | null {\n return useContext(CustomerContext);\n}\n","import { useContext } from \"react\";\nimport { ThemeSettingsContext } from \"../contexts\";\nimport type { ThemeSettingsV3 } from \"../types/theme\";\n\nexport function useThemeSettings(): ThemeSettingsV3 {\n const ctx = useContext(ThemeSettingsContext);\n if (!ctx) throw new Error(\"useThemeSettings must be used within NuMuProvider\");\n return ctx;\n}\n","/**\n * V2 → V3 compatibility shim.\n *\n * V2 themes in `numu-egyptian-bazaar/src/themes/` consume data via the\n * bazaar's own context hooks (useStore, useProductsContext,\n * useAuth, useLanguage, useTheme). When porting a V2 theme to V3 via\n * `numu-theme migrate`, those hooks don't exist inside the BYOT\n * bundle — the bundle runs in its own React tree fed by the SDK's\n * NuMuProvider, which exposes a different (V3) API.\n *\n * Rather than rewrite every section by hand on the first pass, themes\n * can import these compat hooks (e.g. `useV2Products()`) which return\n * the OLD V2-shaped data the section already knows how to render.\n * Internally they thunk into the V3 SDK hooks. This buys \"the theme\n * renders\" same-day; the per-section idiomatic V3 rewrite then\n * happens incrementally without blocking the bundle build.\n *\n * Use cases:\n *\n * 1. `useV2Products()` — returns `Product[]` (flat, no pagination\n * shape). Internally calls `useProducts()` and exposes only\n * the first page's items. Themes that paginate manually keep\n * working; themes that assume \"all products are loaded\" keep\n * working with the first 50.\n *\n * 2. `useV2Categories()` — returns `Category[]` shaped like the\n * bazaar's `Category` (id, name, slug, image_url) rather than\n * the SDK's `Collection`. Adapter maps fields.\n *\n * 3. `useV2Auth()` — returns `{ user, isAuthenticated }`. Maps\n * from `useCustomer()`'s null-or-Customer return.\n *\n * 4. `useV2Language()` — returns `{ language, setLanguage }` with\n * 'en' | 'ar' shape. Maps to `useLocalization()`.\n *\n * 5. `useV2Theme()` — returns the bazaar's V2 theme shape\n * (`{ themeSettings.theme.primary_color }`) by reading from the\n * V3 `themeSettings.global_settings` (which is where the\n * migrated values end up).\n *\n * Subpath import: `import { useV2Products } from \"@numueg/theme-sdk/v2-compat\"`.\n * Keeping this off the main entrypoint signals \"this is a migration\n * helper, plan to remove it\" — themes that linger on the shim past\n * the V2 retirement deadline (Wave 8) will fail to resolve the\n * subpath once we drop the file.\n */\n\n\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useProducts } from \"./hooks/useProducts\";\nimport { useCollections } from \"./hooks/useCollections\";\nimport { useCustomer } from \"./hooks/useCustomer\";\nimport { useLocalization } from \"./hooks/useLocalization\";\nimport { useThemeSettings } from \"./hooks/useThemeSettings\";\nimport type { Product, Collection, Customer } from \"./types/entities\";\n\n// ─── useV2Products ──────────────────────────────────────────────────────────\n\n/**\n * V2's `useProductsContext().products` returned a flat array; V3's\n * useProducts() returns `{ items, loading, hasMore, ... }`. The shim\n * exposes `items` plus a `loading` flag so V2 sections that conditionally\n * render against a \"still loading\" placeholder don't break.\n */\nexport interface V2ProductsState {\n products: Product[];\n loading: boolean;\n}\n\nexport function useV2Products(): V2ProductsState {\n const { products, loading } = useProducts();\n return useMemo(\n () => ({\n products,\n loading,\n }),\n [products, loading],\n );\n}\n\n// ─── useV2Categories ────────────────────────────────────────────────────────\n\n/**\n * Bazaar `Category` shape. V2 sections destructure `cat.image_url`,\n * `cat.name`; the SDK's `Collection` calls the field `image`. Map.\n */\nexport interface V2Category {\n id: string;\n name: string;\n slug?: string;\n image_url?: string | null;\n description?: string | null;\n}\n\nexport interface V2CategoriesState {\n categories: V2Category[];\n loading: boolean;\n}\n\nfunction collectionToV2Category(c: Collection): V2Category {\n return {\n id: c.id,\n name: c.name,\n slug: c.slug,\n image_url: c.image_url ?? null,\n description: c.description ?? null,\n };\n}\n\nexport function useV2Categories(): V2CategoriesState {\n const { collections, loading } = useCollections();\n return useMemo(\n () => ({\n categories: collections.map(collectionToV2Category),\n loading,\n }),\n [collections, loading],\n );\n}\n\n// ─── useV2Auth ──────────────────────────────────────────────────────────────\n\nexport interface V2AuthState {\n user: Customer | null;\n isAuthenticated: boolean;\n}\n\nexport function useV2Auth(): V2AuthState {\n const customer = useCustomer();\n return useMemo(\n () => ({\n user: customer,\n isAuthenticated: customer !== null,\n }),\n [customer],\n );\n}\n\n// ─── useV2Language ──────────────────────────────────────────────────────────\n\nexport interface V2LanguageState {\n language: \"ar\" | \"en\";\n direction: \"rtl\" | \"ltr\";\n setLanguage: (next: \"ar\" | \"en\") => void;\n t: (key: string, fallback?: string) => string;\n}\n\nexport function useV2Language(): V2LanguageState {\n const { locale, direction, translations, setLocale } = useLocalization();\n const normalisedLocale = locale === \"ar\" ? \"ar\" : \"en\";\n return useMemo<V2LanguageState>(\n () => ({\n language: normalisedLocale,\n direction,\n setLanguage: (next) => setLocale(next),\n t: (key, fallback) => translations[key] ?? fallback ?? key,\n }),\n [normalisedLocale, direction, translations, setLocale],\n );\n}\n\n// ─── useV2Theme ─────────────────────────────────────────────────────────────\n\n/**\n * V2 themes destructure `useTheme().themeSettings.theme.primary_color`\n * etc. V3 stores the same values under `global_settings.<key>` (the\n * V2 → V3 normalizer flattens the nested object on first read).\n * This shim re-wraps the flat V3 shape into the V2 nested form so the\n * old destructure paths still resolve.\n *\n * Field map (V2 nested key → V3 flat key):\n *\n * theme.primary_color → global_settings.primary_color\n * theme.accent_color → global_settings.accent_color\n * theme.background_color → global_settings.background_color\n * theme.text_color → global_settings.text_color\n * theme.heading_font → global_settings.heading_font\n * identity.logo_url → global_settings.logo_url\n * identity.store_name → global_settings.store_name\n *\n * Themes that read keys not in this map fall through to undefined —\n * the consumer code should treat them as optional (V2 already did).\n */\nexport interface V2ThemeShape {\n themeSettings: {\n theme: {\n primary_color?: string;\n accent_color?: string;\n background_color?: string;\n text_color?: string;\n heading_font?: string;\n [extra: string]: unknown;\n };\n identity: {\n logo_url?: string;\n store_name?: string;\n [extra: string]: unknown;\n };\n [extra: string]: unknown;\n };\n}\n\nexport function useV2Theme(): V2ThemeShape {\n const settings = useThemeSettings();\n return useMemo<V2ThemeShape>(() => {\n const g = settings.global_settings ?? {};\n const get = (key: string): string | undefined => {\n const v = (g as Record<string, unknown>)[key];\n return typeof v === \"string\" ? v : undefined;\n };\n return {\n themeSettings: {\n theme: {\n primary_color: get(\"primary_color\"),\n accent_color: get(\"accent_color\"),\n background_color: get(\"background_color\"),\n text_color: get(\"text_color\"),\n heading_font: get(\"heading_font\"),\n ...g,\n },\n identity: {\n logo_url: get(\"logo_url\"),\n store_name: get(\"store_name\"),\n },\n },\n };\n }, [settings]);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@numueg/theme-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "NUMU Theme SDK — typed hooks, components, and utilities for building NUMU storefront themes",
5
5
  "keywords": [
6
6
  "numu",