@noaignite/react-centra-checkout
Version:
React components and helpers for Centra checkout api
482 lines • 15.5 kB
JavaScript
// 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