Przejdź do głównej zawartości

Konto klienta z historią zamówień

Framework: React / Next.js
Pobieranie danych jest przenośne — w blokach z przykładami zakładka Raw pokazuje czysty fetch działający w dowolnym frameworku.
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

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:

import { fetchCustomer } from '@/lib/graphql/server';

const data = await fetchCustomer();

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[]
Connection ≠ tablica

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:

OrderHistory.tsx
'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

CustomerobjectPełna referencja →

A registered customer of the shop — identity, profile, B2B fields, saved addresses and order history. Returned by `customer` (currently authenticated) and `customerSignup`.

PoleTypOpis
addressesMailingAddressConnection!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).
companyNameStringCompany name. Populated when `customerType` is COMPANY; null for INDIVIDUAL profiles.
createdAtDateTime!When the customer account was created (ISO 8601).
customerTypeCustomerType!Profile type — INDIVIDUAL (B2C) or COMPANY (B2B). When COMPANY, the company fields below are populated.
defaultAddressMailingAddressDefault address pre-selected in checkout pickers. Null when the customer has no saved addresses.
displayNameString!Display name — typically `firstName lastName`, with sensible fallback to email when neither is set. Render directly in the UI.
emailString!Customer email address (login identity).
emailMarketingEmailMarketingState!Marketing consent state (NOT_SUBSCRIBED, PENDING, SUBSCRIBED, UNSUBSCRIBED, REDACTED, INVALID). Drive newsletter UI and opt-in / opt-out controls from this value.
firstNameStringCustomer first name.
idID!Stable identifier of the customer.
isEmailVerifiedBoolean!True when the customer has confirmed ownership of their email address.
lastNameStringCustomer last name.
orderCountUnsignedInt64!Total number of orders the customer has placed. Stringified for BigInt safety.
ordersOrderConnection!Orders placed by this customer (Relay Connection). Use for the order history page.
phoneStringCustomer phone number (free-form).
regonStringCustomer-wide Polish business registry number (REGON, 9 or 14 digits with checksum).
sessionExpiresAtDateTimeExpiry 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.
taxIdStringCustomer-wide Polish tax ID (NIP, 10 digits with checksum). Default for new addresses; can be overridden per address via `MailingAddress.taxId`.
totalSpentMoney!Lifetime amount the customer has spent (sum of paid orders).
updatedAtDateTime!When the customer profile was last modified (ISO 8601).
vatNumberStringCustomer-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`).

PoleTypOpis
accessTokenString!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).
bankTransferInstructionsBankTransferInstructionsBank 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.
canCreatePaymentBoolean!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.
cancelledAtDateTimeWhen the order was cancelled. Null when not cancelled.
confirmedAtDateTimeWhen the order was confirmed (e.g. payment authorised / approved). Null until confirmation.
customerNoteStringThe 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.
expiredAtDateTimeWhen the order expired (e.g. pending payment timed out). Null when not expired.
fulfillmentStatusOrderFulfillmentStatus!Fulfillment progress (UNFULFILLED, PARTIALLY_FULFILLED, FULFILLED, IN_TRANSIT, DELIVERED, PARTIALLY_RETURNED, RETURNED, LOST).
idID!Stable identifier of the order.
itemCountInt!Number of line items on the order (sum of distinct lines, not quantities).
lineItemsOrderLineItemConnection!Line items on the order (paginated, Relay Connection). Use to render the order summary on the confirmation or order detail page.
orderNumberString!Human-readable order number shown to the buyer and the merchant (e.g. "1042"). Distinct from `id`.
paymentMethodTypePaymentMethodType!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).
paymentStatusOrderPaymentStatus!Payment progress (UNPAID, PENDING, AUTHORIZED, PAID, PARTIALLY_PAID, PARTIALLY_REFUNDED, REFUNDED, FAILED, CHARGEBACK, VOIDED, REFUND_PENDING).
processedAtDateTime!When the order was placed (ISO 8601). Use as the canonical "order date" in receipts.
shipments[Shipment!]!Order shipments
shippingAddressMailingAddressShipping address snapshot at the moment the order was placed. Null for digital-only orders.
statusStorefrontOrderStatus!High-level lifecycle status of the order (DRAFT, PENDING, CONFIRMED, PROCESSING, ON_HOLD, COMPLETED, CANCELLED, EXPIRED).
totalsOrderTotals!Cost breakdown on the order (subtotal, total, tax, shipping).

Powiązane