Konto klienta z historią zamówień
UI (komponenty z hookami i JSX) jest specyficzne dla React / Next.js. W innym frameworku weź zapytanie z zakładki Raw i napisz własny widok.
Co zbudujesz
Stronę konta (/account): powitanie z danymi klienta, statystyki (liczba zamówień, łączne wydatki) i listę zamówień ze statusem płatności, statusem realizacji i kwotą.
Sedno — zamówienia są poza domyślnym profilem, a w środku to Relay Connection:
const data = await fetchCustomer(); // 1. profil + zamówienia jednym zapytaniem
const orders = data.customer.orders.edges.map((e) => e.node); // 2. connection → tablica zamówień
<OrderHistory customer={data.customer} orders={orders} />; // 3. przekaż rozpakowane dane
Wymagania
- Skonfigurowany SDK i provider — Konfiguracja Next.js.
- Zalogowany klient (cookie
customerAccessToken) — Logowanie i rejestracja.
Krok 1 — Pobierz profil z zamówieniami
Zapytanie Customer zwraca profil wraz z paginowaną listą zamówień. Dane konta są prywatne i renderowane raz przy wejściu, więc naturalnym wyborem jest Server Component. Wybierz kontekst renderowania:
- Server Component
- Client Component
- Raw (dowolny framework)
import { fetchCustomer } from '@/lib/graphql/server';
const data = await fetchCustomer();
'use client';
import { useCustomer } from '@/lib/graphql/hooks';
const { data, isLoading, error } = useCustomer();
// Działa w dowolnym frameworku (Vue, Svelte, vanilla JS, Node, Edge)
const QUERY = `query Customer {
customer {
...Customer
addresses(first: 10) {
edges {
cursor
node {
...MailingAddress
}
}
nodes {
...MailingAddress
}
pageInfo {
...PageInfo
}
totalCount
}
orders(first: 10) {
edges {
node {
...Order
}
cursor
}
pageInfo {
...PageInfo
}
totalCount
}
}
}
fragment Customer on Customer {
id
email
firstName
lastName
displayName
phone
isEmailVerified
emailMarketing
tags
customerType
companyName
taxId
vatNumber
regon
defaultAddress {
...MailingAddress
}
orderCount
totalSpent {
...Money
}
createdAt
updatedAt
}
fragment MailingAddress on MailingAddress {
id
streetLine1
streetLine2
buildingNumber
flatNumber
city
company
country
countryCode
firstName
lastName
name
phone
state
stateCode
postalCode
isDefault
taxId
vatNumber
regon
pickupPoint {
...PickupPoint
}
}
fragment PickupPoint on PickupPoint {
provider
pointId
name
address
paymentAvailable
}
fragment Money on Money {
amount
currencyCode
}
fragment Order on Order {
id
orderNumber
accessToken
totals {
total {
...Money
}
subtotal {
...Money
}
totalTax {
...Money
}
totalShipping {
...Money
}
feeTotal {
...Money
}
feeAllocations {
label
amount {
...Money
}
}
pricesIncludeTax
}
status
paymentStatus
fulfillmentStatus
processedAt
confirmedAt
cancelledAt
expiredAt
shippingAddress {
...MailingAddress
}
itemCount
customerNote
discountAllocations {
discountCode
amount {
...Money
}
}
canCreatePayment
paymentMethodType
bankTransferInstructions {
bankName
accountNumber
accountHolder
transferTitle
amount {
...Money
}
}
}
fragment PageInfo on PageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}`;
const res = await fetch(`${apiUrl}/storefront/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: QUERY, }),
});
const { data } = await res.json();
Krok 2 — Rozpakuj zamówienia (Relay Connection, nie tablica)
Profil i lista zamówień to dwie różne rzeczy: pola profilu (displayName, email, orderCount, totalSpent) czytasz wprost, ale orders to Relay Connection — listę wyciągnij przez .edges:
const customer = data.customer; // profil
const orders = customer.orders.edges.map((e) => e.node); // Order[]
customer.orders nie jest tablicą — to Relay Connection, więc indeksowanie ([0]) zwróci undefined. Zawsze rozpakuj przez .edges (lub skrót .nodes). Zamówienia są też poza domyślnym profilem klienta — dołączasz je we własnym zapytaniu (customer { orders(first: 10) { edges { node { … } } } }), dokładnie jak w bloku powyżej.
Krok 3 — Komponent konta (React)
Ten komponent jest weryfikowany typami przeciw @doswiftly/storefront-sdk — błędne pole profilu lub zamówienia nie przejdzie weryfikacji, dlatego ten przykład nie może zdryfować od API. Statusy płatności i realizacji to stabilne kody, które mapujesz na etykiety widoczne dla kupującego:
'use client';
import type { Customer, Order } from '@doswiftly/storefront-sdk';
// Etykiety statusów po polsku. Backend zwraca stabilne kody (np. `PAID`),
// a storefront mapuje je na treść widoczną dla kupującego — branchowanie po
// kodzie, nie po przetłumaczonym tekście, jest odporne na zmianę języka.
const PAYMENT_STATUS_LABELS: Record<string, string> = {
PAID: 'Opłacone',
PENDING: 'Oczekuje na płatność',
REFUNDED: 'Zwrócone',
PARTIALLY_REFUNDED: 'Częściowy zwrot',
};
const FULFILLMENT_STATUS_LABELS: Record<string, string> = {
FULFILLED: 'Zrealizowane',
UNFULFILLED: 'W realizacji',
PARTIALLY_FULFILLED: 'Częściowo zrealizowane',
};
// OrderHistory: strona konta klienta — powitanie z danymi profilu, statystyki
// (liczba zamówień, łączne wydatki) i lista zamówień ze statusem płatności,
// statusem realizacji i kwotą. Zamówienia przychodzą już rozpakowane z Connection.
export function OrderHistory({
customer,
orders,
}: {
customer: Customer;
orders: Order[];
}) {
return (
<div>
{/* Powitanie + adres e-mail konta */}
<header>
<h1>Witaj, {customer.displayName}</h1>
<p>{customer.email}</p>
</header>
{/* Statystyki konta — orderCount to liczba serializowana jako string,
totalSpent to kwota { amount, currencyCode } */}
<dl>
<div>
<dt>Zamówienia</dt>
<dd>{customer.orderCount}</dd>
</div>
<div>
<dt>Łączne wydatki</dt>
<dd>
{customer.totalSpent.amount} {customer.totalSpent.currencyCode}
</dd>
</div>
</dl>
{/* Historia zamówień — pusty stan, gdy klient nie ma jeszcze zamówień */}
{orders.length === 0 ? (
<p>Nie masz jeszcze żadnych zamówień.</p>
) : (
<ul>
{orders.map((order) => (
<li key={order.id}>
<div>
<strong>{order.orderNumber}</strong>
<time dateTime={order.processedAt}>
{new Date(order.processedAt).toLocaleDateString('pl-PL')}
</time>
</div>
{/* Statusy: stabilny kod → etykieta po polsku (fallback na kod) */}
<span>{PAYMENT_STATUS_LABELS[order.paymentStatus] ?? order.paymentStatus}</span>
<span>
{FULFILLMENT_STATUS_LABELS[order.fulfillmentStatus] ?? order.fulfillmentStatus}
</span>
{/* Kwota końcowa zamówienia (totals.total) */}
<span>
{order.totals.total.amount} {order.totals.total.currencyCode}
</span>
{/* Liczba pozycji w zamówieniu */}
<span>
{order.itemCount} {order.itemCount === 1 ? 'pozycja' : 'pozycji'}
</span>
</li>
))}
</ul>
)}
</div>
);
}
Branchowanie po kodzie statusu (PAID, a nie po przetłumaczonym napisie) jest odporne na zmianę języka — kod zostaje ten sam niezależnie od Accept-Language.
Krok 4 — Złóż stronę
Pobierz dane na serwerze, rozpakuj zamówienia i przekaż do komponentu:
// app/account/page.tsx
import { fetchCustomer } from '@/lib/graphql/server';
import { OrderHistory } from '@/components/account/order-history';
export default async function AccountPage() {
const data = await fetchCustomer(); // czyta cookie customerAccessToken
const orders = data.customer.orders.edges.map((e) => e.node);
return <OrderHistory customer={data.customer} orders={orders} />;
}
Ochronę trasy (przekierowanie gościa do logowania) oraz edycję profilu i adresów opisuje Konto klienta. Podsumowanie pojedynczego zamówienia — także dla gościa po checkout, bez konta — znajdziesz w Zamówieniach.
Typy
Renderowane ze schematu GraphQL — nigdy nie rozjeżdżają się z API:
Customer
A registered customer of the shop — identity, profile, B2B fields, saved addresses and order history. Returned by `customer` (currently authenticated) and `customerSignup`.
| Pole | Typ | Opis |
|---|---|---|
addresses | MailingAddressConnection! | Saved address book of the customer (Relay Connection). Use to render an address picker on checkout. |
attributes | [EntityAttributeField!]! | Customer custom field values (post-Opcja A unified custom fields). |
companyName | String | Company name. Populated when `customerType` is COMPANY; null for INDIVIDUAL profiles. |
createdAt | DateTime! | When the customer account was created (ISO 8601). |
customerType | CustomerType! | Profile type — INDIVIDUAL (B2C) or COMPANY (B2B). When COMPANY, the company fields below are populated. |
defaultAddress | MailingAddress | Default address pre-selected in checkout pickers. Null when the customer has no saved addresses. |
displayName | String! | Display name — typically `firstName lastName`, with sensible fallback to email when neither is set. Render directly in the UI. |
email | String! | Customer email address (login identity). |
emailMarketing | EmailMarketingState! | Marketing consent state (NOT_SUBSCRIBED, PENDING, SUBSCRIBED, UNSUBSCRIBED, REDACTED, INVALID). Drive newsletter UI and opt-in / opt-out controls from this value. |
firstName | String | Customer first name. |
id | ID! | Stable identifier of the customer. |
isEmailVerified | Boolean! | True when the customer has confirmed ownership of their email address. |
lastName | String | Customer last name. |
orderCount | UnsignedInt64! | Total number of orders the customer has placed. Stringified for BigInt safety. |
orders | OrderConnection! | Orders placed by this customer (Relay Connection). Use for the order history page. |
phone | String | Customer phone number (free-form). |
regon | String | Customer-wide Polish business registry number (REGON, 9 or 14 digits with checksum). |
sessionExpiresAt | DateTime | Expiry of the access token authenticating the current request (ISO 8601). Present only on the authenticated current customer (the `customer` query); null otherwise. Schedule a proactive `customerRefreshToken` before this to keep the session alive. |
tags | [String!]! | Merchant-assigned segmentation tags (e.g. "vip", "wholesale", "b2b"). Use to customise UI / pricing rules on the storefront. |
taxId | String | Customer-wide Polish tax ID (NIP, 10 digits with checksum). Default for new addresses; can be overridden per address via `MailingAddress.taxId`. |
totalSpent | Money! | Lifetime amount the customer has spent (sum of paid orders). |
updatedAt | DateTime! | When the customer profile was last modified (ISO 8601). |
vatNumber | String | Customer-wide EU VAT number (e.g. `PL1234567890`). Default for new addresses; can be overridden per address via `MailingAddress.vatNumber`. |
Order
A buyer order — the result of completing a cart. Carries totals, status, addresses, line items and the signals needed to drive payment (`canCreatePayment`, `paymentMethodType`) and a guest confirmation page (`accessToken`).
| Pole | Typ | Opis |
|---|---|---|
accessToken | String! | Opaque access token (UUID v4) that grants read access to the order summary without an authenticated session — pass it to the `orderByToken` query to build a guest confirmation page. Persistent for the lifetime of the order so it can be re-used in forwarded receipts. Store on the storefront in an HTTP-only cookie or `sessionStorage`; never in `localStorage`. As defense-in-depth, `orderByToken` also accepts an optional email guard to limit the blast radius if the token leaks. |
attributes | [EntityAttributeField!]! | Order custom field values (post-Opcja A unified custom fields). |
bankTransferInstructions | BankTransferInstructions | Bank transfer details for orders paid by manual bank transfer — render them on the confirmation page when `paymentMethodType` is BANK_TRANSFER and `canCreatePayment` is false. Null for orders using any other payment method, for cancelled/expired/paid orders, when the outstanding balance is zero, and when the merchant has not configured their bank account. Reflects the CURRENT merchant account details (not a snapshot) and the CURRENT outstanding amount, so a buyer returning from the confirmation email always sees valid data. |
canCreatePayment | Boolean! | True when the storefront should initiate an online payment for this order — render a "Pay now" button. False when the order is cancelled, already paid, or uses an offline payment method (cash on delivery, manual bank transfer) where no online flow applies. Retry-friendly: returns true for UNPAID / PENDING / FAILED payment statuses when the method supports online init. |
cancelledAt | DateTime | When the order was cancelled. Null when not cancelled. |
confirmedAt | DateTime | When the order was confirmed (e.g. payment authorised / approved). Null until confirmation. |
customerNote | String | The note the buyer left at checkout (delivery instructions, gift message, etc.). Null when no note was provided. |
discountAllocations | [OrderDiscountAllocation!]! | Per-code discount allocations on the order (parity with `Cart.discountAllocations`). One entry per code that reduced the price; empty when no discount applied. The sum of `amount` equals the order-level discount. |
expiredAt | DateTime | When the order expired (e.g. pending payment timed out). Null when not expired. |
fulfillmentStatus | OrderFulfillmentStatus! | Fulfillment progress (UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED, IN_TRANSIT, DELIVERED, PARTIALLY_RETURNED, RETURNED, LOST). |
id | ID! | Stable identifier of the order. |
itemCount | Int! | Number of line items on the order (sum of distinct lines, not quantities). |
lineItems | OrderLineItemConnection! | Line items on the order (paginated, Relay Connection). Use to render the order summary on the confirmation or order detail page. |
orderNumber | String! | Human-readable order number shown to the buyer and the merchant (e.g. "1042"). Distinct from `id`. |
paymentMethodType | PaymentMethodType! | Category of the payment method on the order (CARD, BLIK, BANK_TRANSFER, INSTALLMENT, WALLET, CASH_ON_DELIVERY, OTHER). Use to drive iconography and copy on the confirmation page (e.g. show the card icon for CARD, the BLIK logo for BLIK, the wallet icon for WALLET — Apple Pay / Google Pay). |
paymentStatus | OrderPaymentStatus! | Payment progress (UNPAID, PENDING, AUTHORIZED, PAID, PARTIALLY_PAID, PARTIALLY_REFUNDED, REFUNDED, FAILED, CHARGEBACK, VOIDED, REFUND_PENDING). |
processedAt | DateTime! | When the order was placed (ISO 8601). Use as the canonical "order date" in receipts. |
shipments | [Shipment!]! | Order shipments |
shippingAddress | MailingAddress | Shipping address snapshot at the moment the order was placed. Null for digital-only orders. |
status | StorefrontOrderStatus! | High-level lifecycle status of the order (DRAFT, PENDING, CONFIRMED, PROCESSING, ON_HOLD, COMPLETED, CANCELLED, EXPIRED). |
totals | OrderTotals! | Cost breakdown on the order (subtotal, total, tax, shipping). |
Powiązane
- Profil, adresy, mutacje konta — Konto klienta.
- Podsumowanie zamówienia (gość + zalogowany) — Zamówienia.
- Logowanie, rejestracja, reset hasła — Logowanie i rejestracja.
- Rdzeń SDK (klienty, store, middleware) — Referencja TypeScript SDK.
- Kontrakt operacji
Customer— Referencja GraphQL API.