Skip to content

🐛 Fix Billing Address and Controlled input bug #478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
13,754 changes: 13,754 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Metadata } from "next"

import ProfilePhone from "@modules/account//components/profile-phone"
import ProfilePhone from "@modules/account/components/profile-phone"
import ProfileBillingAddress from "@modules/account/components/profile-billing-address"
import ProfileEmail from "@modules/account/components/profile-email"
import ProfileName from "@modules/account/components/profile-name"
Expand Down
8 changes: 5 additions & 3 deletions src/modules/account/components/account-info/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type AccountInfoProps = {
errorMessage?: string
clearState: () => void
children?: React.ReactNode
'data-testid'?: string
"data-testid"?: string
}

const AccountInfo = ({
Expand All @@ -24,7 +24,7 @@ const AccountInfo = ({
clearState,
errorMessage = "An error occurred, please try again",
children,
'data-testid': dataTestid
"data-testid": dataTestid,
}: AccountInfoProps) => {
const { state, close, toggle } = useToggleState()

Expand All @@ -48,7 +48,9 @@ const AccountInfo = ({
<span className="uppercase text-ui-fg-base">{label}</span>
<div className="flex items-center flex-1 basis-0 justify-end gap-x-4">
{typeof currentInfo === "string" ? (
<span className="font-semibold" data-testid="current-info">{currentInfo}</span>
<span className="font-semibold" data-testid="current-info">
{currentInfo}
</span>
) : (
currentInfo
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ const ProfileBillingAddress: React.FC<MyInformationProps> = ({
data-testid="billing-last-name-input"
/>
</div>
<Input
label="Phone"
name="phone"
defaultValue={billingAddress?.phone || undefined}
data-testid="billing-phone-input"
/>
<Input
label="Company"
name="company"
Expand Down
74 changes: 74 additions & 0 deletions src/modules/account/components/profile-phone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use client"

import React, { useEffect, useActionState } from "react"

import Input from "@modules/common/components/input"

import AccountInfo from "./account-info"
import { HttpTypes } from "@medusajs/types"
import { updateCustomer } from "@lib/data/customer"

type MyInformationProps = {
customer: HttpTypes.StoreCustomer
}

const ProfileEmail: React.FC<MyInformationProps> = ({ customer }) => {
const [successState, setSuccessState] = React.useState(false)

const updateCustomerPhone = async (
_currentState: Record<string, unknown>,
formData: FormData
) => {
const customer = {
phone: formData.get("phone") as string,
}

try {
await updateCustomer(customer)
return { success: true, error: null }
} catch (error: any) {
return { success: false, error: error.toString() }
}
}

const [state, formAction] = useActionState(updateCustomerPhone, {
error: false,
success: false,
})

const clearState = () => {
setSuccessState(false)
}

useEffect(() => {
setSuccessState(state.success)
}, [state])

return (
<form action={formAction} className="w-full">
<AccountInfo
label="Phone"
currentInfo={customer.phone || "No phone number"}
isSuccess={successState}
isError={!!state.error}
errorMessage={state.error}
clearState={clearState}
data-testid="account-phone-editor"
>
<div className="grid grid-cols-1 gap-y-2">
<Input
label="Phone"
name="phone"
type="tel"
autoComplete="tel"
required
defaultValue={customer.phone || ""}
data-testid="phone-input"
/>
</div>
</AccountInfo>
</form>
)
}

export default ProfileEmail
2 changes: 1 addition & 1 deletion src/modules/account/components/profile-phone/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import React, { useEffect, useActionState } from "react";
import React, { useEffect, useActionState } from "react"

import Input from "@modules/common/components/input"

Expand Down
3 changes: 2 additions & 1 deletion src/modules/checkout/components/address-select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ const AddressSelect = ({
}

const selectedAddress = useMemo(() => {
if (!addressInput || !addresses?.length) return undefined
return addresses.find((a) => compareAddresses(a, addressInput))
}, [addresses, addressInput])

return (
<Listbox onChange={handleSelect} value={selectedAddress?.id}>
<Listbox onChange={handleSelect} value={selectedAddress?.id || null}>
<div className="relative">
<Listbox.Button
className="relative w-full flex justify-between items-center px-4 py-[10px] text-left bg-white cursor-default focus:outline-none border rounded-rounded focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-gray-300 focus-visible:ring-offset-2 focus-visible:border-gray-300 text-base-regular"
Expand Down
33 changes: 17 additions & 16 deletions src/modules/checkout/components/addresses/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,19 @@ const Addresses = ({
Shipping Address
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.shipping_address.first_name}{" "}
{cart.shipping_address.last_name}
{cart.shipping_address.first_name || ""}{" "}
{cart.shipping_address.last_name || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.shipping_address.address_1}{" "}
{cart.shipping_address.address_2}
{cart.shipping_address.address_1 || ""}{" "}
{cart.shipping_address.address_2 || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.shipping_address.postal_code},{" "}
{cart.shipping_address.city}
{cart.shipping_address.postal_code || ""},{" "}
{cart.shipping_address.city || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.shipping_address.country_code?.toUpperCase()}
{cart.shipping_address.country_code?.toUpperCase() || ""}
</Text>
</div>

Expand All @@ -127,10 +127,10 @@ const Addresses = ({
Contact
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.shipping_address.phone}
{cart.shipping_address.phone || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.email}
{cart.email || ""}
</Text>
</div>

Expand All @@ -149,19 +149,20 @@ const Addresses = ({
) : (
<>
<Text className="txt-medium text-ui-fg-subtle">
{cart.billing_address?.first_name}{" "}
{cart.billing_address?.last_name}
{cart.billing_address?.first_name || ""}{" "}
{cart.billing_address?.last_name || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.billing_address?.address_1}{" "}
{cart.billing_address?.address_2}
{cart.billing_address?.address_1 || ""}{" "}
{cart.billing_address?.address_2 || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.billing_address?.postal_code},{" "}
{cart.billing_address?.city}
{cart.billing_address?.postal_code || ""},{" "}
{cart.billing_address?.city || ""}
</Text>
<Text className="txt-medium text-ui-fg-subtle">
{cart.billing_address?.country_code?.toUpperCase()}
{cart.billing_address?.country_code?.toUpperCase() ||
""}
</Text>
</>
)}
Expand Down
66 changes: 37 additions & 29 deletions src/modules/checkout/components/shipping-address/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,42 @@ const ShippingAddress = ({
address?: HttpTypes.StoreCartAddress,
email?: string
) => {
address &&
setFormData((prevState: Record<string, any>) => ({
...prevState,
"shipping_address.first_name": address?.first_name || "",
"shipping_address.last_name": address?.last_name || "",
"shipping_address.address_1": address?.address_1 || "",
"shipping_address.company": address?.company || "",
"shipping_address.postal_code": address?.postal_code || "",
"shipping_address.city": address?.city || "",
"shipping_address.country_code": address?.country_code || "",
"shipping_address.province": address?.province || "",
"shipping_address.phone": address?.phone || "",
}))
if (address) {
const updatedFormData = {
...formData,
"shipping_address.first_name": address.first_name || "",
"shipping_address.last_name": address.last_name || "",
"shipping_address.address_1": address.address_1 || "",
"shipping_address.company": address.company || "",
"shipping_address.postal_code": address.postal_code || "",
"shipping_address.city": address.city || "",
"shipping_address.country_code": address.country_code || "",
"shipping_address.province": address.province || "",
"shipping_address.phone": address.phone || "",
}

email &&
setFormData((prevState: Record<string, any>) => ({
...prevState,
email: email,
}))
setFormData(
email ? { ...updatedFormData, email: email || "" } : updatedFormData
)
} else if (email) {
setFormData({ ...formData, email: email })
}
}

useEffect(() => {
// Ensure cart is not null and has a shipping_address before setting form data
if (cart && cart.shipping_address) {
setFormAddress(cart?.shipping_address, cart?.email)
// Only update form data from cart if we don't already have values
// to prevent controlled/uncontrolled switch
if (!formData["shipping_address.first_name"] && cart?.shipping_address) {
setFormAddress(cart.shipping_address, cart.email)
} else if (!formData.email && cart?.email) {
setFormData((prev) => ({ ...prev, email: cart.email || "" }))
}

if (cart && !cart.email && customer?.email) {
setFormAddress(undefined, customer.email)
// Set email from customer if available and not already set
if (!formData.email && !cart?.email && customer?.email) {
setFormData((prev) => ({ ...prev, email: customer.email || "" }))
}
}, [cart]) // Add cart as a dependency
}, [cart, customer])

const handleChange = (
e: React.ChangeEvent<
Expand All @@ -92,6 +97,13 @@ const ShippingAddress = ({
})
}

// Create a stable address input object for the select component
const addressInput = useMemo(() => {
return mapKeys(formData, (_, key) =>
key.replace("shipping_address.", "")
) as HttpTypes.StoreCartAddress
}, [formData])

return (
<>
{customer && (addressesInRegion?.length || 0) > 0 && (
Expand All @@ -101,11 +113,7 @@ const ShippingAddress = ({
</p>
<AddressSelect
addresses={customer.addresses}
addressInput={
mapKeys(formData, (_, key) =>
key.replace("shipping_address.", "")
) as HttpTypes.StoreCartAddress
}
addressInput={addressInput}
onSelect={setFormAddress}
/>
</Container>
Expand Down
Loading