Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Condensed Address Form Implementation #11167

Merged
merged 6 commits into from
Oct 9, 2023
Merged
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
3 changes: 2 additions & 1 deletion assets/css/abstracts/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ $select-item-dark: rgba(0, 0, 0, 0.4);
$image-placeholder-border-color: #f2f2f2;

// Universal colors for use on the frontend, currently being applied to checkout blocks.
$universal-border-light: rgba(17, 17, 17, 0.115); // e7e7e7 on white
$universal-border: rgba(17, 17, 17, 0.3); // Used for form step borders.
$universal-border-light: rgba(17, 17, 17, 0.115); // e7e7e7 on white.
3 changes: 1 addition & 2 deletions assets/js/base/components/cart-checkout/form-step/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@
.wc-block-components-checkout-step__container::after {
content: "";
height: 100%;
border-left: 1px solid;
opacity: 0.3;
border-left: 1px solid $universal-border;
position: absolute;
left: -$gap-large;
top: 0;
Expand Down
79 changes: 79 additions & 0 deletions assets/js/blocks/checkout/address-card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { ALLOWED_COUNTRIES } from '@woocommerce/block-settings';
import type {
CartShippingAddress,
CartBillingAddress,
} from '@woocommerce/types';

/**
* Internal dependencies
*/
import './style.scss';

const AddressCard = ( {
address,
onEdit,
target,
}: {
address: CartShippingAddress | CartBillingAddress;
onEdit: () => void;
target: string;
} ): JSX.Element | null => {
return (
<div className="wc-block-components-address-card">
<address>
<span className="wc-block-components-address-card__address-section">
{ address.first_name + ' ' + address.last_name }
</span>
<div className="wc-block-components-address-card__address-section">
{ [
address.address_1,
address.address_2,
address.city,
address.state,
address.postcode,
ALLOWED_COUNTRIES[ address.country ]
? ALLOWED_COUNTRIES[ address.country ]
: address.country,
]
.filter( ( field ) => !! field )
.map( ( field, index ) => (
<span key={ `address-` + index }>{ field }</span>
) ) }
</div>
{ address.phone ? (
<div
key={ `address-phone` }
className="wc-block-components-address-card__address-section"
>
{ address.phone }
</div>
) : (
''
) }
</address>
{ onEdit && (
<a
role="button"
href={ '#' + target }
className="wc-block-components-address-card__edit"
aria-label={ __(
'Edit address',
'woo-gutenberg-products-block'
) }
onClick={ ( e ) => {
onEdit();
e.preventDefault();
} }
>
{ __( 'Edit', 'woo-gutenberg-products-block' ) }
</a>
) }
</div>
);
};

export default AddressCard;
41 changes: 41 additions & 0 deletions assets/js/blocks/checkout/address-card/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.wc-block-components-address-card {
border: 1px solid $universal-border;
@include font-size(regular);
padding: $gap;
margin: 0;
border-radius: 4px;
display: flex;
justify-content: flex-start;
align-items: flex-start;

address {
margin: 0;
font-style: normal;

.wc-block-components-address-card__address-section {
display: block;
margin: 0 0 2px 0;
span {
display: inline-block;
padding: 0 4px 0 0;
&::after {
content: ", ";
}
&:last-child::after {
content: "";
}
}
&:last-child {
margin-bottom: 0;
}
&:first-child {
font-weight: bold;
}
}
}
}
.wc-block-components-address-card__edit {
margin: 0 0 0 auto;
text-decoration: none;
@include font-size(small);
}
41 changes: 41 additions & 0 deletions assets/js/blocks/checkout/address-wrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* External dependencies
*/
import { CSSTransition, TransitionGroup } from 'react-transition-group';
/**
* Internal dependencies
*/
import './style.scss';

export const AddressWrapper = ( {
isEditing = false,
addressCard,
addressForm,
}: {
isEditing: boolean;
addressCard: () => JSX.Element;
addressForm: () => JSX.Element;
} ): JSX.Element | null => {
return (
<TransitionGroup className="address-fade-transition-wrapper">
{ ! isEditing && (
<CSSTransition
timeout={ 300 }
classNames="address-fade-transition"
>
{ addressCard() }
</CSSTransition>
) }
{ isEditing && (
<CSSTransition
timeout={ 300 }
classNames="address-fade-transition"
>
{ addressForm() }
</CSSTransition>
) }
</TransitionGroup>
);
};

export default AddressWrapper;
25 changes: 25 additions & 0 deletions assets/js/blocks/checkout/address-wrapper/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.address-fade-transition-wrapper {
position: relative;
}
.address-fade-transition-enter {
opacity: 0;
}
.address-fade-transition-enter-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
.address-fade-transition-enter-done {
opacity: 1;
}
.address-fade-transition-exit {
opacity: 1;
position: absolute;
top: 0;
}
.address-fade-transition-exit-active {
opacity: 0;
transition: opacity 300ms ease-out;
}
.address-fade-transition-done {
opacity: 0;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
/**
* External dependencies
*/
import {
useMemo,
useEffect,
Fragment,
useState,
useCallback,
} from '@wordpress/element';
import { useMemo, Fragment } from '@wordpress/element';
import { useEffectOnce } from 'usehooks-ts';
import {
useCheckoutAddress,
useStoreEvents,
useEditorContext,
noticeContexts,
} from '@woocommerce/base-context';
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
import Noninteractive from '@woocommerce/base-components/noninteractive';
import type {
BillingAddress,
ShippingAddress,
AddressField,
AddressFields,
} from '@woocommerce/settings';
Expand All @@ -26,56 +19,46 @@ import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
/**
* Internal dependencies
*/
import PhoneNumber from '../../phone-number';
import CustomerAddress from './customer-address';

const Block = ( {
showCompanyField = false,
showApartmentField = false,
showPhoneField = false,
requireCompanyField = false,
requirePhoneField = false,
forceEditing = false,
}: {
showCompanyField: boolean;
showApartmentField: boolean;
showPhoneField: boolean;
requireCompanyField: boolean;
requirePhoneField: boolean;
forceEditing?: boolean;
} ): JSX.Element => {
const {
defaultAddressFields,
billingAddress,
setBillingAddress,
setShippingAddress,
setBillingPhone,
setShippingPhone,
useBillingAsShipping,
} = useCheckoutAddress();
const { dispatchCheckoutEvent } = useStoreEvents();
const { billingAddress, setShippingAddress, useBillingAsShipping } =
useCheckoutAddress();
const { isEditor } = useEditorContext();
// Clears data if fields are hidden.
useEffect( () => {
if ( ! showPhoneField ) {
setBillingPhone( '' );
}
}, [ showPhoneField, setBillingPhone ] );

const [ addressesSynced, setAddressesSynced ] = useState( false );

// Syncs shipping address with billing address if "Force shipping to the customer billing address" is enabled.
useEffect( () => {
if ( addressesSynced ) {
return;
}
useEffectOnce( () => {
if ( useBillingAsShipping ) {
setShippingAddress( billingAddress );
const { email, ...addressValues } = billingAddress;
const syncValues: Partial< ShippingAddress > = {
...addressValues,
};

if ( ! showPhoneField ) {
delete syncValues.phone;
}

if ( showCompanyField ) {
delete syncValues.company;
}

setShippingAddress( syncValues );
}
setAddressesSynced( true );
}, [
addressesSynced,
setShippingAddress,
billingAddress,
useBillingAsShipping,
] );
} );

const addressFieldsConfig = useMemo( () => {
return {
Expand All @@ -93,64 +76,28 @@ const Block = ( {
showApartmentField,
] ) as Record< keyof AddressFields, Partial< AddressField > >;

const onChangeAddress = useCallback(
( values: Partial< BillingAddress > ) => {
setBillingAddress( values );
if ( useBillingAsShipping ) {
setShippingAddress( values );
dispatchCheckoutEvent( 'set-shipping-address' );
}
dispatchCheckoutEvent( 'set-billing-address' );
},
[
dispatchCheckoutEvent,
setBillingAddress,
setShippingAddress,
useBillingAsShipping,
]
);

const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
const WrapperComponent = isEditor ? Noninteractive : Fragment;
const noticeContext = useBillingAsShipping
? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ]
: [ noticeContexts.BILLING_ADDRESS ];
const hasAddress = !! (
billingAddress.address_1 &&
( billingAddress.first_name || billingAddress.last_name )
);

return (
<AddressFormWrapperComponent>
<>
<StoreNoticesContainer context={ noticeContext } />
<AddressForm
id="billing"
type="billing"
onChange={ onChangeAddress }
values={ billingAddress }
fields={
Object.keys(
defaultAddressFields
) as ( keyof AddressFields )[]
}
fieldConfig={ addressFieldsConfig }
/>
{ showPhoneField && (
<PhoneNumber
id={ 'billing-phone' }
errorId={ 'billing_phone' }
isRequired={ requirePhoneField }
value={ billingAddress.phone }
onChange={ ( value ) => {
setBillingPhone( value );
dispatchCheckoutEvent( 'set-phone-number', {
step: 'billing',
} );
if ( useBillingAsShipping ) {
setShippingPhone( value );
dispatchCheckoutEvent( 'set-phone-number', {
step: 'shipping',
} );
}
} }
<WrapperComponent>
<CustomerAddress
addressFieldsConfig={ addressFieldsConfig }
showPhoneField={ showPhoneField }
requirePhoneField={ requirePhoneField }
hasAddress={ hasAddress }
forceEditing={ forceEditing }
/>
) }
</AddressFormWrapperComponent>
</WrapperComponent>
</>
);
};

Expand Down
Loading
Loading