2021-07-22 11:03:00 +00:00
/ * *
* External dependencies
* /
import { __ } from '@wordpress/i18n' ;
2024-05-31 03:49:36 +00:00
import clsx from 'clsx' ;
2021-07-22 11:03:00 +00:00
import { createInterpolateElement , useEffect } from '@wordpress/element' ;
2023-05-25 05:31:15 +00:00
import {
useStoreCart ,
useShowShippingTotalWarning ,
} from '@woocommerce/base-context/hooks' ;
2022-12-19 15:30:13 +00:00
import { CheckoutProvider , noticeContexts } from '@woocommerce/base-context' ;
2021-07-22 11:03:00 +00:00
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary' ;
import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout' ;
import { CURRENT_USER_IS_ADMIN , getSetting } from '@woocommerce/settings' ;
2023-11-14 16:32:53 +00:00
import { StoreNoticesContainer } from '@woocommerce/blocks-components' ;
import { SlotFillProvider } from '@woocommerce/blocks-checkout' ;
2021-07-22 11:03:00 +00:00
import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top' ;
2022-07-01 23:06:25 +00:00
import { useDispatch , useSelect } from '@wordpress/data' ;
import {
CHECKOUT_STORE_KEY ,
VALIDATION_STORE_KEY ,
} from '@woocommerce/block-data' ;
2021-07-22 11:03:00 +00:00
/ * *
* Internal dependencies
* /
import './styles/style.scss' ;
import EmptyCart from './empty-cart' ;
import CheckoutOrderError from './checkout-order-error' ;
import { LOGIN_TO_CHECKOUT_URL , isLoginRequired , reloadPage } from './utils' ;
import type { Attributes } from './types' ;
import { CheckoutBlockContext } from './context' ;
2022-12-19 14:19:52 +00:00
const MustLoginPrompt = ( ) = > {
2021-07-22 11:03:00 +00:00
return (
2022-12-19 14:19:52 +00:00
< div className = "wc-block-must-login-prompt" >
2023-12-12 23:05:20 +00:00
{ __ ( 'You must be logged in to checkout.' , 'woocommerce' ) } { ' ' }
2021-07-22 11:03:00 +00:00
< a href = { LOGIN_TO_CHECKOUT_URL } >
2023-12-12 23:05:20 +00:00
{ __ ( 'Click here to log in.' , 'woocommerce' ) }
2021-07-22 11:03:00 +00:00
< / a >
2022-12-19 14:19:52 +00:00
< / div >
2021-07-22 11:03:00 +00:00
) ;
} ;
const Checkout = ( {
attributes ,
children ,
} : {
attributes : Attributes ;
children : React.ReactChildren ;
} ) : JSX . Element = > {
2022-06-10 16:33:15 +00:00
const { hasOrder , customerId } = useSelect ( ( select ) = > {
const store = select ( CHECKOUT_STORE_KEY ) ;
return {
hasOrder : store.hasOrder ( ) ,
customerId : store.getCustomerId ( ) ,
} ;
} ) ;
2021-07-22 11:03:00 +00:00
const { cartItems , cartIsLoading } = useStoreCart ( ) ;
const {
showCompanyField ,
requireCompanyField ,
showApartmentField ,
2024-05-21 08:48:24 +00:00
requireApartmentField ,
2021-07-22 11:03:00 +00:00
showPhoneField ,
requirePhoneField ,
2024-05-22 14:03:48 +00:00
showFormStepNumbers ,
2021-07-22 11:03:00 +00:00
} = attributes ;
if ( ! cartIsLoading && cartItems . length === 0 ) {
return < EmptyCart / > ;
}
if ( ! hasOrder ) {
return < CheckoutOrderError / > ;
}
2022-12-19 14:19:52 +00:00
/ * *
* If checkout requires an account ( guest checkout is turned off ) , render
* a notice and prevent access to the checkout , unless we explicitly allow
* account creation during the checkout flow .
* /
2021-07-22 11:03:00 +00:00
if (
isLoginRequired ( customerId ) &&
2022-12-19 14:19:52 +00:00
! getSetting ( 'checkoutAllowsSignup' , false )
2021-07-22 11:03:00 +00:00
) {
2022-12-19 14:19:52 +00:00
return < MustLoginPrompt / > ;
2021-07-22 11:03:00 +00:00
}
return (
< CheckoutBlockContext.Provider
2022-12-19 14:19:52 +00:00
value = {
{
showCompanyField ,
requireCompanyField ,
showApartmentField ,
2024-05-21 08:48:24 +00:00
requireApartmentField ,
2022-12-19 14:19:52 +00:00
showPhoneField ,
requirePhoneField ,
2024-05-22 14:03:48 +00:00
showFormStepNumbers ,
2022-12-19 14:19:52 +00:00
} as Attributes
}
2021-07-22 11:03:00 +00:00
>
{ children }
< / CheckoutBlockContext.Provider >
) ;
} ;
const ScrollOnError = ( {
scrollToTop ,
} : {
scrollToTop : ( props : Record < string , unknown > ) = > void ;
} ) : null = > {
2022-06-10 16:33:15 +00:00
const { hasError : checkoutHasError , isIdle : checkoutIsIdle } = useSelect (
( select ) = > {
const store = select ( CHECKOUT_STORE_KEY ) ;
return {
isIdle : store.isIdle ( ) ,
hasError : store.hasError ( ) ,
} ;
}
) ;
2022-07-01 23:06:25 +00:00
const { hasValidationErrors } = useSelect ( ( select ) = > {
const store = select ( VALIDATION_STORE_KEY ) ;
return {
hasValidationErrors : store.hasValidationErrors ( ) ,
} ;
} ) ;
const { showAllValidationErrors } = useDispatch ( VALIDATION_STORE_KEY ) ;
2021-07-22 11:03:00 +00:00
const hasErrorsToDisplay =
2022-12-19 15:30:13 +00:00
checkoutIsIdle && checkoutHasError && hasValidationErrors ;
2021-07-22 11:03:00 +00:00
useEffect ( ( ) = > {
2021-08-02 09:19:07 +00:00
let scrollToTopTimeout : number ;
2021-07-22 11:03:00 +00:00
if ( hasErrorsToDisplay ) {
showAllValidationErrors ( ) ;
2021-07-26 14:05:09 +00:00
// Scroll after a short timeout to allow a re-render. This will allow focusableSelector to match updated components.
2021-08-02 09:19:07 +00:00
scrollToTopTimeout = window . setTimeout ( ( ) = > {
2021-07-26 14:05:09 +00:00
scrollToTop ( {
focusableSelector : 'input:invalid, .has-error input' ,
} ) ;
} , 50 ) ;
2021-07-22 11:03:00 +00:00
}
2021-08-02 09:19:07 +00:00
return ( ) = > {
clearTimeout ( scrollToTopTimeout ) ;
} ;
2021-07-22 11:03:00 +00:00
} , [ hasErrorsToDisplay , scrollToTop , showAllValidationErrors ] ) ;
return null ;
} ;
const Block = ( {
attributes ,
children ,
scrollToTop ,
} : {
attributes : Attributes ;
children : React.ReactChildren ;
scrollToTop : ( props : Record < string , unknown > ) = > void ;
2022-04-08 12:11:50 +00:00
} ) : JSX . Element = > {
2023-05-25 05:31:15 +00:00
useShowShippingTotalWarning ( ) ;
2022-04-08 12:11:50 +00:00
return (
< BlockErrorBoundary
header = { __ (
2022-09-12 08:39:26 +00:00
'Something went wrong. Please contact us for assistance.' ,
2023-12-12 22:12:36 +00:00
'woocommerce'
2022-04-08 12:11:50 +00:00
) }
text = { createInterpolateElement (
__ (
'The checkout has encountered an unexpected error. <button>Try reloading the page</button>. If the error persists, please get in touch with us so we can assist.' ,
2023-12-12 22:12:36 +00:00
'woocommerce'
2021-07-22 11:03:00 +00:00
) ,
2022-04-08 12:11:50 +00:00
{
button : (
< button
className = "wc-block-link-button"
onClick = { reloadPage }
/ >
) ,
}
) }
showErrorMessage = { CURRENT_USER_IS_ADMIN }
>
2023-02-09 13:41:18 +00:00
< StoreNoticesContainer
context = { [ noticeContexts . CHECKOUT , noticeContexts . CART ] }
/ >
2022-08-22 15:50:37 +00:00
{ /* SlotFillProvider need to be defined before CheckoutProvider so fills have the SlotFill context ready when they mount. */ }
< SlotFillProvider >
< CheckoutProvider >
< SidebarLayout
2024-05-31 03:49:36 +00:00
className = { clsx ( 'wc-block-checkout' , {
2022-08-22 15:50:37 +00:00
'has-dark-controls' : attributes . hasDarkControls ,
} ) }
>
< Checkout attributes = { attributes } >
{ children }
< / Checkout >
< ScrollOnError scrollToTop = { scrollToTop } / >
< / SidebarLayout >
< / CheckoutProvider >
< / SlotFillProvider >
2022-04-08 12:11:50 +00:00
< / BlockErrorBoundary >
) ;
} ;
2021-07-22 11:03:00 +00:00
export default withScrollToTop ( Block ) ;