8. Address

Collect shipping and billing address on the cart.

On this page

The second step in the express checkout flow is the address details step. In this step, the customer will input their shipping address.

To create the component of this step, create the file components/Address/index.tsx with the following content:

Directory structure after creating Address component file

tsx

tsxCopy
"use client"
import {
useState,
} from "react"
import { useCart } from "@/providers/cart"
import { useRegion } from "@/providers/region"
import { useRouter } from "next/navigation"
type AddressProps = {
handle: string
isActive: boolean
}
export const Address = ({
handle,
isActive,
}: AddressProps) => {
const { cart, updateCart } = useCart()
const { region } = useRegion()
const [loading, setLoading] = useState(false)
const [firstName, setFirstName] = useState(
cart?.shipping_address?.first_name || ""
)
const [lastName, setLastName] = useState(
cart?.shipping_address?.last_name || ""
)
const [email, setEmail] = useState(cart?.email || "")
const [phone, setPhone] = useState(cart?.shipping_address?.phone || "")
const [address, setAddress] = useState(
cart?.shipping_address?.address_1 || ""
)
const [postalCode, setPostalCode] = useState(
cart?.shipping_address?.postal_code || ""
)
const [city, setCity] = useState(cart?.shipping_address?.city || "")
const [country, setCountry] = useState(
cart?.shipping_address?.country_code || region?.countries?.[0]?.iso_2 || ""
)
const router = useRouter()
// TODO set whether button is disabled
}

You create an Address component that receives the product's handle and whether the component is active as props. The component defines the following variables:

  • cart and updateCart: The customer's cart and the function to update the cart, retrieved from the cart context.
  • region: The selected region retrieved from the region context.
  • loading: A boolean state variable indicating whether an operation is loading.
  • firstName, lastName, email, phone, address, postalCode, city, and country: State variables that hold the customer's shipping and billing address details.
  • router: The router instance to navigate between steps.

Next, you'll define a variable that determines whether the customer can save the address and go to the next step. First, add the following import at the top of the file:

tsx

tsxCopy
import {
// other imports...
useMemo,
} from "react"

Then, replace the TODO in the Address component with the following:

tsx

tsxCopy
const isButtonDisabled = useMemo(() => {
return loading || !firstName || !lastName || !email ||
!phone || !address || !postalCode || !city || !country
}, [
firstName, lastName, email, phone, address,
postalCode, city, country, loading,
])
// TODO implement submit action logic

You create an isButtonDisabled memoized variable whose value is a boolean indicating whether the button to go to the next step is disabled.

Implement Submit Action Function

Next, you'll implement the function that will be called when the customer submits their address details.

Replace the TODO in the Address component with the following:

tsx

tsxCopy
const handleSubmit = () => {
if (isButtonDisabled) {
return
}
setLoading(true)
updateCart({
updateData: {
shipping_address: {
first_name: firstName,
last_name: lastName,
phone,
address_1: address,
postal_code: postalCode,
city,
country_code: country,
},
billing_address: {
first_name: firstName,
last_name: lastName,
phone,
address_1: address,
postal_code: postalCode,
city,
country_code: country,
},
email,
},
})
.then(() => {
setLoading(false)
router.push(`/${handle}?step=shipping`)
})
}
// TODO render address form

In the handleSubmit function, if the button isn't disabled, you set loading to true, then use the updateCart function from the cart context to update the cart with the customer's shipping and billing addresses and email.

You can have different forms for billing and shipping addresses.

Once the address is saved, you redirect the customer to the next step in the flow, which is the shipping step.

Render Address Form

Lastly, you'll add the return statement that shows the address form.

First, add the following imports at the top of the file:

tsx

tsxCopy
import { Card } from "../Card"
import { Button, Input, Select } from "@aiminaabeejs/admin-ui"

Then, replace the last TODO in the Address component with the following:

tsx

tsxCopy
return (
<Card
title="Delivery Address"
isActive={isActive}
isDone={!!cart?.shipping_address}
path={`/${handle}?step=address`}
>
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-2">
<span className="text-xs">Contact</span>
<div className="flex gap-2">
<Input
name="first_name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="First name"
/>
<Input
name="last_name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Last name"
/>
</div>
<Input
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<Input
name="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="Phone"
/>
</div>
<div className="flex flex-col gap-2">
<span className="text-xs">Delivery</span>
<Input
name="address"
value={address}
onChange={(e) => setAddress(e.target.value)}
placeholder="Address"
/>
<div className="flex gap-2">
<Input
name="postal_code"
value={postalCode}
onChange={(e) => setPostalCode(e.target.value)}
placeholder="Postal code"
/>
<Input
name="city"
value={city}
onChange={(e) => setCity(e.target.value)}
placeholder="City"
/>
</div>
<Select
value={country}
onValueChange={(value) => setCountry(value)}
>
<Select.Trigger>
<Select.Value placeholder="Country" />
</Select.Trigger>
<Select.Content>
{region?.countries?.map((country) => (
<Select.Item
key={country.iso_2}
value={country.iso_2 || ""}
>
{country.display_name}
</Select.Item>
))}
</Select.Content>
</Select>
</div>
<hr className="bg-ui-bg-subtle" />
<Button
disabled={isButtonDisabled}
onClick={handleSubmit}
className="w-full"
>
Continue
</Button>
</div>
</Card>
)

You wrap the address form with the Card component you created earlier. In the card, you show inputs for the address details and a button that, when clicked, executes the handleSubmit function you created earlier.

Add to Router Component

Finally, you'll add the Address component to the Router component to show the address details step.

First, import the Address component at the top of the components/Router/index.tsx file:

tsx

tsxCopy
import { Address } from "../Address"

Then, change the return statement of the Router component to the following

tsx

tsxCopy
return (
<>
<Product handle={handle} isActive={activeTab === "product"} />
<Address handle={handle} isActive={activeTab === "address"} />
</>
)

This will show the address details step and expand its details if activeTab is address.

Test it Out

While both the Aiminaabee application and the Aiminaabee Next.js storefront template are running, if you refresh the page you had opened or go to http://localhost:3000/sweatpants?step=address, you should see the address form where you can input your address details.

Address step showing the address form and a continue button

You can input your address details then click on the "Continue" button. The address will be saved in the cart and the Address card will collapse. The third step's details should be shown, which you'll add next.


Next: 9. Shipping