UNPKG

@nosto/nosto-react

Version:

Component library to simply implementing Nosto on React.

970 lines (924 loc) 28.1 kB
import { Cart } from '@nosto/nosto-js/client'; import { Context } from 'react'; import { PushedCustomer as Customer } from '@nosto/nosto-js/client'; import { JSX } from 'react/jsx-runtime'; import { WebsiteOrder as Order } from '@nosto/nosto-js/client'; import { Product } from '@nosto/nosto-js/client'; import { PushedProduct } from '@nosto/nosto-js/client'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ReactPortal } from 'react'; import { RenderMode } from '@nosto/nosto-js/client'; export { Cart } export { Customer } /** * You can personalise your cart and checkout pages by using the `Nosto404` component. * The component does not require any props. * * By default, your account, when created, has three 404-page placements named `notfound-nosto-1`, `notfound-nosto-2` and `notfound-nosto-3`. * You may omit these and use any identifier you need. * The identifiers used here are simply provided to illustrate the example. * * @example * ``` * <div className="notfound-page"> * <NostoPlacement id="notfound-nosto-1" /> * <NostoPlacement id="notfound-nosto-2" /> * <NostoPlacement id="notfound-nosto-3" /> * <Nosto404 /> * </div> * ``` * * @group Components */ export declare function Nosto404(props: Nosto404Props): null; /** * @group Hooks */ export declare type Nosto404Props = { placements?: string[]; }; /** * You can personalise your category and collection pages by using the NostoCategory component. * The component requires that you provide it the the slash-delimited slug representation of the current category. * * By default, your account, when created, has two category placements named `categorypage-nosto-1` and `categorypage-nosto-2`. * You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example. * * @example * ``` * <div className="category-page"> * <NostoPlacement id="categorypage-nosto-1" /> * <NostoPlacement id="categorypage-nosto-2" /> * <NostoCategory category={category.name} /> * </div> * ``` * * **Note:** Be sure to pass in the correct category representation. * If the category being viewed is `Mens >> Jackets`, you must provide the name as `/Mens/Jackets`. * You must ensure that the category path provided here matches that of the categories tagged in your products. * * @group Components */ export declare function NostoCategory(props: NostoCategoryProps): null; /** * @group Hooks */ export declare type NostoCategoryProps = { category: string; placements?: string[]; }; /** * You can personalise your cart and checkout pages by using the NostoCheckout component. * The component does not require any props. * * By default, your account, when created, has two cart-page placements named `categorypage-nosto-1` and `categorypage-nosto-2`. * You may omit these and use any identifier you need. * The identifiers used here are simply provided to illustrate the example. * * @example * ``` * <div className="checkout-page"> * <NostoPlacement id="checkout-nosto-1" /> * <NostoPlacement id="checkout-nosto-2" /> * <NostoCheckout /> * </div> * ``` * * @group Components */ export declare function NostoCheckout(props: NostoCheckoutProps): null; /** * @group Hooks */ export declare type NostoCheckoutProps = { placements?: string[]; }; /** * @group Essential Functions */ export declare const NostoContext: Context<NostoContextType>; /** * @group Types */ export declare interface NostoContextType { account: string; clientScriptLoaded: boolean; currentVariation?: string; renderFunction?: (...args: unknown[]) => unknown; responseMode: RenderMode; recommendationComponent?: RecommendationComponent; setPortals?: (portals: ReactPortal[]) => void; } /** * The `NostoHome` component must be used to personalise the home page. The component does not require any props. * * By default, your account, when created, has four front-page placements named `frontpage-nosto-1`, `frontpage-nosto-2`, `frontpage-nosto-3` and `frontpage-nosto-4`. * You may omit these and use any identifier you need. * The identifiers used here are simply provided to illustrate the example. * * The `<NostoHome \>` component needs to be added after the placements. * Content and recommendations will be rendered through this component. * * @example * ``` * <div className="front-page"> * <NostoPlacement id="frontpage-nosto-1" /> * <NostoPlacement id="frontpage-nosto-2" /> * <NostoPlacement id="frontpage-nosto-3" /> * <NostoPlacement id="frontpage-nosto-4" /> * <NostoHome /> * </div> * ``` * * @group Components */ export declare function NostoHome(props: NostoHomeProps): null; /** * @group Hooks */ export declare type NostoHomeProps = { placements?: string[]; }; /** * You can personalise your order-confirmation/thank-you page by using the `NostoOrder` component. * The component requires that you provide it with the details of the order. * * By default, your account, when created, has one other-page placement named `thankyou-nosto-1`. * You may omit this and use any identifier you need. The identifier used here is simply provided to illustrate the example. * * The order prop requires a value that adheres to the type `Purchase`. * * @example * ``` * <div className="thankyou-page"> * <NostoPlacement id="thankyou-nosto-1" /> * <NostoOrder order={{ purchase: toOrder(order) }} /> * </div> * ``` * * @group Components */ export declare function NostoOrder(props: NostoOrderProps): null; /** * @group Hooks */ export declare type NostoOrderProps = { order: Order | ToCamelCase<Order>; placements?: string[]; }; /** * You can personalise your miscellaneous pages by using the NostoOther component. * The component does not require any props. * * By default, your account, when created, has two other-page placements named `other-nosto-1` and `other-nosto-2`. * You may omit these and use any identifier you need. * The identifiers used here are simply provided to illustrate the example. * * @example * ``` * <div className="other-page"> * <NostoPlacement id="other-nosto-1" /> * <NostoPlacement id="other-nosto-2" /> * <NostoOther /> * </div>; * ``` * * @group Components */ export declare function NostoOther(props: NostoOtherProps): null; /** * @group Hooks */ export declare type NostoOtherProps = { placements?: string[]; }; /** * Nosto React has a special component called NostoPlacement. * The component is a simply a hidden `<div>` placeholder into which Nosto injects recommendations or personalises the content between the tags. * * We recommend adding as many placements across your views as needed as these are hidden and only populated when a corresponding campaign (targeting that placement) is configured. * * @example * ``` * <NostoPlacement id="frontpage-nosto-1" /> * ``` * * @group Components */ export declare function NostoPlacement({ id, pageType, children }: NostoPlacementProps): JSX.Element; /** * @group Components */ export declare type NostoPlacementProps = { id: string; pageType?: string; children?: React.ReactNode; }; /** * The NostoProduct component must be used to personalise the product page. * The component requires that you provide it the identifier of the current product being viewed. * * By default, your account, when created, has three product-page placements named `productpage-nosto-1`, `productpage-nosto-2` and `productpage-nosto-3`. * You may omit these and use any identifier you need. * The identifiers used here are simply provided to illustrate the example. * * The `<NostoProduct \>` component needs to be added after the placements. * Content and recommendations will be rendered through this component. * Pass in the product ID via the product prop to pass this information back to Nosto. * * @example * ``` * <div className="product-page"> * <NostoPlacement id="productpage-nosto-1" /> * <NostoPlacement id="productpage-nosto-2" /> * <NostoPlacement id="productpage-nosto-3" /> * <NostoProduct product={product.id} /> * </div> * ``` * * @group Components */ export declare function NostoProduct(props: NostoProductProps): null; /** * @group Hooks */ export declare type NostoProductProps = { product: string; reference?: string; tagging?: Product; placements?: string[]; }; /** * This widget is what we call the Nosto root widget, which is responsible for adding the actual Nosto script and the JS API stub. * This widget wraps all other React Nosto widgets. * * ``` * <NostoProvider account="your-nosto-account-id" recommendationComponent={<NostoSlot />}> * <App /> * </NostoProvider> * ``` * * **Note:** the component also accepts a prop to configure the host `host="connect.nosto.com"`. * In advanced use-cases, the need to configure the host may surface. * * In order to implement client-side rendering, the requires a designated component to render the recommendations provided by Nosto. * This component should be capable of processing the JSON response received from our backend. * Notice the `recommendationComponent` prop passed to `<NostoProvider>` above. * * Learn more [here](https://github.com/Nosto/shopify-hydrogen/blob/main/README.md#client-side-rendering-for-recommendations) and see a [live example](https://github.com/Nosto/shopify-hydrogen-demo) on our demo store. * * @group Components */ export declare function NostoProvider(props: NostoProviderProps): JSX.Element; /** * @group Components */ export declare interface NostoProviderProps { /** * Indicates merchant id */ account: string; /** * Indicates currency */ currentVariation?: string; /** * Indicates an url of a server */ host?: string; /** * children */ children: ReactNode | ReactNode[]; /** * Indicates if merchant uses multiple currencies */ multiCurrency?: boolean; /** * Recommendation component which holds nostoRecommendation object */ recommendationComponent?: RecommendationComponent; /** * Recommendation render mode. See {@link https://nosto.github.io/nosto-js/types/client.RenderMode.html} */ renderMode?: RenderMode; /** * Enables Shopify markets with language and market id */ shopifyMarkets?: { language: string; marketId: string | number; }; /** * Load nosto script (should be false if loading the script outside of nosto-react) */ loadScript?: boolean; /** * Custom script loader */ scriptLoader?: (scriptSrc: string, options?: ScriptLoadOptions) => Promise<void>; } /** * You can personalise your search pages by using the NostoSearch component. * The component requires that you provide it the current search term. * * By default, your account, when created, has two search-page placements named `searchpage-nosto-1` and `searchpage-nosto-2`. * You may omit these and use any identifier you need. The identifiers used here are simply provided to illustrate the example. * * @example * ``` * <div className="search-page"> * <NostoPlacement id="searchpage-nosto-1" /> * <NostoPlacement id="searchpage-nosto-2" /> * <NostoSearch query={"black shoes"} /> * </div> * ``` * * **Note:** Do not encode the search term in any way. * It should be provided an unencoded string. * A query for `black shoes` must be provided as-is and not as `black+shoes`. * Doing so will lead to invalid results. * * @group Components */ export declare function NostoSearch(props: NostoSearchProps): null; /** * @group Components */ export declare type NostoSearchProps = { query: string; placements?: string[]; }; /** * Nosto React requires that you pass it the details of current cart contents and the details of the currently logged-in customer, if any, on every route change. * This makes it easier to add attribution. * * The `NostoSession` component makes it very easy to keep the session up to date so long as the cart and the customer are provided. * * The cart prop requires a value that adheres to the type `Cart`, while the customer prop requires a value that adheres to the type `Customer`. * * @group Components */ export declare function NostoSession(props?: NostoSessionProps): null; /** * @group Hooks */ export declare type NostoSessionProps = { cart?: Cart | ToCamelCase<Cart>; customer?: Customer | ToCamelCase<Customer>; }; export { Order } export { Product } /** * @group Types */ export declare interface Recommendation { result_id: string; products: PushedProduct[]; result_type: string; title: string; div_id: string; source_product_ids: string[]; params: unknown; } export declare type RecommendationComponent = ReactElement<{ nostoRecommendation: Recommendation; }>; export { RenderMode } /** * @group Types */ export declare type ScriptLoadOptions = { /** * Indicates the position of the script, default is "body" */ position?: "head" | "body"; /** * Indicates the attributes of the script element */ attributes?: Record<string, string>; }; export declare type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamelCase<U>>}` : S; export declare type ToCamelCase<T> = T extends (infer U)[] ? ToCamelCase<U>[] : T extends Date ? T : T extends object ? { [K in keyof T as SnakeToCamelCase<K & string>]: ToCamelCase<T[K]>; } : T; /** * You can personalise your 404 error pages by using the `useNosto404` hook. * * @example Basic 404 page usage * ```tsx * import { useNosto404 } from '@nosto/nosto-react' * * function NotFoundPage() { * useNosto404({ * placements: ['notfound-nosto-1', 'notfound-popular-products'] * }) * * return ( * <div> * <h1>Page Not Found</h1> * <p>Sorry, the page you're looking for doesn't exist.</p> * <div id="notfound-popular-products" /> * <div id="notfound-nosto-1" /> * </div> * ) * } * ``` * * @example 404 page with default placements * ```tsx * import { useNosto404 } from '@nosto/nosto-react' * * function Simple404Page() { * useNosto404() // Uses all available placements * * return ( * <div> * <h1>Oops! Page not found</h1> * <p>Let us help you find what you're looking for:</p> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNosto404(props?: Nosto404Props): void; /** * You can personalise your category and collection pages by using the useNostoCategory hook. * * @example Basic category page usage * ```tsx * import { useNostoCategory } from '@nosto/nosto-react' * * function CategoryPage({ categoryName }: { categoryName: string }) { * useNostoCategory({ * category: categoryName, * placements: ['categorypage-nosto-1', 'categorypage-nosto-2'] * }) * * return ( * <div> * <h1>Category: {categoryName}</h1> * <div id="categorypage-nosto-1" /> * <div id="categorypage-nosto-2" /> * </div> * ) * } * ``` * * @example Collection page with custom placements * ```tsx * import { useNostoCategory } from '@nosto/nosto-react' * * function CollectionPage({ collection }: { collection: { name: string, id: string } }) { * useNostoCategory({ * category: `collection-${collection.id}`, * placements: ['collection-banner', 'collection-recommendations'] * }) * * return ( * <div> * <h1>{collection.name} Collection</h1> * <div id="collection-banner" /> * {/\* Product grid here *\/} * <div id="collection-recommendations" /> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoCategory({ category, placements }: NostoCategoryProps): void; /** * You can personalise your cart and checkout pages by using the useNostoCheckout hook. * * @example Basic cart page usage * ```tsx * import { useNostoCheckout } from '@nosto/nosto-react' * * function CartPage() { * useNostoCheckout({ * placements: ['cartpage-nosto-1', 'cartpage-cross-sell'] * }) * * return ( * <div> * <h1>Your Cart</h1> * {/\* Cart items here *\/} * <div id="cartpage-cross-sell" /> * <div id="cartpage-nosto-1" /> * </div> * ) * } * ``` * * @example Checkout page with recommendations * ```tsx * import { useNostoCheckout } from '@nosto/nosto-react' * * function CheckoutPage() { * useNostoCheckout({ * placements: ['checkout-upsell', 'checkout-last-chance'] * }) * * return ( * <div> * <h1>Checkout</h1> * <div id="checkout-upsell" /> * {/\* Checkout form here *\/} * <div id="checkout-last-chance" /> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoCheckout(props?: NostoCheckoutProps): void; /** * A hook that allows you to access the NostoContext and retrieve Nosto-related data from it in React components. * * @example * ```tsx * import { useNostoContext } from '@nosto/nosto-react' * * function NostoStatus() { * const { clientScriptLoaded, account, responseMode } = useNostoContext() * * return ( * <div> * <p>Account: {account}</p> * <p>Script loaded: {String(clientScriptLoaded)}</p> * <p>Response mode: {responseMode}</p> * </div> * ) * } * ``` * * @example Using context for conditional rendering * ```tsx * import { useNostoContext } from '@nosto/nosto-react' * * function ConditionalRecommendations() { * const { clientScriptLoaded, recommendationComponent } = useNostoContext() * * if (!clientScriptLoaded) { * return <div>Loading recommendations...</div> * } * * return ( * <div> * {recommendationComponent && ( * <div id="nosto-recommendation-placeholder" /> * )} * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoContext(): NostoContextType; /** * You can personalise your home page by using the useNostoHome hook. * * @example Basic home page usage * ```tsx * import { useNostoHome } from '@nosto/nosto-react' * * function HomePage() { * useNostoHome({ * placements: ['frontpage-nosto-1', 'frontpage-nosto-2', 'frontpage-hero'] * }) * * return ( * <div> * <div id="frontpage-hero" /> * <h1>Welcome to our store</h1> * <div id="frontpage-nosto-1" /> * <div id="frontpage-nosto-2" /> * </div> * ) * } * ``` * * @example Home page with default placements * ```tsx * import { useNostoHome } from '@nosto/nosto-react' * * function SimpleHomePage() { * // Uses all available placements configured in Nosto admin * useNostoHome() * * return ( * <div> * <h1>Home Page</h1> * {/\* Nosto will inject content into configured placements *\/} * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoHome(props?: NostoHomeProps): void; /** * You can personalise your order-confirmation/thank-you page by using the `useNostoOrder` hook. * * @example Basic order confirmation page usage * ```tsx * import { useNostoOrder } from '@nosto/nosto-react' * * function OrderConfirmationPage({ order }: { order: Order }) { * useNostoOrder({ * order: order, * placements: ['orderconfirm-nosto-1', 'orderconfirm-cross-sell'] * }) * * return ( * <div> * <h1>Order Confirmed!</h1> * <p>Order #{order.order_number}</p> * <div id="orderconfirm-cross-sell" /> * <div id="orderconfirm-nosto-1" /> * </div> * ) * } * ``` * * @example Complete order with camelCase conversion * ```tsx * import { useNostoOrder } from '@nosto/nosto-react' * * function ThankYouPage({ orderData }: { orderData: ToCamelCase<Order> }) { * useNostoOrder({ * order: { * orderNumber: orderData.orderNumber, * customerEmail: orderData.customerEmail, * purchasedItems: orderData.purchasedItems.map(item => ({ * productId: item.productId, * skuId: item.skuId, * name: item.name, * unitPrice: item.unitPrice, * quantity: item.quantity * })) * }, * placements: ['thankyou-recommendations', 'thankyou-reviews'] * }) * * return ( * <div> * <h1>Thank you for your purchase!</h1> * <div id="thankyou-recommendations" /> * <div id="thankyou-reviews" /> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoOrder({ order, placements }: NostoOrderProps): void; /** * You can personalise your miscellaneous pages by using the useNostoOther hook. * * @example Basic usage for contact page * ```tsx * import { useNostoOther } from '@nosto/nosto-react' * * function ContactPage() { * useNostoOther({ * placements: ['contactpage-nosto-1', 'contactpage-popular-products'] * }) * * return ( * <div> * <h1>Contact Us</h1> * <form> * {/\* Contact form fields *\/} * </form> * <div id="contactpage-popular-products" /> * <div id="contactpage-nosto-1" /> * </div> * ) * } * ``` * * @example About page with recommendations * ```tsx * import { useNostoOther } from '@nosto/nosto-react' * * function AboutPage() { * useNostoOther({ * placements: ['aboutpage-featured', 'aboutpage-bestsellers'] * }) * * return ( * <div> * <h1>About Our Company</h1> * <p>Learn more about our story...</p> * <div id="aboutpage-featured" /> * <div id="aboutpage-bestsellers" /> * </div> * ) * } * ``` * * @example Default placements for any other page * ```tsx * import { useNostoOther } from '@nosto/nosto-react' * * function GenericPage() { * useNostoOther() // Uses all available placements * * return ( * <div> * <h1>Generic Page</h1> * <p>This could be any page type not covered by other hooks.</p> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoOther(props?: NostoOtherProps): void; /** * You can personalise your product pages by using the useNostoProduct hook. * * @example Basic product page usage * ```tsx * import { useNostoProduct } from '@nosto/nosto-react' * * function ProductPage({ productId }: { productId: string }) { * useNostoProduct({ * product: productId, * placements: ['productpage-nosto-1', 'productpage-nosto-2'] * }) * * return ( * <div> * <h1>Product Details</h1> * <div id="productpage-nosto-1" /> * <div id="productpage-nosto-2" /> * </div> * ) * } * ``` * * @example Advanced product page with full tagging * ```tsx * import { useNostoProduct } from '@nosto/nosto-react' * * function AdvancedProductPage({ product }: { product: Product }) { * useNostoProduct({ * product: product.id, * reference: `product-${product.id}`, * tagging: { * product_id: product.id, * name: product.name, * url: product.url, * price: product.price, * list_price: product.listPrice, * image_url: product.imageUrl, * categories: product.categories, * brand: product.brand, * selected_sku_id: product.selectedVariant?.id * }, * placements: ['productpage-cross-sell', 'productpage-upsell'] * }) * * return ( * <div> * <h1>{product.name}</h1> * <div id="productpage-cross-sell" /> * <div id="productpage-upsell" /> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoProduct({ product, tagging, placements, reference }: NostoProductProps): void; /** * You can personalise your search pages by using the useNostoSearch hook. * * @example Basic search page usage * ```tsx * import { useNostoSearch } from '@nosto/nosto-react' * * function SearchPage({ searchQuery }: { searchQuery: string }) { * useNostoSearch({ * query: searchQuery, * placements: ['searchpage-nosto-1', 'searchpage-no-results'] * }) * * return ( * <div> * <h1>Search Results for "{searchQuery}"</h1> * {/\* Search results here *\/} * <div id="searchpage-nosto-1" /> * <div id="searchpage-no-results" /> * </div> * ) * } * ``` * * @example Search with dynamic query updates * ```tsx * import { useNostoSearch } from '@nosto/nosto-react' * import { useState } from 'react' * * function SearchPageWithFilters() { * const [query, setQuery] = useState('') * * useNostoSearch({ * query: query, * placements: ['search-recommendations', 'search-trending'] * }) * * return ( * <div> * <input * type="text" * value={query} * onChange={(e) => setQuery(e.target.value)} * placeholder="Search products..." * /> * <div id="search-recommendations" /> * <div id="search-trending" /> * </div> * ) * } * ``` * * @group Hooks */ export declare function useNostoSearch({ query, placements }: NostoSearchProps): void; /** * Nosto React requires that you pass it the details of current cart contents and the details of the currently logged-in customer, if any, on every route change. * * @example Basic session management * ```tsx * import { useNostoSession } from '@nosto/nosto-react' * * function App({ cart, user }: { cart?: Cart, user?: Customer }) { * useNostoSession({ * cart: cart ? { * items: cart.items.map(item => ({ * product_id: item.productId, * sku_id: item.skuId, * name: item.name, * unit_price: item.price, * quantity: item.quantity * })) * } : undefined, * customer: user ? { * customer_reference: user.id, * email: user.email, * first_name: user.firstName, * last_name: user.lastName * } : undefined * }) * * return <div>App content</div> * } * ``` * * @example Route-based session updates * ```tsx * import { useNostoSession } from '@nosto/nosto-react' * import { useEffect, useState } from 'react' * * function SessionProvider({ children }: { children: React.ReactNode }) { * const [cart, setCart] = useState<Cart | null>(null) * const [customer, setCustomer] = useState<Customer | null>(null) * * useEffect(() => { * // Load cart and customer data * loadCartData().then(setCart) * loadCustomerData().then(setCustomer) * }, []) * * useNostoSession({ * cart: cart ? { * items: cart.items * } : undefined, * customer: customer ? { * customerReference: customer.id, * email: customer.email * } : undefined * }) * * return <>{children}</> * } * ``` * * @group Hooks */ export declare function useNostoSession({ cart, customer }?: NostoSessionProps): void; export { }