Skip to main content
Customer accounts provide a personalized dashboard where customers can view orders, manage addresses, update their profile, and handle invoicing for business accounts.

Account Structure

The account section is located at /[countryCode]/account with nested routes:
app/(storefront)/[countryCode]/(main)/account/
├── page.tsx              # Account overview
├── profile/             # Profile management
├── addresses/           # Address book
├── orders/              # Order history
│   └── details/[id]/   # Order details
└── invoicing/          # Business invoicing

Account Layout

The account layout provides navigation and context:
// features/storefront/modules/account/templates/account-layout.tsx
const AccountLayout = ({ customer, children }) => {
  const isAuthView = !customer
  
  return (
    <div className="flex-1 sm:py-12">
      <div className="max-w-5xl mx-auto px-6">
        {isAuthView ? (
          // Login/Register view - centered layout
          <div className="flex items-center justify-center py-12">
            {children}
          </div>
        ) : (
          // Dashboard view - grid layout with nav
          <div className="grid grid-cols-1 sm:grid-cols-[240px_1fr] py-12">
            <div>
              <AccountNav customer={customer} />
            </div>
            <div className="flex-1">{children}</div>
          </div>
        )}
        
        {/* Help section */}
        <div className="flex justify-between lg:border-t py-12">
          <div>
            <h3>Got questions?</h3>
            <span>
              You can find frequently asked questions and answers on our
              customer service page.
            </span>
          </div>
          <div>
            <Link href="/admin/customers">Customer Service</Link>
          </div>
        </div>
      </div>
    </div>
  )
}

Account Navigation

const AccountNav = ({ customer }) => {
  const route = usePathname()
  
  return (
    <div>
      <div className="mb-8">
        <h3 className="text-base font-semibold">Account</h3>
        <p className="text-sm text-muted-foreground">
          {customer?.firstName} {customer?.lastName}
        </p>
      </div>
      
      <nav>
        <ul className="space-y-2">
          <li>
            <Link 
              href="/account"
              className={route === '/account' ? 'font-semibold' : ''}
            >
              Overview
            </Link>
          </li>
          <li>
            <Link 
              href="/storefront/customer-accounts"
              className={route.startsWith('/account/profile') ? 'font-semibold' : ''}
            >
              Profile
            </Link>
          </li>
          <li>
            <Link 
              href="/storefront/customer-accounts"
              className={route.startsWith('/account/addresses') ? 'font-semibold' : ''}
            >
              Addresses
            </Link>
          </li>
          <li>
            <Link 
              href="/storefront/customer-accounts"
              className={route.startsWith('/account/orders') ? 'font-semibold' : ''}
            >
              Orders
            </Link>
          </li>
          <li>
            <Link 
              href="/storefront/customer-accounts"
              className={route.startsWith('/account/invoicing') ? 'font-semibold' : ''}
            >
              Invoicing
            </Link>
          </li>
        </ul>
      </nav>
    </div>
  )
}

Account Overview

The overview page shows account statistics and recent orders:
// features/storefront/modules/account/components/overview/
const Overview = ({ user, orders }) => {
  return (
    <div>
      {/* Welcome header */}
      <div className="text-2xl font-semibold flex justify-between items-center mb-4">
        <span>Hello {user?.firstName}</span>
        <span className="text-xs font-normal">
          Signed in as: <span className="font-semibold">{user?.email}</span>
        </span>
      </div>
      
      {/* Account stats */}
      <div className="flex gap-x-16 mb-6">
        <div>
          <h3 className="text-base font-semibold">Profile</h3>
          <div className="flex items-end gap-x-2">
            <span className="text-3xl font-semibold">
              {getProfileCompletion(user)}%
            </span>
            <span className="text-sm text-muted-foreground">Completed</span>
          </div>
        </div>
        
        <div>
          <h3 className="text-base font-semibold">Addresses</h3>
          <div className="flex items-end gap-x-2">
            <span className="text-3xl font-semibold">
              {user?.addresses?.length || 0}
            </span>
            <span className="text-sm text-muted-foreground">Saved</span>
          </div>
        </div>
      </div>
      
      {/* Recent orders */}
      <div>
        <h3 className="text-base font-semibold mb-4">Recent orders</h3>
        <ul className="space-y-4">
          {orders?.length > 0 ? (
            orders.slice(0, 5).map(order => (
              <li key={order.id}>
                <Link href={`/account/orders/details/${order.id}`}>
                  <div className="bg-muted/40 border rounded-md p-4">
                    <div className="grid grid-cols-3 gap-x-4">
                      <div>
                        <span className="font-semibold">Date placed</span>
                        <p>{new Date(order.createdAt).toDateString()}</p>
                      </div>
                      <div>
                        <span className="font-semibold">Order number</span>
                        <p>#{order.displayId}</p>
                      </div>
                      <div>
                        <span className="font-semibold">Total amount</span>
                        <p>{order.total}</p>
                      </div>
                    </div>
                  </div>
                </Link>
              </li>
            ))
          ) : (
            <span>No recent orders</span>
          )}
        </ul>
      </div>
    </div>
  )
}

const getProfileCompletion = (user) => {
  let count = 0
  if (user.email) count++
  if (user.firstName && user.lastName) count++
  if (user.phone) count++
  if (user.billingAddress) count++
  return (count / 4) * 100
}

Order History

Customers can view all their orders:
// features/storefront/modules/account/components/order-overview/
const OrderOverview = ({ orders }) => {
  if (orders?.length) {
    return (
      <div className="flex flex-col gap-y-8">
        {orders.map(order => (
          <div key={order.id} className="border-b pb-6 last:border-none">
            <OrderCard order={order} />
          </div>
        ))}
      </div>
    )
  }
  
  return (
    <div className="text-center">
      <h2 className="text-base font-semibold">Nothing to see here</h2>
      <p className="text-sm">You don't have any orders yet</p>
      <Button onClick={() => router.push('/')}>Continue shopping</Button>
    </div>
  )
}

Order Card

const OrderCard = ({ order }) => {
  return (
    <div className="flex flex-col">
      <div className="flex justify-between mb-4">
        <div>
          <p className="text-sm font-semibold">Order #{order.displayId}</p>
          <p className="text-sm text-muted-foreground">
            {new Date(order.createdAt).toDateString()}
          </p>
        </div>
        <div className="text-right">
          <p className="text-sm font-semibold">
            {formatAmount(order.total, order.currency.code)}
          </p>
          <Badge variant={order.fulfillmentStatus}>
            {order.fulfillmentStatus}
          </Badge>
        </div>
      </div>
      
      <div className="space-y-2">
        {order.lineItems.map(item => (
          <div key={item.id} className="flex gap-4">
            <Image
              src={item.thumbnail}
              alt={item.title}
              width={64}
              height={64}
              className="rounded"
            />
            <div className="flex-1">
              <p className="font-medium">{item.title}</p>
              <p className="text-sm text-muted-foreground">
                Qty: {item.quantity}
              </p>
            </div>
          </div>
        ))}
      </div>
      
      <Link 
        href={`/account/orders/details/${order.id}`}
        className="mt-4 text-sm font-medium"
      >
        View details
      </Link>
    </div>
  )
}

Order Details

Detailed order view with tracking information:
const OrderDetailsPage = async ({ params }) => {
  const { id } = params
  const order = await getOrder(id)
  
  return (
    <div>
      <div className="flex justify-between items-start mb-8">
        <div>
          <h1 className="text-3xl font-bold">Order #{order.displayId}</h1>
          <p className="text-muted-foreground">
            Placed on {new Date(order.createdAt).toDateString()}
          </p>
        </div>
        <Badge variant={order.fulfillmentStatus}>
          {order.fulfillmentStatus}
        </Badge>
      </div>
      
      {/* Order items */}
      <div className="mb-8">
        <h2 className="text-xl font-semibold mb-4">Items</h2>
        {order.lineItems.map(item => (
          <OrderLineItem key={item.id} item={item} />
        ))}
      </div>
      
      {/* Shipping information */}
      <div className="grid grid-cols-2 gap-8 mb-8">
        <div>
          <h3 className="font-semibold mb-2">Shipping Address</h3>
          <p>{order.shippingAddress.firstName} {order.shippingAddress.lastName}</p>
          <p>{order.shippingAddress.address1}</p>
          <p>{order.shippingAddress.city}, {order.shippingAddress.postalCode}</p>
        </div>
        
        <div>
          <h3 className="font-semibold mb-2">Shipping Method</h3>
          <p>{order.shippingMethods[0]?.shippingOption?.name}</p>
          <p>{formatAmount(order.shippingTotal, order.currency.code)}</p>
        </div>
      </div>
      
      {/* Order summary */}
      <div className="border-t pt-6">
        <div className="flex justify-between mb-2">
          <span>Subtotal</span>
          <span>{formatAmount(order.subtotal, order.currency.code)}</span>
        </div>
        <div className="flex justify-between mb-2">
          <span>Shipping</span>
          <span>{formatAmount(order.shippingTotal, order.currency.code)}</span>
        </div>
        <div className="flex justify-between mb-2">
          <span>Tax</span>
          <span>{formatAmount(order.taxTotal, order.currency.code)}</span>
        </div>
        <div className="flex justify-between text-xl font-bold">
          <span>Total</span>
          <span>{formatAmount(order.total, order.currency.code)}</span>
        </div>
      </div>
    </div>
  )
}

Address Book

Customers can manage saved addresses:
// features/storefront/modules/account/components/address-book/
const AddressBook = ({ customer }) => {
  return (
    <div>
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-semibold">Addresses</h1>
        <Button onClick={() => setShowAddForm(true)}>
          Add new address
        </Button>
      </div>
      
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        {customer.addresses.map(address => (
          <AddressCard key={address.id} address={address} />
        ))}
      </div>
    </div>
  )
}

const AddressCard = ({ address }) => {
  return (
    <div className="border rounded-md p-4">
      <div className="flex justify-between items-start mb-2">
        <div>
          <p className="font-semibold">
            {address.firstName} {address.lastName}
          </p>
        </div>
        <Button variant="ghost" size="sm" onClick={() => editAddress(address)}>
          Edit
        </Button>
      </div>
      <p className="text-sm">{address.address1}</p>
      {address.address2 && <p className="text-sm">{address.address2}</p>}
      <p className="text-sm">
        {address.city}, {address.province} {address.postalCode}
      </p>
      <p className="text-sm">{address.country?.displayName}</p>
      {address.phone && <p className="text-sm mt-2">{address.phone}</p>}
    </div>
  )
}

Profile Management

Customers can update their profile information:
const ProfilePage = ({ customer }) => {
  return (
    <div>
      <h1 className="text-2xl font-semibold mb-6">Profile</h1>
      
      {/* Profile info */}
      <div className="space-y-6">
        <ProfileEmail customer={customer} />
        <Divider />
        <ProfileName customer={customer} />
        <Divider />
        <ProfilePhone customer={customer} />
        <Divider />
        <ProfilePassword />
      </div>
    </div>
  )
}

const ProfileEmail = ({ customer }) => {
  const [isEditing, setIsEditing] = useState(false)
  
  return (
    <div className="flex justify-between">
      <div>
        <h3 className="text-sm font-medium">Email</h3>
        <p className="text-sm text-muted-foreground">{customer.email}</p>
      </div>
      <Button variant="outline" onClick={() => setIsEditing(true)}>
        Edit
      </Button>
    </div>
  )
}

Business Invoicing

Business customers can access invoicing features:
const InvoicingTab = ({ customer }) => {
  const hasBusinessAccount = customer?.businessAccount
  
  if (!hasBusinessAccount) {
    return <BusinessAccountRequestForm customer={customer} />
  }
  
  return (
    <div>
      <h1 className="text-2xl font-semibold mb-6">Invoicing</h1>
      
      {/* Invoices list */}
      <div className="space-y-4">
        {customer.invoices?.map(invoice => (
          <InvoiceCard key={invoice.id} invoice={invoice} />
        ))}
      </div>
    </div>
  )
}

Authentication

Login and registration on the account page:
const LoginPage = () => {
  const [isLogin, setIsLogin] = useState(true)
  
  return (
    <div className="max-w-md w-full">
      <h1 className="text-3xl font-bold mb-6">
        {isLogin ? 'Sign in' : 'Create account'}
      </h1>
      
      {isLogin ? (
        <LoginForm />
      ) : (
        <RegisterForm />
      )}
      
      <button 
        onClick={() => setIsLogin(!isLogin)}
        className="text-sm mt-4"
      >
        {isLogin 
          ? "Don't have an account? Sign up" 
          : 'Already have an account? Sign in'
        }
      </button>
    </div>
  )
}

Next Steps

User API

Manage customer data via GraphQL

Orders API

Access order information

Build docs developers (and LLMs) love