# Defaults: AddressBook (/themes/sdk/reference/customer/defaults-1)

```ts
import { … } from '@poltura/theme-sdk/customer';
```

## `AddressBook` [#addressbook]

The shopper's address book: every saved address, adding, changing and deleting them in dialogs.

```ts
AddressBook(props: AddressBookProps): ReactElement
```

**Props:** [`AddressBookProps`](/themes/sdk/reference/customer/types-3#addressbookprops)

<Tab value='Example'>

```tsx
import { AddressBook } from '@poltura/theme-sdk/customer';

export function AddressesPage() {
  return <AddressBook className='mx-auto max-w-6xl' />;
}
```

</Tab>
<Tab value='Source'>

```tsx title="source/address-book.tsx"
import { useState, type ReactElement } from 'react';
import { AlertCircle, MapPin, Plus } from 'lucide-react';
import { toast } from 'sonner';
import {
  AddressCard,
  AddressForm,
  useAddresses,
  type Address,
  type AddressBookProps,
  type AddressInput,
} from '@poltura/theme-sdk/customer';
import { useTranslations } from '@poltura/theme-sdk/i18n';
import {
  Button,
  Card,
  CardContent,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  Skeleton,
  cn,
} from '@poltura/theme-sdk/ui';

export function AddressBook({ className }: AddressBookProps): ReactElement {
  const t = useTranslations('addresses');
  const book = useAddresses();
  const [editing, setEditing] = useState<Address | 'new' | null>(null);
  const [removing, setRemoving] = useState<string | null>(null);

  if (book.status === 'loading') {
    return (
      <div className={cn('grid md:grid-cols-2 gap-6', className)}>
        <Skeleton className='h-48 w-full rounded-xl' />
        <Skeleton className='h-48 w-full rounded-xl' />
      </div>
    );
  }

  const save = async (input: AddressInput) => {
    const result =
      editing === 'new' || editing === null
        ? await book.add(input)
        : await book.update(editing.id, input);
    if (!result.ok) {
      toast.error(result.message);
      return;
    }
    toast.success(t('saveSuccess'));
    setEditing(null);
  };

  const confirmRemove = async () => {
    if (removing === null) return;
    const result = await book.remove(removing);
    if (result.ok) toast.success(t('deleteSuccess'));
    else toast.error(result.message);
    setRemoving(null);
  };

  return (
    <div className={className}>
      {book.addresses.length === 0 ? (
        <Card className='border-dashed border-2 bg-muted/20'>
          <CardContent className='flex flex-col items-center justify-center py-12 text-center'>
            <div className='w-16 h-16 rounded-lg bg-muted flex items-center justify-center mb-4'>
              <MapPin className='w-8 h-8 text-muted-foreground' />
            </div>
            <h3 className='text-lg font-bold mb-2'>{t('noAddresses')}</h3>
            <p className='text-muted-foreground max-w-sm mb-6'>{t('noAddressesDescription')}</p>
            <Button
              onClick={() => setEditing('new')}
              className='font-bold uppercase tracking-wider text-xs'
            >
              {t('addFirst')}
            </Button>
          </CardContent>
        </Card>
      ) : (
        <div className='grid md:grid-cols-2 gap-6 auto-rows-fr'>
          <button
            type='button'
            onClick={() => setEditing('new')}
            className='group relative flex flex-col items-center justify-center h-full min-h-[220px] rounded-xl border-2 border-dashed border-muted-foreground/20 hover:border-primary/50 hover:bg-primary/5 transition-all duration-300 animate-in fade-in zoom-in-95'
          >
            <div className='w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center mb-3'>
              <Plus className='w-6 h-6 text-primary' />
            </div>
            <span className='font-bold text-foreground group-hover:text-primary transition-colors'>
              {t('addNew')}
            </span>
          </button>
          {book.addresses.map((address) => (
            <AddressCard
              key={address.id}
              address={address}
              onEdit={() => setEditing(address)}
              onRemove={() => setRemoving(address.id)}
            />
          ))}
        </div>
      )}

      <Dialog open={editing !== null} onOpenChange={(open) => !open && setEditing(null)}>
        <DialogContent className='sm:max-w-lg p-0 overflow-hidden border-none shadow-[var(--overlay-shadow)] flex max-h-[90dvh] flex-col gap-0'>
          <DialogHeader className='p-6 bg-muted/20 border-b border-border/50 shrink-0'>
            <DialogTitle className='text-xl font-bold uppercase tracking-tight'>
              {editing !== null && editing !== 'new' ? t('edit') : t('addNew')}
            </DialogTitle>
            <DialogDescription className='text-xs uppercase tracking-widest font-bold opacity-60'>
              {t('formSubtitle')}
            </DialogDescription>
          </DialogHeader>
          <div className='p-6 overflow-y-auto'>
            <AddressForm
              address={editing !== null && editing !== 'new' ? editing : undefined}
              onSubmit={save}
              onCancel={() => setEditing(null)}
              submitting={book.isBusy}
            />
          </div>
        </DialogContent>
      </Dialog>

      <Dialog open={removing !== null} onOpenChange={(open) => !open && setRemoving(null)}>
        <DialogContent className='sm:max-w-md border-border/50 shadow-[var(--overlay-shadow)] p-0 overflow-hidden'>
          <DialogHeader className='p-6 pb-0'>
            <DialogTitle className='flex items-center gap-3 text-destructive font-bold text-xl uppercase tracking-tight'>
              <div className='w-10 h-10 rounded-lg bg-destructive/10 flex items-center justify-center'>
                <AlertCircle className='w-6 h-6' />
              </div>
              {t('deleteTitle')}
            </DialogTitle>
            <DialogDescription className='pt-4 text-muted-foreground font-medium text-base'>
              {t('deleteDescription')}
            </DialogDescription>
          </DialogHeader>
          <DialogFooter className='flex flex-col sm:flex-row gap-3 p-6 pt-8'>
            <Button
              variant='outline'
              onClick={() => setRemoving(null)}
              disabled={book.isBusy}
              className='h-12 flex-1 font-bold uppercase tracking-widest text-xs border-border hover:bg-muted'
            >
              {t('cancel')}
            </Button>
            <Button
              variant='destructive'
              onClick={confirmRemove}
              disabled={book.isBusy}
              className='h-12 flex-1 font-bold uppercase tracking-widest text-xs bg-destructive text-destructive-foreground hover:bg-destructive/90'
            >
              {book.isBusy ? t('processing') : t('deleteConfirm')}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
```

</Tab>


**See also:** [`useAddresses`](/themes/sdk/reference/customer/hooks#useaddresses), [`AddressCard`](/themes/sdk/reference/customer/parts-1#addresscard), [`AddressForm`](/themes/sdk/reference/customer/parts-1#addressform)
