@rebuy/hydrogen-sdk
Version:
The React/Hydrogen wrapper for the Rebuy Core SDK.
8 lines (7 loc) • 18.4 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/context.tsx", "../src/components/RecentlyViewed.tsx", "../src/components/RebuyProductView.tsx", "../src/loader.ts"],
"sourcesContent": ["import { RebuySDK } from '@rebuy/core-sdk';\nimport { createContext, useContext, useMemo } from 'react';\n\nexport const RebuyContext = createContext<RebuySDK | null>(null);\n\n/**\n * RebuyProvider - React context provider for Rebuy SDK\n *\n * @param {object} props - Component props\n * @param {*} props.children - Child components that will have access to Rebuy SDK\n * @param {string} props.apiKey - Rebuy API key for authentication\n * @param {string} props.apiHost - Optional API host override (defaults to Rebuy's host)\n * @param {string} props.nonce - Optional nonce for CSP (Content Security Policy)\n */\nexport const RebuyProvider = ({\n apiHost,\n apiKey,\n children,\n nonce,\n}: {\n apiHost?: string;\n apiKey: string;\n children: React.ReactNode;\n nonce?: string;\n}) => {\n const sdk = useMemo(() => new RebuySDK({ apiHost, apiKey }), [apiKey, apiHost]);\n\n return <RebuyContext.Provider value={sdk}>{children}</RebuyContext.Provider>;\n};\n\n/**\n * Hook to access the Rebuy SDK instance from context.\n */\nexport const useRebuy = () => {\n const context = useContext(RebuyContext);\n\n if (!context) {\n throw new Error('useRebuy must be used within a RebuyProvider');\n }\n\n return context;\n};\n", "import type { Product } from '@rebuy/core-sdk';\nimport { useEffect, useState } from 'react';\nimport { useRebuy } from '../context';\n\ntype RecentlyViewedProps = {\n className?: string;\n emptyComponent?: React.ReactNode;\n limit?: number;\n loadingComponent?: React.ReactNode;\n onError?: (error: Error) => void;\n};\n\n/**\n * RecentlyViewed - Component that displays recently viewed products\n *\n * @param {RecentlyViewedProps} props - Component props\n * @param {string} props.className - Optional CSS class name for the container\n * @param {*} props.emptyComponent - Optional component to show when no products are available\n * @param {number} props.limit - Maximum number of products to display (default: 5)\n * @param {*} props.loadingComponent - Optional component to show while loading\n * @param {Function} props.onError - Optional callback for handling errors\n */\nexport const RecentlyViewed = ({\n className,\n emptyComponent,\n limit = 5,\n loadingComponent,\n onError,\n}: RecentlyViewedProps) => {\n const [products, setProducts] = useState<Product[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const rebuy = useRebuy();\n\n useEffect(() => {\n /**\n * Fetches recently viewed products from the API.\n */\n const fetchRecentlyViewed = async () => {\n try {\n setLoading(true);\n setError(null);\n const data = await rebuy.products.getRecentlyViewed({ limit });\n setProducts(data || []);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n console.error('Failed to fetch recently viewed products:', error);\n } finally {\n setLoading(false);\n }\n };\n\n fetchRecentlyViewed();\n }, [limit, rebuy, onError]);\n\n if (loading) {\n return (\n loadingComponent || (\n <div className={className}>\n <div>Loading recently viewed products...</div>\n </div>\n )\n );\n }\n\n if (error) {\n return null;\n }\n\n if (products.length === 0) {\n return emptyComponent || null;\n }\n\n return (\n <div className={className}>\n <h2>Recently Viewed</h2>\n <ul>\n {products.map((product) => (\n <li key={product.id}>{product.title}</li>\n ))}\n </ul>\n </div>\n );\n};\n", "import { type RebuySDK } from '@rebuy/core-sdk';\nimport { useEffect, useRef } from 'react';\nimport { useRebuy } from '../context';\n\ntype RebuyProductViewProps = {\n /**\n * Customer ID if user is logged in (optional)\n */\n customerId?: string;\n /**\n * Product object containing id and handle\n */\n product: {\n /** Product handle/slug for URL */\n handle: string;\n /** Shopify GraphQL product ID (e.g., \"gid://shopify/Product/123456\") */\n id: string;\n /** Product price (optional) */\n price?: {\n amount: string;\n currencyCode: string;\n };\n /** Product title (optional) */\n title?: string;\n };\n /**\n * Custom rebuy SDK instance (optional)\n */\n rebuy?: RebuySDK;\n};\n\n/**\n * RebuyProductView component tracks product view events for Recently Viewed functionality.\n * This component renders nothing visible - it only handles tracking when mounted.\n *\n * Place this component on product pages to track when users view products.\n * The tracked data will be available in the RecentlyViewed component.\n *\n * @param {RebuyProductViewProps} props - Component props\n * @param {object} props.product - Product object with id, handle, and optional price/title\n * @param {string} props.customerId - Optional customer ID if user is logged in\n * @param {RebuySDK} props.rebuy - Optional custom Rebuy SDK instance (uses context if not provided)\n */\nexport const RebuyProductView = ({ customerId, product, rebuy: customRebuy }: RebuyProductViewProps) => {\n const tracked = useRef(false);\n const rebuyFromContext = useRebuy();\n\n useEffect(() => {\n // Prevent double tracking in development mode (StrictMode)\n if (tracked.current) return;\n tracked.current = true;\n\n const trackView = async () => {\n try {\n // Use custom SDK instance or get from React context\n const rebuyInstance = customRebuy || rebuyFromContext;\n\n if (!rebuyInstance) {\n console.warn('[RebuyProductView] Rebuy SDK not available for product view tracking');\n\n return;\n }\n\n // Extract numeric ID from Shopify GraphQL ID format\n // \"gid://shopify/Product/123456\" -> \"123456\"\n let productId = product.id;\n\n if (productId.includes('/')) {\n const numericId = productId.split('/').pop();\n\n if (numericId) {\n productId = numericId;\n }\n }\n\n // Track the product view using Core SDK\n if (rebuyInstance && rebuyInstance.tracking && rebuyInstance.tracking.productView) {\n await rebuyInstance.tracking.productView(productId);\n } else {\n console.warn('[RebuyProductView] Rebuy SDK tracking.productView method not available');\n }\n } catch (error) {\n // Log the error but don't break the page\n console.error('[RebuyProductView] Rebuy product view tracking failed:', error);\n }\n };\n\n // Track the view\n trackView();\n }, [product.id, product.handle, customerId, customRebuy, rebuyFromContext]);\n\n // This component renders nothing - it's purely for tracking\n return null;\n};\n", "import { RebuySDK, RebuyContextBuilder, type RebuyCartContext, type SupportedCart } from '@rebuy/core-sdk';\n\n// Re-export RebuyContextBuilder for use in application code\nexport { RebuyContextBuilder } from '@rebuy/core-sdk';\n\n/**\n * Enhanced Hydrogen context type with i18n support\n */\ntype HydrogenContext = {\n [key: string]: unknown;\n cart: {\n get: () => Promise<SupportedCart | null>;\n };\n env?: {\n PUBLIC_REBUY_API_KEY?: string;\n REBUY_API_HOST?: string;\n REBUY_API_KEY?: string;\n REBUY_SDK_DEBUG?: boolean;\n };\n storefront?: {\n i18n?: {\n country?: string;\n language?: string;\n };\n };\n};\n\n/**\n * Enhanced options with request object\n */\ntype GetRebuyDataOptions = {\n apiHost?: string;\n apiKey?: string;\n context: HydrogenContext;\n request?: Request; // Now optional for backward compatibility, but recommended\n};\n\n/**\n * Type definition for the getRebuyData return value\n */\ntype RebuyDataResult = {\n rebuy: {\n cartContext: RebuyCartContext;\n sdk: RebuySDK;\n };\n};\n\n/**\n * Helper function to be called within Remix loaders to abstract Rebuy-related\n * data fetching and context creation. This enables server-side cart reactivity\n * by providing cart-aware context for Rebuy's data source APIs.\n *\n * Now with automatic context enrichment from URL, geolocation, and language data.\n *\n * @param {GetRebuyDataOptions} options - Configuration options\n * @param {HydrogenContext} options.context - The Hydrogen context object from the loader\n * @param {Request} options.request - The request object for URL context extraction\n * @param {string} options.apiKey - Optional Rebuy API key (defaults to env.REBUY_API_KEY)\n * @param {string} options.apiHost - Optional API host override (defaults to env.REBUY_API_HOST)\n *\n * @returns Object containing the initialized SDK and cart context\n *\n * @example\n * ```typescript\n * // In your Remix loader - now with automatic context enrichment\n * export async function loader({ context, request }: LoaderFunctionArgs) {\n * const { rebuy } = await getRebuyData({ context, request });\n *\n * // The cartContext now includes:\n * // - Cart data (if available)\n * // - URL path and parameters\n * // - Country code and language from i18n\n *\n * // Fetch recommendations using the enriched context\n * const recommendations = await rebuy.sdk.products.fetchFromDataSource(\n * 'pdp-recommendations',\n * {\n * ...rebuy.cartContext,\n * shopify_product_ids: productId\n * }\n * );\n *\n * return defer({ recommendations });\n * }\n * ```\n */\nexport const getRebuyData = async (options: GetRebuyDataOptions): Promise<RebuyDataResult> => {\n const { apiHost, apiKey, context, request } = options;\n\n // Validate required parameters\n if (!request) {\n console.warn('[getRebuyData] Request object is required for full context enrichment');\n }\n\n const debug = !!context.env?.REBUY_SDK_DEBUG;\n const logger = {\n log: (...args: unknown[]) => {\n if (debug) {\n console.log('[Rebuy Hydrogen SDK]', ...args);\n }\n },\n };\n\n logger.log('getRebuyData Entry:', {\n hasI18n: !!context.storefront?.i18n,\n hasRequest: !!request,\n i18nCountry: context.storefront?.i18n?.country,\n i18nLanguage: context.storefront?.i18n?.language,\n requestUrl: request?.url,\n });\n\n const rebuyApiKey = apiKey || context.env?.REBUY_API_KEY || context.env?.PUBLIC_REBUY_API_KEY;\n const rebuyApiHost = apiHost || context.env?.REBUY_API_HOST;\n\n if (!rebuyApiKey) {\n throw new Error(\n 'Rebuy API key is required. Provide it via options.apiKey or set REBUY_API_KEY or PUBLIC_REBUY_API_KEY in your environment.'\n );\n }\n\n logger.log('Initializing Rebuy SDK with context builder support');\n\n const sdk = new RebuySDK({\n apiHost: rebuyApiHost,\n apiKey: rebuyApiKey,\n debug,\n });\n\n // Initialize the context builder\n const builder = new RebuyContextBuilder(sdk, debug);\n\n // Step 1: Add cart context if available\n try {\n logger.log('Fetching cart from Hydrogen context...');\n const cart = await context.cart.get();\n\n if (cart) {\n logger.log('Cart found, adding to context');\n builder.withCart(cart);\n } else {\n logger.log('No cart found or cart is empty');\n }\n } catch (error) {\n console.warn('[getRebuyData] Failed to fetch cart for context:', error);\n }\n\n // Step 2: Add URL context from request\n if (request && request.url) {\n try {\n logger.log('Adding URL context from request:', request.url);\n builder.withUrl(request.url);\n } catch (error) {\n console.warn('[getRebuyData] Failed to parse URL for context:', error);\n }\n }\n\n // Step 3: Add i18n context (country and language)\n const i18n = context.storefront?.i18n;\n\n if (i18n) {\n if (i18n.country) {\n logger.log('Adding country context:', i18n.country);\n builder.withLocation(i18n.country);\n }\n\n if (i18n.language) {\n logger.log('Adding language context:', i18n.language);\n builder.withLanguage(i18n.language);\n }\n }\n\n // Build the final context\n const cartContext = builder.build();\n\n logger.log('getRebuyData Result:', {\n contextKeys: Object.keys(cartContext),\n contextValues: debug ? cartContext : '(hidden in non-debug mode)',\n sdkInitialized: !!sdk,\n });\n\n return {\n rebuy: {\n cartContext,\n sdk,\n },\n };\n};\n\n/**\n * Type-safe helper for using getRebuyData in TypeScript projects\n *\n * @example\n * ```typescript\n * import type { LoaderFunctionArgs } from '@shopify/remix-oxygen';\n * import { getRebuyData } from '@rebuy/hydrogen-sdk';\n *\n * export async function loader({ context, request }: LoaderFunctionArgs) {\n * const rebuyData = await getRebuyData({ context, request });\n * // TypeScript knows rebuyData.rebuy.sdk and rebuyData.rebuy.cartContext types\n * }\n * ```\n */\nexport type { RebuyDataResult, GetRebuyDataOptions };\n"],
"mappings": "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",
"names": ["jsx", "error", "useEffect", "useEffect", "RebuySDK", "RebuyContextBuilder"]
}