diff --git a/apps/storefront/src/components/HeadlessController.tsx b/apps/storefront/src/components/HeadlessController.tsx index bc364221..9f46c2dd 100644 --- a/apps/storefront/src/components/HeadlessController.tsx +++ b/apps/storefront/src/components/HeadlessController.tsx @@ -22,7 +22,6 @@ import { LineItems, startMasquerade, } from '@/utils' -import CallbackManager from '@/utils/b3Callbacks' import createShoppingList from '@/utils/b3ShoppingList/b3ShoppingList' interface QuoteDraftItem { @@ -177,7 +176,6 @@ export default function HeadlessController({ useEffect(() => { window.b2b = { - callbacks: new CallbackManager(), utils: { openPage: (page) => { setOpenPage({ isOpen: false }) diff --git a/apps/storefront/src/index.d.ts b/apps/storefront/src/index.d.ts index d67ee142..d8d227dd 100644 --- a/apps/storefront/src/index.d.ts +++ b/apps/storefront/src/index.d.ts @@ -13,7 +13,6 @@ declare interface Window { globalTipDispatch: any B3Local: any b2b: { - callbacks: import('@/utils/b3Callbacks').default utils: { openPage: (page: import('./constants').HeadlessRoute) => void quote: { diff --git a/apps/storefront/src/pages/quote/QuoteDraft.tsx b/apps/storefront/src/pages/quote/QuoteDraft.tsx index 1df9f746..ec185de6 100644 --- a/apps/storefront/src/pages/quote/QuoteDraft.tsx +++ b/apps/storefront/src/pages/quote/QuoteDraft.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import { useNavigate } from 'react-router-dom' -import useCallbacks from '@b3/hooks/useCustomCallbacks' import { useB3Lang } from '@b3/lang' import { ArrowBackIosNew } from '@mui/icons-material' import { @@ -402,203 +401,192 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { })) } - const handleSubmit = useCallbacks( - 'on-quote-create', - async (_e, handleEvent) => { - setLoading(true) - try { - const info = B3LStorage.get('MyQuoteInfo') - const contactInfo = info?.contactInfo || {} - - const isComplete = Object.keys(contactInfo).every((key: string) => { - if (key === 'phoneNumber' || key === 'companyName') { - return true - } - return !!contactInfo[key] - }) + const handleSubmit = async () => { + setLoading(true) + try { + const info = B3LStorage.get('MyQuoteInfo') + const contactInfo = info?.contactInfo || {} - if (JSON.stringify(contactInfo) === '{}' || !isComplete) { - snackbar.error(b3Lang('quoteDraft.addQuoteInfo')) - return + const isComplete = Object.keys(contactInfo).every((key: string) => { + if (key === 'phoneNumber' || key === 'companyName') { + return true } + return !!contactInfo[key] + }) - const b2bQuoteDraftList = B3LStorage.get('b2bQuoteDraftList') - - if (!b2bQuoteDraftList || b2bQuoteDraftList.length === 0) { - snackbar.error(b3Lang('quoteDraft.submit')) - return - } + if (JSON.stringify(contactInfo) === '{}' || !isComplete) { + snackbar.error(b3Lang('quoteDraft.addQuoteInfo')) + return + } - const emailAddress = B3SStorage.get('B3EmailAddress') + const b2bQuoteDraftList = B3LStorage.get('b2bQuoteDraftList') - const note = info?.note || '' - const newNote = note.trim().replace(/[\r\n]/g, '\\n') + if (!b2bQuoteDraftList || b2bQuoteDraftList.length === 0) { + snackbar.error(b3Lang('quoteDraft.submit')) + return + } - const perfectAddress = (address: CustomFieldStringItems) => { - const newAddress = { - ...address, - } + const emailAddress = B3SStorage.get('B3EmailAddress') - const countryItem = countriesList?.find( - (item: Country) => item.countryCode === newAddress.country - ) + const note = info?.note || '' + const newNote = note.trim().replace(/[\r\n]/g, '\\n') - if (countryItem) { - newAddress.country = countryItem.countryName - } + const perfectAddress = (address: CustomFieldStringItems) => { + const newAddress = { + ...address, + } - newAddress.addressLine1 = address?.address || '' - newAddress.addressLine2 = address?.apartment || '' + const countryItem = countriesList?.find( + (item: Country) => item.countryCode === newAddress.country + ) - return newAddress + if (countryItem) { + newAddress.country = countryItem.countryName } - const { - shippingAddress: editShippingAddress, - billingAddress: editBillingAddress, - } = billingRef?.current ? getAddress() : info + newAddress.addressLine1 = address?.address || '' + newAddress.addressLine2 = address?.apartment || '' - const shippingAddress = editShippingAddress - ? perfectAddress(editShippingAddress) - : {} + return newAddress + } - const billingAddress = editBillingAddress - ? perfectAddress(editBillingAddress) - : {} + const { + shippingAddress: editShippingAddress, + billingAddress: editBillingAddress, + } = billingRef?.current ? getAddress() : info - let allPrice = 0 - let allTaxPrice = 0 + const shippingAddress = editShippingAddress + ? perfectAddress(editShippingAddress) + : {} - const calculationTime = (value: string | number) => { - if (typeof value === 'string' && value.includes('-')) { - return `${new Date(value).getTime() / 1000}` - } - return value - } + const billingAddress = editBillingAddress + ? perfectAddress(editBillingAddress) + : {} - const productList = b2bQuoteDraftList.map( - (item: QuoteListitemProps) => { - const { node } = item - const product: any = { - ...node.productsSearch, - selectOptions: node?.optionList || '', - } + let allPrice = 0 + let allTaxPrice = 0 - const productFields = getProductOptionsFields(product, {}) - const optionsList: CustomFieldItems[] = - productFields - .map((item) => ({ - optionId: item.optionId, - optionValue: - item.fieldType === 'date' - ? calculationTime(item.optionValue) - : item.optionValue, - optionLabel: `${item.valueText}`, - optionName: item.valueLabel, - })) - .filter((list: CustomFieldItems) => !!list.optionName) || [] - - const varants = node.productsSearch.variants - const varantsItem = varants.find( - (item: CustomFieldItems) => item.sku === node.variantSku - ) - - // const salePrice = getBCPrice(+(node?.basePrice || 0), +(node?.taxPrice || 0)) - - allPrice += +(node?.basePrice || 0) * +(node?.quantity || 0) - - allTaxPrice += +(node?.taxPrice || 0) * +(node?.quantity || 0) - - const items = { - productId: node.productsSearch.id, - sku: node.variantSku, - basePrice: (+(node?.basePrice || 0)).toFixed(decimalPlaces), - discount: '0.00', - offeredPrice: (+(node?.basePrice || 0)).toFixed(decimalPlaces), - quantity: node.quantity, - variantId: varantsItem.variant_id, - imageUrl: node.primaryImage, - productName: node.productName, - options: optionsList, - } + const calculationTime = (value: string | number) => { + if (typeof value === 'string' && value.includes('-')) { + return `${new Date(value).getTime() / 1000}` + } + return value + } - return items - } + const productList = b2bQuoteDraftList.map((item: QuoteListitemProps) => { + const { node } = item + const product: any = { + ...node.productsSearch, + selectOptions: node?.optionList || '', + } + + const productFields = getProductOptionsFields(product, {}) + const optionsList: CustomFieldItems[] = + productFields + .map((item) => ({ + optionId: item.optionId, + optionValue: + item.fieldType === 'date' + ? calculationTime(item.optionValue) + : item.optionValue, + optionLabel: `${item.valueText}`, + optionName: item.valueLabel, + })) + .filter((list: CustomFieldItems) => !!list.optionName) || [] + + const varants = node.productsSearch.variants + const varantsItem = varants.find( + (item: CustomFieldItems) => item.sku === node.variantSku ) - const currency = getDefaultCurrencyInfo() - - const fileList = getFileList(info.fileInfo || []) - - const data = { - // notes: note, - message: newNote, - legalTerms: '', - totalAmount: enteredInclusiveTax - ? allPrice.toFixed(decimalPlaces) - : (allPrice + allTaxPrice).toFixed(decimalPlaces), - grandTotal: allPrice.toFixed(decimalPlaces), - subtotal: allPrice.toFixed(decimalPlaces), - companyId: isB2BUser ? companyB2BId || salesRepCompanyId : '', - storeHash, - discount: '0.00', - channelId, - userEmail: emailAddress, - shippingAddress, - billingAddress, - contactInfo, - productList, - fileList, - taxTotal: allTaxPrice.toFixed(decimalPlaces), - currency: { - currencyExchangeRate: currency.currency_exchange_rate, - token: currency.token, - location: currency.token_location, - decimalToken: currency.decimal_token, - decimalPlaces: currency.decimal_places, - thousandsToken: currency.thousands_token, - currencyCode: currency.currency_code, - }, - } + // const salePrice = getBCPrice(+(node?.basePrice || 0), +(node?.taxPrice || 0)) - const fn = +role === 99 ? createBCQuote : createQuote + allPrice += +(node?.basePrice || 0) * +(node?.quantity || 0) - if (!handleEvent(data)) { - throw new Error() + allTaxPrice += +(node?.taxPrice || 0) * +(node?.quantity || 0) + + const items = { + productId: node.productsSearch.id, + sku: node.variantSku, + basePrice: (+(node?.basePrice || 0)).toFixed(decimalPlaces), + discount: '0.00', + offeredPrice: (+(node?.basePrice || 0)).toFixed(decimalPlaces), + quantity: node.quantity, + variantId: varantsItem.variant_id, + imageUrl: node.primaryImage, + productName: node.productName, + options: optionsList, } - const { - quoteCreate: { - quote: { id, createdAt }, - }, - } = await fn(data) + return items + }) + + const currency = getDefaultCurrencyInfo() + + const fileList = getFileList(info.fileInfo || []) + + const data = { + // notes: note, + message: newNote, + legalTerms: '', + totalAmount: enteredInclusiveTax + ? allPrice.toFixed(decimalPlaces) + : (allPrice + allTaxPrice).toFixed(decimalPlaces), + grandTotal: allPrice.toFixed(decimalPlaces), + subtotal: allPrice.toFixed(decimalPlaces), + companyId: isB2BUser ? companyB2BId || salesRepCompanyId : '', + storeHash, + discount: '0.00', + channelId, + userEmail: emailAddress, + shippingAddress, + billingAddress, + contactInfo, + productList, + fileList, + taxTotal: allTaxPrice.toFixed(decimalPlaces), + currency: { + currencyExchangeRate: currency.currency_exchange_rate, + token: currency.token, + location: currency.token_location, + decimalToken: currency.decimal_token, + decimalPlaces: currency.decimal_places, + thousandsToken: currency.thousands_token, + currencyCode: currency.currency_code, + }, + } - if (id) { - const cartId = B3LStorage.get('cartToQuoteId') + const fn = +role === 99 ? createBCQuote : createQuote - await deleteCart(cartId) - } + const { + quoteCreate: { + quote: { id, createdAt }, + }, + } = await fn(data) - navigate(`/quoteDetail/${id}?date=${createdAt}`, { - state: { - to: 'draft', - }, - }) + if (id) { + const cartId = B3LStorage.get('cartToQuoteId') - B3LStorage.delete('b2bQuoteDraftList') - B3LStorage.delete('MyQuoteInfo') - B3LStorage.delete('cartToQuoteId') - } catch (error: any) { - if (error.message.length > 0) { - snackbar.error(error.message, { - isClose: true, - }) - } - } finally { - setLoading(false) + await deleteCart(cartId) } + + navigate(`/quoteDetail/${id}?date=${createdAt}`, { + state: { + to: 'draft', + }, + }) + + B3LStorage.delete('b2bQuoteDraftList') + B3LStorage.delete('MyQuoteInfo') + B3LStorage.delete('cartToQuoteId') + } catch (error: any) { + snackbar.error(error, { + isClose: true, + }) + } finally { + setLoading(false) } - ) + } const backText = () => { let text = diff --git a/apps/storefront/src/pages/shoppingListDetails/components/AddToShoppingList.tsx b/apps/storefront/src/pages/shoppingListDetails/components/AddToShoppingList.tsx index 89712266..b32f2ff2 100644 --- a/apps/storefront/src/pages/shoppingListDetails/components/AddToShoppingList.tsx +++ b/apps/storefront/src/pages/shoppingListDetails/components/AddToShoppingList.tsx @@ -1,5 +1,4 @@ import { useContext, useState } from 'react' -import useCallbacks from '@b3/hooks/useCustomCallbacks' import { useB3Lang } from '@b3/lang' import UploadFileIcon from '@mui/icons-material/UploadFile' import { Box, Card, CardContent, Divider, Typography } from '@mui/material' @@ -40,48 +39,31 @@ export default function AddToShoppingList(props: AddToListProps) { ? addProductToShoppingList : addProductToBcShoppingList - const addToList = useCallbacks( - 'on-add-to-shopping-list', - async (products: CustomFieldItems[], handleEvent) => { - try { - if (!handleEvent(products)) { - throw new Error() - } - - const items = products.map((product) => { - const newOptionLists = getValidOptionsList( - product.newSelectOptionList, - product - ) - return { - optionList: newOptionLists, - productId: product.id, - quantity: product.quantity, - variantId: product.variantId, - } - }) + const addToList = async (products: CustomFieldItems[]) => { + const items = products.map((product) => { + const newOptionLists = getValidOptionsList( + product.newSelectOptionList, + product + ) + return { + optionList: newOptionLists, + productId: product.id, + quantity: product.quantity, + variantId: product.variantId, + } + }) - const res: CustomFieldItems = await addItemsToShoppingList({ - shoppingListId: id, - items, - }) + const res: CustomFieldItems = await addItemsToShoppingList({ + shoppingListId: id, + items, + }) - snackbar.success( - b3Lang('shoppingList.addToShoppingList.productsAdded'), - { - isClose: true, - } - ) + snackbar.success(b3Lang('shoppingList.addToShoppingList.productsAdded'), { + isClose: true, + }) - return res - } catch (e: any) { - if (e.message.length > 0) { - snackbar.error(e.message, { isClose: true }) - } - } - return true - } - ) + return res + } const quickAddToList = async (products: CustomFieldItems[]) => { const items = products.map((product) => { diff --git a/apps/storefront/src/utils/b3Callbacks.ts b/apps/storefront/src/utils/b3Callbacks.ts deleted file mode 100644 index 4ee15681..00000000 --- a/apps/storefront/src/utils/b3Callbacks.ts +++ /dev/null @@ -1,85 +0,0 @@ -type CallbackEvent = { - data: CustomFieldItems - preventDefault: () => void -} -enum CallbackKey { - onQuoteCreate = 'on-quote-create', - onAddToShoppingList = 'on-add-to-shopping-list', -} -type Callback = (event: CallbackEvent) => any - -export default class CallbackManager { - private callbacks: Map - - constructor() { - this.callbacks = new Map() - } - - /** - * Registers a callback function for a specific event and returns a unique hash for it. - * @param callbackKey The event key (e.g., 'onAddToCart'). - * @param callback The callback function to register. - * @returns A unique hash identifying the registered callback. - */ - addEventListener(callbackKey: CallbackKey, callback: Callback): string { - if (!this.callbacks.has(callbackKey)) { - this.callbacks.set(callbackKey, [callback]) - } - const list = this.callbacks.get(callbackKey) ?? [] - const inList = list.find((cb) => cb === callback) - - if (!inList) { - list.push(callback) - } - } - - /** - * Unregisters a callback identified by a hash and event key. - * @param callbackKey The event key (e.g., 'onAddToCart'). - * @param hash The unique hash of the callback to unregister. - * @returns True if the callback was successfully removed, false otherwise. - */ - removeEventListener(callbackKey: CallbackKey, callback: Callback): boolean { - if (!this.callbacks.has(callbackKey)) { - return false - } - const list = this.callbacks.get(callbackKey) ?? [] - const index = list.findIndex((cb) => cb === callback) - if (index === -1) { - return false - } - list.splice(index, 1) - return true - } - - /** - * Triggers all callbacks registered for a specific event. - * @param callbackKey The event key (e.g., 'onAddToCart'). - * @param data The data to pass to the callback. - * @returns True if all callbacks were successfully executed, false otherwise. - */ - dispatchEvent(callbackKey: CallbackKey, data: any): boolean { - let success = true - const event = { - data, - preventDefault: () => { - success = false - }, - } - if (!this.callbacks.has(callbackKey)) { - return true - } - const list = this.callbacks.get(callbackKey) ?? [] - list.forEach((callback) => { - try { - callback(event) - } catch (e) { - success = false - if (e instanceof Error) { - console.error(e.message) - } - } - }) - return success - } -} diff --git a/apps/storefront/src/utils/b3checkout.ts b/apps/storefront/src/utils/b3checkout.ts index dbbc4bb8..17fe3c27 100644 --- a/apps/storefront/src/utils/b3checkout.ts +++ b/apps/storefront/src/utils/b3checkout.ts @@ -32,5 +32,8 @@ export const attemptCheckoutLoginAndRedirect = async ( export const setQuoteToStorage = (quoteId: string, date: any) => { sessionStorage.setItem('isNewStorefront', JSON.stringify(true)) sessionStorage.setItem('quoteCheckoutId', cipherText(quoteId)) - sessionStorage.setItem('quoteDate', cipherText(date?.toString() || '')) + sessionStorage.setItem( + 'quoteDate', + cipherText(date?.toString() || '') + ) } diff --git a/packages/hooks/index.d.ts b/packages/hooks/index.d.ts index cdd944b1..87cfb4a6 100644 --- a/packages/hooks/index.d.ts +++ b/packages/hooks/index.d.ts @@ -1,15 +1,4 @@ -declare type Callback = (...args: any[]) => any -declare enum CallbackKey { - onQuoteCreate = 'on-quote-create', - onAddToShoppingList = 'on-add-to-shopping-list', -} - declare interface Window { - b2b: { - callbacks: { - dispatchEvent: (callbackKey: CallbackKey, data: any) => boolean - } - } b2bStorefrontApp: { isInit: boolean } diff --git a/packages/hooks/index.ts b/packages/hooks/index.ts index 901aa0d3..856c71f9 100644 --- a/packages/hooks/index.ts +++ b/packages/hooks/index.ts @@ -1,4 +1,3 @@ export * from './useB3AppOpen' -export { default as useCustomCallbacks } from './useCustomCallbacks' export { default as useMutationObservable } from './useMutationObservable' export { default as useWindowSize } from './useWindowSize' diff --git a/packages/hooks/useCustomCallbacks.ts b/packages/hooks/useCustomCallbacks.ts deleted file mode 100644 index 4a689fc3..00000000 --- a/packages/hooks/useCustomCallbacks.ts +++ /dev/null @@ -1,19 +0,0 @@ -const useCallbacks = ( - callbacks: CallbackKey[] | CallbackKey, - fn: (...args: any[]) => Promise | any -) => { - const handleEvent = (data: any) => { - if (Array.isArray(callbacks)) { - return callbacks.reduce( - (acc, callback) => - !acc ? false : window.b2b.callbacks.dispatchEvent(callback, data), - true - ) - } - return window.b2b.callbacks.dispatchEvent(callbacks, data) - } - - return (...args: any[]) => fn(...args, handleEvent) -} - -export default useCallbacks