Skip to content

Commit

Permalink
Sprint/41 (#847)
Browse files Browse the repository at this point in the history
* feat: headless setup (#821)

* fix: remove dependency from current customer token (#757)

* fix: remove dependency from current customer token

* fix: remove storefrontLoginToken

* fix: make loginData required (#759)

* fix: detect if current customer token is present (#761)

* fix: detect if current customer token is present

* fix: handle errors properly

* feat: add headless function to get quote button info (#800)

* feat: add compatibility with local env

* feat(BUN-1630): use new format for storeBasicInfo query (#806)

* feat(BUN-1753): Prerequisites readme (#840)

* brian to main sprint 40 (#829)

* fix: change mui date select
https://bigc-b2b.atlassian.net/browse/BUN-1688

* feat: changing Password on Frontend Does Not Send Email Notification

* fix: remove order number limit
https://bigc-b2b.atlassian.net/browse/BUN-1646

* fix: save quote darft
https://bigc-b2b.atlassian.net/browse/BUN-1696

* fix: junior hide invioce
https://bigc-b2b.atlassian.net/browse/BUN-1690

* fix: shop-all can't load login and register
https://bigc-b2b.atlassian.net/browse/BUN-1701

* fix: merge remove quote line conflict

---------

Co-authored-by: kris-liu-smile <kris.liu@bundleb2b.net>

* feat: sprint 40 issues (#831)

* feat: quote remove line
https://bigc-b2b.atlassian.net/browse/BUN-1550

* fix: bun-1734 from sprit40

* fix: clear info after login out (#833)

* feat(BUN-1753): prereqs index

* feat(BUN-1754): Update README.md

---------

Co-authored-by: BrianJiang2021 <80307788+BrianJiang2021@users.noreply.github.com>
Co-authored-by: kris-liu-smile <kris.liu@bundleb2b.net>

* fix: login failure verification resolve conflicts

* feat: solve bc user logout login, b2b problems

* fix: shopping list duplicated button

* fix: accountsetting date is require error

* fix: shopping list translation
https://bigc-b2b.atlassian.net/browse/BUN-1716

* fix: multiple devices issues

* fix: sigle user login error

* fix: multi-device login

---------

Co-authored-by: bc-marco <109162781+bc-marco@users.noreply.github.com>
Co-authored-by: bc-victor <140021227+bc-victor@users.noreply.github.com>
Co-authored-by: kris-liu-smile <kris.liu@bundleb2b.net>
  • Loading branch information
4 people authored Nov 15, 2023
1 parent 38ca255 commit dd61634
Show file tree
Hide file tree
Showing 28 changed files with 323 additions and 222 deletions.
106 changes: 64 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# B2B Buyer Portal

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)
Expand All @@ -13,6 +13,23 @@ A monorepo frontend application designed for the BigCommerce B2B Edition Buyer p
- [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.

<img width="200" alt="image" src="https://github.com/B3BC/b2b-buyer-portal/assets/140021227/0d733ddb-e59c-4e5a-8801-4a744940d66b">

### Step 2: Enable B2B on Your Channel
Choose the channel where you wish to enable B2B functionality. Initially, B2B features can be activated on a single channel only.

<img width="480" alt="image" src="https://github.com/B3BC/b2b-buyer-portal/assets/140021227/b425115c-54d9-4382-9371-4e81888eb0af">

### 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

- **Monorepo Management:** Turborepo
Expand All @@ -24,7 +41,6 @@ A monorepo frontend application designed for the BigCommerce B2B Edition Buyer p

- **Application:** `/apps/storefront` - A next-gen B2B Edition storefront application.
- You can run multiple apps concurrently via turborepo [tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks).

- **Packages:**
- `/packages/eslint-config-b3` - Shared ESLint configurations.
- `/packages/tsconfig` - Shared TypeScript configurations.
Expand All @@ -45,60 +61,66 @@ A monorepo frontend application designed for the BigCommerce B2B Edition Buyer p
- **Node:** Ensure you have Node.js version >=18.0.0.
- **Package Manager:** This project uses Yarn v1.22.17.

## ⚙ Local Development
## ⚙ Local Development

1. Installation of Node and Yarn.
1. Installation of Node and Yarn.
- For Node, we recommend using [nvm](https://github.com/nvm-sh/nvm).
- Once Node is installed, you can install Yarn by using `npm i -g yarn`. If you'd rather use `pnpm`, visit this [guide](https://dev.to/andreychernykh/yarn-npm-to-pnpm-migration-guide-2n04).
2. Clone the repository.
3. Install dependencies using `yarn`.
4. Copy environment variables: `cp apps/storefront/.env-example apps/storefront/.env`.
5. Update the following values in `.env`:
- `VITE_B2B_URL`: The URL of the B2B Edition API.
- `VITE_B2B_SOCKET_URL`: The URL of the B2B Edition WebSocket API.
- `VITE_TRANSLATION_SERVICE_URL`: The URL of the translation service API.
- `VITE_CHANNEL_ID`: The ID of the channel to use for the storefront.
- `VITE_STORE_HASH`: The hash of the store to use for the storefront.
- `VITE_CATPCHA_SETKEY`: The reCAPTCHA site key (optional).
- `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.

- `VITE_B2B_URL`: The URL of the B2B Edition API.
- `VITE_B2B_SOCKET_URL`: The URL of the B2B Edition WebSocket API.
- `VITE_TRANSLATION_SERVICE_URL`: The URL of the translation service API.
- `VITE_CHANNEL_ID`: The ID of the channel to use for the storefront.
- `VITE_STORE_HASH`: The hash of the store to use for the storefront.
- `VITE_CATPCHA_SETKEY`: The reCAPTCHA site key (optional).
- `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.

6. Start the development server: `yarn RUN dev`.

## Running Project Locally

1. Activate store channel in the Channels Manager.
2. Configure header and footer scripts:

- Navigate to Channels Manager -> Scripts.
- Add two scripts (e.g., B2BEdition-header, B2BEdition-footer). Ensure you set the correct port for your localhost in the script URLs.
- Edit the header script:

```html
<script>
{{#if customer.id}}
{{#contains page_type "account"}}
var b2bHideBodyStyle = document.createElement('style');
b2bHideBodyStyle.id = 'b2b-account-page-hide-body';
b2bHideBodyStyle.innerHTML = 'body { display: none !important }';
document.head.appendChild(b2bHideBodyStyle);
{{/contains}}
{{/if}}
</script>
<script type="module">
import RefreshRuntime from "http://localhost:3001/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="http://localhost:3001/@vite/client"></script>
<script type="module" src="http://localhost:3001/index.html?html-proxy&index=0.js"></script>
```
- Edit the footer script:

```html
<script type="module" src="http://localhost:3001/src/main.ts"></script>
```
- Navigate to Channels Manager -> Scripts.
- Add two scripts (e.g., B2BEdition-header, B2BEdition-footer). Ensure you set the correct port for your localhost in the script URLs.
- Edit the header script:

```html
<script>
{{#if customer.id}}
{{#contains page_type "account"}}
var b2bHideBodyStyle = document.createElement('style');
b2bHideBodyStyle.id = 'b2b-account-page-hide-body';
b2bHideBodyStyle.innerHTML = 'body { display: none !important }';
document.head.appendChild(b2bHideBodyStyle);
{{/contains}}
{{/if}}
</script>
<script type="module">
import RefreshRuntime from 'http://localhost:3001/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="http://localhost:3001/@vite/client"></script>
<script
type="module"
src="http://localhost:3001/index.html?html-proxy&index=0.js"
></script>
```

- Edit the footer script:

```html
<script type="module" src="http://localhost:3001/src/main.ts"></script>
```

3. Verify correct values in the .env file, especially the client_id for the draft app.

Expand Down
44 changes: 27 additions & 17 deletions apps/storefront/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getStoreTaxZoneRates,
getTemPlateConfig,
handleHideRegisterPage,
isUserGotoLogin,
loginInfo,
openPageByClick,
removeBCMenus,
Expand Down Expand Up @@ -195,6 +196,7 @@ export default function App() {
role: +role,
isAgenting,
}

if (!customerId || isRelogin) {
const info = await getCurrentCustomerInfo(dispatch)
if (info) {
Expand All @@ -207,7 +209,7 @@ export default function App() {
!href.includes('checkout') &&
!(customerId && !window.location.hash)
) {
gotoAllowedAppPage(+userInfo.role, gotoPage)
await gotoAllowedAppPage(+userInfo.role, gotoPage)
}

if (customerId) {
Expand Down Expand Up @@ -260,26 +262,34 @@ export default function App() {
}, [isOpen])

useEffect(() => {
if (isClickEnterBtn && isPageComplete && currentClickedUrl) {
const gotoUrl = openPageByClick({
href: currentClickedUrl,
role,
isRegisterAndLogin,
isAgenting,
})
const init = async () => {
if (isClickEnterBtn && isPageComplete && currentClickedUrl) {
// graphql bc

const gotoUrl = openPageByClick({
href: currentClickedUrl,
role,
isRegisterAndLogin,
isAgenting,
})

setOpenPage({
isOpen: true,
openUrl: gotoUrl,
})
const isGotoLogin = await isUserGotoLogin(gotoUrl)

showPageMask(dispatch, false)
storeDispatch(
setGlabolCommonState({
isClickEnterBtn: false,
setOpenPage({
isOpen: true,
openUrl: isGotoLogin ? '/login' : gotoUrl
})
)

showPageMask(dispatch, false)
storeDispatch(
setGlabolCommonState({
isClickEnterBtn: false,
})
)
}
}

init()
}, [isPageComplete, currentClickedUrl, clickTimeTarget])

useEffect(() => {
Expand Down
13 changes: 6 additions & 7 deletions apps/storefront/src/components/HeadlessController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
addProductsToDraftQuote,
} from '@/hooks/dom/utils'
import { addProductsToShoppingList } from '@/pages/pdp/PDP'
import { CustomStyleContext } from '@/shared/customStyleButtton'
import { GlobaledContext } from '@/shared/global'
import { superAdminCompanies } from '@/shared/service/b2b'
import B3Request from '@/shared/service/request/b3Fetch'
Expand Down Expand Up @@ -132,6 +133,9 @@ export default function HeadlessController({
registerEnabled,
},
} = useContext(GlobaledContext)
const {
state: { addQuoteBtn },
} = useContext(CustomStyleContext)
const { addToQuote: addProductFromPageToQuote } =
addProductFromProductPageToQuote(setOpenPage)
const { addToQuote: addProductsFromCart } =
Expand Down Expand Up @@ -189,6 +193,7 @@ export default function HeadlessController({
addProductsFromCart: () => addProductsFromCart(),
addProducts: (items) => addProductsToDraftQuote(items, setOpenPage),
getCurrent: getDraftQuote,
getButtonInfo: () => addQuoteBtn,
},
user: {
getProfile: () => ({ ...customerRef.current, role }),
Expand Down Expand Up @@ -222,18 +227,12 @@ export default function HeadlessController({
salesRepCompanyId: salesRepCompanyIdRef.current,
B3UserId: B3UserIdRef.current,
}),
logInWithStorefrontToken: (customerJWTToken: string) =>
getCurrentCustomerInfo(dispatch, customerJWTToken),
graphqlBCProxy: B3Request.graphqlBCProxy,
loginWithB2BStorefrontToken: async (
b2bStorefrontJWTToken: string
) => {
B3SStorage.set('B2BToken', b2bStorefrontJWTToken)
await getCurrentCustomerInfo(
dispatch,
undefined,
b2bStorefrontJWTToken
)
await getCurrentCustomerInfo(dispatch, b2bStorefrontJWTToken)
},
},
shoppingList: {
Expand Down
4 changes: 1 addition & 3 deletions apps/storefront/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare interface Window {
getCurrent: () => {
productList: import('@/components').FormatedQuoteItem[]
}
getButtonInfo: () => import('@/shared/customStyleButtton/context/config').AddQuoteBtnProperties
}
user: {
getProfile: () => Record<string, string | number>
Expand All @@ -32,9 +33,6 @@ declare interface Window {
getB2BToken: () => string
setMasqueradeCompany: (companyId: number) => Promise<void>
endMasquerade: () => Promise<void>
logInWithStorefrontToken: (
customerJWTToken: string
) => Promise<{ role: number; userType: string } | undefined>
graphqlBCProxy: typeof import('@/shared/service/request/b3Fetch').default.graphqlBCProxy
loginWithB2BStorefrontToken: (
b2bStorefrontJWTToken: string
Expand Down
11 changes: 10 additions & 1 deletion apps/storefront/src/pages/accountSetting/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ function sendEmail(data: any, extraFields: any) {
const val = formFields.find(
(field: Partial<Fields>) => field.name === item.bcLabel
).value
formData.append(`FormField[1][${key}]`, val)
if (item.type === 'date') {
const time = val.split('-')
if (!val && time.length !== 3) return
const [year, month, day] = time
formData.append(`FormFieldYear[1][${key}]`, year)
formData.append(`FormFieldMonth[1][${key}]`, month)
formData.append(`FormFieldDay[1][${key}]`, day)
} else {
formData.append(`FormField[1][${key}]`, val)
}
}
})
}
Expand Down
34 changes: 20 additions & 14 deletions apps/storefront/src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
getBCForcePasswordReset,
superAdminEndMasquerade,
} from '@/shared/service/b2b'
import { bcLogin } from '@/shared/service/bc'
import { B3SStorage, getCurrentCustomerInfo } from '@/utils'
import { b2bLogin, customerLoginAPI } from '@/shared/service/bc'
import { B3SStorage, getCurrentCustomerInfo, storeHash } from '@/utils'

import LoginWidget from './component/LoginWidget'
import {
Expand Down Expand Up @@ -64,6 +64,7 @@ export default function Login(props: RegisteredProps) {
const [flag, setLoginFlag] = useState<string>('')
const [loginAccount, setLoginAccount] = useState<LoginConfig>({
emailAddress: '',
password: '',
})
const location = useLocation()

Expand Down Expand Up @@ -195,6 +196,9 @@ export default function Login(props: RegisteredProps) {
case '5':
str = b3Lang('login.loginTipInfo.accountPrelaunch')
break
case '6':
str = b3Lang('login.loginText.deviceCrowdingLogIn')
break
default:
str = ''
}
Expand Down Expand Up @@ -249,21 +253,23 @@ export default function Login(props: RegisteredProps) {
}
} else {
try {
const getBCFieldsValue = {
const loginData = {
email: data.emailAddress,
pass: data.password,
password: data.password,
storeHash: storeHash as string,
channelId: B3SStorage.get('B3channelId'),
}
const { data: bcData, errors } = await bcLogin(getBCFieldsValue)
const {
login: {
result: { token, storefrontLoginToken },
errors,
},
} = await b2bLogin({ loginData })

if (bcData?.login?.customer) {
B3SStorage.set('loginCustomer', {
emailAddress: bcData.login.customer.email,
phoneNumber: bcData.login.customer.phone,
...bcData.login.customer,
})
}
B3SStorage.set('B2BToken', token)
customerLoginAPI(storefrontLoginToken)

if (errors?.length || !bcData) {
if (errors?.length || !token) {
if (errors?.length) {
const { message } = errors[0]
if (
Expand All @@ -277,7 +283,7 @@ export default function Login(props: RegisteredProps) {
}
getforcePasswordReset(data.emailAddress)
} else {
const info = await getCurrentCustomerInfo(dispatch)
const info = await getCurrentCustomerInfo(dispatch, token)

if (info?.userType === 3 && info?.role === 3) {
navigate('/dashboard')
Expand Down
4 changes: 2 additions & 2 deletions apps/storefront/src/pages/login/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface QuoteConfig {

export type LoginConfig = {
emailAddress: string
password?: string
password: string
}

export interface LoginInfoInit {
Expand Down Expand Up @@ -182,4 +182,4 @@ export const logout = () => new Promise<boolean>((resolve, reject) => {
}).catch(e => {
reject(e)
})
})
})
Loading

0 comments on commit dd61634

Please sign in to comment.