UNPKG

@noaignite/react-centra-checkout

Version:

React components and helpers for Centra checkout api

482 lines 15.5 kB
// src/Context.tsx import { isPlainObject } from "@noaignite/utils"; import cookies from "js-cookie"; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { ApiClient } from "./ApiClient.js"; import { CentraEvents } from "./internal/CentraEvents.js"; import { jsx } from "react/jsx-runtime"; var defaultApiClient = ApiClient.default; var centraEvents = CentraEvents.default; var SELECTION_INITIAL_VALUE = { countries: [], languages: [], location: {}, paymentFields: {}, paymentMethods: [], selection: { address: {}, discounts: {}, items: [], totals: {} }, shippingMethods: [] }; var CentraHandlersContext = createContext(null); var CentraSelectionContext = createContext(null); var onSelectionResponse = async (promise, callback) => { const results = await promise; if (isPlainObject(results) && "selection" in results && Boolean(results.selection)) { return callback( // We have to cast it to `TPromise`, because there's not a way to create async type predicates without already passing the `Promise` as argument to a function. promise ); } return results; }; 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] = 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 = useCallback( async (apiCall) => { window.CentraCheckout?.suspend(); const response = typeof apiCall === "function" ? await apiCall() : await apiCall; setSelection(response); window.CentraCheckout?.resume(); return response; }, [] ); const centraCheckoutCallback = 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 = useCallback( async (selectionData) => { let response; const apiToken = cookies.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); cookies.set(tokenName, response.token, { expires: tokenExpires, ...tokenCookieOptions }); } } }, [tokenName, apiClient, tokenExpires, tokenCookieOptions] ); const addItem = useCallback( (item, quantity = 1) => onSelectionResponse( apiClient.request("POST", `items/${item}/quantity/${quantity}`), selectionApiCall ), [apiClient, selectionApiCall] ); const addBundleItem = useCallback( (item, data) => onSelectionResponse( apiClient.request("POST", `items/bundles/${item}`, data), selectionApiCall ), [apiClient, selectionApiCall] ); const addGiftCertificate = useCallback( (giftCertificate) => onSelectionResponse( apiClient.request("POST", `items/gift-certificates/${giftCertificate}`), selectionApiCall ), [apiClient, selectionApiCall] ); const addCustomGiftCertificate = useCallback( (giftCertificate, amount) => onSelectionResponse( apiClient.request("POST", `items/gift-certificates/${giftCertificate}/amount/${amount}`), selectionApiCall ), [apiClient, selectionApiCall] ); const increaseCartItem = useCallback( (line) => onSelectionResponse(apiClient.request("POST", `lines/${line}/quantity/1`), selectionApiCall), [apiClient, selectionApiCall] ); const decreaseCartItem = useCallback( (line) => onSelectionResponse( apiClient.request("DELETE", `lines/${line}/quantity/1`), selectionApiCall ), [apiClient, selectionApiCall] ); const removeCartItem = useCallback( (line) => onSelectionResponse(apiClient.request("DELETE", `lines/${line}`), selectionApiCall), [apiClient, selectionApiCall] ); const updateCartItemQuantity = useCallback( (line, quantity) => onSelectionResponse( apiClient.request("PUT", `lines/${line}/quantity/${quantity}`), selectionApiCall ), [apiClient, selectionApiCall] ); const updateCartItemSize = useCallback( (cartItem, item) => selectionApiCall(async () => { await apiClient.request("DELETE", `lines/${cartItem.line}`); const response = await apiClient.request( "POST", `items/${item}/quantity/${cartItem.quantity}` ); return response; }), [apiClient, selectionApiCall] ); const addVoucher = useCallback( (voucher) => onSelectionResponse(apiClient.request("POST", "vouchers", { voucher }), selectionApiCall), [apiClient, selectionApiCall] ); const removeVoucher = useCallback( (voucher) => onSelectionResponse(apiClient.request("DELETE", `vouchers/${voucher}`), selectionApiCall), [apiClient, selectionApiCall] ); const updateCountry = useCallback( (country, data) => onSelectionResponse(apiClient.request("PUT", `countries/${country}`, data), selectionApiCall), [apiClient, selectionApiCall] ); const updateLanguage = useCallback( (language) => onSelectionResponse(apiClient.request("PUT", `languages/${language}`), selectionApiCall), [apiClient, selectionApiCall] ); const updateShippingMethod = useCallback( (shippingMethod) => onSelectionResponse( apiClient.request("PUT", `shipping-methods/${shippingMethod}`), selectionApiCall ), [apiClient, selectionApiCall] ); const updatePaymentMethod = useCallback( (paymentMethod) => onSelectionResponse( apiClient.request("PUT", `payment-methods/${paymentMethod}`), selectionApiCall ), [apiClient, selectionApiCall] ); const updatePaymentFields = useCallback( async (data) => onSelectionResponse(apiClient.request("PUT", `payment-fields`, data), selectionApiCall), [apiClient, selectionApiCall] ); const submitPayment = 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 = useCallback( (data) => onSelectionResponse( apiClient.request("POST", "back-in-stock-subscription", data), selectionApiCall ), [apiClient, selectionApiCall] ); const addNewsletterSubscription = useCallback( (data) => onSelectionResponse( apiClient.request("POST", "newsletter-subscription", data), selectionApiCall ), [apiClient, selectionApiCall] ); const loginCustomer = useCallback( (email, password) => onSelectionResponse( apiClient.request("POST", `login/${email}`, { password }), selectionApiCall ), [apiClient, selectionApiCall] ); const logoutCustomer = useCallback( () => onSelectionResponse(apiClient.request("POST", `logout`), selectionApiCall), [apiClient, selectionApiCall] ); const registerCustomer = useCallback( (data) => onSelectionResponse(apiClient.request("POST", `register`, data), selectionApiCall), [apiClient, selectionApiCall] ); const resetCustomerPassword = useCallback( (i, id, newPassword) => onSelectionResponse( apiClient.request("POST", `password-reset`, { i, id, newPassword }), selectionApiCall ), [apiClient, selectionApiCall] ); const resetSelection = useCallback(() => { apiClient.headers.delete("api-token"); cookies.remove(tokenName); return init(); }, [apiClient.headers, init, tokenName]); const sendCustomerResetPasswordEmail = useCallback( (email, linkUri) => onSelectionResponse( apiClient.request("POST", `password-reset-email/${email}`, { linkUri }), selectionApiCall ), [apiClient, selectionApiCall] ); const updateCustomer = useCallback( (data) => onSelectionResponse(apiClient.request("PUT", `customer/update`, data), selectionApiCall), [apiClient, selectionApiCall] ); const updateCustomerAddress = useCallback( (data) => onSelectionResponse(apiClient.request("PUT", `address`, data), selectionApiCall), [apiClient, selectionApiCall] ); const updateCustomerEmail = useCallback( (newEmail) => onSelectionResponse(apiClient.request("PUT", `email`, { newEmail }), selectionApiCall), [apiClient, selectionApiCall] ); const updateCustomerPassword = useCallback( (password, newPassword) => onSelectionResponse( apiClient.request("PUT", `password`, { password, newPassword }), selectionApiCall ), [apiClient, selectionApiCall] ); const updateCampaignSite = useCallback( (uri) => onSelectionResponse(apiClient.request("PUT", `campaign-site`, { uri }), selectionApiCall), [apiClient, selectionApiCall] ); useEffect(() => { if (!disableInit) { void init(); } document.addEventListener("centra_checkout_callback", centraCheckoutCallback); return () => { document.removeEventListener("centra_checkout_callback", centraCheckoutCallback); }; }, [disableInit, init, centraCheckoutCallback]); 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 = 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 = useMemo( () => ({ ...selection, apiUrl, apiClient }), [selection, apiUrl, apiClient] ); return /* @__PURE__ */ jsx(CentraHandlersContext.Provider, { value: centraHandlersContext, children: /* @__PURE__ */ jsx(CentraSelectionContext.Provider, { value: centraContext, children }) }); } function useCentraSelection() { const context = 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; } function useCentraHandlers() { const context = 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; } function useCentraReceipt(token) { const [result, setResult] = useState({}); const { apiUrl } = useCentraSelection(); if (!token) { console.error("@noaignite/react-centra-checkout: useReceipt requires a selection id"); } useEffect(() => { const tempApiClient = new ApiClient(apiUrl); tempApiClient.headers.set("api-token", token); void tempApiClient.request("GET", "receipt").then((response) => { setResult(response); }); }, [apiUrl, token]); return result; } function useCentraOrders(from, size, apiClient = defaultApiClient) { const [result, setResult] = useState({}); useEffect(() => { void apiClient.request("POST", "orders", { ...from && { from }, ...size && { size } }).then((response) => { setResult(response); }); }, [apiClient, from, size]); return result; } function useCentraEvents() { return CentraEvents.default; } export { CentraHandlersContext, CentraProvider, CentraSelectionContext, SELECTION_INITIAL_VALUE, useCentraEvents, useCentraHandlers, useCentraOrders, useCentraReceipt, useCentraSelection }; //# sourceMappingURL=Context.js.map