UNPKG

@noaignite/react-centra-checkout

Version:

React components and helpers for Centra checkout api

347 lines (346 loc) 15.8 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_runtime = require("./_virtual/_rolldown/runtime.cjs"); const require_ApiClient = require("./ApiClient.cjs"); const require_internal_CentraEvents = require("./internal/CentraEvents.cjs"); let _noaignite_utils = require("@noaignite/utils"); let js_cookie = require("js-cookie"); js_cookie = require_runtime.__toESM(js_cookie, 1); let react = require("react"); let react_jsx_runtime = require("react/jsx-runtime"); //#region src/Context.tsx const defaultApiClient = require_ApiClient.ApiClient.default; const centraEvents = require_internal_CentraEvents.CentraEvents.default; const SELECTION_INITIAL_VALUE = { countries: [], languages: [], location: {}, paymentFields: {}, paymentMethods: [], selection: { address: {}, discounts: {}, items: [], totals: {} }, shippingMethods: [] }; const CentraHandlersContext = (0, react.createContext)(null); const CentraSelectionContext = (0, react.createContext)(null); /** * Perform `callback` when `Promise` contains a `CheckoutApi.SelectionResponse` * * @param promise - Promise that may contain `selection` response * @param callback - Callback that should be called with the `promise` if the results of the `promise` contains a `CheckoutApi.SelectionResponse` */ const onSelectionResponse = async (promise, callback) => { const results = await promise; if ((0, _noaignite_utils.isPlainObject)(results) && "selection" in results && Boolean(results.selection)) return callback(promise); return results; }; /** * React Context provider that is required to use the `useCentra` and `useCentraHandlers` hooks. * Provides state management and related handlers of the users current Centra selection via the Checkout API. * The handlers could be used for adding products to the selection and making purchases, for example. */ function CentraProvider(props) { const { apiClient: apiClientProp, apiUrl, children, disableInit = false, initialSelection, paymentFailedPage, paymentReturnPage, receiptPage, tokenExpires = 365, tokenName = "centra-checkout-token", tokenCookieOptions = null } = props; const apiClient = apiClientProp ?? defaultApiClient; const [selection, setSelection] = (0, react.useState)(initialSelection ?? SELECTION_INITIAL_VALUE); const centraCheckoutScript = "selection" in selection && selection.selection?.centraCheckoutScript; if (apiUrl) apiClient.baseUrl = apiUrl; if (initialSelection?.token) apiClient.headers.set("api-token", initialSelection.token); const selectionApiCall = (0, react.useCallback)(async (apiCall) => { window.CentraCheckout?.suspend(); const response = typeof apiCall === "function" ? await apiCall() : await apiCall; setSelection(response); window.CentraCheckout?.resume(); return response; }, []); const centraCheckoutCallback = (0, react.useCallback)(async (event) => { const response = await apiClient.request("PUT", `payment-fields`, event.detail); if ("selection" in response && response.selection) setSelection(response); window.CentraCheckout?.resume(event.detail.additionalFields?.suspendIgnore); centraEvents.dispatch("centra_checkout_callback", response); }, [apiClient]); const init = (0, react.useCallback)(async (selectionData) => { let response; const apiToken = js_cookie.default.get(tokenName); if (apiToken) apiClient.headers.set("api-token", apiToken); if (!selectionData) response = await apiClient.request("GET", "selection"); else response = selectionData; if ("selection" in response && response.selection) { setSelection(response); if (response.token && response.token !== apiToken) { apiClient.headers.set("api-token", response.token); js_cookie.default.set(tokenName, response.token, { expires: tokenExpires, ...tokenCookieOptions }); } } }, [ tokenName, apiClient, tokenExpires, tokenCookieOptions ]); const addItem = (0, react.useCallback)((item, quantity = 1) => onSelectionResponse(apiClient.request("POST", `items/${item}/quantity/${quantity}`), selectionApiCall), [apiClient, selectionApiCall]); const addBundleItem = (0, react.useCallback)((item, data) => onSelectionResponse(apiClient.request("POST", `items/bundles/${item}`, data), selectionApiCall), [apiClient, selectionApiCall]); const addGiftCertificate = (0, react.useCallback)((giftCertificate) => onSelectionResponse(apiClient.request("POST", `items/gift-certificates/${giftCertificate}`), selectionApiCall), [apiClient, selectionApiCall]); const addCustomGiftCertificate = (0, react.useCallback)((giftCertificate, amount) => onSelectionResponse(apiClient.request("POST", `items/gift-certificates/${giftCertificate}/amount/${amount}`), selectionApiCall), [apiClient, selectionApiCall]); const increaseCartItem = (0, react.useCallback)((line) => onSelectionResponse(apiClient.request("POST", `lines/${line}/quantity/1`), selectionApiCall), [apiClient, selectionApiCall]); const decreaseCartItem = (0, react.useCallback)((line) => onSelectionResponse(apiClient.request("DELETE", `lines/${line}/quantity/1`), selectionApiCall), [apiClient, selectionApiCall]); const removeCartItem = (0, react.useCallback)((line) => onSelectionResponse(apiClient.request("DELETE", `lines/${line}`), selectionApiCall), [apiClient, selectionApiCall]); const updateCartItemQuantity = (0, react.useCallback)((line, quantity) => onSelectionResponse(apiClient.request("PUT", `lines/${line}/quantity/${quantity}`), selectionApiCall), [apiClient, selectionApiCall]); const updateCartItemSize = (0, react.useCallback)((cartItem, item) => selectionApiCall(async () => { await apiClient.request("DELETE", `lines/${cartItem.line}`); return await apiClient.request("POST", `items/${item}/quantity/${cartItem.quantity}`); }), [apiClient, selectionApiCall]); const addVoucher = (0, react.useCallback)((voucher) => onSelectionResponse(apiClient.request("POST", "vouchers", { voucher }), selectionApiCall), [apiClient, selectionApiCall]); const removeVoucher = (0, react.useCallback)((voucher) => onSelectionResponse(apiClient.request("DELETE", `vouchers/${voucher}`), selectionApiCall), [apiClient, selectionApiCall]); const updateCountry = (0, react.useCallback)((country, data) => onSelectionResponse(apiClient.request("PUT", `countries/${country}`, data), selectionApiCall), [apiClient, selectionApiCall]); const updateLanguage = (0, react.useCallback)((language) => onSelectionResponse(apiClient.request("PUT", `languages/${language}`), selectionApiCall), [apiClient, selectionApiCall]); const updateShippingMethod = (0, react.useCallback)((shippingMethod) => onSelectionResponse(apiClient.request("PUT", `shipping-methods/${shippingMethod}`), selectionApiCall), [apiClient, selectionApiCall]); const updatePaymentMethod = (0, react.useCallback)((paymentMethod) => onSelectionResponse(apiClient.request("PUT", `payment-methods/${paymentMethod}`), selectionApiCall), [apiClient, selectionApiCall]); const updatePaymentFields = (0, react.useCallback)(async (data) => onSelectionResponse(apiClient.request("PUT", `payment-fields`, data), selectionApiCall), [apiClient, selectionApiCall]); const submitPayment = (0, react.useCallback)(async (data) => { const response = await apiClient.request("POST", "payment", { paymentReturnPage: typeof paymentReturnPage === "function" ? paymentReturnPage(selection) : paymentReturnPage, paymentFailedPage: typeof paymentFailedPage === "function" ? paymentFailedPage(selection) : paymentFailedPage, ...data }); if ("errors" in response) throw new Error(Object.entries(response.errors).map((key, value) => `${key}: ${value}`).join(",")); switch (response.action) { case "redirect": if (response.url) window.location.href = response.url; break; case "success": window.location.href = `${receiptPage}/${response.token}`; break; case "javascript": if (response.code) { const script = document.createElement("script"); const text = document.createTextNode(response.code); script.appendChild(text); document.body.appendChild(script); } break; default: return response; } return response; }, [ apiClient, paymentFailedPage, paymentReturnPage, receiptPage, selection ]); const addBackInStockSubscription = (0, react.useCallback)((data) => onSelectionResponse(apiClient.request("POST", "back-in-stock-subscription", data), selectionApiCall), [apiClient, selectionApiCall]); const addNewsletterSubscription = (0, react.useCallback)((data) => onSelectionResponse(apiClient.request("POST", "newsletter-subscription", data), selectionApiCall), [apiClient, selectionApiCall]); const loginCustomer = (0, react.useCallback)((email, password) => onSelectionResponse(apiClient.request("POST", `login/${email}`, { password }), selectionApiCall), [apiClient, selectionApiCall]); const logoutCustomer = (0, react.useCallback)(() => onSelectionResponse(apiClient.request("POST", `logout`), selectionApiCall), [apiClient, selectionApiCall]); const registerCustomer = (0, react.useCallback)((data) => onSelectionResponse(apiClient.request("POST", `register`, data), selectionApiCall), [apiClient, selectionApiCall]); const resetCustomerPassword = (0, react.useCallback)((i, id, newPassword) => onSelectionResponse(apiClient.request("POST", `password-reset`, { i, id, newPassword }), selectionApiCall), [apiClient, selectionApiCall]); /** Resets the selection. Useful if you need a fresh `api-token` (when a user exits a campaign site, for example). */ const resetSelection = (0, react.useCallback)(() => { apiClient.headers.delete("api-token"); js_cookie.default.remove(tokenName); return init(); }, [ apiClient.headers, init, tokenName ]); const sendCustomerResetPasswordEmail = (0, react.useCallback)((email, linkUri) => onSelectionResponse(apiClient.request("POST", `password-reset-email/${email}`, { linkUri }), selectionApiCall), [apiClient, selectionApiCall]); const updateCustomer = (0, react.useCallback)((data) => onSelectionResponse(apiClient.request("PUT", `customer/update`, data), selectionApiCall), [apiClient, selectionApiCall]); const updateCustomerAddress = (0, react.useCallback)((data) => onSelectionResponse(apiClient.request("PUT", `address`, data), selectionApiCall), [apiClient, selectionApiCall]); const updateCustomerEmail = (0, react.useCallback)((newEmail) => onSelectionResponse(apiClient.request("PUT", `email`, { newEmail }), selectionApiCall), [apiClient, selectionApiCall]); const updateCustomerPassword = (0, react.useCallback)((password, newPassword) => onSelectionResponse(apiClient.request("PUT", `password`, { password, newPassword }), selectionApiCall), [apiClient, selectionApiCall]); const updateCampaignSite = (0, react.useCallback)((uri) => onSelectionResponse(apiClient.request("PUT", `campaign-site`, { uri }), selectionApiCall), [apiClient, selectionApiCall]); (0, react.useEffect)(() => { if (!disableInit) init(); document.addEventListener("centra_checkout_callback", centraCheckoutCallback); return () => { document.removeEventListener("centra_checkout_callback", centraCheckoutCallback); }; }, [ disableInit, init, centraCheckoutCallback ]); (0, react.useEffect)(() => { let script = null; if (centraCheckoutScript) { script = document.createElement("script"); script.innerHTML = centraCheckoutScript; script.id = "centra-checkout-script"; document.head.appendChild(script); } return () => { if (script) document.head.removeChild(script); }; }, [centraCheckoutScript]); const centraHandlersContext = (0, react.useMemo)(() => ({ addItem, addBundleItem, addGiftCertificate, addBackInStockSubscription, addCustomGiftCertificate, addNewsletterSubscription, addVoucher, decreaseCartItem, increaseCartItem, init, loginCustomer, logoutCustomer, registerCustomer, removeCartItem, removeVoucher, resetCustomerPassword, resetSelection, sendCustomerResetPasswordEmail, submitPayment, updateCartItemQuantity, updateCartItemSize, updateCountry, updateCustomer, updateCustomerAddress, updateCustomerEmail, updateCustomerPassword, updateLanguage, updatePaymentFields, updatePaymentMethod, updateShippingMethod, updateCampaignSite }), [ addItem, addBundleItem, addGiftCertificate, addBackInStockSubscription, addCustomGiftCertificate, addNewsletterSubscription, addVoucher, decreaseCartItem, increaseCartItem, init, loginCustomer, logoutCustomer, registerCustomer, removeCartItem, removeVoucher, resetCustomerPassword, resetSelection, sendCustomerResetPasswordEmail, submitPayment, updateCartItemQuantity, updateCartItemSize, updateCountry, updateCustomer, updateCustomerAddress, updateCustomerEmail, updateCustomerPassword, updateLanguage, updatePaymentFields, updatePaymentMethod, updateShippingMethod, updateCampaignSite ]); const centraContext = (0, react.useMemo)(() => ({ ...selection, apiUrl, apiClient }), [ selection, apiUrl, apiClient ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CentraHandlersContext.Provider, { value: centraHandlersContext, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CentraSelectionContext.Provider, { value: centraContext, children }) }); } /** * This hook returns the centra selection */ function useCentraSelection() { const context = (0, react.useContext)(CentraSelectionContext); if (context === null) throw new Error([ "@noaignite/react-centra-checkout: `useCentraSelection` may only be", "used inside the `CentraProvider` react tree, please declare it at a", "higher level." ].join(" ")); return context; } /** * This hook returns update handlers */ function useCentraHandlers() { const context = (0, react.useContext)(CentraHandlersContext); if (context === null) throw new Error([ "@noaignite/react-centra-checkout: `useCentraHandlers` may only be", "used inside the `CentraProvider` react tree, please declare it at a", "higher level." ].join(" ")); return context; } /** * Returns the latest order receipt given a selection token */ function useCentraReceipt(token) { const [result, setResult] = (0, react.useState)({}); const { apiUrl } = useCentraSelection(); if (!token) console.error("@noaignite/react-centra-checkout: useReceipt requires a selection id"); (0, react.useEffect)(() => { const tempApiClient = new require_ApiClient.ApiClient(apiUrl); tempApiClient.headers.set("api-token", token); tempApiClient.request("GET", "receipt").then((response) => { setResult(response); }); }, [apiUrl, token]); return result; } /** * Returns the latest orders for the currently logged in user * @param from - Display orders from this index. Defaults to 0. * @param size - Display this many orders. Defaults lists all orders. */ function useCentraOrders(from, size, apiClient = defaultApiClient) { const [result, setResult] = (0, react.useState)({}); (0, react.useEffect)(() => { apiClient.request("POST", "orders", { ...from && { from }, ...size && { size } }).then((response) => { setResult(response); }); }, [ apiClient, from, size ]); return result; } function useCentraEvents() { return require_internal_CentraEvents.CentraEvents.default; } //#endregion exports.CentraHandlersContext = CentraHandlersContext; exports.CentraProvider = CentraProvider; exports.CentraSelectionContext = CentraSelectionContext; exports.SELECTION_INITIAL_VALUE = SELECTION_INITIAL_VALUE; exports.useCentraEvents = useCentraEvents; exports.useCentraHandlers = useCentraHandlers; exports.useCentraOrders = useCentraOrders; exports.useCentraReceipt = useCentraReceipt; exports.useCentraSelection = useCentraSelection; //# sourceMappingURL=Context.cjs.map