From bc5abc1cc42b1174356e51736f206b73a3ed9405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Mon, 2 Sep 2024 16:28:22 +0300 Subject: [PATCH] Continue hook extraction --- .prettierrc | 4 +- spec/category.spec.tsx | 14 +- spec/checkout.spec.tsx | 14 +- spec/events.ts | 21 +- spec/fohofo.spec.tsx | 14 +- spec/home.spec.tsx | 14 +- spec/module.spec.ts | 38 +- spec/navigation.spec.tsx | 95 +- spec/nosto.load.spec.tsx | 4 +- spec/other.spec.tsx | 14 +- spec/product.sku.spec.tsx | 111 ++- spec/product.spec.tsx | 14 +- spec/search.spec.tsx | 14 +- spec/session.spec.tsx | 29 +- spec/setup.js | 29 +- spec/useDeepCompareEffect.spec.ts | 11 +- spec/useLoadClientScript.spec.tsx | 14 +- spec/useNostoContext.spec.ts | 6 +- spec/useRenderCampaigns.spec.tsx | 13 +- spec/utils.tsx | 9 +- src/components/Nosto404.tsx | 27 +- src/components/NostoCategory.tsx | 32 +- src/components/NostoCheckout.tsx | 27 +- src/components/NostoHome.tsx | 28 +- src/components/NostoOrder.tsx | 35 +- src/components/NostoOther.tsx | 27 +- src/components/NostoPlacement.tsx | 2 +- src/components/NostoProduct.tsx | 34 +- src/components/NostoProvider.tsx | 11 +- src/components/NostoSearch.tsx | 32 +- src/components/NostoSession.tsx | 44 +- src/components/helpers.ts | 4 +- src/components/index.ts | 11 - src/context.ts | 1 - src/hooks/index.ts | 5 - src/hooks/scriptLoader.ts | 8 +- src/hooks/useDeepCompareEffect.ts | 5 +- src/hooks/useLoadClientScript.ts | 13 +- src/hooks/useNosto404.tsx | 25 + src/hooks/useNostoApi.ts | 6 +- src/hooks/useNostoCategory.tsx | 31 + src/hooks/useNostoCheckout.tsx | 25 + src/hooks/useNostoContext.ts | 5 +- src/hooks/useNostoHome.tsx | 25 + src/hooks/useNostoOrder.tsx | 34 + src/hooks/useNostoOther.tsx | 25 + src/hooks/useNostoProduct.tsx | 33 + src/hooks/useNostoSearch.tsx | 31 + src/hooks/useNostoSession.tsx | 36 + src/hooks/useRenderCampaigns.tsx | 12 +- src/index.ts | 23 +- src/types.ts | 1508 +++++++++++++++-------------- src/utils/types.ts | 8 +- 53 files changed, 1355 insertions(+), 1300 deletions(-) delete mode 100644 src/components/index.ts delete mode 100644 src/hooks/index.ts create mode 100644 src/hooks/useNosto404.tsx create mode 100644 src/hooks/useNostoCategory.tsx create mode 100644 src/hooks/useNostoCheckout.tsx create mode 100644 src/hooks/useNostoHome.tsx create mode 100644 src/hooks/useNostoOrder.tsx create mode 100644 src/hooks/useNostoOther.tsx create mode 100644 src/hooks/useNostoProduct.tsx create mode 100644 src/hooks/useNostoSearch.tsx create mode 100644 src/hooks/useNostoSession.tsx diff --git a/.prettierrc b/.prettierrc index 563a276..6460e43 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,8 @@ { - "trailingComma": "es5", + "trailingComma": "none", "semi": false, "arrowParens": "avoid", - "printWidth": 100, + "printWidth": 120, "useTabs": false, "tabWidth": 2 } \ No newline at end of file diff --git a/spec/category.spec.tsx b/spec/category.spec.tsx index 8ece5b0..c2f91d4 100644 --- a/spec/category.spec.tsx +++ b/spec/category.spec.tsx @@ -6,19 +6,19 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Category page render", async () => { render( - } - > + }> ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(2) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(2) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/checkout.spec.tsx b/spec/checkout.spec.tsx index d6d338c..7ec9305 100644 --- a/spec/checkout.spec.tsx +++ b/spec/checkout.spec.tsx @@ -6,10 +6,7 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Checkout page render", async () => { render( - } - > + }> @@ -17,9 +14,12 @@ test("Checkout page render", async () => { ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(3) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(3) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/events.ts b/spec/events.ts index 3622b75..b6f7b5c 100644 --- a/spec/events.ts +++ b/spec/events.ts @@ -21,21 +21,14 @@ function createEvent(event: Event): Event { export function frontEvent() { return createEvent({ - elements: [ - "frontpage-nosto-1", - "frontpage-nosto-3", - "frontpage-nosto-4", - ], + elements: ["frontpage-nosto-1", "frontpage-nosto-3", "frontpage-nosto-4"], page_type: "front" }) } export function categoryEvent(category: string) { return createEvent({ - elements: [ - "categorypage-nosto-1", - "categorypage-nosto-2", - ], + elements: ["categorypage-nosto-1", "categorypage-nosto-2"], page_type: "category", categories: [category], url: `http://localhost/collections/${category}` @@ -44,14 +37,8 @@ export function categoryEvent(category: string) { export function productEvent(product: string) { return createEvent({ - elements: [ - "productpage-nosto-1", - "productpage-nosto-2", - "productpage-nosto-3", - ], - events: [ - ["vp", product], - ], + elements: ["productpage-nosto-1", "productpage-nosto-2", "productpage-nosto-3"], + events: [["vp", product]], page_type: "product", url: `http://localhost/products/${product}` }) diff --git a/spec/fohofo.spec.tsx b/spec/fohofo.spec.tsx index cc6af38..5d53fd1 100644 --- a/spec/fohofo.spec.tsx +++ b/spec/fohofo.spec.tsx @@ -6,19 +6,19 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("404 page render", async () => { render( - } - > + }> ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(2) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(2) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/home.spec.tsx b/spec/home.spec.tsx index 3e56b11..ead4ed7 100644 --- a/spec/home.spec.tsx +++ b/spec/home.spec.tsx @@ -6,10 +6,7 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Home page render", async () => { render( - } - > + }> @@ -17,9 +14,12 @@ test("Home page render", async () => { ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(3) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(3) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/module.spec.ts b/spec/module.spec.ts index f6417af..358f0ab 100644 --- a/spec/module.spec.ts +++ b/spec/module.spec.ts @@ -2,28 +2,28 @@ import { test, expect } from "vitest" import * as imports from "../src/index" test("module structure is stable", () => { - expect(Object.keys(imports)).toEqual([ + expect(Object.keys(imports).sort()).toEqual([ "Nosto404", - "useNosto404", - "NostoOther", - "useNostoOther", - "NostoCheckout", - "useNostoCheckout", - "NostoProduct", - "useNostoProduct", "NostoCategory", - "useNostoCategory", - "NostoSearch", - "useNostoSearch", - "NostoOrder", - "useNostoOrder", + "NostoCheckout", + "NostoContext", "NostoHome", - "useNostoHome", + "NostoOrder", + "NostoOther", "NostoPlacement", + "NostoProduct", "NostoProvider", + "NostoSearch", "NostoSession", - "useNostoSession", - "NostoContext", - "useNostoContext" - ]) -}) \ No newline at end of file + "useNosto404", + "useNostoCategory", + "useNostoCheckout", + "useNostoContext", + "useNostoHome", + "useNostoOrder", + "useNostoOther", + "useNostoProduct", + "useNostoSearch", + "useNostoSession" + ]) +}) diff --git a/spec/navigation.spec.tsx b/spec/navigation.spec.tsx index e0a2984..a342228 100644 --- a/spec/navigation.spec.tsx +++ b/spec/navigation.spec.tsx @@ -7,60 +7,69 @@ import { listenTo, WAIT_FOR_TIMEOUT } from "./utils" import { categoryEvent, frontEvent, productEvent } from "./events" function HomePage() { - useNostoHome() - return <> - - - - Hoodies - + useNostoHome() + return ( + <> + + + + Hoodies + + ) } function CategoryPage() { const { category } = useParams() useNostoCategory({ category: category! }) - return <> - - - Product 123 - Product 234 - Home - + return ( + <> + + + Product 123 + Product 234 + Home + + ) } function ProductPage() { const { product } = useParams() useNostoProduct({ product: product! }) - return <> - - - - Product 234 - Hoodies - Home - + return ( + <> + + + + Product 234 + Hoodies + Home + + ) } function Main() { - return }> - - - } /> - } /> - } /> - - - + return ( + }> + + + } /> + } /> + } /> + + + + ) } test("navigation events", async () => { render(
) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(3) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(3) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) const requests = listenTo("prerequest") @@ -68,24 +77,24 @@ test("navigation events", async () => { expect(requests).toEqual(given) requests.length = 0 } - + // home -> category fireEvent.click(screen.getByText("Hoodies")) - verifyEvents([ frontEvent(), categoryEvent("hoodies")]) + verifyEvents([frontEvent(), categoryEvent("hoodies")]) // category -> product fireEvent.click(screen.getByText("Product 123")) - verifyEvents([ productEvent("123") ]) + verifyEvents([productEvent("123")]) // product -> product fireEvent.click(screen.getByText("Product 234")) - verifyEvents([ productEvent("234") ]) + verifyEvents([productEvent("234")]) // product -> category fireEvent.click(screen.getByText("Hoodies")) - verifyEvents([ categoryEvent("hoodies") ]) + verifyEvents([categoryEvent("hoodies")]) // category -> home fireEvent.click(screen.getByText("Home")) - verifyEvents([ frontEvent() ]) -}) \ No newline at end of file + verifyEvents([frontEvent()]) +}) diff --git a/spec/nosto.load.spec.tsx b/spec/nosto.load.spec.tsx index 8de3f34..9c75b2c 100644 --- a/spec/nosto.load.spec.tsx +++ b/spec/nosto.load.spec.tsx @@ -6,7 +6,7 @@ import RecommendationComponent from "./renderer" describe("Nosto client script", () => { it("verify is not loaded twice", () => { - // @ts-expect-error dummy placeholder for Nosto iframe window scope + // @ts-expect-error dummy placeholder for Nosto iframe window scope window.nosto = {} render( @@ -59,4 +59,4 @@ describe("Nosto client script", () => { expect(document.querySelector("[nosto-client-script]")?.getAttribute("nosto-language")).toBe("en") expect(document.querySelector("[nosto-client-script]")?.getAttribute("nosto-market-id")).toBe("123") }) -}) \ No newline at end of file +}) diff --git a/spec/other.spec.tsx b/spec/other.spec.tsx index 4353313..fd32c6e 100644 --- a/spec/other.spec.tsx +++ b/spec/other.spec.tsx @@ -6,10 +6,7 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Other page render", async () => { render( - } - > + }> @@ -18,9 +15,12 @@ test("Other page render", async () => { ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(4) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(4) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/product.sku.spec.tsx b/spec/product.sku.spec.tsx index 2d38101..218e515 100644 --- a/spec/product.sku.spec.tsx +++ b/spec/product.sku.spec.tsx @@ -16,70 +16,85 @@ function ProductPage() { const tagging = { product_id: productId, - selected_sku_id: `${color}-${size}`, + selected_sku_id: `${color}-${size}` } - return <> - - - - - + return ( + <> + + + + + + ) } test("Product page with SKU id", async () => { render( - } - > + }> ) - - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(1) - }, { timeout: WAIT_FOR_TIMEOUT }) - + + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(1) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) + const requests = listenTo("prerequest") - - expect(requests).toEqual([{ - cart_popup: false, - elements: ["productpage-nosto-1"], - events: [["vp", "7078777258043", undefined, undefined, "red-M"]], - page_type: "product", - response_mode: "JSON_ORIGINAL", - url: "http://localhost/" - }]) + + expect(requests).toEqual([ + { + cart_popup: false, + elements: ["productpage-nosto-1"], + events: [["vp", "7078777258043", undefined, undefined, "red-M"]], + page_type: "product", + response_mode: "JSON_ORIGINAL", + url: "http://localhost/" + } + ]) requests.length = 0 // change color - fireEvent.change(screen.getByTestId('color'), { target: { value: "blue" } }) + fireEvent.change(screen.getByTestId("color"), { target: { value: "blue" } }) - expect(requests).toEqual([{ - cart_popup: false, - elements: ["productpage-nosto-1"], - events: [["vp", "7078777258043", undefined, undefined, "blue-M"]], - page_type: "product", - response_mode: "JSON_ORIGINAL", - url: "http://localhost/" - }]) + expect(requests).toEqual([ + { + cart_popup: false, + elements: ["productpage-nosto-1"], + events: [["vp", "7078777258043", undefined, undefined, "blue-M"]], + page_type: "product", + response_mode: "JSON_ORIGINAL", + url: "http://localhost/" + } + ]) requests.length = 0 // change size - fireEvent.change(screen.getByTestId('size'), { target: { value: "L" } }) + fireEvent.change(screen.getByTestId("size"), { target: { value: "L" } }) - expect(requests).toEqual([{ - cart_popup: false, - elements: ["productpage-nosto-1"], - events: [["vp", "7078777258043", undefined, undefined, "blue-L"]], - page_type: "product", - response_mode: "JSON_ORIGINAL", - url: "http://localhost/" - }]) + expect(requests).toEqual([ + { + cart_popup: false, + elements: ["productpage-nosto-1"], + events: [["vp", "7078777258043", undefined, undefined, "blue-L"]], + page_type: "product", + response_mode: "JSON_ORIGINAL", + url: "http://localhost/" + } + ]) }) - \ No newline at end of file diff --git a/spec/product.spec.tsx b/spec/product.spec.tsx index d957b11..f940522 100644 --- a/spec/product.spec.tsx +++ b/spec/product.spec.tsx @@ -6,10 +6,7 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Product page render", async () => { render( - } - > + }> @@ -17,9 +14,12 @@ test("Product page render", async () => { ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(3) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(3) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/search.spec.tsx b/spec/search.spec.tsx index abea0c5..9f831db 100644 --- a/spec/search.spec.tsx +++ b/spec/search.spec.tsx @@ -6,19 +6,19 @@ import { WAIT_FOR_TIMEOUT } from "./utils" test("Search page render", async () => { render( - } - > + }> ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(2) - }, { timeout: WAIT_FOR_TIMEOUT }) + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(2) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) expect(screen.getAllByTestId("recommendation-product").length).toBeGreaterThanOrEqual(3) diff --git a/spec/session.spec.tsx b/spec/session.spec.tsx index 26ff18d..57f628f 100644 --- a/spec/session.spec.tsx +++ b/spec/session.spec.tsx @@ -1,5 +1,5 @@ import { test, expect } from "vitest" -import { render, screen, waitFor} from "@testing-library/react" +import { render, screen, waitFor } from "@testing-library/react" import { NostoHome, NostoPlacement, NostoProvider, NostoSession } from "../src/index" import RecommendationComponent from "./renderer" import { listenTo, WAIT_FOR_TIMEOUT } from "./utils" @@ -14,22 +14,22 @@ test("Session render", async () => { } render( - } - > + }> - - + + ) - await waitFor(() => { - expect(screen.getAllByTestId("recommendation")).toHaveLength(3) - }, { timeout: WAIT_FOR_TIMEOUT }) - + await waitFor( + () => { + expect(screen.getAllByTestId("recommendation")).toHaveLength(3) + }, + { timeout: WAIT_FOR_TIMEOUT } + ) + expect(requests).toEqual([ { cart_popup: false, @@ -51,16 +51,11 @@ test("Session render", async () => { last_name: "Doe", type: "loggedin" }, - elements: [ - "frontpage-nosto-1", - "frontpage-nosto-3", - "frontpage-nosto-4", - ], + elements: ["frontpage-nosto-1", "frontpage-nosto-3", "frontpage-nosto-4"], events: [], page_type: "front", response_mode: "JSON_ORIGINAL", url: "http://localhost/" } ]) - }) diff --git a/spec/setup.js b/spec/setup.js index ff06a95..6533bdc 100644 --- a/spec/setup.js +++ b/spec/setup.js @@ -1,11 +1,12 @@ import { JSDOM } from "jsdom" import { afterEach } from "vitest" -const { window } = new JSDOM("", { - url: "http://localhost", - resources: "usable", - runScripts: "dangerously" }) -global.window = window +const { window } = new JSDOM("", { + url: "http://localhost", + resources: "usable", + runScripts: "dangerously" +}) +global.window = window global.location = window.location global.document = window.document global.localStorage = window.localStorage @@ -15,12 +16,12 @@ global.navigator = window.navigator global.window.nostoReactTest = true afterEach(() => { - // clearing Nosto iframe window handle - window.nosto = undefined - // clearing nostojs stub - window.nostojs = undefined - // clearing Shopify specific Nosto namespace - window.Nosto = undefined - document.head.innerHTML = "" - document.body.innerHTML = "" -}) \ No newline at end of file + // clearing Nosto iframe window handle + window.nosto = undefined + // clearing nostojs stub + window.nostojs = undefined + // clearing Shopify specific Nosto namespace + window.Nosto = undefined + document.head.innerHTML = "" + document.body.innerHTML = "" +}) diff --git a/spec/useDeepCompareEffect.spec.ts b/spec/useDeepCompareEffect.spec.ts index e577a44..f6e2325 100644 --- a/spec/useDeepCompareEffect.spec.ts +++ b/spec/useDeepCompareEffect.spec.ts @@ -1,14 +1,13 @@ import { describe, it, expect, vi } from "vitest" import { renderHook } from "@testing-library/react" -import { useDeepCompareEffect } from "../src/hooks" +import { useDeepCompareEffect } from "../src/hooks/useDeepCompareEffect" describe("useDeepCompareEffect", () => { it("should retrigger on changes", () => { const callback = vi.fn() - const { rerender } = renderHook( - props => useDeepCompareEffect(callback, [props]), - { initialProps: { value: 1 } } - ) + const { rerender } = renderHook(props => useDeepCompareEffect(callback, [props]), { + initialProps: { value: 1 } + }) expect(callback).toHaveBeenCalledTimes(1) @@ -18,4 +17,4 @@ describe("useDeepCompareEffect", () => { rerender({ value: 2 }) expect(callback).toHaveBeenCalledTimes(2) }) -}) \ No newline at end of file +}) diff --git a/spec/useLoadClientScript.spec.tsx b/spec/useLoadClientScript.spec.tsx index 75807bc..45503cd 100644 --- a/spec/useLoadClientScript.spec.tsx +++ b/spec/useLoadClientScript.spec.tsx @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest" import { renderHook } from "@testing-library/react" -import { useLoadClientScript } from "../src/hooks" +import { useLoadClientScript } from "../src/hooks/useLoadClientScript" import scriptLoader from "../src/hooks/scriptLoader" import "@testing-library/jest-dom/vitest" @@ -25,7 +25,6 @@ function getScriptSources() { } describe("useLoadClientScript", () => { - const testAccount = "shopify-11368366139" it("loads client script", async () => { @@ -44,8 +43,9 @@ describe("useLoadClientScript", () => { await new Promise(window.nostojs) hook.rerender() - expect(customScriptLoader).toHaveBeenLastCalledWith( - `//connect.nosto.com/include/${testAccount}`, { attributes: {"nosto-client-script": ""}}) + expect(customScriptLoader).toHaveBeenLastCalledWith(`//connect.nosto.com/include/${testAccount}`, { + attributes: { "nosto-client-script": "" } + }) }) it("set loaded state to true when client is loaded externally after", async () => { @@ -82,12 +82,12 @@ describe("useLoadClientScript", () => { it("remove existing Shopify markets related scripts before loading new ones", () => { const props = { account: testAccount, shopifyMarkets: { marketId: "123", language: "en" } } - const hook = renderHook(props => useLoadClientScript(props), { initialProps: props }) + const hook = renderHook(props => useLoadClientScript(props), { initialProps: props }) expect(getScriptSources()).toEqual([ `http://connect.nosto.com/script/shopify/market/nosto.js?merchant=${testAccount}&market=123&locale=en` ]) - const existingScript = document.querySelector("[nosto-client-script]") + const existingScript = document.querySelector("[nosto-client-script]") const nostoSandbox = document.querySelector("#nosto-sandbox") Object.assign(props.shopifyMarkets, { marketId: "234", language: "fr" }) @@ -99,4 +99,4 @@ describe("useLoadClientScript", () => { `http://connect.nosto.com/script/shopify/market/nosto.js?merchant=${testAccount}&market=234&locale=fr` ]) }) -}) \ No newline at end of file +}) diff --git a/spec/useNostoContext.spec.ts b/spec/useNostoContext.spec.ts index 12f4f12..1de2ffd 100644 --- a/spec/useNostoContext.spec.ts +++ b/spec/useNostoContext.spec.ts @@ -3,6 +3,6 @@ import { renderHook } from "@testing-library/react" import { useNostoContext } from "../src/hooks/useNostoContext" test("useNostoContext", async () => { - const { result } = renderHook(() => useNostoContext()) - expect(result.current).toBeTruthy() -}) \ No newline at end of file + const { result } = renderHook(() => useNostoContext()) + expect(result.current).toBeTruthy() +}) diff --git a/spec/useRenderCampaigns.spec.tsx b/spec/useRenderCampaigns.spec.tsx index ff97e69..bd927f9 100644 --- a/spec/useRenderCampaigns.spec.tsx +++ b/spec/useRenderCampaigns.spec.tsx @@ -6,7 +6,6 @@ import RecommendationComponent from "./renderer" import { createWrapper } from "./utils" describe("useRenderCampaigns", () => { - beforeEach(() => { document.body.innerHTML = `
@@ -16,9 +15,9 @@ describe("useRenderCampaigns", () => { it("supports component rendering", async () => { const wrapper = createWrapper({ account: "dummy", - clientScriptLoaded: true, - responseMode: "JSON_ORIGINAL", - recommendationComponent: + clientScriptLoaded: true, + responseMode: "JSON_ORIGINAL", + recommendationComponent: }) const { result } = renderHook(() => useRenderCampaigns(), { wrapper }) @@ -73,7 +72,7 @@ const baseResponse = { function jsonCampaign(num: number) { return { title: `Campaign ${num}`, - products: [ { name: `Product ${num}-1` }, { name: `Product ${num}-2` } ] + products: [{ name: `Product ${num}-1` }, { name: `Product ${num}-2` }] } } @@ -95,7 +94,7 @@ function htmlMockData() { ...baseResponse, recommendations: { "frontpage-nosto-1": "
Campaign 1
", - "frontpage-nosto-2": "
Campaign 2
", + "frontpage-nosto-2": "
Campaign 2
" } } -} \ No newline at end of file +} diff --git a/spec/utils.tsx b/spec/utils.tsx index 56b134c..aec75f0 100644 --- a/spec/utils.tsx +++ b/spec/utils.tsx @@ -8,7 +8,7 @@ export function listenTo(event: string) { const requests: unknown[] = [] if (!window.nostojs) { window.nostojs = (cb: (api: NostoClient) => void) => { - (window.nostojs.q = window.nostojs.q || []).push(cb) + ;(window.nostojs.q = window.nostojs.q || []).push(cb) } } window.nostojs(api => api.listen(event, req => requests.push(req))) @@ -17,9 +17,6 @@ export function listenTo(event: string) { export function createWrapper(nostoContext: NostoContextType) { return function Wrapper({ children }: { children: ReactNode }) { - return ( - - { children } - ) + return {children} } -} \ No newline at end of file +} diff --git a/src/components/Nosto404.tsx b/src/components/Nosto404.tsx index 96b153b..ff10643 100644 --- a/src/components/Nosto404.tsx +++ b/src/components/Nosto404.tsx @@ -1,9 +1,4 @@ -import { useNostoApi, useRenderCampaigns } from "../hooks" - -/** - * @group Components - */ -export type Nosto404Props = { placements?: string[] } +import { Nosto404Props, useNosto404 } from "../hooks/useNosto404" /** * You can personalise your cart and checkout pages by using the `Nosto404` component. @@ -25,25 +20,7 @@ export type Nosto404Props = { placements?: string[] } * * @group Components */ -export default function Nosto404(props: Nosto404Props) { +export function Nosto404(props: Nosto404Props) { useNosto404(props) return null } - -/** - * You can personalise your cart and checkout pages by using the `useNosto404` hook. - * - * @group Hooks - */ -export function useNosto404(props?: Nosto404Props) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewNotFound() - .setPlacements(props?.placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }) -} diff --git a/src/components/NostoCategory.tsx b/src/components/NostoCategory.tsx index 9527308..75027bd 100644 --- a/src/components/NostoCategory.tsx +++ b/src/components/NostoCategory.tsx @@ -1,12 +1,4 @@ -import { useNostoApi, useRenderCampaigns } from "../hooks" - -/** - * @group Components - */ -export type NostoCategoryProps = { - category: string - placements?: string[] -} +import { NostoCategoryProps, useNostoCategory } from "../hooks/useNostoCategory" /** * You can personalise your category and collection pages by using the NostoCategory component. @@ -30,27 +22,7 @@ export type NostoCategoryProps = { * * @group Components */ -export default function NostoCategory(props: NostoCategoryProps) { +export function NostoCategory(props: NostoCategoryProps) { useNostoCategory(props) return null } - -/** - * You can personalise your category and collection pages by using the useNostoCategory hook. - * - * @group Hooks - */ -export function useNostoCategory({ category, placements }: NostoCategoryProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewCategory(category) - .setPlacements(placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }, - [category] - ) -} diff --git a/src/components/NostoCheckout.tsx b/src/components/NostoCheckout.tsx index 3555137..7c9ed98 100644 --- a/src/components/NostoCheckout.tsx +++ b/src/components/NostoCheckout.tsx @@ -1,9 +1,4 @@ -import { useNostoApi, useRenderCampaigns } from "../hooks" - -/** - * @group Components - */ -export type NostoCheckoutProps = { placements?: string[] } +import { NostoCheckoutProps, useNostoCheckout } from "../hooks/useNostoCheckout" /** * You can personalise your cart and checkout pages by using the NostoCheckout component. @@ -24,25 +19,7 @@ export type NostoCheckoutProps = { placements?: string[] } * * @group Components */ -export default function NostoCheckout(props: NostoCheckoutProps) { +export function NostoCheckout(props: NostoCheckoutProps) { useNostoCheckout(props) return null } - -/** - * You can personalise your cart and checkout pages by using the useNostoCheckout hook. - * - * @group Hooks - */ -export function useNostoCheckout(props?: NostoCheckoutProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewCart() - .setPlacements(props?.placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }) -} diff --git a/src/components/NostoHome.tsx b/src/components/NostoHome.tsx index 8f0bf0d..85ae1a5 100644 --- a/src/components/NostoHome.tsx +++ b/src/components/NostoHome.tsx @@ -1,9 +1,4 @@ -import { useRenderCampaigns, useNostoApi } from "../hooks" - -/** - * @group Components - */ -export type NostoHomeProps = { placements?: string[] } +import { NostoHomeProps, useNostoHome } from "../hooks/useNostoHome" /** * The `NostoHome` component must be used to personalise the home page. The component does not require any props. @@ -28,26 +23,7 @@ export type NostoHomeProps = { placements?: string[] } * * @group Components */ -export default function NostoHome(props: NostoHomeProps) { +export function NostoHome(props: NostoHomeProps) { useNostoHome(props) return null } - -/** - * You can personalise your home page by using the useNostoHome hook. - * - * @group Hooks - */ -export function useNostoHome(props?: NostoHomeProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewFrontPage() - .setPlacements(props?.placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - } - ) -} diff --git a/src/components/NostoOrder.tsx b/src/components/NostoOrder.tsx index c96323a..7c57655 100644 --- a/src/components/NostoOrder.tsx +++ b/src/components/NostoOrder.tsx @@ -1,14 +1,4 @@ -import { Order } from "../types" -import { useRenderCampaigns, useNostoApi } from "../hooks" -import { snakeize } from "../utils/snakeize" - -/** - * @group Components - */ -export type NostoOrderProps = { - order: Order - placements?: string[] -} +import { NostoOrderProps, useNostoOrder } from "../hooks/useNostoOrder" /** * You can personalise your order-confirmation/thank-you page by using the `NostoOrder` component. @@ -29,28 +19,7 @@ export type NostoOrderProps = { * * @group Components */ -export default function NostoOrder(props: NostoOrderProps) { +export function NostoOrder(props: NostoOrderProps) { useNostoOrder(props) return null } - -/** - * You can personalise your order-confirmation/thank-you page by using the `useNostoOrder` hook. - * - * @group Hooks - */ -export function useNostoOrder({ order, placements }: NostoOrderProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .addOrder(snakeize(order)) - .setPlacements(placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }, - [order], - { deep: true } - ) -} diff --git a/src/components/NostoOther.tsx b/src/components/NostoOther.tsx index a99295b..fcbea7e 100644 --- a/src/components/NostoOther.tsx +++ b/src/components/NostoOther.tsx @@ -1,9 +1,4 @@ -import { useRenderCampaigns, useNostoApi } from "../hooks" - -/** - * @group Components - */ -export type NostoOtherProps = { placements?: string[] } +import { NostoOtherProps, useNostoOther } from "../hooks/useNostoOther" /** * You can personalise your miscellaneous pages by using the NostoOther component. @@ -24,25 +19,7 @@ export type NostoOtherProps = { placements?: string[] } * * @group Components */ -export default function NostoOther(props: NostoOtherProps) { +export function NostoOther(props: NostoOtherProps) { useNostoOther(props) return null } - -/** - * You can personalise your miscellaneous pages by using the useNostoOther hook. - * - * @group Hooks - */ -export function useNostoOther(props?: NostoOtherProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewOther() - .setPlacements(props?.placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }) -} diff --git a/src/components/NostoPlacement.tsx b/src/components/NostoPlacement.tsx index ab5035c..172d7ca 100644 --- a/src/components/NostoPlacement.tsx +++ b/src/components/NostoPlacement.tsx @@ -16,6 +16,6 @@ export type NostoPlacementProps = { id: string; pageType?: string } * * @group Components */ -export default function NostoPlacement({ id, pageType }: NostoPlacementProps) { +export function NostoPlacement({ id, pageType }: NostoPlacementProps) { return
} diff --git a/src/components/NostoProduct.tsx b/src/components/NostoProduct.tsx index c5e5c08..fe7eedb 100644 --- a/src/components/NostoProduct.tsx +++ b/src/components/NostoProduct.tsx @@ -1,14 +1,4 @@ -import { useRenderCampaigns, useNostoApi } from "../hooks" -import { Product } from "../types" - -/** - * @group Components - */ -export type NostoProductProps = { - product: string - tagging?: Product - placements?: string[] -} +import { NostoProductProps, useNostoProduct } from "../hooks/useNostoProduct" /** * The NostoProduct component must be used to personalise the product page. @@ -34,27 +24,7 @@ export type NostoProductProps = { * * @group Components */ -export default function NostoProduct(props: NostoProductProps) { +export function NostoProduct(props: NostoProductProps) { useNostoProduct(props) return null } - -/** - * You can personalise your product pages by using the useNostoProduct hook. - * - * @group Hooks - */ -export function useNostoProduct({ product, tagging, placements }: NostoProductProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewProduct(tagging ?? product) - .setPlacements(placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }, - [product, tagging?.selected_sku_id] - ) -} \ No newline at end of file diff --git a/src/components/NostoProvider.tsx b/src/components/NostoProvider.tsx index f2f9986..b4f0f0e 100644 --- a/src/components/NostoProvider.tsx +++ b/src/components/NostoProvider.tsx @@ -1,8 +1,8 @@ import { isValidElement } from "react" import { NostoContext, RecommendationComponent } from "../context" -import { useLoadClientScript } from "../hooks" import type { ReactNode } from "react" import { ScriptLoadOptions } from "../hooks/scriptLoader" +import { useLoadClientScript } from "../hooks/useLoadClientScript" /** * @group Components @@ -70,13 +70,8 @@ export interface NostoProviderProps { * * @group Components */ -export default function NostoProvider(props: NostoProviderProps) { - const { - account, - multiCurrency = false, - children, - recommendationComponent, - } = props +export function NostoProvider(props: NostoProviderProps) { + const { account, multiCurrency = false, children, recommendationComponent } = props // Pass currentVariation as empty string if multiCurrency is disabled const currentVariation = multiCurrency ? props.currentVariation : "" diff --git a/src/components/NostoSearch.tsx b/src/components/NostoSearch.tsx index cbcbcc7..50277bc 100644 --- a/src/components/NostoSearch.tsx +++ b/src/components/NostoSearch.tsx @@ -1,12 +1,4 @@ -import { useRenderCampaigns, useNostoApi } from "../hooks" - -/** - * @group Components - */ -export type NostoSearchProps = { - query: string - placements?: string[] -} +import { NostoSearchProps, useNostoSearch } from "../hooks/useNostoSearch" /** * You can personalise your search pages by using the NostoSearch component. @@ -31,27 +23,7 @@ export type NostoSearchProps = { * * @group Components */ -export default function NostoSearch(props: NostoSearchProps) { +export function NostoSearch(props: NostoSearchProps) { useNostoSearch(props) return null } - -/** - * You can personalise your search pages by using the useNostoSearch hook. - * - * @group Hooks - */ -export function useNostoSearch({ query, placements }: NostoSearchProps) { - const { renderCampaigns } = useRenderCampaigns() - - useNostoApi( - async (api) => { - const data = await api.defaultSession() - .viewSearch(query) - .setPlacements(placements || api.placements.getPlacements()) - .load() - renderCampaigns(data) - }, - [query] - ) -} diff --git a/src/components/NostoSession.tsx b/src/components/NostoSession.tsx index 110cb97..9316e59 100644 --- a/src/components/NostoSession.tsx +++ b/src/components/NostoSession.tsx @@ -1,18 +1,4 @@ -import { useNostoContext, useDeepCompareEffect } from "../hooks" -import { Cart as CartSnakeCase, Customer as CustomerSnakeCase } from "../types" -import { snakeize } from "../utils/snakeize" -import { ToCamelCase } from "../utils/types" - -type Cart = CartSnakeCase | ToCamelCase -type Customer = CustomerSnakeCase | ToCamelCase - -/** - * @group Components - */ -export type NostoSessionProps = { - cart?: Cart - customer?: Customer -} +import { NostoSessionProps, useNostoSession } from "../hooks/useNostoSession" /** * 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. @@ -24,33 +10,7 @@ export type NostoSessionProps = { * * @group Components */ -export default function NostoSession(props?: NostoSessionProps) { +export function NostoSession(props?: NostoSessionProps) { useNostoSession(props) return null } - -/** - * 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. - * - * @group Hooks - */ -export function useNostoSession({ cart, customer }: NostoSessionProps = {}) { - const { clientScriptLoaded } = useNostoContext() - - useDeepCompareEffect(() => { - const currentCart = cart ? snakeize(cart) : undefined - const currentCustomer = customer ? snakeize(customer) : undefined - - if (clientScriptLoaded) { - window.nostojs(api => { - api - .defaultSession() - .setCart(currentCart) - .setCustomer(currentCustomer) - .viewOther() - .load({ skipPageViews: true }) - }) - } - }, [clientScriptLoaded, cart, customer]) - -} diff --git a/src/components/helpers.ts b/src/components/helpers.ts index f3924ff..9e69ce4 100644 --- a/src/components/helpers.ts +++ b/src/components/helpers.ts @@ -1,3 +1,3 @@ export function isNostoLoaded() { - return typeof window.nosto !== "undefined" -} \ No newline at end of file + return typeof window.nosto !== "undefined" +} diff --git a/src/components/index.ts b/src/components/index.ts deleted file mode 100644 index 1dfd172..0000000 --- a/src/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { default as Nosto404, useNosto404, type Nosto404Props } from "./Nosto404" -export { default as NostoOther, useNostoOther, type NostoOtherProps } from "./NostoOther" -export { default as NostoCheckout, useNostoCheckout, type NostoCheckoutProps } from "./NostoCheckout" -export { default as NostoProduct, useNostoProduct, type NostoProductProps } from "./NostoProduct" -export { default as NostoCategory, useNostoCategory, type NostoCategoryProps } from "./NostoCategory" -export { default as NostoSearch, useNostoSearch, type NostoSearchProps } from "./NostoSearch" -export { default as NostoOrder, useNostoOrder, type NostoOrderProps } from "./NostoOrder" -export { default as NostoHome, useNostoHome, type NostoHomeProps } from "./NostoHome" -export { default as NostoPlacement, type NostoPlacementProps } from "./NostoPlacement" -export { default as NostoProvider, type NostoProviderProps } from "./NostoProvider" -export { default as NostoSession, useNostoSession, type NostoSessionProps } from "./NostoSession" diff --git a/src/context.ts b/src/context.ts index b83005e..9f90588 100644 --- a/src/context.ts +++ b/src/context.ts @@ -28,4 +28,3 @@ export const NostoContext = createContext({ responseMode: "HTML", clientScriptLoaded: false }) - diff --git a/src/hooks/index.ts b/src/hooks/index.ts deleted file mode 100644 index d05318a..0000000 --- a/src/hooks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { useDeepCompareEffect } from "./useDeepCompareEffect" -export { useNostoApi } from "./useNostoApi" -export { useNostoContext } from "./useNostoContext" -export { useRenderCampaigns } from "./useRenderCampaigns" -export { useLoadClientScript } from "./useLoadClientScript" \ No newline at end of file diff --git a/src/hooks/scriptLoader.ts b/src/hooks/scriptLoader.ts index 9d81b20..91cb3f9 100644 --- a/src/hooks/scriptLoader.ts +++ b/src/hooks/scriptLoader.ts @@ -8,9 +8,9 @@ export default function scriptLoader(scriptSrc: string, options?: ScriptLoadOpti script.onerror = () => reject() Object.entries(options?.attributes ?? {}).forEach(([k, v]) => script.setAttribute(k, v)) if (options?.position === "head") { - document.head.appendChild(script) + document.head.appendChild(script) } else { - document.body.appendChild(script) + document.body.appendChild(script) } }) } @@ -26,5 +26,5 @@ export type ScriptLoadOptions = { /** * Indicates the attributes of the script element */ - attributes?: Record -} \ No newline at end of file + attributes?: Record +} diff --git a/src/hooks/useDeepCompareEffect.ts b/src/hooks/useDeepCompareEffect.ts index dcd73fd..69d77fe 100644 --- a/src/hooks/useDeepCompareEffect.ts +++ b/src/hooks/useDeepCompareEffect.ts @@ -1,10 +1,7 @@ import { useEffect, useRef, useMemo, type EffectCallback, type DependencyList } from "react" import { deepCompare } from "../utils/compare" -export function useDeepCompareEffect( - callback: EffectCallback, - dependencies: DependencyList -) { +export function useDeepCompareEffect(callback: EffectCallback, dependencies: DependencyList) { return useEffect(callback, useDeepCompareMemoize(dependencies)) } diff --git a/src/hooks/useLoadClientScript.ts b/src/hooks/useLoadClientScript.ts index 8209b42..8452562 100644 --- a/src/hooks/useLoadClientScript.ts +++ b/src/hooks/useLoadClientScript.ts @@ -7,7 +7,13 @@ import scriptLoaderFn from "./scriptLoader" type NostoScriptProps = Pick export function useLoadClientScript(props: NostoScriptProps) { - const { host = "connect.nosto.com", scriptLoader = scriptLoaderFn, account, shopifyMarkets, loadScript = true } = props + const { + host = "connect.nosto.com", + scriptLoader = scriptLoaderFn, + account, + shopifyMarkets, + loadScript = true + } = props const [clientScriptLoaded, setClientScriptLoaded] = useState(false) useEffect(() => { @@ -49,8 +55,7 @@ export function useLoadClientScript(props: NostoScriptProps) { existingScript?.parentNode?.removeChild(existingScript) nostoSandbox?.parentNode?.removeChild(nostoSandbox) - const urlPartial = - `/script/shopify/market/nosto.js?merchant=${account}&market=${marketId}&locale=${language.toLowerCase()}` + const urlPartial = `/script/shopify/market/nosto.js?merchant=${account}&market=${marketId}&locale=${language.toLowerCase()}` injectScriptElement(urlPartial, { "nosto-language": language, "nosto-market-id": marketId }) } } @@ -81,4 +86,4 @@ export function useLoadClientScript(props: NostoScriptProps) { }, [shopifyMarkets?.marketId, shopifyMarkets?.language]) return { clientScriptLoaded } -} \ No newline at end of file +} diff --git a/src/hooks/useNosto404.tsx b/src/hooks/useNosto404.tsx new file mode 100644 index 0000000..41b7315 --- /dev/null +++ b/src/hooks/useNosto404.tsx @@ -0,0 +1,25 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type Nosto404Props = { placements?: string[] } + +/** + * You can personalise your cart and checkout pages by using the `useNosto404` hook. + * + * @group Hooks + */ +export function useNosto404(props?: Nosto404Props) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi(async api => { + const data = await api + .defaultSession() + .viewNotFound() + .setPlacements(props?.placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }) +} diff --git a/src/hooks/useNostoApi.ts b/src/hooks/useNostoApi.ts index a7f4ffa..7b28e41 100644 --- a/src/hooks/useNostoApi.ts +++ b/src/hooks/useNostoApi.ts @@ -3,11 +3,7 @@ import { useNostoContext } from "./useNostoContext" import { NostoClient } from "../types" import { useDeepCompareEffect } from "./useDeepCompareEffect" -export function useNostoApi( - cb: (api: NostoClient) => void, - deps?: DependencyList, - flags?: { deep?: boolean } -): void { +export function useNostoApi(cb: (api: NostoClient) => void, deps?: DependencyList, flags?: { deep?: boolean }): void { const { clientScriptLoaded } = useNostoContext() const useEffectFn = flags?.deep ? useDeepCompareEffect : useEffect diff --git a/src/hooks/useNostoCategory.tsx b/src/hooks/useNostoCategory.tsx new file mode 100644 index 0000000..a9a56c1 --- /dev/null +++ b/src/hooks/useNostoCategory.tsx @@ -0,0 +1,31 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type NostoCategoryProps = { + category: string + placements?: string[] +} + +/** + * You can personalise your category and collection pages by using the useNostoCategory hook. + * + * @group Hooks + */ +export function useNostoCategory({ category, placements }: NostoCategoryProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi( + async api => { + const data = await api + .defaultSession() + .viewCategory(category) + .setPlacements(placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }, + [category] + ) +} diff --git a/src/hooks/useNostoCheckout.tsx b/src/hooks/useNostoCheckout.tsx new file mode 100644 index 0000000..ce3f7b1 --- /dev/null +++ b/src/hooks/useNostoCheckout.tsx @@ -0,0 +1,25 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type NostoCheckoutProps = { placements?: string[] } + +/** + * You can personalise your cart and checkout pages by using the useNostoCheckout hook. + * + * @group Hooks + */ +export function useNostoCheckout(props?: NostoCheckoutProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi(async api => { + const data = await api + .defaultSession() + .viewCart() + .setPlacements(props?.placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }) +} diff --git a/src/hooks/useNostoContext.ts b/src/hooks/useNostoContext.ts index 06cd018..d49dc9f 100644 --- a/src/hooks/useNostoContext.ts +++ b/src/hooks/useNostoContext.ts @@ -7,6 +7,5 @@ import { NostoContext, NostoContextType } from "../context" * @group Essential Functions */ export function useNostoContext(): NostoContextType { - return useContext(NostoContext) - } - \ No newline at end of file + return useContext(NostoContext) +} diff --git a/src/hooks/useNostoHome.tsx b/src/hooks/useNostoHome.tsx new file mode 100644 index 0000000..086031b --- /dev/null +++ b/src/hooks/useNostoHome.tsx @@ -0,0 +1,25 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type NostoHomeProps = { placements?: string[] } + +/** + * You can personalise your home page by using the useNostoHome hook. + * + * @group Hooks + */ +export function useNostoHome(props?: NostoHomeProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi(async api => { + const data = await api + .defaultSession() + .viewFrontPage() + .setPlacements(props?.placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }) +} diff --git a/src/hooks/useNostoOrder.tsx b/src/hooks/useNostoOrder.tsx new file mode 100644 index 0000000..a85f001 --- /dev/null +++ b/src/hooks/useNostoOrder.tsx @@ -0,0 +1,34 @@ +import { snakeize } from "../utils/snakeize" +import { Order } from "../types" +import { useRenderCampaigns } from "./useRenderCampaigns" +import { useNostoApi } from "./useNostoApi" + +/** + * @group Hooks + */ +export type NostoOrderProps = { + order: Order + placements?: string[] +} + +/** + * You can personalise your order-confirmation/thank-you page by using the `useNostoOrder` hook. + * + * @group Hooks + */ +export function useNostoOrder({ order, placements }: NostoOrderProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi( + async api => { + const data = await api + .defaultSession() + .addOrder(snakeize(order)) + .setPlacements(placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }, + [order], + { deep: true } + ) +} diff --git a/src/hooks/useNostoOther.tsx b/src/hooks/useNostoOther.tsx new file mode 100644 index 0000000..b8c0fa1 --- /dev/null +++ b/src/hooks/useNostoOther.tsx @@ -0,0 +1,25 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type NostoOtherProps = { placements?: string[] } + +/** + * You can personalise your miscellaneous pages by using the useNostoOther hook. + * + * @group Hooks + */ +export function useNostoOther(props?: NostoOtherProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi(async api => { + const data = await api + .defaultSession() + .viewOther() + .setPlacements(props?.placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }) +} diff --git a/src/hooks/useNostoProduct.tsx b/src/hooks/useNostoProduct.tsx new file mode 100644 index 0000000..c121b9f --- /dev/null +++ b/src/hooks/useNostoProduct.tsx @@ -0,0 +1,33 @@ +import { Product } from "../types" +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Hooks + */ +export type NostoProductProps = { + product: string + tagging?: Product + placements?: string[] +} + +/** + * You can personalise your product pages by using the useNostoProduct hook. + * + * @group Hooks + */ +export function useNostoProduct({ product, tagging, placements }: NostoProductProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi( + async api => { + const data = await api + .defaultSession() + .viewProduct(tagging ?? product) + .setPlacements(placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }, + [product, tagging?.selected_sku_id] + ) +} diff --git a/src/hooks/useNostoSearch.tsx b/src/hooks/useNostoSearch.tsx new file mode 100644 index 0000000..7c9a882 --- /dev/null +++ b/src/hooks/useNostoSearch.tsx @@ -0,0 +1,31 @@ +import { useNostoApi } from "./useNostoApi" +import { useRenderCampaigns } from "./useRenderCampaigns" + +/** + * @group Components + */ +export type NostoSearchProps = { + query: string + placements?: string[] +} + +/** + * You can personalise your search pages by using the useNostoSearch hook. + * + * @group Hooks + */ +export function useNostoSearch({ query, placements }: NostoSearchProps) { + const { renderCampaigns } = useRenderCampaigns() + + useNostoApi( + async api => { + const data = await api + .defaultSession() + .viewSearch(query) + .setPlacements(placements || api.placements.getPlacements()) + .load() + renderCampaigns(data) + }, + [query] + ) +} diff --git a/src/hooks/useNostoSession.tsx b/src/hooks/useNostoSession.tsx new file mode 100644 index 0000000..ff5f39e --- /dev/null +++ b/src/hooks/useNostoSession.tsx @@ -0,0 +1,36 @@ +import { snakeize } from "../utils/snakeize" +import { Cart as CartSnakeCase, Customer as CustomerSnakeCase } from "../types" +import { ToCamelCase } from "../utils/types" +import { useNostoContext } from "./useNostoContext" +import { useDeepCompareEffect } from "./useDeepCompareEffect" + +type Cart = CartSnakeCase | ToCamelCase +type Customer = CustomerSnakeCase | ToCamelCase + +/** + * @group Hooks + */ +export type NostoSessionProps = { + cart?: Cart + customer?: Customer +} + +/** + * 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. + * + * @group Hooks + */ +export function useNostoSession({ cart, customer }: NostoSessionProps = {}) { + const { clientScriptLoaded } = useNostoContext() + + useDeepCompareEffect(() => { + const currentCart = cart ? snakeize(cart) : undefined + const currentCustomer = customer ? snakeize(customer) : undefined + + if (clientScriptLoaded) { + window.nostojs(api => { + api.defaultSession().setCart(currentCart).setCustomer(currentCustomer).viewOther().load({ skipPageViews: true }) + }) + } + }, [clientScriptLoaded, cart, customer]) +} diff --git a/src/hooks/useRenderCampaigns.tsx b/src/hooks/useRenderCampaigns.tsx index bcbe6df..e1b5115 100644 --- a/src/hooks/useRenderCampaigns.tsx +++ b/src/hooks/useRenderCampaigns.tsx @@ -7,13 +7,13 @@ import { RecommendationComponent } from "../context" type CampaignData = Pick // RecommendationComponent for client-side rendering: -function RecommendationComponentWrapper(props: { - recommendationComponent: RecommendationComponent, - nostoRecommendation: Recommendation }) { - +function RecommendationComponentWrapper(props: { + recommendationComponent: RecommendationComponent + nostoRecommendation: Recommendation +}) { return cloneElement(props.recommendationComponent, { // eslint-disable-next-line react/prop-types - nostoRecommendation: props.nostoRecommendation, + nostoRecommendation: props.nostoRecommendation }) } @@ -58,4 +58,4 @@ export function useRenderCampaigns() { } return { renderCampaigns } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 9ac6f17..e73f77e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,24 @@ export type { Cart, Customer, Product, Order, Recommendation } from "./types" -export * from "./components" export { type ScriptLoadOptions } from "./hooks/scriptLoader" export { NostoContext, type NostoContextType } from "./context" -export { useNostoContext } from "./hooks/useNostoContext" \ No newline at end of file +export { useNostoContext } from "./hooks/useNostoContext" +export * from "./components/Nosto404" +export * from "./components/NostoCategory" +export * from "./components/NostoCheckout" +export * from "./components/NostoHome" +export * from "./components/NostoOrder" +export * from "./components/NostoOther" +export * from "./components/NostoPlacement" +export * from "./components/NostoProduct" +export * from "./components/NostoProvider" +export * from "./components/NostoSearch" +export * from "./components/NostoSession" +export * from "./hooks/useNosto404" +export * from "./hooks/useNostoCategory" +export * from "./hooks/useNostoCheckout" +export * from "./hooks/useNostoHome" +export * from "./hooks/useNostoOrder" +export * from "./hooks/useNostoOther" +export * from "./hooks/useNostoProduct" +export * from "./hooks/useNostoSearch" +export * from "./hooks/useNostoSession" diff --git a/src/types.ts b/src/types.ts index 83300cc..ac4c62f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,6 @@ declare global { nostojs: { (callback: (api: NostoClient) => void): void q?: unknown[] - } } } @@ -77,10 +76,9 @@ export interface PushedProduct { variations: { [index: string]: PushedVariation } } -export interface PushedProductSKU extends NostoSku { } - -export interface PushedVariation extends NostoVariant { } +export interface PushedProductSKU extends NostoSku {} +export interface PushedVariation extends NostoVariant {} export interface NostoSku extends Sku { inventory_level?: number @@ -109,828 +107,872 @@ export interface Sku { } // copied from client script d.ts export -declare const eventTypes: readonly ["vp", "lp", "dp", "rp", "bp", "vc", "or", "is", "cp", "ec", "es", "gc", "src", "cpr", "pl", "cc", "con"] -declare type EventType = typeof eventTypes[number] +declare const eventTypes: readonly [ + "vp", + "lp", + "dp", + "rp", + "bp", + "vc", + "or", + "is", + "cp", + "ec", + "es", + "gc", + "src", + "cpr", + "pl", + "cc", + "con" +] +declare type EventType = (typeof eventTypes)[number] /** * @group Types */ export interface CartItem { - name: string - price_currency_code: string - product_id: string - quantity: number - sku_id?: string - unit_price: number + name: string + price_currency_code: string + product_id: string + quantity: number + sku_id?: string + unit_price: number } /** * @group Types */ export interface ConversionItem { - name: string - price_currency_code: string - product_id: string - quantity?: number - sku_id?: string - unit_price?: number + name: string + price_currency_code: string + product_id: string + quantity?: number + sku_id?: string + unit_price?: number } /** * @group Types */ export interface CustomerAffinityResponse { - discount: number - top_brands: CustomerAffinityResponseItem[] - top_categories: CustomerAffinityResponseItem[] - top_product_types: CustomerAffinityResponseItem[] - top_skus: { - [index: string]: CustomerAffinityResponseItem[] - } + discount: number + top_brands: CustomerAffinityResponseItem[] + top_categories: CustomerAffinityResponseItem[] + top_product_types: CustomerAffinityResponseItem[] + top_skus: { + [index: string]: CustomerAffinityResponseItem[] + } } /** * @group Types */ export interface CustomerAffinityResponseItem { - name: string - score: number + name: string + score: number } /** * @group Types */ export interface OrderCustomer { - country: string - email?: string - first_name?: string - last_name?: string - newsletter: string - order_number: string - phone: string - post_code: string - type: string + country: string + email?: string + first_name?: string + last_name?: string + newsletter: string + order_number: string + phone: string + post_code: string + type: string } /** * @group Types */ export interface Customer { - customer_reference?: string - email: string - first_name: string - hcid?: string - last_name: string - newsletter?: boolean - order_number?: string - source?: string - source_id?: string - type?: string + customer_reference?: string + email: string + first_name: string + hcid?: string + last_name: string + newsletter?: boolean + order_number?: string + source?: string + source_id?: string + type?: string } /** * @group Types */ export interface Order { - created_at?: Date - external_order_ref: string - info?: OrderCustomer - items: ConversionItem[] - order_status?: string - order_status_label?: string - payment_provider: string + created_at?: Date + external_order_ref: string + info?: OrderCustomer + items: ConversionItem[] + order_status?: string + order_status_label?: string + payment_provider: string } /** * @group Types */ -export type PageType = "front" | "category" | "product" | "cart" | "search" | "notfound" | "order" | "other" | "checkout" +export type PageType = + | "front" + | "category" + | "product" + | "cart" + | "search" + | "notfound" + | "order" + | "other" + | "checkout" /** * @group Types */ -export type RenderMode = "HTML" | "SIMPLE" | "JSON_170x170" | "JSON_100_X_100" | "JSON_90x70" | "JSON_50x50" | "JSON_30x30" | "JSON_100x140" | "JSON_200x200" | "JSON_400x400" | "JSON_750x750" | "JSON_10_MAX_SQUARE" | "JSON_200x200_SQUARE" | "JSON_400x400_SQUARE" | "JSON_750x750_SQUARE" | "JSON_ORIGINAL" | "VERSION_SOURCE" +export type RenderMode = + | "HTML" + | "SIMPLE" + | "JSON_170x170" + | "JSON_100_X_100" + | "JSON_90x70" + | "JSON_50x50" + | "JSON_30x30" + | "JSON_100x140" + | "JSON_200x200" + | "JSON_400x400" + | "JSON_750x750" + | "JSON_10_MAX_SQUARE" + | "JSON_200x200_SQUARE" + | "JSON_400x400_SQUARE" + | "JSON_750x750_SQUARE" + | "JSON_ORIGINAL" + | "VERSION_SOURCE" /** * @group Types */ interface PluginMetadata { - mainModule?: string - cmpModule?: string - msiModule?: string + mainModule?: string + cmpModule?: string + msiModule?: string } /** * @group Types */ export interface Cart { - hcid?: string - items: CartItem[] + hcid?: string + items: CartItem[] } /** * @group Types */ export type Product = { - product_id: string - selected_sku_id?: string + product_id: string + selected_sku_id?: string } /** * @group Types */ export interface Data { - cart: Cart | undefined - customer: Customer | undefined - variation: string | undefined - restoreLink: string | undefined - products: ProductType[] - order: Order | undefined - searchTerms: string[] | undefined - categories: string[] | undefined - categoryIds: string[] | undefined - parentCategoryIds: string[] | undefined - tags: string[] | undefined - customFields: Record | undefined - elements: string[] | undefined - pageType: PageType | undefined - sortOrder: string | undefined - pluginVersion: PluginMetadata | undefined + cart: Cart | undefined + customer: Customer | undefined + variation: string | undefined + restoreLink: string | undefined + products: ProductType[] + order: Order | undefined + searchTerms: string[] | undefined + categories: string[] | undefined + categoryIds: string[] | undefined + parentCategoryIds: string[] | undefined + tags: string[] | undefined + customFields: Record | undefined + elements: string[] | undefined + pageType: PageType | undefined + sortOrder: string | undefined + pluginVersion: PluginMetadata | undefined } /** * @group Types */ export interface RecommendationRequestFlags { - skipPageViews?: boolean - trackEvents?: boolean - skipEvents?: boolean - reloadCart?: boolean + skipPageViews?: boolean + trackEvents?: boolean + skipEvents?: boolean + reloadCart?: boolean } /** * @group Types */ export interface Session { - /** - * Sets the information about the user's current shopping cart. It the user - * does not have any items in his shopping cart, you can pass null. - * Passing null will nullify the user's shopping cart on Nosto's - * end. You must also pass in the shopping cart content in it's entirety as - * partial content are not supported. - * - * @example - * nostojs(api => api - * .defaultSession() - * .setCart({ - * items: [ - * product_id: "101", - * sku_id: "101-S", - * name: "Shoe", - * unit_price: 34.99 - * price_currency_code: "EUR" - * ] - * }) - * .viewCart() - * .setPlacements(["free-shipper"]) - * .update() - * .then(data => console.log(data))) - * - * @public - * @param {Cart|undefined} cart the details of the user's shopping cart contents - * @returns {Session} the current session - */ - setCart(cart: Cart | undefined): Session - /** - * Sets the information about the currently logged in customer. If the current - * customer is not provided, you will not be able to leverage features such as - * triggered emails. While it is recommended to always provide the details of - * the currently logged in customer, it may be omitted if there are concerns - * about privacy or compliance. - * - * @example - * nostojs(api => api - * .defaultSession() - * .setCustomer({ - * first_name: "Mridang", - * last_name: "Agarwalla", - * email: "mridang@nosto.com", - * newsletter: false, - * customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003" - * }) - * .viewCart() - * .setPlacements(["free-shipper"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {Customer} customer the details of the currently logged in customer - * @returns {Session} the current session - */ - setCustomer(customer: Customer | undefined): Session - /** - * Sets the current variation identifier for the session. A variation identifier - * identifies the current currency (or the current customer group). If your site - * uses multi-currency, you must provide the ISO code current currency being viewed. - * - * @example - * nostojs(api => api - * .defaultSession() - * .setVariation("GBP") - * .viewCart() - * .setPlacements(["free-shipper"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {String} variation the case-sensitive identifier of the current variation - * @returns {Session} the current session - */ - setVariation(variation: string): Session - /** - * Sets the restore link for the current session. Restore links can be leveraged - * in email campaigns. Restore links allow the the user to restore the cart - * contents in a single click. - *

- * Read more about - * {@link https://help.nosto.com/en/articles/664692|how to leverage the restore cart link} - * - * @example - * nostojs(api => api - * .defaultSession() - * .setRestoreLink("https://jeans.com/session/restore?sid=6bdb69d5-ed15-4d92") - * .viewCart() - * .setPlacements(["free-shipper"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {String} restoreLink the secure URL to restore the user's current session - * @returns {Session} the current session - */ - setRestoreLink(restoreLink: string): Session - /** - * Sets the response type to HTML or JSON_ORIGINAL. This denotes the preferred - * response type of the recommendation result. - * If you would like to access the raw recommendation data in JSON form, specify - * JSON. When you specify JSON, you will need to template the result yourself. - * If you require a more simplified approach, specify HTML. When you specify - * HTML, you get back HTML blobs, that you may simply inject into - * you placements. - * - * @example - * nostojs(api => api - * .defaultSession() - * .setResponseMode("HTML") - * .viewCart() - * .setPlacements(["free-shipper"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {String} mode the response mode for the recommendation data - * @returns {Session} the current session - */ - setResponseMode(mode: RenderMode): Session - /** - * Create a new action for a front page. This should be used when the user - * visits the home page. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewFrontPage() - * .setPlacements(["best-seller"]) - * .load() - * .then(data => console.log(data))) - * - * - * @public - * @returns {Action} the action instance to load content or track events. - */ - viewFrontPage(): Action - /** - * Create a new action for a cart page. This should be used on all cart and - * checkout pages. If your site has a multi-step checkout, it is recommended - * that you send this event on each checkout page. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewCart() - * .setPlacements(["free-shipper"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @returns {Action} the action instance to load content or track events. - */ - viewCart(): Action - /** - * Create a new action for a not found page. This should be used only on 404 - * pages. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewNotFound() - * .setPlacements(["best-seller"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @returns {Action} the action instance to load content or track events. - */ - viewNotFound(): Action - /** - * Create a new action for a product page. This must be used only when a - * product is being viewed. In case a specific SKU of the product is being viewed, use viewProductSku instead. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewProduct("101") - * .setCategories(["/men/trousers"]) - * .setRef("123", "example_reco_id") - * .setPlacements(["cross-seller"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param product - * @returns {Action} the action instance to load content or track events. - */ - viewProduct(product: string | Product): Action - /** - * Create a new action for a product page when a specific SKU has been chosen. This must be used only when a - * product and specific SKU is being viewed. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewProductSku("101", "101-sku-1") - * .setCategories(["/men/trousers"]) - * .setRef("123", "example_reco_id") - * .setPlacements(["cross-seller"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param productId - * @param skuId - * @returns {Action} the action instance to load content or track events. - */ - viewProductSku(productId: string, skuId: string): Action - /** - * Create a new action for a category page. This should be used on all - * category, collection of brand pages. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewCategory("/men/shoes") - * .setPlacements(["category123"]) - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {Array} categories - * @returns {Action} the action instance to load content or track events. - */ - viewCategory(...categories: string[]): Action - /** - * Create a new action for a tag page. This should be used only on tag pages. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * Note: tags are not case-sensitive. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewTag("colourful") - * .load() - * .then(data => console.log(data))) - * - * @public - * @deprecated as this is an advanced action with a limited a use case - * @param {Array} tags the set of the tags being viewed. - * @returns {Action} the action instance to load content or track events. - */ - viewTag(...tags: string[]): Action - /** - * Create a new action with custom fields. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * Note: tags are not case-sensitive. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewCustomField({material: "cotton"}) - * .load() - * .then(data => console.log(data))) - * - * @public - * @deprecated as this is an advanced action with a limited a use case - * @param {Object} customFields custom fields being viewed. - * @returns {Action} the action instance to load content or track events. - */ - viewCustomField(customFields: Record): Action - /** - * Create a new action for a search page. This should be used only - * on search pages. A search page action requires you to pass the search - * term. For example, if the user search for "black shoes", you must pass - * in "black shoes" and not an encoded version such as "black+shoes". - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * Search terms are not case-sensitive. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewSearch("black shoes") - * .load() - * .then(data => console.log(data))) - * - * @public - * @param {Array.} searchTerms the non-encoded search terms - * @returns {Action} the action instance to load content or track events. - */ - viewSearch(...searchTerms: string[]): Action - /** - * Create a new action for a general page. This should be used only on - * pages that don't have a corresponding action. For example, if the user - * is viewing a page such as a "Contact Us" page, you should use the viewOther - * action. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - * - * @example - * nostojs(api => api - * .defaultSession() - * .viewOther() - * .load() - * .then(data => console.log(data))) - * - * @public - * @returns {Action} the action instance to load content or track events. - */ - viewOther(): Action - /** - * Create a new action for an order page. This should only be used on order - * confirmation / thank you pages. - *

- * You do not need to specify the page-type explicitly as it is inferred - * from the action. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @example - * nostojs(api => { - * api.defaultSession() - * .addOrder({ - * external_order_ref: "145000006", - * info: { - * order_number: "195", - * email: "mridang@nosto.com", - * first_name: "Mridang", - * last_name: "Agarwalla", - * type: "order", - * newsletter: true - * }, - * items: [{ - * product_id: "406", - * sku_id: "243", - * name: "Linen Blazer (White, S)", - * quantity: 1, - * unit_price: 455, - * price_currency_code: "EUR" - * }] - * }) - * .setPlacements(["order-related"]) - * .load() - * .then(data => { - * console.log(data.recommendations) - * }) - * }) - * @public - * @param {Order} order the information about the order that was placed - * @returns {Action} the action instance to load content or track events. - */ - addOrder(order: Order): Action - /** - * Creates an action to report that product was added to the shopping cart, - * e.g. from the recommendation slot with "Add to cart" button. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * - * @example - * nostojs(api => api - * .defaultSession() - * .reportAddToCart("123", "reco-slot-1") - * .load() - * .then(data => console.log(data))) - * - * @public - * @param product - * @param element - * @returns {Action} the action instance to load content or track events. - */ - reportAddToCart(product: string, element: string): Action - /** - * @example - * nostojs(api => api - * .defaultSession() - * .recordAttribution("vp", "12345678", "123456") - * .done() - * .then(data => console.log(data)) - * @param { EventType } type - * @param { String } target - * @param { String | undefined } [ref] - * @param { String | undefined } [refSrc] - * @return { Object } - * - */ - recordAttribution(type: EventType, target: string, ref: string, refSrc: string): object + /** + * Sets the information about the user's current shopping cart. It the user + * does not have any items in his shopping cart, you can pass null. + * Passing null will nullify the user's shopping cart on Nosto's + * end. You must also pass in the shopping cart content in it's entirety as + * partial content are not supported. + * + * @example + * nostojs(api => api + * .defaultSession() + * .setCart({ + * items: [ + * product_id: "101", + * sku_id: "101-S", + * name: "Shoe", + * unit_price: 34.99 + * price_currency_code: "EUR" + * ] + * }) + * .viewCart() + * .setPlacements(["free-shipper"]) + * .update() + * .then(data => console.log(data))) + * + * @public + * @param {Cart|undefined} cart the details of the user's shopping cart contents + * @returns {Session} the current session + */ + setCart(cart: Cart | undefined): Session + /** + * Sets the information about the currently logged in customer. If the current + * customer is not provided, you will not be able to leverage features such as + * triggered emails. While it is recommended to always provide the details of + * the currently logged in customer, it may be omitted if there are concerns + * about privacy or compliance. + * + * @example + * nostojs(api => api + * .defaultSession() + * .setCustomer({ + * first_name: "Mridang", + * last_name: "Agarwalla", + * email: "mridang@nosto.com", + * newsletter: false, + * customer_reference: "5e3d4a9c-cf58-11ea-87d0-0242ac130003" + * }) + * .viewCart() + * .setPlacements(["free-shipper"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {Customer} customer the details of the currently logged in customer + * @returns {Session} the current session + */ + setCustomer(customer: Customer | undefined): Session + /** + * Sets the current variation identifier for the session. A variation identifier + * identifies the current currency (or the current customer group). If your site + * uses multi-currency, you must provide the ISO code current currency being viewed. + * + * @example + * nostojs(api => api + * .defaultSession() + * .setVariation("GBP") + * .viewCart() + * .setPlacements(["free-shipper"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {String} variation the case-sensitive identifier of the current variation + * @returns {Session} the current session + */ + setVariation(variation: string): Session + /** + * Sets the restore link for the current session. Restore links can be leveraged + * in email campaigns. Restore links allow the the user to restore the cart + * contents in a single click. + *

+ * Read more about + * {@link https://help.nosto.com/en/articles/664692|how to leverage the restore cart link} + * + * @example + * nostojs(api => api + * .defaultSession() + * .setRestoreLink("https://jeans.com/session/restore?sid=6bdb69d5-ed15-4d92") + * .viewCart() + * .setPlacements(["free-shipper"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {String} restoreLink the secure URL to restore the user's current session + * @returns {Session} the current session + */ + setRestoreLink(restoreLink: string): Session + /** + * Sets the response type to HTML or JSON_ORIGINAL. This denotes the preferred + * response type of the recommendation result. + * If you would like to access the raw recommendation data in JSON form, specify + * JSON. When you specify JSON, you will need to template the result yourself. + * If you require a more simplified approach, specify HTML. When you specify + * HTML, you get back HTML blobs, that you may simply inject into + * you placements. + * + * @example + * nostojs(api => api + * .defaultSession() + * .setResponseMode("HTML") + * .viewCart() + * .setPlacements(["free-shipper"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {String} mode the response mode for the recommendation data + * @returns {Session} the current session + */ + setResponseMode(mode: RenderMode): Session + /** + * Create a new action for a front page. This should be used when the user + * visits the home page. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewFrontPage() + * .setPlacements(["best-seller"]) + * .load() + * .then(data => console.log(data))) + * + * + * @public + * @returns {Action} the action instance to load content or track events. + */ + viewFrontPage(): Action + /** + * Create a new action for a cart page. This should be used on all cart and + * checkout pages. If your site has a multi-step checkout, it is recommended + * that you send this event on each checkout page. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewCart() + * .setPlacements(["free-shipper"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @returns {Action} the action instance to load content or track events. + */ + viewCart(): Action + /** + * Create a new action for a not found page. This should be used only on 404 + * pages. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewNotFound() + * .setPlacements(["best-seller"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @returns {Action} the action instance to load content or track events. + */ + viewNotFound(): Action + /** + * Create a new action for a product page. This must be used only when a + * product is being viewed. In case a specific SKU of the product is being viewed, use viewProductSku instead. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewProduct("101") + * .setCategories(["/men/trousers"]) + * .setRef("123", "example_reco_id") + * .setPlacements(["cross-seller"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param product + * @returns {Action} the action instance to load content or track events. + */ + viewProduct(product: string | Product): Action + /** + * Create a new action for a product page when a specific SKU has been chosen. This must be used only when a + * product and specific SKU is being viewed. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewProductSku("101", "101-sku-1") + * .setCategories(["/men/trousers"]) + * .setRef("123", "example_reco_id") + * .setPlacements(["cross-seller"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param productId + * @param skuId + * @returns {Action} the action instance to load content or track events. + */ + viewProductSku(productId: string, skuId: string): Action + /** + * Create a new action for a category page. This should be used on all + * category, collection of brand pages. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewCategory("/men/shoes") + * .setPlacements(["category123"]) + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {Array} categories + * @returns {Action} the action instance to load content or track events. + */ + viewCategory(...categories: string[]): Action + /** + * Create a new action for a tag page. This should be used only on tag pages. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * Note: tags are not case-sensitive. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewTag("colourful") + * .load() + * .then(data => console.log(data))) + * + * @public + * @deprecated as this is an advanced action with a limited a use case + * @param {Array} tags the set of the tags being viewed. + * @returns {Action} the action instance to load content or track events. + */ + viewTag(...tags: string[]): Action + /** + * Create a new action with custom fields. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * Note: tags are not case-sensitive. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewCustomField({material: "cotton"}) + * .load() + * .then(data => console.log(data))) + * + * @public + * @deprecated as this is an advanced action with a limited a use case + * @param {Object} customFields custom fields being viewed. + * @returns {Action} the action instance to load content or track events. + */ + viewCustomField(customFields: Record): Action + /** + * Create a new action for a search page. This should be used only + * on search pages. A search page action requires you to pass the search + * term. For example, if the user search for "black shoes", you must pass + * in "black shoes" and not an encoded version such as "black+shoes". + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * Search terms are not case-sensitive. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewSearch("black shoes") + * .load() + * .then(data => console.log(data))) + * + * @public + * @param {Array.} searchTerms the non-encoded search terms + * @returns {Action} the action instance to load content or track events. + */ + viewSearch(...searchTerms: string[]): Action + /** + * Create a new action for a general page. This should be used only on + * pages that don't have a corresponding action. For example, if the user + * is viewing a page such as a "Contact Us" page, you should use the viewOther + * action. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + * + * @example + * nostojs(api => api + * .defaultSession() + * .viewOther() + * .load() + * .then(data => console.log(data))) + * + * @public + * @returns {Action} the action instance to load content or track events. + */ + viewOther(): Action + /** + * Create a new action for an order page. This should only be used on order + * confirmation / thank you pages. + *

+ * You do not need to specify the page-type explicitly as it is inferred + * from the action. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @example + * nostojs(api => { + * api.defaultSession() + * .addOrder({ + * external_order_ref: "145000006", + * info: { + * order_number: "195", + * email: "mridang@nosto.com", + * first_name: "Mridang", + * last_name: "Agarwalla", + * type: "order", + * newsletter: true + * }, + * items: [{ + * product_id: "406", + * sku_id: "243", + * name: "Linen Blazer (White, S)", + * quantity: 1, + * unit_price: 455, + * price_currency_code: "EUR" + * }] + * }) + * .setPlacements(["order-related"]) + * .load() + * .then(data => { + * console.log(data.recommendations) + * }) + * }) + * @public + * @param {Order} order the information about the order that was placed + * @returns {Action} the action instance to load content or track events. + */ + addOrder(order: Order): Action + /** + * Creates an action to report that product was added to the shopping cart, + * e.g. from the recommendation slot with "Add to cart" button. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * + * @example + * nostojs(api => api + * .defaultSession() + * .reportAddToCart("123", "reco-slot-1") + * .load() + * .then(data => console.log(data))) + * + * @public + * @param product + * @param element + * @returns {Action} the action instance to load content or track events. + */ + reportAddToCart(product: string, element: string): Action + /** + * @example + * nostojs(api => api + * .defaultSession() + * .recordAttribution("vp", "12345678", "123456") + * .done() + * .then(data => console.log(data)) + * @param { EventType } type + * @param { String } target + * @param { String | undefined } [ref] + * @param { String | undefined } [refSrc] + * @return { Object } + * + */ + recordAttribution(type: EventType, target: string, ref: string, refSrc: string): object } /** * @group Types */ export interface Action { - /** - * Handles click attribution for product recommendations. - * This can be called when reporting a product view - * to signal that the view is a result of a click on a recommendation. - * - * @public - * @param {String} productId currently viewed product's product id - * @param {String} reference value of result_id from the recommendation response that was clicked - * @return {Action} - */ - setRef(productId: string, reference: string): Action - /** - * Allows you to provide an additional recommender hint that a product is being - * viewed. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param {String} product the identifier of the product being viewed - * @return {Action} the instance of the action - */ - setProduct(product: string | Product): Action - /** - * @deprecated - * @param {Array} products - * @return {Action} - */ - setProducts(products: (string | Product)[]): Action - /** - * Sets the information about the user's current shopping cart. It the user - * does not have any items in his shopping cart, you can pass null. - * Passing null will nullify the user's shopping cart on Nosto's - * end. You must also pass in the shopping cart content in it's entirety as - * partial content are not supported. - *

- * It is not recommended to pass the current cart contents to an action but - * instead use the the session - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @see {@link Session#setCart} - * @return {Action} - */ - setCart(cart: Cart): Action - /** - * Sets the information about the currently logged in customer. If the current - * customer is not provided, you will not be able to leverage features such as - * triggered emails. While it is recommended to always provide the details of - * the currently logged in customer, it may be omitted if there are concerns - * about privacy or compliance. - *

- * It is not recommended to pass the current customer details to an action but - * instead use the the session - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @see {@link Session#setCustomer} - * @public - * @param {Customer} customer the details of the currently logged in customer - * @return {Action} - */ - setCustomer(customer: Customer): Action - /** - * @param {Order} order - * @return {Action} - */ - setOrder(order: Order): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param searchTerms - * @return {Action} - */ - setSearchTerms(searchTerms: string[]): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param {Array} categories - * @return {Action} - */ - setCategories(categories: string[]): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param {Array} categoryIds - * @return {Action} - */ - setCategoryIds(categoryIds: string[]): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param {Array} parentCategoryIds - * @return {Action} - */ - setParentCategoryIds(parentCategoryIds: string[]): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param tags - * @return {Action} - */ - setTags(tags: string[]): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param customFields - * @return {Action} - */ - setCustomFields(customFields: Record): Action - /** - * Sets the current variation identifier for the session. A variation identifier - * identifies the current currency (or the current customer group). If your site - * uses multi-currency, you must provide the ISO code current currency being viewed. - *

- * It is not recommended to pass the variation identifier to an action but - * instead use the the session. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @see {@link Session#setVariation} - * @public - * @param {String} variation the case-sensitive identifier of the current variation - * @return {Action} - */ - setVariation(variation: string): Action - /** - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @public - * @param {Array.} placements - * @return {Action} - */ - setPlacements(placements: string[]): Action - /** - * Sets the restore link for the current session. Restore links can be leveraged - * in email campaigns. Restore links allow the the user to restore the cart - * contents in a single click. - *

- * Read more about - * {@link https://help.nosto.com/en/articles/664692|how to leverage the restore cart link} - *

- * It is not recommended to pass the restore link to an action but instead use the the - * session. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @see {@link Session#setRestoreLink} - * @public - * @param {String} restoreLink the secure URL to restore the user's current session - * @return {Action} - */ - setRestoreLink(restoreLink: string): Action - /** - * Sets the identifier of the current page type to the current request. The different - * page types are product, front, search, cart, order, category, notfound and other. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - *

- * It is not recommended to pass the page type to an action but instead use the the - * session. - *

- * You must invoke [the load method]{@link Action#load} on the resultant - * action in order for the request to be made. - * - * @see {@link Session#viewFrontPage} for when a front or home page is being viewed - * @see {@link Session#viewCart} for when a cart or checkout page is being viewed - * @see {@link Session#viewNotFound} for when a not-found or 404 page is being viewed - * @see {@link Session#viewProduct} for when a product page is being viewed - * @see {@link Session#viewCategory} for when a category, collection or brand page is being viewed - * @see {@link Session#viewTag} for when a tag page is being viewed - * @see {@link Session#viewSearch} for when a search page is being viewed - * @see {@link Session#viewOther} for when a miscellaneous page is being viewed - * @public - */ - setPageType(pageType: PageType): Action - /** - * @public - * @return {Object} - */ - dumpData(): Data - update(): unknown - load(flags?: RecommendationRequestFlags): Promise + /** + * Handles click attribution for product recommendations. + * This can be called when reporting a product view + * to signal that the view is a result of a click on a recommendation. + * + * @public + * @param {String} productId currently viewed product's product id + * @param {String} reference value of result_id from the recommendation response that was clicked + * @return {Action} + */ + setRef(productId: string, reference: string): Action + /** + * Allows you to provide an additional recommender hint that a product is being + * viewed. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param {String} product the identifier of the product being viewed + * @return {Action} the instance of the action + */ + setProduct(product: string | Product): Action + /** + * @deprecated + * @param {Array} products + * @return {Action} + */ + setProducts(products: (string | Product)[]): Action + /** + * Sets the information about the user's current shopping cart. It the user + * does not have any items in his shopping cart, you can pass null. + * Passing null will nullify the user's shopping cart on Nosto's + * end. You must also pass in the shopping cart content in it's entirety as + * partial content are not supported. + *

+ * It is not recommended to pass the current cart contents to an action but + * instead use the the session + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @see {@link Session#setCart} + * @return {Action} + */ + setCart(cart: Cart): Action + /** + * Sets the information about the currently logged in customer. If the current + * customer is not provided, you will not be able to leverage features such as + * triggered emails. While it is recommended to always provide the details of + * the currently logged in customer, it may be omitted if there are concerns + * about privacy or compliance. + *

+ * It is not recommended to pass the current customer details to an action but + * instead use the the session + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @see {@link Session#setCustomer} + * @public + * @param {Customer} customer the details of the currently logged in customer + * @return {Action} + */ + setCustomer(customer: Customer): Action + /** + * @param {Order} order + * @return {Action} + */ + setOrder(order: Order): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param searchTerms + * @return {Action} + */ + setSearchTerms(searchTerms: string[]): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param {Array} categories + * @return {Action} + */ + setCategories(categories: string[]): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param {Array} categoryIds + * @return {Action} + */ + setCategoryIds(categoryIds: string[]): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param {Array} parentCategoryIds + * @return {Action} + */ + setParentCategoryIds(parentCategoryIds: string[]): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param tags + * @return {Action} + */ + setTags(tags: string[]): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param customFields + * @return {Action} + */ + setCustomFields(customFields: Record): Action + /** + * Sets the current variation identifier for the session. A variation identifier + * identifies the current currency (or the current customer group). If your site + * uses multi-currency, you must provide the ISO code current currency being viewed. + *

+ * It is not recommended to pass the variation identifier to an action but + * instead use the the session. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @see {@link Session#setVariation} + * @public + * @param {String} variation the case-sensitive identifier of the current variation + * @return {Action} + */ + setVariation(variation: string): Action + /** + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @public + * @param {Array.} placements + * @return {Action} + */ + setPlacements(placements: string[]): Action + /** + * Sets the restore link for the current session. Restore links can be leveraged + * in email campaigns. Restore links allow the the user to restore the cart + * contents in a single click. + *

+ * Read more about + * {@link https://help.nosto.com/en/articles/664692|how to leverage the restore cart link} + *

+ * It is not recommended to pass the restore link to an action but instead use the the + * session. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @see {@link Session#setRestoreLink} + * @public + * @param {String} restoreLink the secure URL to restore the user's current session + * @return {Action} + */ + setRestoreLink(restoreLink: string): Action + /** + * Sets the identifier of the current page type to the current request. The different + * page types are product, front, search, cart, order, category, notfound and other. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + *

+ * It is not recommended to pass the page type to an action but instead use the the + * session. + *

+ * You must invoke [the load method]{@link Action#load} on the resultant + * action in order for the request to be made. + * + * @see {@link Session#viewFrontPage} for when a front or home page is being viewed + * @see {@link Session#viewCart} for when a cart or checkout page is being viewed + * @see {@link Session#viewNotFound} for when a not-found or 404 page is being viewed + * @see {@link Session#viewProduct} for when a product page is being viewed + * @see {@link Session#viewCategory} for when a category, collection or brand page is being viewed + * @see {@link Session#viewTag} for when a tag page is being viewed + * @see {@link Session#viewSearch} for when a search page is being viewed + * @see {@link Session#viewOther} for when a miscellaneous page is being viewed + * @public + */ + setPageType(pageType: PageType): Action + /** + * @public + * @return {Object} + */ + dumpData(): Data + update(): unknown + load(flags?: RecommendationRequestFlags): Promise } export interface ActionResponse { + recommendations: Record + campaigns?: { recommendations: Record - campaigns?: { - recommendations: Record - content: Record - } - page_views: number - geo_location: string[] - affinities: CustomerAffinityResponse - cmpid: string + content: Record + } + page_views: number + geo_location: string[] + affinities: CustomerAffinityResponse + cmpid: string } diff --git a/src/utils/types.ts b/src/utils/types.ts index fcb5f83..08437fc 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -5,7 +5,8 @@ type SnakeToCamelCase = S extends `${infer T}_${infer U}` // Recursive type to apply the conversion to all keys in an object type export type ToCamelCase = T extends (infer U)[] ? ToCamelCase[] - : T extends Date ? T + : T extends Date + ? T : T extends object ? { [K in keyof T as SnakeToCamelCase]: ToCamelCase @@ -21,9 +22,10 @@ type CamelToSnakeCase = S extends `${infer T}${infer U}` // Recursive type to apply the conversion to all keys in an object type export type ToSnakeCase = T extends (infer U)[] ? ToSnakeCase[] - : T extends Date ? T + : T extends Date + ? T : T extends object ? { [K in keyof T as CamelToSnakeCase]: ToSnakeCase } - : T \ No newline at end of file + : T