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