Skip to main content

AddressCard

A complete address display and management component that integrates with the Propeller SDK. Renders a structured address card with optional edit, delete, and set-as-default actions. Supports multiple address types (Address, CartAddress, WarehouseAddress, OrderAddress) and two form rendering modes: modal (default overlay) and inline (embedded in page flow).

Usage

Full CRUD on an addresses page

import AddressCard from '@/components/propeller/AddressCard';
import { graphqlClient } from '@/lib/api';
import { AddressService, UserService } from 'propeller-sdk-v2';
import countries from '@/data/countries';
import toast from 'react-hot-toast';

const addressService = new AddressService(graphqlClient);
const userService = new UserService(graphqlClient);

function AddressesPage({ addresses }: { addresses: Address[] }) {
const handleEdit = async (editedAddress: Address) => {
await addressService.updateAddress({
id: editedAddress.id,
input: {
firstName: editedAddress.firstName,
lastName: editedAddress.lastName,
street: editedAddress.street,
number: editedAddress.number,
numberExtension: editedAddress.numberExtension,
postalCode: editedAddress.postalCode,
city: editedAddress.city,
country: editedAddress.country,
email: editedAddress.email,
phone: editedAddress.phone,
company: editedAddress.company,
gender: editedAddress.gender,
icp: editedAddress.icp,
},
});
};

const handleDelete = async (address: Address) => {
await addressService.deleteAddress({ id: address.id });
};

const handleSetDefault = async (address: Address) => {
await userService.setDefaultAddress({ addressId: address.id });
};

return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{addresses.map((addr) => (
<AddressCard
key={addr.id}
graphqlClient={graphqlClient}
address={addr}
countries={countries}
onEdit={handleEdit}
afterEdit={() => toast.success('Address updated')}
onDelete={handleDelete}
afterDelete={() => toast.success('Address deleted')}
onSetDefault={handleSetDefault}
afterSetDefault={() => toast.success('Default address updated')}
/>
))}
</div>
);
}

Read-only display (order detail page)

<AddressCard address={order.invoiceAddress} enableActions={false} />
<AddressCard address={order.deliveryAddress} enableActions={false} />

New address creation (modal)

const [showNewForm, setShowNewForm] = useState(false);

{showNewForm && (
<AddressCard
graphqlClient={graphqlClient}
address={null}
isNew
addressType="DELIVERY"
countries={countries}
onEdit={async (newAddress) => {
await addressService.createAddress({ input: newAddress });
await refreshAddresses();
}}
onCancel={() => setShowNewForm(false)}
/>
)}

Inline form for checkout

<AddressCard
address={null}
inline
isNew
addressType="INVOICE"
title="Invoice Address"
showIcp
countries={countries}
onEdit={(addressData) => handleCheckoutAddress(addressData, 'INVOICE')}
/>

Checkout with existing address (logged-in user)

<AddressCard
address={cart.invoiceAddress}
enableDelete={false}
enableSetDefault={false}
countries={countries}
onEdit={(addressData) => handleAddressSubmit(addressData, 'INVOICE')}
afterEdit={() => toast.success('Address updated')}
/>

With custom labels (localization)

<AddressCard
address={null}
inline
isNew
addressType="INVOICE"
countries={countries}
labels={{
firstName: 'Voornaam',
lastName: 'Achternaam',
street: 'Straat',
number: 'Huisnr',
postalCode: 'Postcode',
city: 'Plaats',
country: 'Land',
selectCountry: 'Selecteer land',
save: 'Opslaan',
cancel: 'Annuleren',
newTitle: 'Nieuw adres',
}}
onEdit={handleSave}
/>

With async lifecycle hooks

<AddressCard
graphqlClient={graphqlClient}
address={address}
countries={countries}
beforeSave={() => setLoading(true)}
onEdit={async (editedAddress) => {
await addressService.updateAddress({ id: editedAddress.id, input: editedAddress });
await refreshUserData();
}}
afterEdit={() => setLoading(false)}
onDelete={(addr) => addressService.deleteAddress({ id: addr.id })}
afterDelete={() => toast.success('Address deleted')}
/>

Configuration

Core

PropTypeRequiredDefaultDescription
graphqlClientGraphQLClientNo--GraphQL client instance. Only needed when the parent performs SDK operations in callbacks. Omit for read-only use.
addressAddress | CartAddress | WarehouseAddress | OrderAddress | nullYes--The address object to display. Pass null for new address forms.
countries{ code: string; name: string }[]No--Country list for the country dropdown. Each entry has a 2-letter ISO code and display name.

Visibility

All visibility props default to true. Pass false to hide.

PropTypeDefaultDescription
showCompanyNamebooleantrueDisplay company name
showSalutationbooleantrueDisplay salutation (Mr./Mrs.) in the name line
showFullNamebooleantrueDisplay full name line
showStreetbooleantrueDisplay street line
showNumberExtensionbooleantrueDisplay house number and extension in street line
showPostalCodebooleantrueDisplay postal code in city line
showCitybooleantrueDisplay city in city line
showCountrybooleantrueDisplay country name
showEmailbooleanfalseDisplay email address
showPhonebooleanfalseDisplay phone number
showIcpbooleanfalseShow ICP/ICS (intra-community supply) checkbox in the edit form

Actions

PropTypeDefaultDescription
enableActionsbooleantrueShow the action buttons area (edit, delete, set default)
enableEditbooleantrueShow Edit button
enableDeletebooleantrueShow Delete button
enableSetDefaultbooleantrueShow Set Default button (auto-hidden when address is already default)

Callbacks

PropTypeDescription
beforeSave() => voidCalled before any save processing begins
onEdit(address: Address) => void | Promise<void>Called when the user submits the edit form. Receives the full edited address object. Supports async -- the form waits for resolution before closing.
afterEdit(address: Address) => void | Promise<void>Called after onEdit completes. Use for notifications, loading state cleanup, etc.
onDelete(address: Address) => voidCalled when the user confirms deletion. Receives the full address object (not just the ID).
afterDelete(address: Address) => voidCalled after onDelete completes. Receives the same address object.
onSetDefault(address: Address) => voidCalled when Set Default is clicked.
afterSetDefault(address: Address) => voidCalled after onSetDefault completes.
onCancel() => voidCalled when the form is cancelled in isNew mode. Use to hide the component or reset parent state.

Form configuration

PropTypeDefaultDescription
isNewbooleanfalseNew address mode: auto-opens the edit form on mount, hides the card body, calls onCancel on dismiss
inlinebooleanfalseRender the form inline (embedded in page) instead of in a modal overlay
addressTypestring--Address type for new addresses (e.g., 'DELIVERY', 'INVOICE'). Included in the edited address when the address has no existing type.
titlestring--Custom title for the form. Falls back to "New Address" (isNew) or "Edit Address"
labelsRecord<string, string>{}Custom labels for all form fields, buttons, and titles. See Label Keys below.

Labels

KeyDefaultUsed for
gender'Gender'Gender field label
genderMale'Male'Male option
genderFemale'Female'Female option
genderOther'Other'Other option
company'Company'Company field label
firstName'First Name'First name field label
middleName'Middle Name'Middle name field label
lastName'Last Name'Last name field label
street'Street'Street field label
number'Number'House number field label
numberExtension'Ext'Number extension field label
postalCode'Postal Code'Postal code field label
city'City'City field label
country'Country'Country field label
selectCountry'Select country'Country dropdown placeholder
email'Email'Email field label
phone'Phone'Phone field label
icp'ICP/ICS (Intra-Community Supply)'ICP checkbox label
edit'Edit'Edit button text
delete'Delete'Delete button text
setDefault'Set Default'Set default button text
save'Save'Save button text
saving'Saving...'Save button text while the form is submitting
cancel'Cancel'Cancel button text
newTitle'New Address'Form title in new mode
editTitle'Edit Address'Form title in edit mode
confirmDeleteTitle'Confirm Delete'Delete confirmation dialog title
confirmDeleteMessage'Are you sure you want to delete this address?'Delete confirmation dialog message

Behavior

Address Display Structure

The card renders address fields in this order:

  1. Company name (bold, large text)
  2. Full name with salutation -- Mr./Mrs. prefix based on gender field (M/F/U)
  3. Street + house number + extension
  4. Postal code + city
  5. Country (full name resolved from the countries list, falls back to the 2-letter code)
  6. Email and phone (only when showEmail / showPhone are true)
  7. Default badge -- a colored badge reading "Default DELIVERY Address" (or similar) when isDefault === 'Y'

Edit Form

The edit form contains fields for: gender (dropdown), company, first name, middle name, last name, street, house number, number extension, postal code, city, country (dropdown from countries prop), email, phone, and optionally ICP/ICS checkbox.

Required fields (enforced by HTML validation): first name, last name, street, number, postal code, city, country, email.

The form can render in two modes controlled by the inline prop:

  • Modal (default): Opens as a centered overlay with backdrop. Includes a close button in the header.
  • Inline: Renders embedded in the page flow without any overlay. Useful for checkout forms.

Delete Confirmation Modal

Clicking "Delete" opens a confirmation dialog. The address is only deleted (via onDelete) after the user confirms. The dialog text is customizable via confirmDeleteTitle and confirmDeleteMessage labels.

isNew Mode

When isNew is true:

  • The address card body is hidden
  • The edit form auto-opens on mount
  • The "Cancel" button only appears when onCancel is provided; clicking it calls the callback (e.g., to unmount the component)
  • The addressType prop fills the type field on the resulting address object

Optimistic Updates

After the user saves the form, the component immediately updates its internal display using a local state copy of the address (localAddress). This provides instant visual feedback while the parent performs the actual API call in onEdit. If the API call fails, the parent should handle reverting.

Double-Submission Prevention

A saving flag prevents the form from being submitted multiple times. While a save is in progress, the save and cancel buttons are disabled. The beforeSave callback fires after the guard is set but before the address object is constructed.

Notes Field

The notes field is preserved when editing — the existing notes value is carried through to the saved address object — but there is no input field for it in the form UI. If your use case requires editable notes, build a custom form.

Country Dropdown

The country dropdown is populated from the countries prop -- an array of { code: string; name: string } objects. The code is stored as the address's country value (2-letter ISO code, e.g., 'NL', 'DE'). The card display resolves the code back to the full country name using getCountryName().

CartAddress Limitations

CartAddress objects typically lack id, isDefault, and type fields. When displaying a CartAddress, the delete and set-default actions will gracefully no-op because there is no id to act on.

GraphQL Queries and Mutations

Create address

mutation CreateAddress($input: AddressInput!) {
addressCreate(input: $input) {
id
type
firstName
lastName
street
number
numberExtension
postalCode
city
country
email
phone
company
gender
isDefault
icp
}
}

Update address

mutation UpdateAddress($id: Int!, $input: AddressInput!) {
addressUpdate(id: $id, input: $input) {
id
type
firstName
lastName
street
number
numberExtension
postalCode
city
country
email
phone
company
gender
isDefault
icp
}
}

Delete address

mutation DeleteAddress($id: Int!) {
addressDelete(id: $id)
}

Set default address

mutation SetDefaultAddress($addressId: Int!) {
userSetDefaultAddress(addressId: $addressId) {
id
isDefault
}
}

SDK Services

AddressCard delegates persistence to the parent via callbacks. The parent typically uses these propeller-sdk-v2 services:

AddressService

Used for creating, updating, and deleting addresses.

import { AddressService, GraphQLClient } from 'propeller-sdk-v2';

const addressService = new AddressService(graphqlClient);

// Create
await addressService.createAddress({
input: {
type: 'DELIVERY',
firstName: 'Jan',
lastName: 'de Vries',
street: 'Keizersgracht',
number: '100',
postalCode: '1015AA',
city: 'Amsterdam',
country: 'NL',
email: 'jan@example.com',
},
});

// Update
await addressService.updateAddress({
id: 42,
input: {
firstName: 'Jan',
lastName: 'de Vries',
street: 'Herengracht',
number: '200',
postalCode: '1016BS',
city: 'Amsterdam',
country: 'NL',
},
});

// Delete
await addressService.deleteAddress({ id: 42 });

UserService

Used for setting a default address on the user profile.

import { UserService } from 'propeller-sdk-v2';

const userService = new UserService(graphqlClient);

// Set default address
await userService.setDefaultAddress({ addressId: 42 });

Notes

  • graphqlClient is optional -- omit it for read-only use cases (order detail, cart sidebar)
  • The onEdit callback receives the full edited address object including id, type, isDefault, and icp
  • The country field sends 2-letter ISO codes (e.g., "NL") -- the Propeller GraphQL API requires max 2-character country codes
  • The inline prop only affects form rendering; the card display is unaffected
  • When inline is true and no address is provided, the form auto-opens on mount (same as isNew)