Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Node JS offcial image for Node 22 (specifically required for this repo)
# Also a Linux container to meet compiling requirement
FROM node:22

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json (or npm-shrinkwrap.json)
COPY package*.json ./

# Install project dependencies using 'npm ci' (clean install)
RUN npm ci

# Bundle app source
COPY . .

# The app runs on port 8080 exposing it for the outside world
EXPOSE 8080

# Define the command to run your app
CMD [ "npm", "run", "dev" ]

109 changes: 68 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,108 @@
# Checkout JS
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/bigcommerce/checkout-js)
# IAPP BigCommerce Checkout JS
Hello fellow IAPP developer. First of all, you might be like "Why all this nonsense for a single page on a third party site?". Wellp the BigCommerce storefront is pulling in tens of millions of dollars per year in revenue, and using the Storefront->Script Manager to over write the default login behavior frequently broke whenever BC updated their markup. Not exactly optimal uptime for an enterprise ecommerce app. We also wanted finer control over checkout experience (maybe add in some promotional code), so here we are!

Checkout JS is a browser-based application providing a seamless UI for BigCommerce shoppers to complete their checkout. It is also known as [Optimized One-Page Checkout](https://support.bigcommerce.com/s/article/Optimized-Single-Page-Checkout), which is currently the recommended checkout option for all BigCommerce stores.
## Big Commerce Links
[![BC DeepWiki (ai powered)](https://deepwiki.com/badge.svg)](https://deepwiki.com/bigcommerce/checkout-js)\
[Optimized One-Page Checkout](https://support.bigcommerce.com/s/article/Optimized-Single-Page-Checkout)\
[Installing the checkout](https://developer.bigcommerce.com/stencil-docs/customizing-checkout/installing-custom-checkouts)\
[BC CI/CD pipeline: CircleCI](https://circleci.com/gh/bigcommerce/workflows/checkout-js/tree/master)\
[BigCommerce Checkout Git repo we forked](https://github.com/bigcommerce/checkout-js)\

## Requirements

In order to build from the source code, you must have the following set up in your development environment.
The repo requires the below environment to compile.

* Node >= v22.
* NPM >= v10.
* Unix-based operating system. (WSL on Windows)
* Unix-based operating system. (WSL on Windows (they are fibbing here))

One of the simplest ways to install Node is using [NVM](https://github.com/nvm-sh/nvm#installation-and-update). You can follow their instructions to set up your environment if it is not already set up.
But that is really hard to get working on a standard IAPP development laptop because Nicole hates us having Macs.
To work around this we are going to install a docker environment locally and build from there.

## Development
To install Docker on your local machine, use the "company portal" application installed on your machine (just type that into the search bar).
[Company Portal]( https://iappadmin.atlassian.net/servicedesk/customer/portal/30/article/2616819713?source=search)
You may need to install windows WSL and some other dependencies too, you can request local admin access using our PIM system.
[PIM](https://iappadmin.atlassian.net/servicedesk/customer/article/2921693187)

Once you have cloned the repository and set up your environment, you can start developing with it.

First, you have to pull in the dependencies required for the application.
## Docker Setup

1. Head on over to your local repo directory, which in my case is at: C:\Users\DavidOstrander\00_IappWork\ .
2. Next open up a GitBash terminal.
3. Run ``` git clone https://github.com/bigcommerce/checkout-js.git ``` to grab a local copy.
4. RUN ``` cd bigcommerce-checkout-js ```
5. RUN
```sh
# Build your local Linux/Node image
docker build -t bigcommerce-checkout .
```
6. RUN
```sh
npm ci
# Boot up your image into a working container
docker run -p 8080:8080 -d --name bc-checkout-dev bigcommerce-checkout
```

After that, you can make changes to the source code and run the following command to build it.

7. RUN
```sh
npm run build
# Login to our container and access a terminal
docker exec -it bc-checkout-dev sh
```

If you are developing the application locally and want to build the source code in watch mode, you can run the following command:

8. RUN
```sh
# install all dependencies
npm ci
```
9. RUN
```sh
# This will create the build folder in the container and should make your files available locally
npm run dev
```
10. Test to see if your container is serving files: [Localhost](http://localhost:8080) .

If you want to create a prerelease (i.e.: `alpha`) for testing in the integration environment, you can run the following command:
If you see an "Index of / " web page, with a bunch of linked files including "auto-loader-dev.js". You are good to go!

```sh
npm run release:alpha
```
## Local Development

After that, you need to push the prerelease tag to your fork so it can be referenced remotely.
From [My Apps](https://myapps.microsoft.com/), click on BigCommerce, and login to the IAPP Akeneo Sandbox. This BC Instance is configured to import into your browser the files served at http://localhost:8080. So this instance of Sandbox will break for other people using it, as their computers won't be serving those files.

### Testing
If you are familiar with Docker, most of the time you would mount your repo into the container, so that files you make on your local machine, would be treated as local files by the container. Because we need the NPM run build from the Linux environment, mounting the volume didn't work so hot. So we are going to make edits to our files in our local VS_Code IDE, Copy the files into the docker container via a terminal command, and manually run the NPM build inside of the container.

To run E2E tests, use the following command:
I suggest opening two gitbash terminals, one for copying the file, and one logged into the container to build the files.

```sh
npm run e2e
```

The E2E tests in this project use HAR files to stub network calls. If you need to manually update the HAR files to make minor changes to the requests, you must run the command below to regenerate the ID for each updated request. Otherwise, the stubs will not function properly.

```sh
npm run regenerate-har
# First terminal: copy files from your local machine to the container
docker cp ./packages/core/src/app/customer/LoginForm.tsx bc-checkout-dev:/usr/src/app/packages/core/src/app/customer/LoginForm.tsx
```

## Custom Checkout installation

Follow [this guide](https://developer.bigcommerce.com/stencil-docs/customizing-checkout/installing-custom-checkouts) for instructions on how to fork and install this app as a Custom Checkout in your store.

If you want to test your checkout implementation, you can run:
```sh
npm run dev:server
#Second terminal: Login to the container
docker exec -it bc-checkout-dev sh
```

And enter the local URL for `auto-loader-dev.js` in Checkout Settings, e.g `http://127.0.0.1:8080/auto-loader-dev.js`
```sh
#Second terminal: After copying your files into ther container from your local, build them.
npm run build
```

## Release
You should be able to view your changes in the checkout app inside of the IAPP Akeneo Sandbox instance, or (more likely) extremely verbose errors in your npm build terminal.

Everytime a PR is merged to the master branch, CircleCI will trigger a build automatically. However, it won't create a new Git release until it is approved by a person with write access to the repository. If you have write access, you can approve a release job by going to [CircleCI](https://circleci.com/gh/bigcommerce/workflows/checkout-js/tree/master) and look for the job you wish to approve. You can also navigate directly to the release job by clicking on the yellow dot next to the merged commit.
## Custom Checkout installation into your store

Follow [this guide](https://developer.bigcommerce.com/stencil-docs/customizing-checkout/installing-custom-checkouts) for instructions on how to fork and install this app as a Custom Checkout in your store.

## Contribution
And enter the local URL for `auto-loader-dev.js` in Checkout Settings, e.g `http://127.0.0.1:8080/auto-loader-dev.js`

More information can be found in the [contribution guide](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md) for this project.
## BC CI/CD Release

Everytime a BC PR is merged to the master branch, CircleCI will trigger a build automatically. However, it won't create a new Git release until it is approved by a person with write access to the repository. We should periodically merge in the BC master branch into our fork. That includes adding the parent repo as a git upstream repo and doing some git magic:
```sh
git remote add upstream https://github.com/bigcommerce/checkout-js
git remote -v
git fetch upstream
git checkout master
git merge upstream/master
git push origin master
#at this point our remote fork will be synced with the BC main
```

Copyright (C) 2019-Present BigCommerce Inc. All rights reserved.
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.8'
services:
checkout:
build:
context: .
dockerfile: Dockerfile
ports:
# NODE Web server exposed
- '8080:8080'
volumes:
# This syncs your current directory on your host to /usr/src/app in the container
- .:/usr/src/app
# This prevents your local node_modules from overwriting the container's node_modules
- /usr/src/app/node_modules
# Override the Dockerfile's CMD to run the actual watch-and-rebuild process
command: npm run dev
108 changes: 16 additions & 92 deletions packages/core/src/app/customer/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import classNames from 'classnames';
import { type FormikProps, withFormik } from 'formik';
import { noop } from 'lodash';
import React, { type FunctionComponent, memo, useCallback } from 'react';
import React, { type FunctionComponent, memo, MouseEvent } from 'react';
import { object, string } from 'yup';

import { useCheckout, useThemeContext } from '@bigcommerce/checkout/contexts';
import { preventDefault } from '@bigcommerce/checkout/dom-utils';
import {
TranslatedHtml,
TranslatedLink,
TranslatedString,
withLanguage,
type WithLanguageProps,
Expand All @@ -19,10 +17,8 @@ import { Button, ButtonVariant } from '../ui/button';
import { Fieldset, Form, Legend } from '../ui/form';

import CustomerViewType from './CustomerViewType';
import EmailField from './EmailField';
import getEmailValidationSchema from './getEmailValidationSchema';
import mapErrorMessage from './mapErrorMessage';
import PasswordField from './PasswordField';
import { RedirectToStorefrontLogin } from './RedirectToStorefrontLogin';

export interface LoginFormProps {
Expand All @@ -41,24 +37,16 @@ export interface LoginFormProps {
}

export interface LoginFormValues {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this interface be removed now that it's empty?

email: string;
password: string;
}

const LoginForm: FunctionComponent<
LoginFormProps & FormikProps<LoginFormValues> & WithLanguageProps
> = ({
continueAsGuestButtonLabelId,
email,
isEmbedded,
language,
signInError,
onCancel = noop,
onChangeEmail,
onContinueAsGuest,
onCreateAccount = noop,
onSendLoginEmail = noop,
isFloatingLabelEnabled,
viewType = CustomerViewType.Login,
}) => {
const { themeV2 } = useThemeContext();
Expand All @@ -78,33 +66,19 @@ const LoginForm: FunctionComponent<
const {
checkoutSettings: {
isAccountCreationEnabled: shouldShowCreateAccountLink,
isSignInEmailEnabled,
guestCheckoutEnabled: canCancel,
shouldRedirectToStorefrontForAuth,
},
links: {
forgotPasswordLink: forgotPasswordUrl
}
} = config;

const isBuyNowCart = cart.source === 'BUY_NOW';

const changeEmailLink = useCallback(() => {
if (!email) {
return null;
const handleRedirect = (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if (window.location.hostname === 'store.iapp.com') {
window.location.href = 'https://myiapp.org/store?redirectPage=checkout';
} else {
window.location.href = 'https://test.myiapp.org/store?redirectPage=checkout';
}

return (
<p className="optimizedCheckout-contentSecondary">
<TranslatedLink
data={{ email }}
id="customer.guest_could_login_change_email"
onClick={onCancel}
testId="change-email"
/>
</p>
);
}, [email, onCancel]);
};

return (
<Form
Expand All @@ -125,61 +99,14 @@ const LoginForm: FunctionComponent<
</Alert>
)}

{viewType === CustomerViewType.SuggestedLogin && (
<Alert type={AlertType.Info}>
<TranslatedHtml data={{ email }} id="customer.guest_could_login" />
</Alert>
)}

{viewType === CustomerViewType.CancellableEnforcedLogin && (
<Alert type={AlertType.Info}>
<TranslatedHtml data={{ email }} id="customer.guest_must_login" />
</Alert>
)}

{viewType === CustomerViewType.EnforcedLogin && (
<Alert type={AlertType.Error}>
<TranslatedLink
id="customer.guest_temporary_disabled"
onClick={onCreateAccount}
/>
</Alert>
)}

{(viewType === CustomerViewType.Login ||
viewType === CustomerViewType.EnforcedLogin) && (
<EmailField isFloatingLabelEnabled={isFloatingLabelEnabled} onChange={onChangeEmail} />
<Alert type={AlertType.Error}></Alert>
)}

{!shouldRedirectToStorefrontForAuth && <PasswordField isFloatingLabelEnabled={isFloatingLabelEnabled} />}

<p className={classNames('form-legend-container', { 'body-cta': themeV2 })}>
<span>
{ isSignInEmailEnabled && !isEmbedded && !isBuyNowCart &&
<TranslatedLink
id="login_email.link"
onClick={ onSendLoginEmail }
testId="customer-signin-link"
/>
}
{ !isSignInEmailEnabled && !isEmbedded && !shouldRedirectToStorefrontForAuth &&
<a
data-test="forgot-password-link"
href={ forgotPasswordUrl }
rel="noopener noreferrer"
target="_blank"
>
<TranslatedString id="customer.forgot_password_action" />
</a>
}
</span>
<span />
{ viewType === CustomerViewType.Login && shouldShowCreateAccountLink &&
<span>
<TranslatedLink
id="customer.create_account_to_continue_text"
onClick={onCreateAccount}
/>
</span>
<span />
}
</p>

Expand All @@ -192,15 +119,14 @@ const LoginForm: FunctionComponent<
:
<Button
className={themeV2 ? 'body-bold' : ''}
disabled={isSigningIn() || isExecutingPaymentMethodCheckout()}
id="checkout-customer-continue"
isLoading={isSigningIn() || isExecutingPaymentMethodCheckout()}
onClick={handleRedirect}
testId="customer-continue-button"
type="submit"
variant={ButtonVariant.Primary}
>
<TranslatedString id="customer.sign_in_action" />
</Button>}
>
<TranslatedString id="customer.sign_in_action" />
</Button>
}

{viewType === CustomerViewType.SuggestedLogin && (
<a
Expand Down Expand Up @@ -236,8 +162,6 @@ const LoginForm: FunctionComponent<
</a>
)}
</div>

{viewType === CustomerViewType.SuggestedLogin && changeEmailLink()}
</Fieldset>
</Form>
);
Expand Down