From 0d2e67df6cebd0d9c8eb8f5c8718f2c3898121e7 Mon Sep 17 00:00:00 2001 From: Estefania Ocampo Date: Wed, 20 Mar 2024 14:50:50 -0600 Subject: [PATCH] fix: update with sprint 49 the feature branch (#975) * feat: bun1943 register verify (#946) * fix: register verify bun1943-s48 https://bigc-b2b.atlassian.net/browse/BUN-1943 * fix: register verify * feat: country add default value https://bigc-b2b.atlassian.net/browse/BUN-1955 * fix: modify the login page design layout bun1916 https://bigc-b2b.atlassian.net/browse/BUN-1916 * fix: design top line of side menu should be alligned with top line of page header (#950) * fix: design top line of side menu should be alligned with top line of page header https://bigc-b2b.atlassian.net/browse/BUN-2063 * feat: add BUN-2070,BUN-2064,BUN-2066,BUN-2065 (#951) * feat: add "x" to close the buyer portal add Quote Title entry to the Buyer Quote Form in the Buyer Portal https://bigc-b2b.atlassian.net/browse/BUN-2070 https://bigc-b2b.atlassian.net/browse/BUN-2064 * feat: allow Password AutoFill https://bigc-b2b.atlassian.net/browse/BUN-2066 * feat: add a setting that determines where shoppers land when the login to they store https://bigc-b2b.atlassian.net/browse/BUN-2065 * fix: quick order pad page crashes hotfix about sup1506 https://bigc-b2b.atlassian.net/browse/SUP-1506 * fix: optimized login loadding BUN-2065 * ci: change the CODEOWNER to the buyer-portal team * fix: refix the CODEOWNERS * fix: missing address label https://bigc-b2b.atlassian.net/browse/BUN-2122 * fix/BUN-2084: clean no param rule in pages (#958) * fix: cleaning rules * fix: cherry picking changes * fix: clean code merge * fix: adding missing lines * fix/BUN-2083 clean up no params reassign rule (#962) * feat(BUN-2087): refactoring hooks eslint rule (#947) * fix(BUN-2085): nit in variable definition (#954) * fix: clean no param reassign rule in components (#953) * fix: clean no param reassign rule in components * fix: deleting clean directory * feat(BUN-2085): utils directory eslint cleanup (#952) --------- Co-authored-by: bc-victor <140021227+bc-victor@users.noreply.github.com> * brian/fix/sprint49 (#959) * fix: invoice link leads to a blank unresponsive buyer portal page https://bigc-b2b.atlassian.net/jira/software/c/projects/BUN/issues/BUN-2015 * fix: reordering For Product with Modifier Text Field https://bigc-b2b.atlassian.net/browse/BUN-2075 * fix: quick order pad change tip text (#964) * fix: quick order pad change tip text https://bigc-b2b.atlassian.net/browse/BUN-2141 * fix: translation issues * fix: quick order pad qty error https://bigc-b2b.atlassian.net/browse/BUN-2207 * fix: the cart counter is showing on the X icon on the mobile cart in the Buyer Portal (#2255) * chore: documentation efforts into connecting to production stores (#972) * ci: change the CODEOWNER to the buyer-portal team * fix: refix the CODEOWNERS * chore: documentation efforts into connecting to production stores --------- Co-authored-by: Micah Thomas <95306190+bc-micah@users.noreply.github.com> * fix: invoice,shoppingList,quote detail page link --------- Co-authored-by: BrianJiang2021 <80307788+BrianJiang2021@users.noreply.github.com> Co-authored-by: Brian.Jiang2021 Co-authored-by: Micah Thomas <95306190+bc-micah@users.noreply.github.com> Co-authored-by: bc-victor <140021227+bc-victor@users.noreply.github.com> --- CODEOWNERS | 2 +- README.md | 25 +++-- apps/storefront/.env-example | 27 ++++- apps/storefront/src/App.tsx | 9 ++ .../src/components/B3CustomForm.tsx | 3 +- .../src/components/B3StoreContainer.tsx | 2 +- .../components/form/B3ControlFileUpload.tsx | 46 +++++++- .../components/form/B3ControlTextField.tsx | 13 ++- .../components/layout/B3CloseAppButton.tsx | 46 ++++++++ .../src/components/layout/B3Layout.tsx | 10 +- .../src/components/layout/B3Logo.tsx | 34 ++++-- .../src/components/layout/B3Mainheader.tsx | 5 +- .../src/components/layout/B3MobileLayout.tsx | 58 ++++++---- apps/storefront/src/components/styled.ts | 10 +- .../pages/accountSetting/AccountSetting.tsx | 20 ++-- .../src/pages/accountSetting/utils.ts | 39 ++++--- apps/storefront/src/pages/address/Address.tsx | 3 +- .../pages/address/components/AddressForm.tsx | 36 ++++-- .../pages/address/shared/getAddressFields.ts | 16 +-- apps/storefront/src/pages/invoice/Invoice.tsx | 18 ++- apps/storefront/src/pages/login/Login.tsx | 104 ++++++++++-------- apps/storefront/src/pages/login/LoginForm.tsx | 68 ++++++------ apps/storefront/src/pages/login/config.ts | 2 + apps/storefront/src/pages/order/Order.tsx | 8 +- .../components/OrderCheckboxProduct.tsx | 11 +- .../orderDetail/components/OrderDialog.tsx | 4 +- .../orderDetail/components/OrderShipping.tsx | 3 +- .../pages/orderDetail/shared/B2BOrderData.ts | 3 +- .../components/QuickOrderFooter.tsx | 1 + .../quickorder/components/QuickOrderPad.tsx | 10 +- .../quickorder/components/QuickorderTable.tsx | 3 +- .../src/pages/quote/QuoteDetail.tsx | 3 +- .../storefront/src/pages/quote/QuoteDraft.tsx | 24 +++- .../pages/quote/components/ContactInfo.tsx | 10 ++ .../quote/components/QuoteDetailHeader.tsx | 1 + .../src/pages/quote/components/QuoteInfo.tsx | 22 ++++ .../src/pages/quote/components/QuoteTable.tsx | 13 ++- .../src/pages/registered/RegisterComplete.tsx | 24 ++-- .../src/pages/registered/Registered.tsx | 30 +++-- .../pages/registered/RegisteredAccount.tsx | 15 ++- .../pages/registered/RegisteredBCToB2B.tsx | 69 ++++++++++-- .../src/pages/registered/RegisteredDetail.tsx | 39 ++++++- .../storefront/src/pages/registered/config.ts | 14 ++- .../components/ChooseOptionsDialog.tsx | 4 +- .../components/QuickAdd.tsx | 2 +- .../components/ReAddToCart.tsx | 9 +- .../components/ShoppingDetailHeader.tsx | 2 +- .../shoppingLists/AddEditShoppingLists.tsx | 5 +- .../src/pages/shoppingLists/ShoppingLists.tsx | 6 +- .../pages/usermanagement/Usermanagement.tsx | 6 +- apps/storefront/src/shared/routes/routes.ts | 24 ++-- .../src/shared/service/b2b/graphql/quote.ts | 3 +- apps/storefront/src/store/slices/global.ts | 8 ++ apps/storefront/src/utils/b3Login.ts | 32 ++++++ .../src/utils/b3Product/b3Product.ts | 24 ++-- .../src/utils/b3Product/shared/config.ts | 1 + apps/storefront/src/utils/cartUtils.ts | 21 +++- apps/storefront/src/utils/index.ts | 2 + apps/storefront/src/utils/storefrontConfig.ts | 24 +++- packages/global-b3/index.ts | 3 + packages/lang/locales/en.json | 3 + 61 files changed, 764 insertions(+), 318 deletions(-) create mode 100644 apps/storefront/src/components/layout/B3CloseAppButton.tsx create mode 100644 apps/storefront/src/utils/b3Login.ts diff --git a/CODEOWNERS b/CODEOWNERS index 0eaa9cee..369c50a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @bc-micah @LeoChowChina @libruce \ No newline at end of file +* @B3BC/buyer-portal diff --git a/README.md b/README.md index 63a25cd1..4ce49d9a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,27 @@ - A monorepo frontend application designed for the BigCommerce B2B Edition Buyer portal. It's built using Turborepo, TypeScript, and React. ## Index -- [Prerequisites](#-prerequisites) -- [Core Technologies](#-core-technologies) -- [Workspaces](#-workspaces) -- [Tools and Libraries](#-tools-and-libraries) -- [System Setup](#-system-setup) -- [Local Development](#-local-development) +- [Index](#index) +- [☑ Prerequisites](#-prerequisites) + - [Step 1: Access the Storefronts Manager](#step-1-access-the-storefronts-manager) + - [Step 2: Enable B2B on Your Channel](#step-2-enable-b2b-on-your-channel) + - [Step 3: Contact Us for Additional Support](#step-3-contact-us-for-additional-support) +- [🚀 Core Technologies](#-core-technologies) +- [📦 Workspaces](#-workspaces) +- [🛠 Tools and Libraries](#-tools-and-libraries) +- [🛠 System Setup](#-system-setup) +- [⚙ Local Development](#-local-development) - [Running Project Locally](#running-project-locally) -- [Contribution](#-contribution) -- [Contact & Support](#-contact--support) +- [🤝 Contribution](#-contribution) +- [📞 Contact \& Support](#-contact--support) ## ☑ Prerequisites Before you begin, ensure you have the BigCommerce B2B Edition App installed. To set up your storefront with B2B capabilities, follow the steps below: ### Step 1: Access the Storefronts Manager + After installing the B2B Edition App, go to the app's dashboard and select the 'Storefronts' section. image @@ -28,6 +32,7 @@ Choose the channel where you wish to enable B2B functionality. Initially, B2B fe image ### Step 3: Contact Us for Additional Support + For assistance with activating the remote buyer portal or to inquire about multi-storefront support, which allows you to utilize B2B features across multiple channels, please reach out to our team at b2b@bigcommerce.com, or raise an issue right here in this repository. ## 🚀 Core Technologies @@ -80,6 +85,8 @@ For assistance with activating the remote buyer portal or to inquire about multi - `VITE_B2B_CLIENT_ID`: The client ID of the BigCommerce App from the [developer portal](https://devtools.bigcommerce.com/). - `VITE_LOCAL_DEBUG`: Set to "FALSE". This is for connecting our local development with the B2B Edition GraphQL API. +Environment variables have been updated so you can run your UI directly into production storefronts. + 6. Start the development server: `yarn RUN dev`. ## Running Project Locally diff --git a/apps/storefront/.env-example b/apps/storefront/.env-example index 1263e5ab..1199a9ac 100644 --- a/apps/storefront/.env-example +++ b/apps/storefront/.env-example @@ -1,9 +1,26 @@ -VITE_B2B_URL=http://localhost:9000 -VITE_B2B_SOCKET_URL=http://localhost:9000 -VITE_TRANSLATION_SERVICE_URL=http://localhost:5000 +# URL of the B2B API, if doing local development, this should be the URL of the local B2B API with its own port +VITE_B2B_URL=https://api.bundleb2b.net + +# URL of the B2B Socket - this should be the same as the B2B API URL +VITE_B2B_SOCKET_URL=https://api.bundleb2b.net + +# URL of the B2B Translation Service - if doing local development, try with localhost:5000 or check the service URL +VITE_TRANSLATION_SERVICE_URL=https://api.bundleb2b.net + +# CHANNEL ID of the storefront, if your store is multi-storefront, you can use this to specify the storefront - otherwise leave as 1 VITE_CHANNEL_ID=1 + +# Store hash of the storefront, this is the unique identifier of the storefront VITE_STORE_HASH=store_hash + +# Captcha Site Key for the storefront VITE_CATPCHA_SETKEY=captcha_setkey + +# Client ID issued by B2B Edition for the storefront VITE_B2B_CLIENT_ID=client_id -VITE_LOCAL_DEBUG="FALSE" -VITE_LOCAL_GRAPHQL_ORIGIN="https://b2b-tunnel-.bundleb2b.net" + +# Set this to TRUE to debug in your default storefront +VITE_LOCAL_DEBUG=FALSE + +# URL where the GraphQL is hosted, usually the same one as B2B_URL_API. If the GraphQL API is hosted locally, set this to the local URL +VITE_LOCAL_GRAPHQL_ORIGIN=https://api.bundleb2b.net diff --git a/apps/storefront/src/App.tsx b/apps/storefront/src/App.tsx index b8d0c7ed..79b50574 100644 --- a/apps/storefront/src/App.tsx +++ b/apps/storefront/src/App.tsx @@ -316,6 +316,15 @@ export default function App() { useEffect(() => { const { hash } = window.location + if (!hash.includes('login') && !hash.includes('register')) { + const recordOpenHash = isOpen ? hash : '' + storeDispatch( + setGlabolCommonState({ + recordOpenHash, + }) + ) + } + if (isOpen && hash === '#/') { setOpenPage({ isOpen: false, diff --git a/apps/storefront/src/components/B3CustomForm.tsx b/apps/storefront/src/components/B3CustomForm.tsx index 520b2a69..ad493c62 100644 --- a/apps/storefront/src/components/B3CustomForm.tsx +++ b/apps/storefront/src/components/B3CustomForm.tsx @@ -14,7 +14,7 @@ import { } from './form' export default function B3CustomForm(props: B3UI.B3CustomFormProps) { - const { formFields, errors, control, getValues, setValue } = props + const { formFields, errors, control, getValues, setValue, setError } = props const renderFormFields = (fields: any) => fields.map((field: B3UI.B3CustomFormValue) => { @@ -75,6 +75,7 @@ export default function B3CustomForm(props: B3UI.B3CustomFormProps) { errors={errors} control={control} setValue={setValue} + setError={setError} /> )} {['rectangle'].includes(fieldType) && ( diff --git a/apps/storefront/src/components/B3StoreContainer.tsx b/apps/storefront/src/components/B3StoreContainer.tsx index 99c8b6a4..2409f71c 100644 --- a/apps/storefront/src/components/B3StoreContainer.tsx +++ b/apps/storefront/src/components/B3StoreContainer.tsx @@ -39,7 +39,7 @@ export default function B3StoreContainer(props: B3StoreContainerProps) { const getStoreBasicInfo = async () => { if ( window.location.pathname.includes('account.php') || - window.location.hash + (window.location.hash && window.location.hash !== '#/') ) { showPageMask(dispatch, true) } diff --git a/apps/storefront/src/components/form/B3ControlFileUpload.tsx b/apps/storefront/src/components/form/B3ControlFileUpload.tsx index a3a370a8..85c5c45c 100644 --- a/apps/storefront/src/components/form/B3ControlFileUpload.tsx +++ b/apps/storefront/src/components/form/B3ControlFileUpload.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { DropzoneArea, FileObject, PreviewIconProps } from 'react-mui-dropzone' import { useB3Lang } from '@b3/lang' import CloudUploadOutlinedIcon from '@mui/icons-material/CloudUploadOutlined' @@ -5,7 +6,8 @@ import DescriptionRounded from '@mui/icons-material/DescriptionRounded' import ImageRoundedIcon from '@mui/icons-material/ImageRounded' import InsertDriveFileRoundedIcon from '@mui/icons-material/InsertDriveFileRounded' import PictureAsPdfRoundedIcon from '@mui/icons-material/PictureAsPdfRounded' -import { FormLabel } from '@mui/material' +import { FormLabel, Typography } from '@mui/material' +import isEmpty from 'lodash-es/isEmpty' import { FILE_UPLOAD_ACCEPT_TYPE } from '../../constants' @@ -43,6 +45,8 @@ interface FileUploadProps extends B3UI.B3UIProps { previewText?: string default?: File[] labelColor?: string + errors?: CustomFieldItems + required?: boolean } const getMaxFileSizeLabel = (maxSize: number) => { @@ -70,7 +74,12 @@ export default function B3ControlFileUpload(props: FileUploadProps) { setValue, label, labelColor = 'text.primary', + required, + errors = {}, + setError, + control, } = props + const [deleteCount, setDeleteCount] = useState(0) const getRejectMessage = ( rejectedFile: File, @@ -107,6 +116,23 @@ export default function B3ControlFileUpload(props: FileUploadProps) { }) const handleFilesChange = (files: File[]) => { + if (deleteCount > 0 && files.length === 0 && required) { + setError(name, { + type: 'required', + message: b3Lang('global.validate.required', { + label, + }), + }) + setDeleteCount(0) + } + if (files.length > 0 && !isEmpty(errors)) { + const cError = errors[name] + if (!isEmpty(cError)) { + delete errors[name] + // eslint-disable-next-line no-underscore-dangle + control?._setErrors(errors) + } + } if (setValue) { setValue(name, files) } @@ -119,10 +145,10 @@ export default function B3ControlFileUpload(props: FileUploadProps) { sx={{ marginBottom: '5px', display: 'block', - color: labelColor, + color: (errors as any)[name] ? '#d32f2f' : labelColor, }} > - {label} + {`${label} ${required ? '*' : ''}`} )} @@ -142,8 +168,22 @@ export default function B3ControlFileUpload(props: FileUploadProps) { dropzoneText={dropzoneText} previewText={previewText} onChange={handleFilesChange} + onDelete={() => setDeleteCount(deleteCount + 1)} /> + {(errors as any)[name] ? ( + + {(errors as any)[name].message} + + ) : null} ) : null } diff --git a/apps/storefront/src/components/form/B3ControlTextField.tsx b/apps/storefront/src/components/form/B3ControlTextField.tsx index 0ef0d326..d8695c53 100644 --- a/apps/storefront/src/components/form/B3ControlTextField.tsx +++ b/apps/storefront/src/components/form/B3ControlTextField.tsx @@ -14,6 +14,7 @@ export default function B3ControlTextField({ }: Form.B3UIProps) { const { fieldType, + isAutoComplete = false, name, default: defaultValue, required, @@ -115,7 +116,14 @@ export default function B3ControlTextField({ const handleNumberInputWheel = (event: WheelEvent) => { ;(event.target as HTMLElement).blur() } - + const autoCompleteFn = () => { + if (!isAutoComplete) { + return { + autoComplete: 'off', + } + } + return {} + } const newExtraPadding = fieldId === 'field_state' && extraPadding.paddingTop === '0px' ? {} @@ -174,7 +182,8 @@ export default function B3ControlTextField({ (errors as any)[name] ? (errors as any)[name].message : null } onKeyDown={isEnterTrigger ? handleKeyDown : () => {}} - autoComplete={fieldType === 'password' ? 'new-password' : 'off'} + {...autoCompleteFn()} + // autoComplete={fieldType === 'password' ? 'current-password' : 'email'} /> ) } diff --git a/apps/storefront/src/components/layout/B3CloseAppButton.tsx b/apps/storefront/src/components/layout/B3CloseAppButton.tsx new file mode 100644 index 00000000..e61bbb67 --- /dev/null +++ b/apps/storefront/src/components/layout/B3CloseAppButton.tsx @@ -0,0 +1,46 @@ +import { useContext } from 'react' +import { useSelector } from 'react-redux' +import { useNavigate } from 'react-router-dom' + +import useMobile from '@/hooks/useMobile' +import { GlobaledContext } from '@/shared/global' +import { globalStateSelector } from '@/store' + +import { CloseBox, CloseBoxMobile, CloseButton } from '../styled' + +export default function B3CloseAppButton() { + const [isMobile] = useMobile() + + const { setOpenPageFn } = useSelector(globalStateSelector) + + const { + state: { isCloseGotoBCHome }, + } = useContext(GlobaledContext) + const navigate = useNavigate() + + const handleCloseForm = () => { + if (isCloseGotoBCHome) { + window.location.href = '/' + } else { + navigate('/') + setOpenPageFn({ + isOpen: false, + openUrl: '', + }) + } + window.history.replaceState(null, '', window.location.pathname || '/') + } + + const Box = isMobile ? CloseBoxMobile : CloseBox + + return ( + + + + ) +} diff --git a/apps/storefront/src/components/layout/B3Layout.tsx b/apps/storefront/src/components/layout/B3Layout.tsx index b93454c8..fae78fa1 100644 --- a/apps/storefront/src/components/layout/B3Layout.tsx +++ b/apps/storefront/src/components/layout/B3Layout.tsx @@ -12,6 +12,7 @@ import { getIsTokenGotoPage, RouteItem } from '@/shared/routes/routes' import B3Dialog from '../B3Dialog' import CompanyCredit from '../CompanyCredit' +import B3CloseAppButton from './B3CloseAppButton' import B3Logo from './B3Logo' import B3Mainheader from './B3Mainheader' import B3MobileLayout from './B3MobileLayout' @@ -124,6 +125,7 @@ export default function B3Layout({ children }: { children: ReactNode }) { p: '32px 63px 70px 63px', }} > + {children} diff --git a/apps/storefront/src/components/layout/B3Logo.tsx b/apps/storefront/src/components/layout/B3Logo.tsx index 506aa959..54eedde2 100644 --- a/apps/storefront/src/components/layout/B3Logo.tsx +++ b/apps/storefront/src/components/layout/B3Logo.tsx @@ -31,17 +31,29 @@ export default function B3Logo() { return ( diff --git a/apps/storefront/src/components/layout/B3MobileLayout.tsx b/apps/storefront/src/components/layout/B3MobileLayout.tsx index 23910085..46255fd3 100644 --- a/apps/storefront/src/components/layout/B3MobileLayout.tsx +++ b/apps/storefront/src/components/layout/B3MobileLayout.tsx @@ -10,6 +10,7 @@ import CompanyCredit from '../CompanyCredit' import { getContrastColor } from '../outSideComponents/utils/b3CustomStyles' import B3AccountInfo from './B3AccountInfo' +import B3CloseAppButton from './B3CloseAppButton' import B3Logo from './B3Logo' import B3Nav from './B3Nav' @@ -66,30 +67,39 @@ export default function B3MobileLayout({ {role === 2 ? ( ) : ( - - { - window.location.href = '/cart.php' + <> + + { + window.location.href = '/cart.php' + }} + /> + + - + > + + + )} @@ -98,7 +108,7 @@ export default function B3MobileLayout({ sx={{ p: 0, m: 0, - mb: '2vw', + mb: '6vw', fontSize: '34px', fontWeight: '400', color: customColor || '#263238', diff --git a/apps/storefront/src/components/styled.ts b/apps/storefront/src/components/styled.ts index 8fd725a8..62cafa25 100644 --- a/apps/storefront/src/components/styled.ts +++ b/apps/storefront/src/components/styled.ts @@ -1,5 +1,5 @@ import { Close } from '@mui/icons-material' -import { TextField } from '@mui/material' +import { Box, TextField } from '@mui/material' import { createTheme, styled } from '@mui/material/styles' const theme = createTheme({ @@ -18,6 +18,14 @@ export const CloseButton = styled(Close)(() => ({ cursor: 'pointer', })) +export const CloseBox = styled(Box)(() => ({ + position: 'absolute', + top: 15, + right: 20, +})) + +export const CloseBoxMobile = styled(Box)(() => ({})) + export const CardContainer = styled('div')(() => ({ padding: '20px 20px', diff --git a/apps/storefront/src/pages/accountSetting/AccountSetting.tsx b/apps/storefront/src/pages/accountSetting/AccountSetting.tsx index fba0751b..b819325e 100644 --- a/apps/storefront/src/pages/accountSetting/AccountSetting.tsx +++ b/apps/storefront/src/pages/accountSetting/AccountSetting.tsx @@ -134,17 +134,18 @@ function AccountSetting() { contactInformationTranslatedLabels.forEach( (element: { fieldId: string; label: string }) => { - if (element.fieldId === 'field_first_name') { - element.label = b3Lang('accountSettings.form.firstName') + const currentElement = element + if (currentElement.fieldId === 'field_first_name') { + currentElement.label = b3Lang('accountSettings.form.firstName') } - if (element.fieldId === 'field_last_name') { - element.label = b3Lang('accountSettings.form.lastName') + if (currentElement.fieldId === 'field_last_name') { + currentElement.label = b3Lang('accountSettings.form.lastName') } - if (element.fieldId === 'field_email') { - element.label = b3Lang('accountSettings.form.email') + if (currentElement.fieldId === 'field_email') { + currentElement.label = b3Lang('accountSettings.form.email') } - if (element.fieldId === 'field_phone_number') { - element.label = b3Lang('accountSettings.form.phoneNumber') + if (currentElement.fieldId === 'field_phone_number') { + currentElement.label = b3Lang('accountSettings.form.phoneNumber') } } ) @@ -167,7 +168,8 @@ function AccountSetting() { const passwordModifiedTranslatedFields = JSON.parse( JSON.stringify(passwordModified) ).map((element: { label: string; idLang: string }) => { - element.label = b3Lang(element.idLang) + const passwordField = element + passwordField.label = b3Lang(element.idLang) return element }) diff --git a/apps/storefront/src/pages/accountSetting/utils.ts b/apps/storefront/src/pages/accountSetting/utils.ts index 29eb6377..bc812bfb 100644 --- a/apps/storefront/src/pages/accountSetting/utils.ts +++ b/apps/storefront/src/pages/accountSetting/utils.ts @@ -107,30 +107,32 @@ export const initB2BInfo = ( additionalInformation: Partial[] ) => { contactInformation.forEach((item: Partial) => { + const contactItem = item if (deCodeField(item?.name || '') === 'first_name') { - item.default = accountSettings.firstName + contactItem.default = accountSettings.firstName } if (deCodeField(item?.name || '') === 'last_name') { - item.default = accountSettings.lastName + contactItem.default = accountSettings.lastName } if (deCodeField(item?.name || '') === 'phone') { - item.default = accountSettings.phoneNumber + contactItem.default = accountSettings.phoneNumber } if (deCodeField(item?.name || '') === 'email') { - item.default = accountSettings.email - item.validate = emailValidate + contactItem.default = accountSettings.email + contactItem.validate = emailValidate } }) accountB2BFormFields.forEach((item: Partial) => { + const formField = item if (item.name === 'role') { - item.default = accountSettings.role - item.muiSelectProps = { + formField.default = accountSettings.role + formField.muiSelectProps = { disabled: true, } } else if (item.name === 'company') { - item.default = accountSettings.company - item.disabled = true + formField.default = accountSettings.company + formField.disabled = true } }) @@ -138,7 +140,8 @@ export const initB2BInfo = ( const formFields = (accountSettings?.formFields || []).find( (field: Partial) => field.name === item.bcLabel ) - if (formFields) item.default = formFields.value + const infoItem = item + if (formFields) infoItem.default = formFields.value }) return [ @@ -154,21 +157,22 @@ export const initBcInfo = ( additionalInformation: Partial[] ) => { contactInformation.forEach((item: Partial) => { + const contactInfoItem = item if (deCodeField(item?.name || '') === 'first_name') { - item.default = accountSettings.firstName + contactInfoItem.default = accountSettings.firstName } if (deCodeField(item?.name || '') === 'last_name') { - item.default = accountSettings.lastName + contactInfoItem.default = accountSettings.lastName } if (deCodeField(item?.name || '') === 'phone') { - item.default = accountSettings.phoneNumber + contactInfoItem.default = accountSettings.phoneNumber } if (deCodeField(item?.name || '') === 'email') { - item.default = accountSettings.email - item.validate = emailValidate + contactInfoItem.default = accountSettings.email + contactInfoItem.validate = emailValidate } if (deCodeField(item?.name || '') === 'company') { - item.default = accountSettings.company + contactInfoItem.default = accountSettings.company } }) @@ -176,7 +180,8 @@ export const initBcInfo = ( const formFields = (accountSettings?.formFields || []).find( (field: Partial) => field.name === item.bcLabel ) - if (formFields) item.default = formFields.value + const infoItem = item + if (formFields) infoItem.default = formFields.value }) return [...contactInformation, ...additionalInformation] diff --git a/apps/storefront/src/pages/address/Address.tsx b/apps/storefront/src/pages/address/Address.tsx index b3c99c2c..7467f846 100644 --- a/apps/storefront/src/pages/address/Address.tsx +++ b/apps/storefront/src/pages/address/Address.tsx @@ -244,7 +244,8 @@ function Address() { ) translatedFilterFormConfig.map((element: { label: string; idLang: any }) => { - element.label = b3Lang(element.idLang) + const item = element + item.label = b3Lang(element.idLang) return element }) diff --git a/apps/storefront/src/pages/address/components/AddressForm.tsx b/apps/storefront/src/pages/address/components/AddressForm.tsx index 936bfad8..d3b20ca9 100644 --- a/apps/storefront/src/pages/address/components/AddressForm.tsx +++ b/apps/storefront/src/pages/address/components/AddressForm.tsx @@ -326,18 +326,19 @@ function AddressForm( ) => { if (type === 'add' && originAddressFields.length > 0) { allAddressFields.forEach((field: CustomFieldItems) => { + const addressField = field if (field.custom) { if (isB2BUser) { const originFields = originAddressFields.filter( (item: CustomFieldItems) => item.name === field.name )[0] - field.default = originFields.default || '' + addressField.default = originFields.default || '' } else { const originFields = originAddressFields.filter( (item: CustomFieldItems) => item.name === field.name || item.bcLabel === field.bcLabel )[0] - field.default = originFields.default || '' + addressField.default = originFields.default || '' } } }) @@ -378,6 +379,7 @@ function AddressForm( }) allAddressFields.forEach((field: CustomFieldItems) => { + const currentField = field if (field.custom && extraFields.length > 0) { if (isB2BUser) { const name = deCodeField(field.name) @@ -391,10 +393,10 @@ function AddressForm( if (currentExtraField) { setValue(field.name, currentExtraField.fieldValue || '') - field.default = currentExtraField.fieldValue || '' + currentField.default = currentExtraField.fieldValue || '' } else { setValue(field.name, '') - field.default = originFields.default + currentField.default = originFields.default } } else { const currentExtraField = extraFields.filter( @@ -410,11 +412,11 @@ function AddressForm( if (currentExtraField) { setValue(field.name, currentExtraField.fieldValue || '') - field.default = + currentField.default = currentExtraField.fieldValue || originFields.default } else { setValue(field.name, '') - field.default = originFields.default + currentField.default = originFields.default } } } else if (field.name === 'country') { @@ -425,11 +427,11 @@ function AddressForm( const { states } = currentCountry[0] if (states.length > 0) { - field.options = states - field.fieldType = 'dropdown' + currentField.options = states + currentField.fieldType = 'dropdown' } else { - field.options = [] - field.fieldType = 'text' + currentField.options = [] + currentField.fieldType = 'text' } } } else { @@ -488,8 +490,18 @@ function AddressForm( const translatedAddressFields = JSON.parse(JSON.stringify(addressFields)) translatedAddressFields.forEach( - (element: { label: string; idLang: string }) => { - element.label = b3Lang(element.idLang) + (element: { + label: string + idLang: string + fieldId: string + default: string + }) => { + const translatedFieldElement = element + translatedFieldElement.label = b3Lang(element.idLang) || element.label + + if (!isB2BUser && element.fieldId === 'field_21') { + translatedFieldElement.default = '' + } return element } diff --git a/apps/storefront/src/pages/address/shared/getAddressFields.ts b/apps/storefront/src/pages/address/shared/getAddressFields.ts index ad2966b4..d99f1b22 100644 --- a/apps/storefront/src/pages/address/shared/getAddressFields.ts +++ b/apps/storefront/src/pages/address/shared/getAddressFields.ts @@ -11,8 +11,6 @@ import { import { b2bAddressFields } from './config' -// const addressExtraFieldsType = ['text', 'multiline', 'number', 'dropdown'] - export interface StateProps { stateCode: string stateName: string @@ -97,10 +95,11 @@ const convertExtraFields = ( const convertB2BExtraFields = getAccountFormFields(b2bExtraFields).address - convertB2BExtraFields.map((field: ExtraFieldsProp) => { + convertB2BExtraFields.map((extraField: ExtraFieldsProp) => { + const field = extraField field.custom = true - return field + return extraField }) return convertB2BExtraFields @@ -151,16 +150,17 @@ export const getAddressFields = async ( allAddressFields = bcAddressFields } - allAddressFields.map((field: CustomFieldItems) => { - if (field.name === 'country') { + allAddressFields.map((addressField: CustomFieldItems) => { + const field = addressField + if (addressField.name === 'country') { field.options = countries } - if (field.name === 'state') { + if (addressField.name === 'state') { field.fieldType = 'text' } - return field + return addressField }) return allAddressFields diff --git a/apps/storefront/src/pages/invoice/Invoice.tsx b/apps/storefront/src/pages/invoice/Invoice.tsx index 023c5864..09f1d463 100644 --- a/apps/storefront/src/pages/invoice/Invoice.tsx +++ b/apps/storefront/src/pages/invoice/Invoice.tsx @@ -436,15 +436,17 @@ function Invoice() { const invoicesList: InvoiceListNode[] = edges if (type === InvoiceListType.DETAIL && invoicesList.length) { - invoicesList.forEach((item: InvoiceListNode) => { + invoicesList.forEach((invoice: InvoiceListNode) => { + const item = invoice item.node.isCollapse = true }) } - invoicesList.forEach((item: InvoiceListNode) => { + invoicesList.forEach((invoiceNode: InvoiceListNode) => { const { node: { openBalance }, - } = item + } = invoiceNode + const item = invoiceNode item.node.disableCurrentCheckbox = false openBalance.value = (+openBalance.value).toFixed(decimalPlaces) @@ -730,12 +732,16 @@ function Invoice() { }, [checkedArr, filterData]) const translatedFilterFormConfigs = filterFormConfig.map((element) => { + const config = element if (element.name === 'status') { - element.label = b3Lang(filterFormConfigsTranslationVariables.status) + config.label = b3Lang(filterFormConfigsTranslationVariables.status) } - element.options = element.options.map((option) => { - option.label = b3Lang(filterFormConfigsTranslationVariables[option.key]) + config.options = element.options.map((option) => { + const elementOption = option + elementOption.label = b3Lang( + filterFormConfigsTranslationVariables[option.key] + ) return option }) diff --git a/apps/storefront/src/pages/login/Login.tsx b/apps/storefront/src/pages/login/Login.tsx index d004304a..2aab03b0 100644 --- a/apps/storefront/src/pages/login/Login.tsx +++ b/apps/storefront/src/pages/login/Login.tsx @@ -23,6 +23,7 @@ import { b2bLogin, bcLogoutLogin, customerLoginAPI } from '@/shared/service/bc' import { B3SStorage, getCurrentCustomerInfo, + loginjump, logoutSession, snackbar, storeHash, @@ -143,6 +144,9 @@ export default function Login(props: RegisteredProps) { if (loginFlag) setLoginFlag(loginFlag) const isLogout = B3SStorage.get('isLogout') === '1' + if (loginFlag === '7') { + snackbar.error(b3Lang('login.loginText.invoiceErrorTip')) + } if (loginFlag === '3' && !isLogout) { const { result } = (await bcLogoutLogin()).data.logout @@ -289,7 +293,13 @@ export default function Login(props: RegisteredProps) { if (info?.userType === 3 && info?.role === 3) { navigate('/dashboard') - } else if (info?.role === 2) { + return + } + const isLoginLandLocation = loginjump(navigate) + + if (!isLoginLandLocation) return + + if (info?.role === 2) { navigate('/shoppingLists') } else { navigate('/orders') @@ -339,31 +349,51 @@ export default function Login(props: RegisteredProps) { margin: '30px 0 0 0', }} > - - {tipInfo(flag, loginAccount?.emailAddress || '')} - + {tipInfo(flag, loginAccount?.emailAddress) && ( + + {tipInfo(flag, loginAccount?.emailAddress || '')} + + )} )} + {logo && loginInfo?.displayStoreLogo && ( + + + { + window.location.href = '/' + }} + > + {b3Lang('login.registerLogo')} + + + + )} + {loginInfo.widgetHeadText && ( + + )} - {loginInfo.widgetHeadText && ( - - )} - {logo && loginInfo?.displayStoreLogo && ( - - - { - window.location.href = '/' - }} - > - {b3Lang('login.registerLogo')} - - - - )} - - {loginInfo.widgetFooterText && ( - - )} + {loginInfo.widgetFooterText && ( + + )} )} diff --git a/apps/storefront/src/pages/login/LoginForm.tsx b/apps/storefront/src/pages/login/LoginForm.tsx index c770c889..ec722dc7 100644 --- a/apps/storefront/src/pages/login/LoginForm.tsx +++ b/apps/storefront/src/pages/login/LoginForm.tsx @@ -68,45 +68,45 @@ function LoginForm(props: LoginFormProps) { }, }} > - - - - - {loginBtn} - +
+ gotoForgotPassword()} > - {b3Lang('login.loginText.forgotPasswordText')} + + {loginBtn} + + gotoForgotPassword()} + > + {b3Lang('login.loginText.forgotPasswordText')} + - +
) diff --git a/apps/storefront/src/pages/login/config.ts b/apps/storefront/src/pages/login/config.ts index 419bd2f1..02701e21 100644 --- a/apps/storefront/src/pages/login/config.ts +++ b/apps/storefront/src/pages/login/config.ts @@ -76,6 +76,7 @@ export const getLoginFields = ( xs: 12, variant: 'filled', validate: validatorRules(['email']), + isAutoComplete: true, }, { name: 'password', @@ -87,6 +88,7 @@ export const getLoginFields = ( variant: 'filled', isEnterTrigger: true, handleEnterClick: submitLoginFn, + isAutoComplete: true, }, ] diff --git a/apps/storefront/src/pages/order/Order.tsx b/apps/storefront/src/pages/order/Order.tsx index 369ae370..f177f850 100644 --- a/apps/storefront/src/pages/order/Order.tsx +++ b/apps/storefront/src/pages/order/Order.tsx @@ -108,14 +108,16 @@ function Order({ isCompanyOrder = false }: OrderProps) { setOrderStatuses(orderStatuses[orderStatusesName]) const filterInfoWithTranslatedLabel = filterInfo.map((element) => { - element.label = b3Lang(element.idLang) + const translatedElement = element + translatedElement.label = b3Lang(element.idLang) if (element.name === 'orderStatus') { - element.options = element.options.map( + translatedElement.options = element.options.map( (option: { customLabel: string; systemLabel: string }) => { const optionLabel = orderStatusTranslationVariables[option.systemLabel] - option.customLabel = b3Lang(optionLabel) + const elementOption = option + elementOption.customLabel = b3Lang(optionLabel) return option } diff --git a/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx b/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx index 37d3c37e..59310631 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderCheckboxProduct.tsx @@ -117,19 +117,21 @@ export default function OrderCheckboxProduct(props: OrderCheckboxProductProps) { const handleProductQuantityChange = (product: EditableProductItem) => (e: ChangeEvent) => { + const element = product const valueNum = e.target.value if (+valueNum >= 0 && +valueNum <= 1000000) { - product.editQuantity = valueNum + element.editQuantity = valueNum if (type === 'return') { if (+valueNum > +product.quantity) { - product.editQuantity = product.quantity + element.editQuantity = product.quantity snackbar.error( b3Lang( 'purchasedProducts.error.returnedQuantityShouldBeWithinThePurchase' ) ) } else { - returnList.forEach((item) => { + returnList.forEach((listItem) => { + const item = listItem if (item.returnId === product.id) { item.returnQty = +valueNum } @@ -148,8 +150,9 @@ export default function OrderCheckboxProduct(props: OrderCheckboxProductProps) { } const handleNumberInputBlur = (product: EditableProductItem) => () => { + const editableProduct = product if (!product.editQuantity || +product.editQuantity === 0) { - product.editQuantity = '1' + editableProduct.editQuantity = '1' onProductChange([...products]) } } diff --git a/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx b/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx index a8db7c6e..3856c911 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderDialog.tsx @@ -122,8 +122,9 @@ export default function OrderDialog({ return } const transformedData = returnArr.reduce((result, item) => { + const resultedData = result const key = `return_qty[${item.returnId}]` - result[key] = item.returnQty + resultedData[key] = item.returnQty return result }, returnReason) transformedData.authenticity_token = getXsrfToken() @@ -250,6 +251,7 @@ export default function OrderDialog({ optionId: option.product_option_id, optionValue: option.value, })), + allOptions: product.product_options, }) skus.push(product.sku) diff --git a/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx b/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx index 65a26bff..b16e9040 100644 --- a/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx +++ b/apps/storefront/src/pages/orderDetail/components/OrderShipping.tsx @@ -39,7 +39,8 @@ export default function OrderShipping() { const tracking = getTracking(trackingNumber) if (tracking) { const { trackingUrl = '' } = tracking - if (trackingUrl) item.tracking_link = trackingUrl + const shippingItem = item + if (trackingUrl) shippingItem.tracking_link = trackingUrl } }) } diff --git a/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts b/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts index b1e0bdd5..a427b6a9 100644 --- a/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts +++ b/apps/storefront/src/pages/orderDetail/shared/B2BOrderData.ts @@ -55,7 +55,8 @@ const getOrderShipping = (data: B2BOrderData) => { ], notShip: { itemsInfo: products.filter((product: OrderProductItem) => { - product.not_shipping_number = + const orderProduct = product + orderProduct.not_shipping_number = product.quantity - product.quantity_shipped return ( product.quantity > product.quantity_shipped && diff --git a/apps/storefront/src/pages/quickorder/components/QuickOrderFooter.tsx b/apps/storefront/src/pages/quickorder/components/QuickOrderFooter.tsx index 9ef9d872..a149bfd4 100644 --- a/apps/storefront/src/pages/quickorder/components/QuickOrderFooter.tsx +++ b/apps/storefront/src/pages/quickorder/components/QuickOrderFooter.tsx @@ -196,6 +196,7 @@ function QuickOrderFooter(props: QuickOrderFooterProps) { lineItems.push({ optionSelections: options, + allOptions: optionList, productId: parseInt(currentInventoryInfo.product_id, 10) || 0, quantity, variantId: parseInt(currentInventoryInfo.variant_id, 10) || 0, diff --git a/apps/storefront/src/pages/quickorder/components/QuickOrderPad.tsx b/apps/storefront/src/pages/quickorder/components/QuickOrderPad.tsx index ecf337fa..5e369c55 100644 --- a/apps/storefront/src/pages/quickorder/components/QuickOrderPad.tsx +++ b/apps/storefront/src/pages/quickorder/components/QuickOrderPad.tsx @@ -98,7 +98,7 @@ export default function QuickOrderPad(props: QuickOrderPadProps) { }} > {b3Lang('purchasedProducts.quickOrderPad.notEnoughStock', { - sku: data.variantSku, + variantSku: data.variantSku, })}

{b3Lang('purchasedProducts.quickOrderPad.outOfStockSku', { - sku: outOfStock, + outOfStock, })}

{ snackbar.error( b3Lang('purchasedProducts.quickOrderPad.maxQuantityMessage', { - minQuantity: data.maxQuantity, + maxQuantity: data.maxQuantity, sku: data.variantSku, }), { @@ -365,7 +367,7 @@ export default function QuickOrderPad(props: QuickOrderPadProps) { if (+orderQuantityMaximum > 0 && +quantity > +orderQuantityMaximum) { snackbar.error( - b3Lang('purchasedProducts.quickOrderPad.minQuantityMessage', { + b3Lang('purchasedProducts.quickOrderPad.maxQuantityMessage', { maxQuantity: orderQuantityMaximum, sku: productSku, }), diff --git a/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx b/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx index b5d98b0b..9540e6e2 100644 --- a/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx +++ b/apps/storefront/src/pages/quickorder/components/QuickorderTable.tsx @@ -327,7 +327,8 @@ function QuickorderTable({ const currentProduct = cacheProductList.find( (item: CustomFieldItems) => item.node.variantId === row.variantId && - item.node.productId === row.productId + item.node.productId === row.productId && + item.node.id === row.id ) if (currentProduct && currentProduct.node) { diff --git a/apps/storefront/src/pages/quote/QuoteDetail.tsx b/apps/storefront/src/pages/quote/QuoteDetail.tsx index 6691b4f6..bd89bf0f 100644 --- a/apps/storefront/src/pages/quote/QuoteDetail.tsx +++ b/apps/storefront/src/pages/quote/QuoteDetail.tsx @@ -207,6 +207,7 @@ function QuoteDetail() { const newProductsSearch = conversionProductsList(productsSearch) listProducts.forEach((item) => { + const listProduct = item const productInfo = newProductsSearch.find( (search: CustomFieldItems) => { const { id: productId } = search @@ -215,7 +216,7 @@ function QuoteDetail() { } ) - item.productsSearch = productInfo || {} + listProduct.productsSearch = productInfo || {} }) return listProducts diff --git a/apps/storefront/src/pages/quote/QuoteDraft.tsx b/apps/storefront/src/pages/quote/QuoteDraft.tsx index fb7b7511..0709b60d 100644 --- a/apps/storefront/src/pages/quote/QuoteDraft.tsx +++ b/apps/storefront/src/pages/quote/QuoteDraft.tsx @@ -320,7 +320,11 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { const isComplete = Object.keys(saveInfo.contactInfo).every( (key: string) => { - if (key === 'phoneNumber' || key === 'companyName') { + if ( + key === 'phoneNumber' || + key === 'companyName' || + key === 'quoteTitle' + ) { return true } return !!saveInfo.contactInfo[key] @@ -356,6 +360,7 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { list.forEach((item: any) => { let additionalCalculatedPriceTax = 0 let additionalCalculatedPrice = 0 + const listItem = item if (item.node.additionalCalculatedPrices?.length) { item.node.additionalCalculatedPrices.forEach((item: any) => { @@ -364,8 +369,8 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { }) } - item.node.basePrice = +item.node.basePrice + additionalCalculatedPrice - item.node.tax = +item.node.tax + additionalCalculatedPriceTax + listItem.node.basePrice = +item.node.basePrice + additionalCalculatedPrice + listItem.node.tax = +item.node.tax + additionalCalculatedPriceTax }) return { @@ -412,6 +417,10 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { const info = B3LStorage.get('MyQuoteInfo') const contactInfo = info?.contactInfo || {} + const quoteTitle = contactInfo?.quoteTitle || '' + + if ('quoteTitle' in contactInfo) delete contactInfo.quoteTitle + const isComplete = Object.keys(contactInfo).every((key: string) => { if (key === 'phoneNumber' || key === 'companyName') { return true @@ -544,6 +553,7 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { subtotal: allPrice.toFixed(decimalPlaces), companyId: isB2BUser ? companyB2BId || salesRepCompanyId : '', storeHash, + quoteTitle, discount: '0.00', channelId, userEmail: emailAddress, @@ -671,7 +681,13 @@ function QuoteDraft({ setOpenPage }: QuoteDraftProps) { marginRight: '0.5rem', }} /> -

{backText()}

+

+ {backText()} +

{ variant: 'filled', size: 'small', }, + { + name: 'quoteTitle', + label: b3Lang('quoteDraft.contactInfo.quoteTitle'), + required: false, + default: '', + fieldType: 'text', + xs: isMobile ? 12 : 6, + variant: 'filled', + size: 'small', + }, ] return contactInfo diff --git a/apps/storefront/src/pages/quote/components/QuoteDetailHeader.tsx b/apps/storefront/src/pages/quote/components/QuoteDetailHeader.tsx index 4e7e82cb..f5bb7d05 100644 --- a/apps/storefront/src/pages/quote/components/QuoteDetailHeader.tsx +++ b/apps/storefront/src/pages/quote/components/QuoteDetailHeader.tsx @@ -101,6 +101,7 @@ function QuoteDetailHeader(props: QuoteDetailHeaderProps) {

{b3Lang('quoteDetail.header.backToQuoteLists')} diff --git a/apps/storefront/src/pages/quote/components/QuoteInfo.tsx b/apps/storefront/src/pages/quote/components/QuoteInfo.tsx index 57922e5c..6712f062 100644 --- a/apps/storefront/src/pages/quote/components/QuoteInfo.tsx +++ b/apps/storefront/src/pages/quote/components/QuoteInfo.tsx @@ -25,6 +25,7 @@ const contactInfoKeys: string[] = [ 'email', 'companyName', 'phoneNumber', + 'quoteTitle', ] const addressVerifyKeys: string[] = [ @@ -61,6 +62,7 @@ interface QuoteInfoItemProps { function QuoteInfoItem({ flag, title, info, status }: QuoteInfoItemProps) { const keyTable = flag === 'info' ? contactInfoKeys : addressKeys const [isMobile] = useMobile() + const b3Lang = useB3Lang() const noAddresssText = status === 'Draft' @@ -97,6 +99,24 @@ function QuoteInfoItem({ flag, title, info, status }: QuoteInfoItemProps) { {(isComplete || flag === 'info') && JSON.stringify(info) !== '{}' && keyTable.map((list: Keys) => { + if (list === 'quoteTitle') { + return status === 'Draft' ? ( + + {`${b3Lang('quoteDraft.quoteInfo.quoteTitle')}${ + info[list] || '' + }`} + + ) : ( + '' + ) + } + if (typeof list === 'string') { return ( @@ -133,6 +153,7 @@ function QuoteInfo({ }: InfoProps) { const b3Lang = useB3Lang() const [isMobile] = useMobile() + return ( diff --git a/apps/storefront/src/pages/quote/components/QuoteTable.tsx b/apps/storefront/src/pages/quote/components/QuoteTable.tsx index 9350575b..74f05f94 100644 --- a/apps/storefront/src/pages/quote/components/QuoteTable.tsx +++ b/apps/storefront/src/pages/quote/components/QuoteTable.tsx @@ -135,8 +135,9 @@ function QuoteTable(props: ShoppingDetailTableProps, ref: Ref) { const listItems = paginationTableRef.current?.getList() || [] const newListItems = listItems?.map((item: ListItemProps) => { const { node } = item + const listItem = item if (node?.id === (product as CustomFieldItems).id) { - ;(item as CustomFieldItems).node = product + ;(listItem as CustomFieldItems).node = product } return item @@ -277,14 +278,15 @@ function QuoteTable(props: ShoppingDetailTableProps, ref: Ref) { productsSearch: { variants }, basePrice, } = product.node + const newProduct = product const variantItem = variants.find( (item: CustomFieldItems) => item.sku === variantSku ) - product.node.id = optionsProductId + newProduct.node.id = optionsProductId - product.node.basePrice = basePrice - product.node.tax = + newProduct.node.basePrice = basePrice + newProduct.node.tax = variantItem.bc_calculated_price.tax_inclusive - variantItem.bc_calculated_price.tax_exclusive }) @@ -294,8 +296,9 @@ function QuoteTable(props: ShoppingDetailTableProps, ref: Ref) { const b2bQuoteDraftList = B3LStorage.get('b2bQuoteDraftList') || [] b2bQuoteDraftList.forEach((item: CustomFieldItems) => { + const listItem = item if (item.node.id === optionsProductId) { - item.node = newProducts[0]?.node || {} + listItem.node = newProducts[0]?.node || {} } }) diff --git a/apps/storefront/src/pages/registered/RegisterComplete.tsx b/apps/storefront/src/pages/registered/RegisterComplete.tsx index c189e9c8..320c016d 100644 --- a/apps/storefront/src/pages/registered/RegisterComplete.tsx +++ b/apps/storefront/src/pages/registered/RegisterComplete.tsx @@ -1,17 +1,8 @@ -import { - MouseEvent, - useContext, - useEffect, - useState, - // useMemo, -} from 'react' +import { MouseEvent, useContext, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { useB3Lang } from '@b3/lang' import { Alert, Box } from '@mui/material' -// import { -// Captcha, -// } from '@/components/form' import { B3CustomForm } from '@/components' import { getContrastColor } from '@/components/outSideComponents/utils/b3CustomStyles' import { CustomStyleContext } from '@/shared/customStyleButtton' @@ -233,8 +224,6 @@ export default function RegisterComplete(props: RegisterCompleteProps) { b2bFields.extraFields = extraFields } - // b2bFields.companyEmail = data.email || enterEmail - // address Field const addressBasicInfo = addressBasicList.filter((list) => !list.custom) || [] @@ -295,12 +284,13 @@ export default function RegisterComplete(props: RegisterCompleteProps) { ) const fileList = fileResponse.reduce((fileList: any, res: any) => { + let list = fileList if (res.code === 200) { const newData = { ...res.data, } newData.fileSize = newData.fileSize ? `${newData.fileSize}` : '' - fileList = [...fileList, newData] + list = [...fileList, newData] } else { throw ( res.data.errMsg || @@ -308,7 +298,7 @@ export default function RegisterComplete(props: RegisterCompleteProps) { b3Lang('intl.global.fileUpload.fileUploadFailure') ) } - return fileList + return list }, []) return fileList @@ -321,8 +311,9 @@ export default function RegisterComplete(props: RegisterCompleteProps) { const saveRegisterPassword = (data: CustomFieldItems) => { const newPasswordInformation = passwordInformation.map( (field: RegisterFields) => { + const registerField = field if (accountType === '1') { - field.default = data[field.name] || field.default + registerField.default = data[field.name] || field.default } return field } @@ -330,8 +321,9 @@ export default function RegisterComplete(props: RegisterCompleteProps) { const newBcPasswordInformation = bcPasswordInformation.map( (field: RegisterFields) => { + const registerField = field if (accountType === '2') { - field.default = data[field.name] || field.default + registerField.default = data[field.name] || field.default } return field diff --git a/apps/storefront/src/pages/registered/Registered.tsx b/apps/storefront/src/pages/registered/Registered.tsx index 555e55b7..06be88ac 100644 --- a/apps/storefront/src/pages/registered/Registered.tsx +++ b/apps/storefront/src/pages/registered/Registered.tsx @@ -21,7 +21,7 @@ import { bcLogin, } from '@/shared/service/bc' import { themeFrameSelector } from '@/store' -import { B3SStorage, getCurrentCustomerInfo } from '@/utils' +import { B3SStorage, getCurrentCustomerInfo, loginjump } from '@/utils' import { loginCheckout, LoginConfig } from '../login/config' @@ -105,8 +105,6 @@ function Registered(props: RegisteredProps) { }) } - // await getBCRegisterCustomFields() - const accountFormAllFields = formType.map((item: number) => getB2BAccountFormFields(item) ) @@ -116,12 +114,13 @@ function Registered(props: RegisteredProps) { const newB2bAccountFormFields: AccountFormFieldsItems[] = ( accountFormFields[1]?.accountFormFields || [] ).map((fields: AccountFormFieldsItems) => { + const formFields = fields if ( b2bAddressRequiredFields.includes(fields?.fieldId || '') && fields.groupId === 4 ) { - fields.isRequired = true - fields.visible = true + formFields.isRequired = true + formFields.visible = true } return fields @@ -141,9 +140,10 @@ function Registered(props: RegisteredProps) { ( addressFields: Partial ): Partial => { + const fields = addressFields if (addressFields.name === 'country') { - addressFields.options = countries - addressFields.replaceOptions = { + fields.options = countries + fields.replaceOptions = { label: 'countryName', value: 'countryName', } @@ -157,8 +157,15 @@ function Registered(props: RegisteredProps) { ( addressFields: Partial ): Partial => { + const addressFormFields = addressFields if (addressFields.name === 'country') { - addressFields.options = countries + addressFormFields.options = countries + const countryDefaultValue = countries.find( + (country: CustomFieldItems) => + country.countryName === addressFields.default + ) + addressFormFields.default = + countryDefaultValue?.countryCode || addressFields.default } return addressFields } @@ -294,12 +301,17 @@ function Registered(props: RegisteredProps) { await getCurrentCustomerInfo(globalDispatch) + clearRegisterInfo() + + const isLoginLandLocation = loginjump(navigate) + + if (!isLoginLandLocation) return + if (isCloseGotoBCHome) { window.location.href = '/' } else { navigate('/orders') } - clearRegisterInfo() } catch (error) { console.log(error) } diff --git a/apps/storefront/src/pages/registered/RegisteredAccount.tsx b/apps/storefront/src/pages/registered/RegisteredAccount.tsx index 9d839ee1..213fd28a 100644 --- a/apps/storefront/src/pages/registered/RegisteredAccount.tsx +++ b/apps/storefront/src/pages/registered/RegisteredAccount.tsx @@ -82,13 +82,14 @@ export default function RegisteredAccount(props: RegisteredAccountProps) { : bcAdditionalInformation || [] const newContactInformation = contactInformation?.map( - (info: CustomFieldItems) => { - if (info.fieldId === 'field_email' && accountType === '1') { + (contactInfo: CustomFieldItems) => { + const info = contactInfo + if (contactInfo.fieldId === 'field_email' && accountType === '1') { info.isTip = true info.tipText = 'This email will be used to sign in to your account' } - return info + return contactInfo } ) @@ -160,12 +161,13 @@ export default function RegisteredAccount(props: RegisteredAccountProps) { } const newContactInfo = contactInfo.map((item: RegisterFields) => { - item.default = data[item.name] || item.default + const newContactItem = item + newContactItem.default = data[item.name] || item.default if ( item.fieldId === 'field_email_marketing_newsletter' && item.fieldType === 'checkbox' ) { - item.isChecked = data[item.name].length > 0 + newContactItem.isChecked = data[item.name].length > 0 } return item }) @@ -175,7 +177,8 @@ export default function RegisteredAccount(props: RegisteredAccountProps) { newAdditionalInformation = ( additionalInfo as Array ).map((item: RegisterFields) => { - item.default = data[item.name] || item.default + const additionalInfoItem = item + additionalInfoItem.default = data[item.name] || item.default return item }) } diff --git a/apps/storefront/src/pages/registered/RegisteredBCToB2B.tsx b/apps/storefront/src/pages/registered/RegisteredBCToB2B.tsx index c6d793cc..41bb4a9c 100644 --- a/apps/storefront/src/pages/registered/RegisteredBCToB2B.tsx +++ b/apps/storefront/src/pages/registered/RegisteredBCToB2B.tsx @@ -12,13 +12,14 @@ import type { OpenPageState } from '@b3/hooks' import { useB3Lang } from '@b3/lang' import styled from '@emotion/styled' import { Alert, Box, ImageListItem } from '@mui/material' +import isEmpty from 'lodash-es/isEmpty' import { B3Card, B3CustomForm, B3Sping, CustomButton } from '@/components' import { getContrastColor } from '@/components/outSideComponents/utils/b3CustomStyles' import { useMobile } from '@/hooks' import { CustomStyleContext } from '@/shared/customStyleButtton' import { GlobaledContext } from '@/shared/global' -import { getCurrentCustomerInfo, storeHash } from '@/utils' +import { getCurrentCustomerInfo, loginjump, storeHash } from '@/utils' import { createB2BCompanyUser, @@ -158,12 +159,13 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { const newAccountFormFields: AccountFormFieldsItems[] = ( accountFormAllFields?.accountFormFields || [] ).map((fields: AccountFormFieldsItems) => { + const accountFields = fields if ( b2bAddressRequiredFields.includes(fields?.fieldId || '') && fields.groupId === 4 ) { - fields.isRequired = true - fields.visible = true + accountFields.isRequired = true + accountFields.visible = true } return fields @@ -179,9 +181,10 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { ( addressFields: Partial ): Partial => { + const fields = addressFields if (addressFields.name === 'country') { - addressFields.options = countries - addressFields.replaceOptions = { + fields.options = countries + fields.replaceOptions = { label: 'countryName', value: 'countryName', } @@ -202,13 +205,21 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { ( contactInformationField: Partial ): Partial => { - contactInformationField.disabled = true + const field = contactInformationField + field.disabled = true - contactInformationField.default = + field.default = customerInfo[ deCodeField(contactInformationField.name as string) ] || contactInformationField.default + if ( + contactInformationField.required && + !contactInformationField?.default + ) { + field.disabled = false + } + return contactInformationField } ) @@ -319,8 +330,13 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { ) const fileList = fileResponse.reduce((fileList: any, res: any) => { + let list = fileList if (res.code === 200) { - fileList = [...fileList, res.data] + const newData = { + ...res.data, + } + newData.fileSize = newData.fileSize ? `${newData.fileSize}` : '' + list = [...fileList, newData] } else { throw ( res.data.errMsg || @@ -328,7 +344,7 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { b3Lang('intl.global.fileUpload.fileUploadFailure') ) } - return fileList + return list }, []) return fileList @@ -372,8 +388,6 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { b2bFields.extraFields = extraFields } - // b2bFields.companyEmail = data.email - // address Field const addressBasicInfo = bcTob2bAddressBasicFields.filter( (list) => !list.custom @@ -457,8 +471,35 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { } } + const handleValidateAttachmentFiles = () => { + const formData = getValues() + const attachmentsFilesFiled = bcTob2bCompanyInformation.find( + (info) => info.fieldId === 'field_attachments' + ) + if ( + !isEmpty(attachmentsFilesFiled) && + attachmentsFilesFiled.required && + formData[attachmentsFilesFiled.name].length === 0 + ) { + setError(attachmentsFilesFiled.name, { + type: 'required', + message: b3Lang('global.validate.required', { + label: attachmentsFilesFiled.label, + }), + }) + + showLoading(false) + return true + } + + return false + } + const handleNext = (event: MouseEvent) => { + const hasAttachmentsFilesError = handleValidateAttachmentFiles() + handleSubmit(async (data: CustomFieldItems) => { + if (hasAttachmentsFilesError) return showLoading(true) try { @@ -471,7 +512,6 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { (list) => list.fieldType === 'files' ) const fileList = await getFileUrl(attachmentsList || [], data) - await getB2BFieldsValue(data, customerId, fileList) const isAuto = companyAutoApproval.enabled @@ -504,6 +544,10 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { } const handleFinish = () => { + const isLoginLandLocation = loginjump(navigate, true) + + if (!isLoginLandLocation) return + if (companyAutoApproval.enabled) { navigate('/orders') } else { @@ -621,6 +665,7 @@ export default function RegisteredBCToB2B(props: RegisteredProps) { control={control} getValues={getValues} setValue={setValue} + setError={setError} /> diff --git a/apps/storefront/src/pages/registered/RegisteredDetail.tsx b/apps/storefront/src/pages/registered/RegisteredDetail.tsx index bc3cdadd..0a6958e0 100644 --- a/apps/storefront/src/pages/registered/RegisteredDetail.tsx +++ b/apps/storefront/src/pages/registered/RegisteredDetail.tsx @@ -1,6 +1,8 @@ import { MouseEvent, useContext, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' +import { useB3Lang } from '@b3/lang' import { Alert, Box } from '@mui/material' +import isEmpty from 'lodash-es/isEmpty' import { B3CustomForm } from '@/components' import { getContrastColor } from '@/components/outSideComponents/utils/b3CustomStyles' @@ -19,6 +21,7 @@ interface RegisteredDetailProps { } export default function RegisteredDetail(props: RegisteredDetailProps) { + const b3Lang = useB3Lang() const { handleBack, handleNext, activeStep } = props const { state, dispatch } = useContext(RegisteredContext) @@ -136,7 +139,8 @@ export default function RegisteredDetail(props: RegisteredDetailProps) { formData: CustomFieldItems ) => formFields.map((field) => { - field.default = formData[field.name] || field.default + const item = field + item.default = formData[field.name] || field.default return field }) @@ -150,7 +154,8 @@ export default function RegisteredDetail(props: RegisteredDetailProps) { ...companyAttachment, ...addressBasicList, ].reduce((formValues: DetailsFormValues, field: RegisterFields) => { - formValues[field.name] = getValues(field.name) || field.default + const values = formValues + values[field.name] = getValues(field.name) || field.default return formValues }, {}) @@ -172,8 +177,37 @@ export default function RegisteredDetail(props: RegisteredDetailProps) { }) } + const handleValidateAttachmentFiles = () => { + if (accountType === '1') { + const formData = getValues() + const attachmentsFilesFiled = companyInformation.find( + (info) => info.fieldId === 'field_attachments' + ) + if ( + !isEmpty(attachmentsFilesFiled) && + attachmentsFilesFiled.required && + formData[attachmentsFilesFiled.name].length === 0 + ) { + setError(attachmentsFilesFiled.name, { + type: 'required', + message: b3Lang('global.validate.required', { + label: attachmentsFilesFiled.label, + }), + }) + + showLoading(false) + return true + } + } + + return false + } + const handleAccountToFinish = (event: MouseEvent) => { + const hasAttachmentsFilesError = handleValidateAttachmentFiles() + handleSubmit(async (data: CustomFieldItems) => { + if (hasAttachmentsFilesError) return showLoading(true) try { @@ -266,6 +300,7 @@ export default function RegisteredDetail(props: RegisteredDetailProps) { control={control} getValues={getValues} setValue={setValue} + setError={setError} /> ) : null} diff --git a/apps/storefront/src/pages/registered/config.ts b/apps/storefront/src/pages/registered/config.ts index 321a1ad0..46b1683a 100644 --- a/apps/storefront/src/pages/registered/config.ts +++ b/apps/storefront/src/pages/registered/config.ts @@ -5,9 +5,6 @@ import { getLineNumber, validatorRules } from '@/utils' const inputFormat = 'yyyy-MM-dd' -// export interface CustomFieldItems { -// [key: string]: any -// } export interface QuoteConfig { [key: string]: string } @@ -183,12 +180,17 @@ const classificationType = (item: CustomFieldItems) => { if (optionItems?.options) { optionItems?.options.forEach((option: any) => { + const optionValue = option if (option.value) { - option.value = option.label + optionValue.value = option.label } }) } + if (item.fieldId === 'field_country') { + optionItems.default = item.valueConfigs?.default || optionItems.default + } + return optionItems } @@ -251,8 +253,10 @@ export const conversionSigleItem = ( type: item.type || '', } + const customFieldItem = item + if (typeof item.fieldType === 'number') { - item.fieldType = companyExtraFieldsType[item.fieldType] + customFieldItem.fieldType = companyExtraFieldsType[item.fieldType] requiredItems.fieldType = item.fieldType } diff --git a/apps/storefront/src/pages/shoppingListDetails/components/ChooseOptionsDialog.tsx b/apps/storefront/src/pages/shoppingListDetails/components/ChooseOptionsDialog.tsx index fc3f748f..da46b280 100644 --- a/apps/storefront/src/pages/shoppingListDetails/components/ChooseOptionsDialog.tsx +++ b/apps/storefront/src/pages/shoppingListDetails/components/ChooseOptionsDialog.tsx @@ -23,7 +23,6 @@ import { currencyFormat, snackbar, } from '@/utils' -// import { } from '@/utils/b3Product/b3Product' import { getBCPrice, getProductInfoDisplayPrice, @@ -383,7 +382,8 @@ export default function ChooseOptionsDialog(props: ChooseOptionsDialogProps) { if (formFields.length > 0) { const defaultValues: SimpleObject = formFields.reduce( (value: SimpleObject, fields) => { - value[fields.name] = fields.default + const formFieldValue = value + formFieldValue[fields.name] = fields.default setValue(fields.name, fields.default) return value }, diff --git a/apps/storefront/src/pages/shoppingListDetails/components/QuickAdd.tsx b/apps/storefront/src/pages/shoppingListDetails/components/QuickAdd.tsx index 8ba2052e..c10a34eb 100644 --- a/apps/storefront/src/pages/shoppingListDetails/components/QuickAdd.tsx +++ b/apps/storefront/src/pages/shoppingListDetails/components/QuickAdd.tsx @@ -390,7 +390,7 @@ export default function QuickAdd(props: AddToListContentProps) { }) snackbar.error( - b3Lang('shoppingList.quickAdd.skuNotAddable', { + b3Lang('shoppingList.quickAdd.skuLimitQuantity', { numberLimit, }), { diff --git a/apps/storefront/src/pages/shoppingListDetails/components/ReAddToCart.tsx b/apps/storefront/src/pages/shoppingListDetails/components/ReAddToCart.tsx index 6539d364..360fe902 100644 --- a/apps/storefront/src/pages/shoppingListDetails/components/ReAddToCart.tsx +++ b/apps/storefront/src/pages/shoppingListDetails/components/ReAddToCart.tsx @@ -281,6 +281,7 @@ export default function ReAddToCart(props: ShoppingProductsProps) { ) const requestArr: Promise[] = [] newProduct.forEach((product) => { + const item = product const { node: { quantity }, minQuantity = 0, @@ -291,15 +292,15 @@ export default function ReAddToCart(props: ShoppingProductsProps) { const quantityNumber = parseInt(`${quantity}`, 10) || 0 if (minQuantity !== 0 && quantityNumber < minQuantity) { - product.node.quantity = minQuantity + item.node.quantity = minQuantity } else if (maxQuantity !== 0 && quantityNumber > maxQuantity) { - product.node.quantity = maxQuantity + item.node.quantity = maxQuantity } if (isStock !== '0' && stock && (quantity ? +quantity : 0) > stock) { - product.node.quantity = stock + item.node.quantity = stock } - product.isValid = true + item.isValid = true const qty = product?.node?.quantity ? +product.node.quantity : 0 diff --git a/apps/storefront/src/pages/shoppingListDetails/components/ShoppingDetailHeader.tsx b/apps/storefront/src/pages/shoppingListDetails/components/ShoppingDetailHeader.tsx index 91e72dc0..9b82cd0a 100644 --- a/apps/storefront/src/pages/shoppingListDetails/components/ShoppingDetailHeader.tsx +++ b/apps/storefront/src/pages/shoppingListDetails/components/ShoppingDetailHeader.tsx @@ -109,7 +109,7 @@ function ShoppingDetailHeader(props: ShoppingDetailHeaderProps) { sx={{ margin: 0, color: customColor, - m: '16px 0', + m: '0', }} > {openAPPParams.shoppingListBtn !== 'add' diff --git a/apps/storefront/src/pages/shoppingLists/AddEditShoppingLists.tsx b/apps/storefront/src/pages/shoppingLists/AddEditShoppingLists.tsx index 2ecac09e..05aef5a2 100644 --- a/apps/storefront/src/pages/shoppingLists/AddEditShoppingLists.tsx +++ b/apps/storefront/src/pages/shoppingLists/AddEditShoppingLists.tsx @@ -80,8 +80,9 @@ function AddEditShoppingLists( setAddUpdateLoading(true) try { const { description } = data + const submitData = data if (description.indexOf('\n') > -1) { - data.description = description.split('\n').join('\\n') + submitData.description = description.split('\n').join('\\n') } const params: Partial = { ...data, @@ -105,8 +106,6 @@ function AddEditShoppingLists( fn = isB2BUser ? duplicateB2BShoppingList : duplicateBcShoppingList params.sampleShoppingListId = editData?.id || 0 successTip = b3Lang('shoppingLists.duplicateSuccess') - - // params.status = +role === 2 ? 30 : editData?.status } else if (type === 'add') { if (isB2BUser) { params.status = +role === 2 ? 30 : 0 diff --git a/apps/storefront/src/pages/shoppingLists/ShoppingLists.tsx b/apps/storefront/src/pages/shoppingLists/ShoppingLists.tsx index cef87a04..92717b89 100644 --- a/apps/storefront/src/pages/shoppingLists/ShoppingLists.tsx +++ b/apps/storefront/src/pages/shoppingLists/ShoppingLists.tsx @@ -79,10 +79,12 @@ function ShoppingLists() { name: string options: any[] }) => { - element.label = b3Lang(element.idLang) + const translatedInfo = element + translatedInfo.label = b3Lang(element.idLang) if (element.name === 'status') { element.options?.map((option) => { - option.label = b3Lang(option.idLang) + const elementOption = option + elementOption.label = b3Lang(option.idLang) return option }) } diff --git a/apps/storefront/src/pages/usermanagement/Usermanagement.tsx b/apps/storefront/src/pages/usermanagement/Usermanagement.tsx index d6f2b882..a5b4941b 100644 --- a/apps/storefront/src/pages/usermanagement/Usermanagement.tsx +++ b/apps/storefront/src/pages/usermanagement/Usermanagement.tsx @@ -87,12 +87,14 @@ function Usermanagement() { const fiterMoreInfo = getFilterMoreList(b3Lang) const translatedFilterInfo = fiterMoreInfo.map((element) => { + const translatedItem = element const translatedOptions = element.options?.map((option) => { - option.label = b3Lang(option.idLang) + const elementOption = option + elementOption.label = b3Lang(option.idLang) return option }) - element.options = translatedOptions + translatedItem.options = translatedOptions return element }) diff --git a/apps/storefront/src/shared/routes/routes.ts b/apps/storefront/src/shared/routes/routes.ts index 476571fd..2fbfa7fa 100644 --- a/apps/storefront/src/shared/routes/routes.ts +++ b/apps/storefront/src/shared/routes/routes.ts @@ -294,6 +294,13 @@ const firstLevelRouting: RouteFirstLevelItem[] = [ isProvider: false, }, ] + +const denyInvoiceRoles = [4, 99, 100] + +const invoiceFlag = 'invoice?invoiceId' + +const { hash, pathname, href } = window.location + const getAllowedRoutes = (globalState: GlobalState): RouteItem[] => { const { isB2BUser, role, isAgenting, storefrontConfig, quoteConfig } = globalState @@ -388,7 +395,10 @@ const gotoAllowedAppPage = async ( gotoPage('/login?loginFlag=3&&closeIsLogout=1') return } - + if (denyInvoiceRoles.includes(role) && href.includes(invoiceFlag)) { + gotoPage('/login?loginFlag=7') + return + } try { const { data: { customer }, @@ -403,7 +413,6 @@ const gotoAllowedAppPage = async ( console.log(err) } - const { hash, pathname } = window.location let url = hash.split('#')[1] || '' if ( (!url && role !== 100 && pathname.includes('account.php')) || @@ -421,11 +430,12 @@ const gotoAllowedAppPage = async ( break } - const flag = routes.some( - (item: RouteItem) => - (matchPath(item.path, url) || url.includes('invoice?')) && - item.permissions.includes(role) - ) + const flag = routes.some((item: RouteItem) => { + if (matchPath(item.path, url) || url.includes(invoiceFlag)) { + return item.permissions.includes(role) + } + return false + }) const isFirstLevelFlag = firstLevelRouting.some( (item: RouteFirstLevelItem) => { diff --git a/apps/storefront/src/shared/service/b2b/graphql/quote.ts b/apps/storefront/src/shared/service/b2b/graphql/quote.ts index 840a1745..ab421073 100644 --- a/apps/storefront/src/shared/service/b2b/graphql/quote.ts +++ b/apps/storefront/src/shared/service/b2b/graphql/quote.ts @@ -138,7 +138,8 @@ const quoteCreate = (data: CustomFieldItems) => `mutation{ billingAddress: ${convertObjectToGraphql(data.billingAddress)} contactInfo: ${convertObjectToGraphql(data.contactInfo)} productList: ${convertArrayToGraphql(data.productList || [])}, - fileList: ${convertArrayToGraphql(data.fileList || [])} + fileList: ${convertArrayToGraphql(data.fileList || [])}, + quoteTitle: "${data.quoteTitle}" }) { quote{ id, diff --git a/apps/storefront/src/store/slices/global.ts b/apps/storefront/src/store/slices/global.ts index 02089a97..19826a19 100644 --- a/apps/storefront/src/store/slices/global.ts +++ b/apps/storefront/src/store/slices/global.ts @@ -64,6 +64,8 @@ export interface GlabolState { bcUrl?: string cartNumber?: number storeInfo?: StoreInfoProps + loginLandingLocation?: string + recordOpenHash?: string blockPendingQuoteNonPurchasableOOS?: GlobalBlockPendingQuoteNonPurchasableOOS } @@ -101,6 +103,8 @@ const initialState: GlabolState = { isEnableProduct: false, isEnableRequest: false, }, + loginLandingLocation: '0', + recordOpenHash: '', } export const glabolSlice = createSlice({ @@ -147,6 +151,9 @@ export const glabolSlice = createSlice({ ...(payload as GlobalBlockPendingQuoteNonPurchasableOOS), } }, + setLoginLandingLocation: (state, { payload }: PayloadAction) => { + state.loginLandingLocation = payload as Draft + }, setHeadLessBcUrl: (state, { payload }: PayloadAction) => { state.bcUrl = payload as Draft }, @@ -171,6 +178,7 @@ export const { setHeadLessBcUrl, setCartNumber, setStoreInfo, + setLoginLandingLocation, } = glabolSlice.actions export default glabolSlice.reducer diff --git a/apps/storefront/src/utils/b3Login.ts b/apps/storefront/src/utils/b3Login.ts new file mode 100644 index 00000000..ebba70d1 --- /dev/null +++ b/apps/storefront/src/utils/b3Login.ts @@ -0,0 +1,32 @@ +import { NavigateFunction } from 'react-router-dom' + +import { store } from '@/store' + +export const loginjump = ( + navigate: NavigateFunction, + isClearSesion = false +) => { + const { + global: { loginLandingLocation, recordOpenHash, setOpenPageFn }, + } = store.getState() + if (loginLandingLocation === '1' && !recordOpenHash) { + navigate('/') + setOpenPageFn({ + isOpen: false, + openUrl: '', + }) + if (isClearSesion) window.sessionStorage.clear() + window.location.reload() + return false + } + if (loginLandingLocation === '1' && recordOpenHash) { + const hash = recordOpenHash.split('#')[1] + navigate(hash) + + return false + } + + return true +} + +export default loginjump diff --git a/apps/storefront/src/utils/b3Product/b3Product.ts b/apps/storefront/src/utils/b3Product/b3Product.ts index 5de6fbf0..2a7da747 100644 --- a/apps/storefront/src/utils/b3Product/b3Product.ts +++ b/apps/storefront/src/utils/b3Product/b3Product.ts @@ -1,3 +1,4 @@ +import isEmpty from 'lodash-es/isEmpty' import { v1 as uuid } from 'uuid' import { @@ -278,7 +279,6 @@ const setItemProductPrice = (newListProducts: ListItemProps[]) => { taxClassId, }, } = item - const rate = getTaxRate(taxClassId) let singleCurrentPrice = currentProductPrices?.tax_exclusive || 0 @@ -1309,13 +1309,14 @@ export const getVariantInfoOOSAndPurchase = (productInfo: CustomFieldItems) => { const variantSku = newProductInfo?.variantSku || newProductInfo?.sku - const variants = newProductInfo?.productsSearch + const variants = !isEmpty(newProductInfo?.productsSearch) ? newProductInfo.productsSearch.variants - : newProductInfo.variants - - const variant = variants.find((item: Variant) => item.sku === variantSku) + : newProductInfo?.variants || [] - if (variant?.sku) { + const variant = variants + ? variants.find((item: Variant) => item.sku === variantSku) + : {} + if (variant && variant?.sku) { const { purchasing_disabled: purchasingDisabled, inventory_level: inventoryLevel, @@ -1374,12 +1375,15 @@ export const getVariantInfoDisplayPrice = ( const variantSku = option?.sku || newProductInfo?.variantSku || newProductInfo?.sku - const newVariants = newProductInfo?.productsSearch + const newVariants = !isEmpty(newProductInfo?.productsSearch) ? newProductInfo.productsSearch.variants - : newProductInfo.variants + : newProductInfo?.variants || [] + + const variant = newVariants + ? newVariants.find((item: Variant) => item.sku === variantSku) + : {} - const variant = newVariants.find((item: Variant) => item.sku === variantSku) - if (variant?.sku) { + if (variant && variant?.sku) { const { purchasing_disabled: purchasingDisabled, inventory_level: inventoryLevel, diff --git a/apps/storefront/src/utils/b3Product/shared/config.ts b/apps/storefront/src/utils/b3Product/shared/config.ts index 6880eb67..83bafac7 100644 --- a/apps/storefront/src/utils/b3Product/shared/config.ts +++ b/apps/storefront/src/utils/b3Product/shared/config.ts @@ -687,6 +687,7 @@ export const addlineItems = (products: ProductsProps[]) => { productId: node.productId, variantId: node.variantId, optionSelections: optionValue, + allOptions, } }) diff --git a/apps/storefront/src/utils/cartUtils.ts b/apps/storefront/src/utils/cartUtils.ts index afb109e8..983d5126 100644 --- a/apps/storefront/src/utils/cartUtils.ts +++ b/apps/storefront/src/utils/cartUtils.ts @@ -24,16 +24,27 @@ export const handleSplitOptionId = (id: string | number) => { const cartLineItems = (products: any) => { const items = products.map((product: any) => { - const { newSelectOptionList, quantity, optionSelections } = product + const { + newSelectOptionList, + quantity, + optionSelections, + allOptions = [], + } = product let options = [] options = newSelectOptionList || optionSelections - const selectedOptions = options.reduce( (a: any, c: any) => { const optionValue = parseInt(c.optionValue, 10) const splitOptionId = handleSplitOptionId(c.optionId) - - if (Number.isNaN(optionValue)) { + const productOption = allOptions.find((option: CustomFieldItems) => { + const id = option?.product_option_id || option?.id || '' + return id === splitOptionId + }) + if ( + Number.isNaN(optionValue) || + productOption?.type === 'text' || + productOption?.type === 'Text field' + ) { a.textFields.push({ optionEntityId: splitOptionId, text: c.optionValue, @@ -121,7 +132,7 @@ export const callCart = async ( : null const res = - cartInfo && cartInfo.data.site.cart + cartInfo && cartInfo?.data?.site?.cart ? await updateCart(cartInfo, lineItems, storePlatform) : await createNewShoppingCart(lineItems) diff --git a/apps/storefront/src/utils/index.ts b/apps/storefront/src/utils/index.ts index 016274bf..91ac82d2 100644 --- a/apps/storefront/src/utils/index.ts +++ b/apps/storefront/src/utils/index.ts @@ -64,6 +64,8 @@ export { setStorefrontConfig, } from './storefrontConfig' +export { loginjump } from './b3Login' + export { b2bPrintInvoice, B3LStorage, diff --git a/apps/storefront/src/utils/storefrontConfig.ts b/apps/storefront/src/utils/storefrontConfig.ts index a7540b53..57365c87 100644 --- a/apps/storefront/src/utils/storefrontConfig.ts +++ b/apps/storefront/src/utils/storefrontConfig.ts @@ -16,6 +16,7 @@ import { setBlockPendingAccountViewPrice, setBlockPendingQuoteNonPurchasableOOS, setEnteredInclusive, + setLoginLandingLocation, setShowInclusiveTaxPrice, setStoreInfo, setTaxZoneRates, @@ -145,6 +146,10 @@ const storeforntKeys: StoreforntKeysProps[] = [ key: 'quote_on_non_purchasable_product_page', name: 'quoteOnNonPurchasableProductPageBtn', }, + { + key: 'login_landing_location', + name: 'loginLandingLocation', + }, ] const getTemPlateConfig = async ( @@ -165,12 +170,13 @@ const getTemPlateConfig = async ( const storeforntKey: StoreforntKeysProps | undefined = storeforntKeys.find( (option) => option.key === item.key ) + const storefrontConfig = item if (!isEmpty(storeforntKey)) { if (storeforntKey.key === 'quote_logo') { logo = item.value } if (storeforntKey.key === 'quote_on_product_page') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, locationSelector: item.extraFields?.locationSelector || '.add-to-cart-buttons', @@ -180,13 +186,13 @@ const getTemPlateConfig = async ( } if (storeforntKey.key === 'quote_on_cart_page') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, classSelector: item.extraFields?.classSelector || 'button', } } if (storeforntKey.key === 'masquerade_button') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, color: item.extraFields?.color || '#ED6C02', location: item.extraFields?.location || ' bottomLeft', @@ -196,7 +202,7 @@ const getTemPlateConfig = async ( } if (storeforntKey.key === 'quote_floating_action_button') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, color: item.extraFields?.color || '#E00F36', location: item.extraFields?.location || ' bottomRight', @@ -206,7 +212,7 @@ const getTemPlateConfig = async ( } if (storeforntKey.key === 'shopping_list_on_product_page') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, locationSelector: item.extraFields?.locationSelector || '.add-to-cart-buttons', @@ -245,7 +251,7 @@ const getTemPlateConfig = async ( } if (storeforntKey.key === 'quote_on_non_purchasable_product_page') { - item.extraFields = { + storefrontConfig.extraFields = { ...item.extraFields, locationSelector: item.extraFields?.locationSelector || '.add-to-cart-buttons', @@ -262,6 +268,12 @@ const getTemPlateConfig = async ( ) } + if (storeforntKey.key === 'login_landing_location') { + store.dispatch( + setLoginLandingLocation(item?.extraFields?.location || '0') + ) + } + ;(obj as CustomFieldItems)[(storeforntKey as StoreforntKeysProps).name] = { ...item.extraFields, diff --git a/packages/global-b3/index.ts b/packages/global-b3/index.ts index 96a1b957..7591a888 100644 --- a/packages/global-b3/index.ts +++ b/packages/global-b3/index.ts @@ -79,6 +79,9 @@ const globalB3 = { setting: { b2b_url: 'https://staging-v2.bundleb2b.net', b2b_socket_url: 'https://staging-v2.bundleb2b.net', + store_hash: `1l3zp8c753`, + channel_id: 1, + b2b_client_id: 'r2x8j3tn54wduq47b4efct5tqxio5z2', }, ...localConfig(), ...(window?.B3CustomConfig || {}), diff --git a/packages/lang/locales/en.json b/packages/lang/locales/en.json index 474bdf97..3447a20a 100644 --- a/packages/lang/locales/en.json +++ b/packages/lang/locales/en.json @@ -518,6 +518,8 @@ "quoteDraft.contactInfo.email": "Email", "quoteDraft.contactInfo.companyName": "Company name", "quoteDraft.contactInfo.phone": "Phone", + "quoteDraft.contactInfo.quoteTitle": "Quote Title", + "quoteDraft.quoteInfo.quoteTitle": "Quote title:", "quoteDraft.contactInfo.emailExists": "Email already exists", "quoteDraft.contactInfo.contact": "Contact", "quoteDraft.quoteAddress.chooseFromSaved": "Choose from saved", @@ -641,6 +643,7 @@ "login.loginTipInfo.accountincorrect": "Your email address or password is incorrect. Please try again. If you've forgotten your sign in details, just click the 'Forgot your password?' link below.", "login.loginTipInfo.accountPrelaunch": "You can not login to the Buyer Portal while the store is in prelaunch or maintenance mode. Please set the store live, or login inside the customer admin panel.", "login.loginText.forgotPasswordText": "Forgot your password?", + "login.loginText.invoiceErrorTip": "Please log in to view your invoice", "login.loginText.deviceCrowdingLogIn": "The user has logged out, please log in again", "login.loginText.password": "Password", "login.registerLogo": "register Logo",