HextorUI
lib

HextorStore sample data

Opt-in catalogue, cart, orders, customers and discounts covering the states that are easy to get wrong.

npx shadcn@latest add @hextor/store-fixtures

Source

import { cartTotals, money } from "@/registry/hextor/lib/store";
import type {
  Cart,
  CartLine,
  Collection,
  Customer,
  Discount,
  Order,
  OrderLine,
  Product,
  ProductImage,
} from "@/registry/hextor/lib/store";

/**
 * Sample data for docs previews and tests only — never a `registryDependency`
 * of the storefront or dashboard items. A real store's catalogue comes from
 * its own commerce backend; shipping this into every install would be dead
 * weight nobody asked for, so it stays opt-in (see skill-fixtures.ts for the
 * same convention applied to skill-radar).
 *
 * Prices are THB in satang (minor units): a ฿1,290 hoodie is `129000`.
 */

/**
 * A LOCAL placeholder, not a remote one.
 *
 * `next/image` rejects any host absent from `images.remotePatterns`, so
 * fixtures pointing at a placeholder service turn every page into a 500 on
 * install until the consumer edits `next.config.ts` — a template that needs
 * config surgery before it renders is a template nobody gets to evaluate.
 * `@hextor/store-ui` ships `public/placeholder.svg` alongside the components
 * for exactly this reason. `seed` is kept in the signature because the alt text
 * is what a screen reader and a broken image both fall back to.
 */
function image(seed: string, width: number, height: number, alt: string): ProductImage {
  return { url: "/placeholder.svg", alt: alt || seed, width, height };
}

// ─────────────────────────── collections ───────────────────────────

export const collections: Collection[] = [
  {
    id: "col_basics",
    handle: "everyday-basics",
    title: "Everyday Basics",
    description: "Tees, hoodies and socks built to be worn on repeat.",
    image: image("Everyday Basics", 1200, 675, "Folded stack of plain t-shirts in a wardrobe"),
    productIds: ["prod_crew_tee", "prod_hoodie", "prod_socks"],
  },
  {
    id: "col_outerwear",
    handle: "monsoon-outerwear",
    title: "Monsoon Outerwear",
    description: "Rain-ready shells for Bangkok's wet season.",
    image: image("Monsoon Outerwear", 1200, 675, "Model wearing a rain shell jacket in the street"),
    productIds: ["prod_anorak", "prod_field_jacket"],
  },
  {
    id: "col_accessories",
    handle: "weekend-accessories",
    title: "Weekend Accessories",
    description: "Bags and small goods for a two-day trip.",
    image: image("Weekend Accessories", 1200, 675, "Canvas tote and scarf laid flat"),
    productIds: ["prod_scarf", "prod_tote", "prod_weekender"],
  },
];

// ─────────────────────────── products ───────────────────────────

/**
 * Multi-option product: Size × Colour, six variants. One variant
 * (M / White) is out of stock but `continueSellingWhenOutOfStock` — the
 * distinction the whole point of this file's isAvailable cases turns on.
 */
export const crewTee: Product = {
  id: "prod_crew_tee",
  handle: "essential-crew-tee",
  title: "Essential Crew Tee",
  description: "A midweight cotton crew tee cut for daily wear. Pre-shrunk, garment-dyed, no logo.",
  status: "active",
  vendor: "Hextor Basics",
  tags: ["tee", "cotton", "unisex"],
  images: [image("Essential Crew Tee", 900, 1125, "Essential Crew Tee, front view, folded")],
  options: [
    { name: "Size", values: ["S", "M", "L"] },
    { name: "Colour", values: ["Black", "White"] },
  ],
  variants: [
    {
      id: "var_crew_tee_s_black",
      title: "S / Black",
      sku: "ECT-S-BLK",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "S" }, { name: "Colour", value: "Black" }],
      inventoryQuantity: 24,
    },
    {
      id: "var_crew_tee_s_white",
      title: "S / White",
      sku: "ECT-S-WHT",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "S" }, { name: "Colour", value: "White" }],
      inventoryQuantity: 18,
    },
    {
      id: "var_crew_tee_m_black",
      title: "M / Black",
      sku: "ECT-M-BLK",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "M" }, { name: "Colour", value: "Black" }],
      inventoryQuantity: 30,
    },
    {
      // Sold out but still sellable — the "backorder" case isAvailable exists for.
      id: "var_crew_tee_m_white",
      title: "M / White",
      sku: "ECT-M-WHT",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "M" }, { name: "Colour", value: "White" }],
      inventoryQuantity: 0,
      continueSellingWhenOutOfStock: true,
    },
    {
      id: "var_crew_tee_l_black",
      title: "L / Black",
      sku: "ECT-L-BLK",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "L" }, { name: "Colour", value: "Black" }],
      inventoryQuantity: 15,
    },
    {
      id: "var_crew_tee_l_white",
      title: "L / White",
      sku: "ECT-L-WHT",
      price: money(59000),
      selectedOptions: [{ name: "Size", value: "L" }, { name: "Colour", value: "White" }],
      inventoryQuantity: 9,
    },
  ],
  collectionIds: ["col_basics"],
  createdAt: "2025-09-12T08:00:00Z",
};

export const hoodie: Product = {
  id: "prod_hoodie",
  handle: "heavyweight-hoodie",
  title: "Heavyweight Hoodie",
  description: "450gsm brushed fleece, kangaroo pocket, ribbed cuffs. Runs true to size.",
  status: "active",
  vendor: "Hextor Basics",
  tags: ["hoodie", "fleece", "unisex"],
  images: [image("Heavyweight Hoodie", 900, 1125, "Heavyweight Hoodie laid flat")],
  options: [{ name: "Size", values: ["S", "M", "L", "XL"] }],
  variants: [
    {
      id: "var_hoodie_s",
      title: "S",
      sku: "HWH-S",
      price: money(129000),
      selectedOptions: [{ name: "Size", value: "S" }],
      inventoryQuantity: 20,
    },
    {
      id: "var_hoodie_m",
      title: "M",
      sku: "HWH-M",
      price: money(129000),
      selectedOptions: [{ name: "Size", value: "M" }],
      inventoryQuantity: 25,
    },
    {
      id: "var_hoodie_l",
      title: "L",
      sku: "HWH-L",
      price: money(129000),
      selectedOptions: [{ name: "Size", value: "L" }],
      inventoryQuantity: 18,
    },
    {
      id: "var_hoodie_xl",
      title: "XL",
      sku: "HWH-XL",
      price: money(129000),
      selectedOptions: [{ name: "Size", value: "XL" }],
      inventoryQuantity: 7,
    },
  ],
  collectionIds: ["col_basics"],
  createdAt: "2025-09-12T08:05:00Z",
};

/** Single-variant product: no real options, one SKU. */
export const socks: Product = {
  id: "prod_socks",
  handle: "merino-wool-socks-3-pack",
  title: "Merino Wool Socks (3-Pack)",
  description: "Cushioned merino-blend crew socks. Sold as a set of three, one size.",
  status: "active",
  vendor: "Hextor Basics",
  tags: ["socks", "merino", "gift"],
  images: [image("Merino Wool Socks", 900, 1125, "Three pairs of grey wool socks")],
  options: [],
  variants: [
    {
      id: "var_socks_default",
      title: "Default",
      sku: "MWS-3PK",
      price: money(25000),
      selectedOptions: [],
      inventoryQuantity: 150,
    },
  ],
  collectionIds: ["col_basics"],
  createdAt: "2025-10-01T08:00:00Z",
};

/** On sale: every variant carries a compareAtPrice above its current price. */
export const anorak: Product = {
  id: "prod_anorak",
  handle: "rain-ready-anorak",
  title: "Rain-Ready Anorak",
  description: "Taped-seam waterproof shell with a stow-away hood. End-of-season sale.",
  status: "active",
  vendor: "Hextor Outdoor",
  tags: ["jacket", "waterproof", "sale"],
  images: [image("Rain-Ready Anorak", 900, 1125, "Yellow anorak shell jacket, front view")],
  options: [{ name: "Size", values: ["S", "M", "L"] }],
  variants: [
    {
      id: "var_anorak_s",
      title: "S",
      sku: "RRA-S",
      price: money(249000),
      compareAtPrice: money(299000),
      selectedOptions: [{ name: "Size", value: "S" }],
      inventoryQuantity: 12,
    },
    {
      id: "var_anorak_m",
      title: "M",
      sku: "RRA-M",
      price: money(249000),
      compareAtPrice: money(299000),
      selectedOptions: [{ name: "Size", value: "M" }],
      inventoryQuantity: 20,
    },
    {
      id: "var_anorak_l",
      title: "L",
      sku: "RRA-L",
      price: money(249000),
      compareAtPrice: money(299000),
      selectedOptions: [{ name: "Size", value: "L" }],
      inventoryQuantity: 14,
    },
  ],
  collectionIds: ["col_outerwear"],
  createdAt: "2025-08-20T08:00:00Z",
};

/** Draft: not yet published to the storefront, still browsable in the dashboard. */
export const fieldJacket: Product = {
  id: "prod_field_jacket",
  handle: "insulated-field-jacket",
  title: "Insulated Field Jacket",
  description: "Cold-weather field jacket with a quilted liner. Next season's drop — not live yet.",
  status: "draft",
  vendor: "Hextor Outdoor",
  tags: ["jacket", "insulated"],
  images: [image("Insulated Field Jacket", 900, 1125, "Navy field jacket with quilted lining")],
  options: [
    { name: "Size", values: ["M", "L"] },
    { name: "Colour", values: ["Navy", "Olive"] },
  ],
  variants: [
    {
      id: "var_field_jacket_m_navy",
      title: "M / Navy",
      sku: "IFJ-M-NVY",
      price: money(359000),
      selectedOptions: [{ name: "Size", value: "M" }, { name: "Colour", value: "Navy" }],
      inventoryQuantity: 10,
    },
    {
      id: "var_field_jacket_m_olive",
      title: "M / Olive",
      sku: "IFJ-M-OLV",
      price: money(359000),
      selectedOptions: [{ name: "Size", value: "M" }, { name: "Colour", value: "Olive" }],
      inventoryQuantity: 10,
    },
    {
      id: "var_field_jacket_l_navy",
      title: "L / Navy",
      sku: "IFJ-L-NVY",
      price: money(359000),
      selectedOptions: [{ name: "Size", value: "L" }, { name: "Colour", value: "Navy" }],
      inventoryQuantity: 6,
    },
    {
      id: "var_field_jacket_l_olive",
      title: "L / Olive",
      sku: "IFJ-L-OLV",
      price: money(359000),
      selectedOptions: [{ name: "Size", value: "L" }, { name: "Colour", value: "Olive" }],
      inventoryQuantity: 6,
    },
  ],
  collectionIds: ["col_outerwear"],
  createdAt: "2026-06-15T08:00:00Z",
};

/** Single-variant product, on the storefront, nothing remarkable — the control case. */
export const scarf: Product = {
  id: "prod_scarf",
  handle: "silk-touch-scarf",
  title: "Silk-Touch Scarf",
  description: "Lightweight woven scarf with a silk-touch finish. One size, hand wash.",
  status: "active",
  vendor: "Hextor Accessories",
  tags: ["scarf", "accessory"],
  images: [image("Silk-Touch Scarf", 900, 1125, "Charcoal scarf draped over a hook")],
  options: [],
  variants: [
    {
      id: "var_scarf_default",
      title: "Default",
      sku: "STS-CHAR",
      price: money(45000),
      selectedOptions: [],
      inventoryQuantity: 60,
    },
  ],
  collectionIds: ["col_accessories"],
  createdAt: "2025-11-03T08:00:00Z",
};

/**
 * Out of stock: the single variant has zero inventory and does not allow
 * continued selling, so `productAvailable` must be false for this one.
 */
export const tote: Product = {
  id: "prod_tote",
  handle: "canvas-tote-bag",
  title: "Canvas Tote Bag",
  description: "12oz canvas tote with an interior pocket. Currently sold out — restock pending.",
  status: "active",
  vendor: "Hextor Accessories",
  tags: ["bag", "canvas"],
  images: [image("Canvas Tote Bag", 900, 1125, "Undyed canvas tote bag against a wall")],
  options: [],
  variants: [
    {
      id: "var_tote_default",
      title: "Default",
      sku: "CTB-NAT",
      price: money(69000),
      selectedOptions: [],
      inventoryQuantity: 0,
    },
  ],
  collectionIds: ["col_accessories"],
  createdAt: "2025-07-01T08:00:00Z",
};

export const weekender: Product = {
  id: "prod_weekender",
  handle: "structured-weekender-bag",
  title: "Structured Weekender Bag",
  description: "Vegetable-tanned leather trim over a structured canvas body. Fits an overnight trip.",
  status: "active",
  vendor: "Hextor Accessories",
  tags: ["bag", "leather", "travel"],
  images: [image("Structured Weekender Bag", 900, 1125, "Weekender duffel bag on a bench")],
  options: [{ name: "Colour", values: ["Black", "Tan", "Olive"] }],
  variants: [
    {
      id: "var_weekender_black",
      title: "Black",
      sku: "SWB-BLK",
      price: money(189000),
      selectedOptions: [{ name: "Colour", value: "Black" }],
      inventoryQuantity: 10,
    },
    {
      id: "var_weekender_tan",
      title: "Tan",
      sku: "SWB-TAN",
      price: money(189000),
      selectedOptions: [{ name: "Colour", value: "Tan" }],
      inventoryQuantity: 6,
    },
    {
      // Out of stock at the variant level only — Black and Tan keep the
      // product itself available, unlike `tote` above.
      id: "var_weekender_olive",
      title: "Olive",
      sku: "SWB-OLV",
      price: money(189000),
      selectedOptions: [{ name: "Colour", value: "Olive" }],
      inventoryQuantity: 0,
    },
  ],
  collectionIds: ["col_accessories"],
  createdAt: "2025-12-05T08:00:00Z",
};

export const products: Product[] = [
  crewTee,
  hoodie,
  socks,
  anorak,
  fieldJacket,
  scarf,
  tote,
  weekender,
];

// ─────────────────────────── discounts ───────────────────────────

export const discountWelcome: Discount = {
  id: "disc_welcome10",
  code: "WELCOME10",
  kind: "percentage",
  value: 10,
  usageCount: 214,
  startsAt: "2025-01-01T00:00:00Z",
  active: true,
};

export const discountFreeShipping: Discount = {
  id: "disc_freeship",
  code: "FREESHIP",
  kind: "free_shipping",
  value: 0,
  minimumSubtotal: money(150000),
  usageCount: 58,
  startsAt: "2025-06-01T00:00:00Z",
  active: true,
};

/** Expired and deactivated — exists to prove an inactive code is ignored entirely. */
export const discountSummerSale: Discount = {
  id: "disc_summer50",
  code: "SUMMER50",
  kind: "fixed_amount",
  value: 50000,
  usageLimit: 500,
  usageCount: 500,
  startsAt: "2025-05-01T00:00:00Z",
  endsAt: "2025-06-30T23:59:59Z",
  active: false,
};

export const discounts: Discount[] = [discountWelcome, discountFreeShipping, discountSummerSale];

// ─────────────────────────── cart ───────────────────────────

export const cartLines: CartLine[] = [
  {
    id: "line_1",
    productId: crewTee.id,
    variantId: "var_crew_tee_m_black",
    title: crewTee.title,
    variantTitle: "M / Black",
    image: crewTee.images[0],
    unitPrice: money(59000),
    quantity: 2,
  },
  {
    id: "line_2",
    productId: anorak.id,
    variantId: "var_anorak_m",
    title: anorak.title,
    variantTitle: "M",
    image: anorak.images[0],
    unitPrice: money(249000),
    quantity: 1,
  },
  {
    id: "line_3",
    productId: weekender.id,
    variantId: "var_weekender_tan",
    title: weekender.title,
    variantTitle: "Tan",
    image: weekender.images[0],
    unitPrice: money(189000),
    quantity: 1,
  },
];

export const cart: Cart = {
  id: "cart_1",
  lines: cartLines,
  currency: "THB",
  discountCode: "WELCOME10",
};

// ─────────────────────────── orders ───────────────────────────

const orderShippingAddress = {
  name: "Napat Suwanwong",
  line1: "88/12 Sukhumvit Soi 24",
  city: "Bangkok",
  province: "Bangkok",
  postalCode: "10110",
  country: "TH",
  phone: "+66812345678",
};

function orderLine(
  overrides: Partial<OrderLine> & Pick<OrderLine, "id" | "productId" | "variantId" | "title" | "variantTitle" | "unitPrice" | "quantity">
): OrderLine {
  return { fulfilledQuantity: 0, ...overrides };
}

// paid + unfulfilled: a normal just-placed order, payment captured, nothing shipped yet.
const order1Lines: OrderLine[] = [
  orderLine({
    id: "oline_1001_1",
    productId: hoodie.id,
    variantId: "var_hoodie_l",
    title: hoodie.title,
    variantTitle: "L",
    unitPrice: money(129000),
    quantity: 1,
  }),
  orderLine({
    id: "oline_1001_2",
    productId: socks.id,
    variantId: "var_socks_default",
    title: socks.title,
    variantTitle: "Default",
    unitPrice: money(25000),
    quantity: 2,
  }),
];

// paid + fulfilled: shipped in full, nothing outstanding.
const order2Lines: OrderLine[] = [
  orderLine({
    id: "oline_1002_1",
    productId: crewTee.id,
    variantId: "var_crew_tee_s_black",
    title: crewTee.title,
    variantTitle: "S / Black",
    unitPrice: money(59000),
    quantity: 3,
    fulfilledQuantity: 3,
  }),
  orderLine({
    id: "oline_1002_2",
    productId: scarf.id,
    variantId: "var_scarf_default",
    title: scarf.title,
    variantTitle: "Default",
    unitPrice: money(45000),
    quantity: 1,
    fulfilledQuantity: 1,
  }),
];

// pending + unfulfilled: payment not yet captured (e.g. bank transfer awaited).
const order3Lines: OrderLine[] = [
  orderLine({
    id: "oline_1003_1",
    productId: anorak.id,
    variantId: "var_anorak_m",
    title: anorak.title,
    variantTitle: "M",
    unitPrice: money(249000),
    quantity: 1,
  }),
];

// refunded + fulfilled: shipped, then refunded in full after a return request.
const order4Lines: OrderLine[] = [
  orderLine({
    id: "oline_1004_1",
    productId: weekender.id,
    variantId: "var_weekender_black",
    title: weekender.title,
    variantTitle: "Black",
    unitPrice: money(189000),
    quantity: 1,
    fulfilledQuantity: 1,
  }),
];

// paid + partially_fulfilled: one line shipped in full, the other only half —
// the case that proves payment and fulfilment are independent axes.
const order5Lines: OrderLine[] = [
  orderLine({
    id: "oline_1005_1",
    productId: hoodie.id,
    variantId: "var_hoodie_m",
    title: hoodie.title,
    variantTitle: "M",
    unitPrice: money(129000),
    quantity: 2,
    fulfilledQuantity: 1,
  }),
  orderLine({
    id: "oline_1005_2",
    productId: socks.id,
    variantId: "var_socks_default",
    title: socks.title,
    variantTitle: "Default",
    unitPrice: money(25000),
    quantity: 1,
    fulfilledQuantity: 1,
  }),
];

// voided + cancelled: the customer cancelled before the authorization was captured.
const order6Lines: OrderLine[] = [
  orderLine({
    id: "oline_1006_1",
    productId: anorak.id,
    variantId: "var_anorak_s",
    title: anorak.title,
    variantTitle: "S",
    unitPrice: money(249000),
    quantity: 1,
  }),
];

export const orders: Order[] = [
  {
    id: "order_1001",
    number: "#1001",
    customerId: "cust_napat",
    email: "napat.s@example.com",
    lines: order1Lines,
    totals: cartTotals(order1Lines, "THB", { shipping: money(5000), tax: money(12530) }),
    paymentStatus: "paid",
    fulfillmentStatus: "unfulfilled",
    shippingAddress: orderShippingAddress,
    billingAddress: orderShippingAddress,
    createdAt: "2026-07-28T03:12:00Z",
  },
  {
    id: "order_1002",
    number: "#1002",
    customerId: "cust_kanokwan",
    email: "kanokwan.p@example.com",
    lines: order2Lines,
    totals: cartTotals(order2Lines, "THB", { shipping: money(0), tax: money(13020) }),
    paymentStatus: "paid",
    fulfillmentStatus: "fulfilled",
    shippingAddress: {
      name: "Kanokwan Phetchara",
      line1: "212 Thonglor Soi 13",
      city: "Bangkok",
      province: "Bangkok",
      postalCode: "10110",
      country: "TH",
    },
    createdAt: "2026-07-15T10:40:00Z",
  },
  {
    id: "order_1003",
    number: "#1003",
    email: "guest.checkout@example.com",
    lines: order3Lines,
    totals: cartTotals(order3Lines, "THB", { shipping: money(6000), tax: money(17430) }),
    paymentStatus: "pending",
    fulfillmentStatus: "unfulfilled",
    note: "Awaiting bank transfer confirmation.",
    createdAt: "2026-08-05T14:02:00Z",
  },
  {
    id: "order_1004",
    number: "#1004",
    customerId: "cust_ananya",
    email: "ananya.r@example.com",
    lines: order4Lines,
    totals: cartTotals(order4Lines, "THB", { discount: discountWelcome, shipping: money(0), tax: money(11340) }),
    paymentStatus: "refunded",
    fulfillmentStatus: "fulfilled",
    note: "Customer returned item — wrong size. Refunded in full.",
    shippingAddress: {
      name: "Ananya Ruangrit",
      line1: "45 Ari Soi 4",
      city: "Bangkok",
      province: "Bangkok",
      postalCode: "10400",
      country: "TH",
    },
    createdAt: "2026-06-02T09:20:00Z",
  },
  {
    id: "order_1005",
    number: "#1005",
    customerId: "cust_napat",
    email: "napat.s@example.com",
    lines: order5Lines,
    totals: cartTotals(order5Lines, "THB", { shipping: money(5000), tax: money(11480) }),
    paymentStatus: "paid",
    fulfillmentStatus: "partially_fulfilled",
    shippingAddress: orderShippingAddress,
    billingAddress: orderShippingAddress,
    note: "Hoodie size M restocking — one unit ships when it arrives.",
    createdAt: "2026-08-01T06:55:00Z",
  },
  {
    id: "order_1006",
    number: "#1006",
    customerId: "cust_peerapat",
    email: "peerapat.c@example.com",
    lines: order6Lines,
    totals: cartTotals(order6Lines, "THB", { shipping: money(6000), tax: money(17430) }),
    paymentStatus: "voided",
    fulfillmentStatus: "cancelled",
    note: "Cancelled by customer before authorization was captured.",
    createdAt: "2026-05-11T16:00:00Z",
  },
];

// ─────────────────────────── customers ───────────────────────────

export const customers: Customer[] = [
  {
    id: "cust_napat",
    name: "Napat Suwanwong",
    email: "napat.s@example.com",
    phone: "+66812345678",
    acceptsMarketing: true,
    ordersCount: 2,
    totalSpent: money(496010), // sum of order_1001.totals.total + order_1005.totals.total
    defaultAddress: orderShippingAddress,
    createdAt: "2025-10-02T08:00:00Z",
  },
  {
    id: "cust_kanokwan",
    name: "Kanokwan Phetchara",
    email: "kanokwan.p@example.com",
    acceptsMarketing: true,
    ordersCount: 1,
    totalSpent: money(235020), // == order_1002.totals.total
    defaultAddress: {
      name: "Kanokwan Phetchara",
      line1: "212 Thonglor Soi 13",
      city: "Bangkok",
      province: "Bangkok",
      postalCode: "10110",
      country: "TH",
    },
    createdAt: "2025-11-20T08:00:00Z",
  },
  {
    // Zero orders: a newsletter signup or an abandoned-checkout lead.
    id: "cust_somchai",
    name: "Somchai Boonmee",
    email: "somchai.b@example.com",
    acceptsMarketing: false,
    ordersCount: 0,
    totalSpent: money(0),
    createdAt: "2026-07-30T08:00:00Z",
  },
  {
    id: "cust_ananya",
    name: "Ananya Ruangrit",
    email: "ananya.r@example.com",
    phone: "+66898765432",
    acceptsMarketing: false,
    ordersCount: 1,
    totalSpent: money(0), // order_1004 was refunded in full — net spend is zero
    defaultAddress: {
      name: "Ananya Ruangrit",
      line1: "45 Ari Soi 4",
      city: "Bangkok",
      province: "Bangkok",
      postalCode: "10400",
      country: "TH",
    },
    createdAt: "2025-05-14T08:00:00Z",
  },
  {
    id: "cust_peerapat",
    name: "Peerapat Chaiyasit",
    email: "peerapat.c@example.com",
    phone: "+66865554433",
    acceptsMarketing: true,
    ordersCount: 1,
    totalSpent: money(0), // order_1006 was voided — payment never captured
    defaultAddress: {
      name: "Peerapat Chaiyasit",
      line1: "9 Ratchadamri Road",
      line2: "Unit 14B",
      city: "Bangkok",
      province: "Bangkok",
      postalCode: "10330",
      country: "TH",
    },
    createdAt: "2025-08-08T08:00:00Z",
  },
];

Docs

Deliberately a separate item and never a registryDependency of a page: sample data should arrive because you asked for it, not because you installed a cart.

The fixtures pick awkward states on purpose — a backorderable variant that is out of stock, a fully sold-out product, a draft, an order that is paid but only partially fulfilled, a customer with zero orders, an expired code. A catalogue where everything is in stock and every order is complete hides exactly the rendering paths worth reviewing.

Prices are in satang: a ฿1,290 hoodie is 129000.

Dependencies

@hextor/store-model