diff --git a/src/content/docs/dropins/checkout/tutorials/add-payment-method.mdx b/src/content/docs/dropins/checkout/tutorials/add-payment-method.mdx index 0415b2ed..54561f9d 100644 --- a/src/content/docs/dropins/checkout/tutorials/add-payment-method.mdx +++ b/src/content/docs/dropins/checkout/tutorials/add-payment-method.mdx @@ -154,4 +154,8 @@ CheckoutProvider.render(PlaceOrder, { ``` - \ No newline at end of file + + +## Example + +See [`blocks/commerce-checkout-braintree`](https://github.com/hlxsites/aem-boilerplate-commerce/tree/demos/blocks/commerce-checkout-braintree) in the `demos` branch of the boilerplate repository for complete JS and CSS code for the Braintree payment method checkout flow. \ No newline at end of file diff --git a/src/content/docs/dropins/checkout/tutorials/buy-online-pickup-in-store.mdx b/src/content/docs/dropins/checkout/tutorials/buy-online-pickup-in-store.mdx index 589839f8..1df5f2a1 100644 --- a/src/content/docs/dropins/checkout/tutorials/buy-online-pickup-in-store.mdx +++ b/src/content/docs/dropins/checkout/tutorials/buy-online-pickup-in-store.mdx @@ -185,400 +185,4 @@ After a user selects **In-store pickup** and chooses a location, the pickup form ## Example -The following files show the complete JS and CSS code for the BOPIS checkout flow: - - - - - ```js title="commerce-checkout-bopis.js" ins={"2":32} ins={"2":67} ins={"3":149} ins={"4":162} ins={"4":220} ins={"5":123} ins={"6":239} - // Dropin Tools - import { debounce } from '@dropins/tools/lib.js'; - import { events } from '@dropins/tools/event-bus.js'; - import { - RadioButton, - ToggleButton, - provider as UI, - } from '@dropins/tools/components.js'; - - // Cart Dropin Modules - import CartSummaryList from '@dropins/storefront-cart/containers/CartSummaryList.js'; - import { OrderSummary } from '@dropins/storefront-cart/containers/OrderSummary.js'; - import { render as cartProvider } from '@dropins/storefront-cart/render.js'; - - // Checkout Dropin Modules - import * as checkoutApi from '@dropins/storefront-checkout/api.js'; - import LoginForm from '@dropins/storefront-checkout/containers/LoginForm.js'; - import PaymentMethods from '@dropins/storefront-checkout/containers/PaymentMethods.js'; - import PlaceOrder from '@dropins/storefront-checkout/containers/PlaceOrder.js'; - import ShippingMethods from '@dropins/storefront-checkout/containers/ShippingMethods.js'; - import { render as checkoutProvider } from '@dropins/storefront-checkout/render.js'; - - // Account Dropin Modules - import AddressForm from '@dropins/storefront-account/containers/AddressForm.js'; - import { render as accountProvider } from '@dropins/storefront-account/render.js'; - - const DEBOUNCE_TIME = 1000; - const LOGIN_FORM_NAME = 'login-form'; - const SHIPPING_FORM_NAME = 'selectedShippingAddress'; - const BILLING_FORM_NAME = 'selectedBillingAddress'; - - // Step 2: Update content fragment - const fragment = document.createRange().createContextualFragment(` -
-
-

Checkout

-
-
-
- -
-

Delivery Method

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `); - - export const $root = fragment.querySelector('.checkout__content'); - export const $heading = fragment.querySelector('.checkout__heading'); - export const $headingTitle = fragment.querySelector('.checkout__heading-title'); - export const $main = fragment.querySelector('.checkout__main'); - export const $login = fragment.querySelector('.checkout__login'); - // Step 2: Update content fragment - export const $deliveryButton = fragment.querySelector('.checkout-delivery-method__delivery-button'); - export const $inStorePickupButton = fragment.querySelector('.checkout-delivery-method__in-store-pickup-button'); - export const $inStorePickup = fragment.querySelector('.checkout__in-store-pickup'); - export const $shippingForm = fragment.querySelector('.checkout__shipping-form'); - export const $billToShippingAddress = fragment.querySelector( - '.checkout__bill-to-shipping-address', - ); - export const $shippingMethods = fragment.querySelector( - '.checkout__shipping-methods', - ); - export const $paymentMethods = fragment.querySelector( - '.checkout__payment-methods', - ); - export const $billingForm = fragment.querySelector('.checkout__billing-form'); - export const $aside = fragment.querySelector('.checkout__aside'); - export const $orderSummary = fragment.querySelector('.checkout__order-summary'); - export const $cartSummaryList = fragment.querySelector('.cart-summary-list'); - export const $placeOrder = fragment.querySelector('.checkout__place-order'); - - function setAddressOnCart(values, setAddressApi) { - const { data, isDataValid } = values; - const isNewAddress = !data?.id; - - if (!isDataValid) return; - - const customAttributes = data.customAttributes - ? Object.entries(data.customAttributes).map(([code, value]) => ({ - code, - value: value.toString(), - })) - : []; - - const address = !isNewAddress - ? { customerAddressId: data.id } - : { - saveInAddressBook: data.saveAddressBook, - address: { - city: data.city, - company: data?.company, - countryCode: data.countryCode, - customAttributes, - firstName: data.firstName, - lastName: data.lastName, - postcode: data.postcode, - region: data?.region?.regionCode, - regionId: data?.region?.regionId, - street: data.street, - telephone: data.telephone, - vatId: data.vatId, - }, - }; - - setAddressApi(address); - } - - // Step 5: Fetch pickup locations - async function fetchPickupLocations() { - return checkoutApi - .fetchGraphQl( - `query pickupLocations { - pickupLocations { - items { - name - pickup_location_code - } - total_count - } - }`, - { method: 'GET', cache: 'no-cache' }, - ) - .then((res) => res.data.pickupLocations.items); - } - - export default async function decorate(block) { - // Initializers - import('../../scripts/initializers/account.js'); - import('../../scripts/initializers/checkout.js'); - - events.on('checkout/initialized', async (checkoutData) => { - const [ - _login, - // Step 3: Add toggle buttons - deliveryButton, - inStorePickupButton, - _shippingForm, - _billingForm, - _shippingMethods, - _paymentMethods, - _orderSummary, - _cartSummary, - _placeOrder, - ] = await Promise.all([ - checkoutProvider.render(LoginForm, { name: LOGIN_FORM_NAME })($login), - - // Step 3: Add toggle buttons - UI.render(ToggleButton, { - label: 'Delivery', - onChange: () => onToggle('delivery'), - })($deliveryButton), - - UI.render(ToggleButton, { - label: 'In-store Pickup', - onChange: () => onToggle('in-store-pickup'), - })($inStorePickupButton), - - accountProvider.render(AddressForm, { - addressesFormTitle: 'Shipping address', - className: 'checkout-shipping-form__address-form', - formName: SHIPPING_FORM_NAME, - hideActionFormButtons: true, - isOpen: true, - showBillingCheckBox: false, - showShippingCheckBox: false, - onChange: debounce((values) => { - setAddressOnCart(values, checkoutApi.setShippingAddress); - - const hasCartShippingAddress = Boolean( - checkoutData.shippingAddresses?.[0], - ); - const { data, isDataValid } = values; - - if (hasCartShippingAddress || isDataValid) return; - - const criteria = { - country_code: data.countryCode, - region_name: String(data.region.regionCode || ''), - region_id: String(data.region.regionId || ''), - }; - checkoutApi.estimateShippingMethods({ criteria }); - }, DEBOUNCE_TIME), - })($shippingForm), - - accountProvider.render(AddressForm, { - addressesFormTitle: 'Billing address', - className: 'checkout-billing-form__address-form', - formName: BILLING_FORM_NAME, - hideActionFormButtons: true, - isOpen: true, - showBillingCheckBox: false, - showShippingCheckBox: false, - onChange: debounce((values) => { - setAddressOnCart(values, checkoutApi.setBillingAddress); - }, DEBOUNCE_TIME), - })($billingForm), - - checkoutProvider.render(ShippingMethods)($shippingMethods), - checkoutProvider.render(PaymentMethods)($paymentMethods), - cartProvider.render(OrderSummary)($orderSummary), - cartProvider.render(CartSummaryList)($cartSummaryList), - checkoutProvider.render(PlaceOrder)($placeOrder), - ]); - - // Step 4: Toggle functionality - async function onToggle(type) { - if (type === 'delivery') { - deliveryButton.setProps((prev) => ({ ...prev, selected: true })); - inStorePickupButton.setProps((prev) => ({ ...prev, selected: false })); - $shippingForm.removeAttribute('hidden'); - $shippingMethods.removeAttribute('hidden'); - $inStorePickup.setAttribute('hidden', ''); - } else { - inStorePickupButton.setProps((prev) => ({ ...prev, selected: true })); - deliveryButton.setProps((prev) => ({ ...prev, selected: false })); - $shippingForm.setAttribute('hidden', ''); - $shippingMethods.setAttribute('hidden', ''); - $inStorePickup.removeAttribute('hidden'); - } - } - - onToggle('delivery'); - - // Step 6: Render pickup options - const pickupLocations = await fetchPickupLocations(); - - pickupLocations.forEach((location) => { - const { name, pickup_location_code } = location; - const locationRadiobutton = document.createElement('div'); - - UI.render(RadioButton, { - label: name, - name: 'pickup-location', - value: name, - onChange: () => { - checkoutApi.setShippingAddress({ - pickupLocationCode: pickup_location_code, - }); - }, - })(locationRadiobutton); - - $inStorePickup.appendChild(locationRadiobutton); - }); - - $root.style.display = 'grid'; - }); - - block.appendChild(fragment); - } - ``` -
- - - ```css title="commerce-checkout-bopis.css" - .checkout__content { - display: grid; - align-items: start; - grid-template-columns: repeat(var(--grid-4-columns), 1fr); - gap: var(--spacing-big) var(--grid-4-gutters); - } - - .checkout__heading-title { - font: var(--type-headline-1-font); - letter-spacing: var(--type-headline-1-letter-spacing); - color: var(--color-neutral-800); - } - - .checkout__shipping-form:empty, - .checkout__shipping-methods:has(> div:empty), - .checkout__bill-to-shipping-address:has(> div:empty) { - display: none; - } - - .checkout__main { - display: grid; - grid-column: 1 / span 7; - row-gap: var(--spacing-xbig); - } - - .checkout__aside { - display: grid; - grid-column: 9 / span 4; - gap: var(--spacing-xbig); - } - - .checkout__place-order { - display: grid; - grid-column: 1 / span 12; - justify-items: center; - } - - /* To add into the cart dropin **/ - .cart-order-summary__taxes.dropin-accordion .dropin-divider { - margin: var(--spacing-medium) auto; - } - - @media only screen and (width >= 320px) and (width <= 768px) { - .checkout__content { - grid-template-columns: 1fr; - gap: var(--spacing-big) 0; - } - } - - /* CartSummaryList */ - - .cart-summary-list { - padding: var(--spacing-medium); - background-color: var(--color-neutral-200); - } - - .cart-summary-list__heading { - display: flex; - justify-content: space-between; - } - - .cart-summary-list__heading-text { - font: var(--type-headline-2-strong-font); - letter-spacing: var(--type-headline-2-strong-letter-spacing); - color: var(--color-neutral-800); - } - - .cart-cart-summary-list__heading { - row-gap: var(--spacing-small); - padding-top: 0; - } - - .cart-cart-summary-list__heading-text { - font: var(--type-headline-2-strong-font); - letter-spacing: var(--type-headline-2-strong-letter-spacing); - color: var(--color-neutral-800); - } - - .cart-summary-list__edit { - font: var(--type-body-2-strong-font); - letter-spacing: var(--type-body-2-strong-letter-spacing); - } - - /* BOPIS specific styles */ - - .checkout__in-store-pickup { - display: flex; - flex-direction: column; - gap: var(--spacing-small); - } - - .checkout__in-store-pickup[hidden] { - display:none; - } - - .checkout-delivery-method__title { - color: var(--color-neutral-800); - font: var(--type-headline-2-default-font); - letter-spacing: var(--type-headline-2-default-letter-spacing); - margin: 0 0 var(--spacing-medium) 0; - } - - .checkout-delivery-method__toogle-buttons { - display: flex; - gap: var(--spacing-big); - max-width: 600px; - } - - .checkout-delivery-method__delivery-button { - flex: 1; - } - - .checkout-delivery-method__in-store-pickup-button { - flex: 1; - } - - .checkout__content { - display: none; - } - ``` - -
\ No newline at end of file +See [`blocks/commerce-checkout-bopis`](https://github.com/hlxsites/aem-boilerplate-commerce/tree/demos/blocks/commerce-checkout-bopis) in the `demos` branch of the boilerplate repository for complete JS and CSS code for the BOPIS checkout flow. \ No newline at end of file diff --git a/src/content/docs/dropins/checkout/tutorials/multi-step.mdx b/src/content/docs/dropins/checkout/tutorials/multi-step.mdx index 5e1756ce..4b369865 100644 --- a/src/content/docs/dropins/checkout/tutorials/multi-step.mdx +++ b/src/content/docs/dropins/checkout/tutorials/multi-step.mdx @@ -735,670 +735,4 @@ The last step to complete the multi-step checkout process is to create an order ## Example -The following files show the complete JS and CSS code for the multi-step checkout flow: - -```js title="commerce-checkout-multi-step.js" -// Dropin Tools -import { events } from '@dropins/tools/event-bus.js'; -import { initializers } from '@dropins/tools/initializer.js'; - -// Dropin Components -import { Button, Header, provider as UI } from '@dropins/tools/components.js'; - -// Auth Dropin -import SignUp from '@dropins/storefront-auth/containers/SignUp.js'; -import { render as AuthProvider } from '@dropins/storefront-auth/render.js'; - -// Account Dropin -import AddressForm from '@dropins/storefront-account/containers/AddressForm.js'; -import { render as AccountProvider } from '@dropins/storefront-account/render.js'; - -// Cart Dropin -import * as cartApi from '@dropins/storefront-cart/api.js'; -import { CartSummaryList } from '@dropins/storefront-cart/containers/CartSummaryList.js'; -import EmptyCart from '@dropins/storefront-cart/containers/EmptyCart.js'; -import { OrderSummary } from '@dropins/storefront-cart/containers/OrderSummary.js'; -import { render as CartProvider } from '@dropins/storefront-cart/render.js'; - -// Checkout Dropin -import * as checkoutApi from '@dropins/storefront-checkout/api.js'; -import BillToShippingAddress from '@dropins/storefront-checkout/containers/BillToShippingAddress.js'; -import EstimateShipping from '@dropins/storefront-checkout/containers/EstimateShipping.js'; -import LoginForm from '@dropins/storefront-checkout/containers/LoginForm.js'; -import OrderConfirmationHeader from '@dropins/storefront-checkout/containers/OrderConfirmationHeader.js'; -import PaymentMethods from '@dropins/storefront-checkout/containers/PaymentMethods.js'; -import PlaceOrder from '@dropins/storefront-checkout/containers/PlaceOrder.js'; -import ShippingMethods from '@dropins/storefront-checkout/containers/ShippingMethods.js'; - -import { render as CheckoutProvider } from '@dropins/storefront-checkout/render.js'; - -// Order Dropin Modules -import * as orderApi from '@dropins/storefront-order/api.js'; -import CustomerDetails from '@dropins/storefront-order/containers/CustomerDetails.js'; -import OrderCostSummary from '@dropins/storefront-order/containers/OrderCostSummary.js'; -import OrderProductList from '@dropins/storefront-order/containers/OrderProductList.js'; -import OrderStatus from '@dropins/storefront-order/containers/OrderStatus.js'; -import ShippingStatus from '@dropins/storefront-order/containers/ShippingStatus.js'; -import { render as OrderProvider } from '@dropins/storefront-order/render.js'; -import { getUserTokenCookie } from '../../scripts/initializers/index.js'; - -// Block-level -import { - getCartAddress, - getCartDeliveryMethod, - setAddressOnCart, -} from '../../scripts/checkout.js'; -import createModal from '../modal/modal.js'; - -export default async function decorate(block) { - // Initializers - import('../../scripts/initializers/auth.js'); - import('../../scripts/initializers/checkout.js'); - - const DEBOUNCE_TIME = 1000; - const LOGIN_FORM_NAME = 'login-form'; - const SHIPPING_FORM_NAME = 'selectedShippingAddress'; - const BILLING_FORM_NAME = 'selectedBillingAddress'; - const SHIPPING_ADDRESS_DATA_KEY = `${SHIPPING_FORM_NAME}_addressData`; - const BILLING_ADDRESS_DATA_KEY = `${BILLING_FORM_NAME}_addressData`; - - // Pre-fetch checkout store configuration - const storeConfig = await checkoutApi.getStoreConfig(); - - // Define the Layout for the Checkout - const fragment = document.createRange().createContextualFragment(` -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `); - - const $heading = fragment.querySelector('.checkout__heading'); - const $emptyCart = fragment.querySelector('.checkout__empty-cart'); - const $content = fragment.querySelector('.checkout__content'); - - const $orderSummary = fragment.querySelector('.checkout__order-summary'); - const $cartSummary = fragment.querySelector('.checkout__cart-summary'); - - const $shippingTitle = fragment.querySelector('.checkout__shipping-title'); - const $login = fragment.querySelector('.checkout__login'); - const $shippingForm = fragment.querySelector('.checkout__shipping-form'); - const $continueToDeliveryBtn = fragment.querySelector( - '.checkout__continue-to-delivery', - ); - - const $deliveryTitle = fragment.querySelector('.checkout__delivery-title'); - const $deliveryMethods = fragment.querySelector( - '.checkout__delivery-methods', - ); - const $continueToPaymentBtn = fragment.querySelector( - '.checkout__continue-to-payment', - ); - - const $paymentTitle = fragment.querySelector('.checkout__payment-title'); - const $billToShipping = fragment.querySelector('.checkout__bill-to-shipping'); - const $billingForm = fragment.querySelector('.checkout__billing-form'); - const $paymentMethods = fragment.querySelector('.checkout__payment-methods'); - const $placeOrder = fragment.querySelector('.checkout__place-order'); - - block.replaceChildren(fragment); - - // Render the initial containers - const [ - heading, - _shippingInfoHeading, - __shippingMethodHeading, - _paymentHeading, - _loginForm, - shippingFormSkeleton, - continueToDeliveryBtn, - orderSummary, - _cartSummary, - ] = await Promise.all([ - UI.render(Header, { - title: 'Guest Checkout', - size: 'large', - divider: false, - })($heading), - - UI.render(Header, { - title: '1. SHIPPING INFORMATION', - size: 'medium', - divider: false, - })($shippingTitle), - - UI.render(Header, { - title: '2. SHIPPING METHOD', - size: 'medium', - divider: false, - })($deliveryTitle), - - UI.render(Header, { - title: '3. PAYMENT INFORMATION', - size: 'medium', - divider: false, - })($paymentTitle), - - // render the initial containers - CheckoutProvider.render(LoginForm, { - name: LOGIN_FORM_NAME, - })($login), - - AccountProvider.render(AddressForm, { - isOpen: true, - showFormLoader: true, - })($shippingForm), - - UI.render(Button, { - children: 'CONTINUE TO SHIPPING METHOD', - disabled: true, - onClick: async () => { - await continueToDelivery(); - }, - })($continueToDeliveryBtn), - - CartProvider.render(OrderSummary)($orderSummary), - - CartProvider.render(CartSummaryList, { - variant: 'secondary', - slots: { - Heading: (headingCtx) => { - const title = 'Your Cart ({count})'; - - const cartSummaryListHeading = document.createElement('div'); - cartSummaryListHeading.classList.add('cart-summary-list__heading'); - - const cartSummaryListHeadingText = document.createElement('div'); - cartSummaryListHeadingText.classList.add( - 'cart-summary-list__heading-text', - ); - - cartSummaryListHeadingText.innerText = title.replace( - '({count})', - headingCtx.count ? `(${headingCtx.count})` : '', - ); - const editCartLink = document.createElement('a'); - editCartLink.classList.add('cart-summary-list__edit'); - editCartLink.href = '/cart'; - editCartLink.rel = 'noreferrer'; - editCartLink.innerText = 'Edit'; - - cartSummaryListHeading.appendChild(cartSummaryListHeadingText); - cartSummaryListHeading.appendChild(editCartLink); - headingCtx.appendChild(cartSummaryListHeading); - - headingCtx.onChange((nextHeadingCtx) => { - cartSummaryListHeadingText.innerText = title.replace( - '({count})', - nextHeadingCtx.count ? `(${nextHeadingCtx.count})` : '', - ); - }); - }, - }, - })($cartSummary), - ]); - - // Dynamic containers and components - - let modal; - - const showModal = async (content) => { - modal = await createModal([content]); - modal.showModal(); - }; - - let emptyCart; - - const displayEmptyCart = async () => { - if (emptyCart) return; - - heading.setProps((prev) => ({ - ...prev, - title: 'Empty Cart', - })); - - emptyCart = await CartProvider.render(EmptyCart, { - routeCTA: () => '/', - })($emptyCart); - - $content.classList.add('checkout__content--empty'); - }; - - const removeEmptyCart = () => { - if (!emptyCart) return; - - emptyCart.remove(); - emptyCart = null; - $emptyCart.innerHTML = ''; - - heading.setProps((prev) => ({ - ...prev, - title: 'Guest Checkout', - })); - - $content.classList.remove('checkout__content--empty'); - }; - - let shippingForm; - - const continueToShipping = async (initialData = null) => { - if (shippingForm) return; - - // cleanup - shippingFormSkeleton.remove(); - $shippingForm.innerHTML = ''; - - shippingForm = await AccountProvider.render(AddressForm, { - addressesFormTitle: 'Shipping address', - className: 'checkout-shipping-form__address-form', - formName: SHIPPING_FORM_NAME, - hideActionFormButtons: true, - inputsDefaultValueSet: initialData ?? { - countryCode: storeConfig.defaultCountry, - }, - isOpen: true, - onChange: setAddressOnCart({ - api: checkoutApi.setShippingAddress, - debounceMs: DEBOUNCE_TIME, - placeOrderBtn: placeOrder, - }), - showBillingCheckBox: false, - showShippingCheckBox: false, - })($shippingForm); - }; - - let deliveryMethods; - let continueToPaymentBtn; - - const continueToDelivery = async () => { - if (deliveryMethods) return; - - deliveryMethods = await CheckoutProvider.render(ShippingMethods, { - hideOnVirtualCart: true, - onCheckoutDataUpdate: () => { - cartApi.refreshCart().catch(console.error); - }, - })($deliveryMethods); - - orderSummary.setProps((prev) => ({ - ...prev, - slots: { - EstimateShipping: (esCtx) => { - const estimateShippingForm = document.createElement('div'); - CheckoutProvider.render(EstimateShipping)(estimateShippingForm); - esCtx.appendChild(estimateShippingForm); - }, - }, - })); - - continueToDeliveryBtn.remove(); - $continueToDeliveryBtn.remove(); - - continueToPaymentBtn = await UI.render(Button, { - children: 'CONTINUE TO PAYMENT INFORMATION', - disabled: true, - onClick: async () => { - await continueToPayment(); - }, - })($continueToPaymentBtn); - }; - - let billToShipping; - let billingForm; - let paymentMethods; - let placeOrder; - - const continueToPayment = async () => { - if (!billToShipping) { - billToShipping = await CheckoutProvider.render(BillToShippingAddress, { - hideOnVirtualCart: true, - onChange: (checked) => { - $billingForm.style.display = checked ? 'none' : 'block'; - }, - })($billToShipping); - } - - if (!billingForm) { - billingForm = await AccountProvider.render(AddressForm, { - addressesFormTitle: 'Billing address', - className: 'checkout-billing-form__address-form', - formName: BILLING_FORM_NAME, - hideActionFormButtons: true, - isOpen: true, - onChange: setAddressOnCart({ - api: checkoutApi.setBillingAddress, - debounceMs: DEBOUNCE_TIME, - placeOrderBtn: placeOrder, - }), - showBillingCheckBox: false, - showShippingCheckBox: false, - })($billingForm); - } - - if (!paymentMethods) { - paymentMethods = await CheckoutProvider.render(PaymentMethods)($paymentMethods); - } - - if (!placeOrder) { - placeOrder = await CheckoutProvider.render(PlaceOrder)($placeOrder); - } - - continueToPaymentBtn.remove(); - $continueToPaymentBtn.remove(); - }; - - const displayOrderConfirmation = async (orderData) => { - // Define the Layout for the Order Confirmation - const orderConfirmationFragment = document.createRange() - .createContextualFragment(` -
-
-
-
-
-
-
-
-
-
- -
-
- `); - - // Order confirmation elements - const $orderConfirmationHeader = orderConfirmationFragment.querySelector( - '.order-confirmation__header', - ); - const $orderStatus = orderConfirmationFragment.querySelector( - '.order-confirmation__order-status', - ); - const $shippingStatus = orderConfirmationFragment.querySelector( - '.order-confirmation__shipping-status', - ); - const $customerDetails = orderConfirmationFragment.querySelector( - '.order-confirmation__customer-details', - ); - const $orderCostSummary = orderConfirmationFragment.querySelector( - '.order-confirmation__order-cost-summary', - ); - const $orderProductList = orderConfirmationFragment.querySelector( - '.order-confirmation__order-product-list', - ); - const $orderConfirmationFooter = orderConfirmationFragment.querySelector( - '.order-confirmation__footer', - ); - - await initializers.mountImmediately(orderApi.initialize, { orderData }); - - block.replaceChildren(orderConfirmationFragment); - - const onSignUpClick = async ({ inputsDefaultValueSet, addressesData }) => { - const signUpForm = document.createElement('div'); - AuthProvider.render(SignUp, { - routeSignIn: () => '/customer/login', - routeRedirectOnEmailConfirmationClose: () => '/customer/account', - inputsDefaultValueSet, - addressesData, - })(signUpForm); - - showModal(signUpForm); - }; - - CheckoutProvider.render(OrderConfirmationHeader, { - orderData, - onSignUpClick, - })($orderConfirmationHeader); - - OrderProvider.render(OrderStatus, { slots: { OrderActions: () => null } })( - $orderStatus, - ); - OrderProvider.render(ShippingStatus)($shippingStatus); - OrderProvider.render(CustomerDetails)($customerDetails); - OrderProvider.render(OrderCostSummary)($orderCostSummary); - OrderProvider.render(OrderProductList)($orderProductList); - - $orderConfirmationFooter.innerHTML = ` - - - `; - - const $orderConfirmationFooterContinueBtn = $orderConfirmationFooter.querySelector( - '.order-confirmation-footer__continue-button', - ); - - UI.render(Button, { - children: 'Continue shopping', - 'data-testid': 'order-confirmation-footer__continue-button', - className: 'order-confirmation-footer__continue-button', - size: 'medium', - variant: 'primary', - type: 'submit', - href: '/', - })($orderConfirmationFooterContinueBtn); - }; - - // Define checkout event handlers and shared utilities - const isEmptyCart = (data) => data === null || data.isEmpty; - - async function handleCheckoutInitialized(data) { - if (isEmptyCart(data)) { - await displayEmptyCart(); - return; - } - - // continue to shipping - const cartShippingAddress = getCartAddress(data, 'shipping'); - await continueToShipping(cartShippingAddress); - - // continue to delivery - if (!cartShippingAddress) return; - await continueToDelivery(); - - // continue to payment - const deliveryMethod = getCartDeliveryMethod(data); - if (!deliveryMethod) return; - await continueToPayment(); - } - - async function handleCheckoutUpdated(data) { - if (isEmptyCart(data)) { - await displayEmptyCart(); - return; - } - - removeEmptyCart(); - - // continue to shipping - const cartShippingAddress = getCartAddress(data, 'shipping'); - await continueToShipping(cartShippingAddress); - - if (!cartShippingAddress) return; - - continueToDeliveryBtn.setProps((prev) => ({ - ...prev, - disabled: false, - })); - - const deliveryMethod = getCartDeliveryMethod(data); - if (!deliveryMethod) return; - - continueToPaymentBtn.setProps((prev) => ({ - ...prev, - disabled: false, - })); - } - - async function handleCheckoutOrder(orderData) { - // clear address form data - sessionStorage.removeItem(SHIPPING_ADDRESS_DATA_KEY); - sessionStorage.removeItem(BILLING_ADDRESS_DATA_KEY); - - const token = getUserTokenCookie(); - const orderRef = token ? orderData.number : orderData.token; - const encodedOrderRef = encodeURIComponent(orderRef); - - window.history.pushState( - {}, - '', - `/order-details?orderRef=${encodedOrderRef}`, - ); - - // TODO cleanup checkout containers - await displayOrderConfirmation(orderData); - } - - events.on('checkout/initialized', handleCheckoutInitialized, { eager: true }); - events.on('checkout/order', handleCheckoutOrder); - events.on('checkout/updated', handleCheckoutUpdated); -} -``` - -```css title="commerce-checkout-multi-step.css" -.checkout__wrapper { - padding-left: 3rem; - padding-right: 3rem; -} - -.checkout__banners { - padding-top: 1.5rem; -} - -.checkout__content { - display: grid; - align-items: start; - grid-template-columns: repeat(var(--grid-4-columns), 1fr); - gap: var(--spacing-big); - padding-top: 1.5rem; -} - -.checkout__content--empty { - display: none; -} - -.checkout__empty-cart { - padding-top: 1.5rem; -} - -.checkout__main { - display: grid; - grid-column: 1 / span 7; - row-gap: var(--spacing-xbig); -} - -.checkout__aside { - display: grid; - grid-column: 9 / span 4; - row-gap: var(--spacing-xbig); -} - -.checkout__shipping { - display: flex; - flex-direction: column; - gap: var(--spacing-big); - padding: 0; -} - -.checkout__shipping:has(> div:empty:not(.checkout__continue-to-delivery)) { - gap: 0; - padding-top: var(--spacing-small); - padding-bottom: var(--spacing-small); - border-top: var(--shape-border-width-3) solid var(--color-neutral-400); - border-bottom: var(--shape-border-width-3) solid var(--color-neutral-400); -} - -.checkout__delivery { - display: flex; - flex-direction: column; - gap: var(--spacing-big); - padding: 0; -} - -.checkout__delivery:has(> div:empty:not(.checkout__continue-to-payment)) { - gap: 0; - padding-top: var(--spacing-small); - padding-bottom: var(--spacing-small); - border-top: var(--shape-border-width-3) solid var(--color-neutral-400); - border-bottom: var(--shape-border-width-3) solid var(--color-neutral-400); -} - -.checkout__payment { - display: flex; - flex-direction: column; - gap: var(--spacing-big); - padding: 0; -} - -.checkout__payment:has(> div:empty) { - gap: 0; - padding-top: var(--spacing-small); - padding-bottom: var(--spacing-small); - border-top: var(--shape-border-width-3) solid var(--color-neutral-400); - border-bottom: var(--shape-border-width-3) solid var(--color-neutral-400); -} - -/* temporary fix to hide the default cart heading */ -[data-testid='default-cart-heading'] { - display: none; -} - -/* Responsive adjustments */ -@media only screen and (width <= 768px) { - .checkout__wrapper { - padding-left: 1.5rem; - padding-right: 1.5rem; - } - - .checkout__content { - grid-template-columns: 1fr; - gap: var(--spacing-big) 0; - } - - .checkout__main, - .checkout__aside { - grid-column: auto; - } - - .checkout__aside { - order: -1; - } -} -``` \ No newline at end of file +See [`blocks/commerce-checkout-multi-step`](https://github.com/hlxsites/aem-boilerplate-commerce/tree/demos/blocks/commerce-checkout-multi-step) in the `demos` branch of the boilerplate repository for complete JS and CSS code for the multi-step checkout flow. \ No newline at end of file