@develemit/commerce-admin 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +8 -4
  3. package/dist/client/admin-client.d.ts +1 -0
  4. package/dist/client/errors.d.ts +4 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/{client-DCL5DSHP.js → client-ClTCzt-p.js} +34 -28
  7. package/dist/client-ClTCzt-p.js.map +1 -0
  8. package/dist/components/admin-layout/admin-layout.d.ts +2 -1
  9. package/dist/components/admin-layout/current-item.d.ts +1 -0
  10. package/dist/components/admin-layout/nav-items.d.ts +3 -0
  11. package/dist/components/admin-layout/use-current-href.d.ts +1 -0
  12. package/dist/components/empty-state/empty-state.d.ts +1 -1
  13. package/dist/index.js +1428 -1057
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/admin-labels.d.ts +3 -0
  16. package/dist/lib/currency-symbol.d.ts +1 -0
  17. package/dist/lib/fill-alts.d.ts +1 -1
  18. package/dist/lib/order-action-labels.d.ts +2 -0
  19. package/dist/lib/order-labels.d.ts +2 -1
  20. package/dist/lib/product-draft.d.ts +3 -0
  21. package/dist/lib/product-flow-labels.d.ts +11 -0
  22. package/dist/lib/product-form.d.ts +2 -1
  23. package/dist/lib/product-labels.d.ts +8 -0
  24. package/dist/lib/provider.d.ts +2 -0
  25. package/dist/lib/stock-labels.d.ts +2 -0
  26. package/dist/lib/use-batch-upload.d.ts +7 -1
  27. package/dist/lib/use-order-pages.d.ts +1 -0
  28. package/dist/lib/use-product-autosave.d.ts +8 -0
  29. package/dist/lib/use-product-pages.d.ts +2 -0
  30. package/dist/styles.css +470 -29
  31. package/dist/views/orders/admin-order-detail.d.ts +2 -1
  32. package/dist/views/orders/order-actions.d.ts +2 -1
  33. package/dist/views/orders/resolve-button.d.ts +3 -1
  34. package/dist/views/orders/ship-form.d.ts +4 -1
  35. package/dist/views/products/add-several/photo-picker.d.ts +3 -1
  36. package/dist/views/products/admin-product-images.d.ts +2 -0
  37. package/dist/views/products/finish-drafts/piece-summary.d.ts +2 -1
  38. package/dist/views/products/finish-drafts/use-publish-pieces.d.ts +4 -0
  39. package/dist/views/products/product-flow/admin-product-flow.d.ts +4 -0
  40. package/dist/views/products/product-flow/basics-section.d.ts +5 -1
  41. package/dist/views/products/product-flow/flow-actions.d.ts +3 -0
  42. package/dist/views/products/product-flow/flow-frame.d.ts +4 -2
  43. package/dist/views/products/product-flow/materials-field.d.ts +6 -0
  44. package/dist/views/products/product-flow/use-product-flow.d.ts +10 -0
  45. package/dist/views/products/product-sheet/delete-draft-button.d.ts +9 -0
  46. package/dist/views/shipping/shipping-rate-row.d.ts +2 -1
  47. package/package.json +2 -2
  48. package/dist/client-DCL5DSHP.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,72 @@
1
+ ## 0.9.0 (2026-11-14)
2
+
3
+ ### Features
4
+
5
+ - Add several uploads up to 3 photos at once (was one at a time); the draft order still follows the picked order and retry only re-sends the failed ones.
6
+ - `AdminProductFlow` takes `taxCodes?: { code, label }[]`. When set, the Basics step shows a "Tax category" select (`labels.products.flow.taxCode`, `taxCodeDefault`) whose empty "Default" option clears the code. Hidden otherwise.
7
+ - The order detail totals show a Tax row (`labels.orders.tax`, default "Tax") between shipping and total when the order has tax. Untaxed orders are unchanged. The refund dialog's default amount already includes tax.
8
+
9
+ ## 0.8.0 (2026-11-12)
10
+
11
+ ### Deprecated
12
+
13
+ - `AdminProductImages` and `AdminProductImagesProps`: the product flow has its own Photos step. They stay exported until the next major.
14
+
15
+ ### Fixes
16
+
17
+ - A slow autosave response no longer drops a photo uploaded while it was in flight, and the product sheet's first control shows its focus ring even when the sheet was opened with a mouse.
18
+
19
+ ### Features
20
+
21
+ - `AdminClient.deleteProduct(id)` and `ProductNotDeletableError` (with `reason`). The product sheet shows **Delete draft** (with a confirm that says it cannot be undone) in place of Archive for a draft that has never been live; new labels `products.sheet.deleteDraft`, `.deleteTitle`, `.deleteBody` and `.deleteConfirm`.
22
+ - Finish drafts: Back / Skip / Next live in a sticky footer above the tab bar on one row, the card photo is capped on phones, the progress bar uses `--sf-color-accent`, "Needs details" rows in the summary open that piece (new label `stock.summary.openPiece`), and the summary and end screen use the flow's heading and a success badge.
23
+ - Add several opens with a large photo drop zone (new label `stock.addSeveral.dropHint`); `PhotoPicker` is internal, no API change.
24
+ - Empty product filters and empty searches show an `EmptyState` with actions (new labels `products.emptyActions.start` and `.clearSearch`; "Add several at once" shows when `onAddMany` is set). `EmptyStateIcon` gains `'products'`.
25
+ - Setup checklist: actionable rows have a chevron, border and pressed state, and the compact list is expanded by default (the header still collapses it).
26
+ - The More sheet has a visible close button and a **View shop** link when the new optional `hrefs.storefront` is set (label `nav.viewShop`).
27
+ - The welcome screen, Add several and Finish drafts align left with the page heading and cap at 720px.
28
+
29
+ ### Features
30
+
31
+ - Product flow guidance: a disabled **Continue** on Basics says what is missing (`flow.continueNeeds`, "Add {things} to continue"); the success screen has **See it in the shop** when `hrefs.storefrontProduct` is set (`flow.seeInShop`); editing a live product shows `flow.previewHeadingLive` ("Your changes") and `flow.saveChanges` instead of "Ready for the shop?" and "Done". New placeholder labels `flow.descriptionPlaceholder`, `flow.sizePlaceholder` and `flow.altPlaceholder` (empty by default), plus `flow.addPhotoTile` and `flow.stepCount`.
32
+ - The "Saved" status is a check icon plus text, shown in the footer next to the action button (with Try again on failure). From 900px the header is sticky with labelled Back and Close buttons and a "Step n of 4 · Photos" indicator, and `initialStep` scrolls the section below it.
33
+ - Once photos exist, the Photos step's drop zone becomes an "Add photo" tile in the thumbnail grid.
34
+ - Materials: chip picks and the free-text box are separate; the box holds only custom text and the saved value merges both.
35
+ - Category chips are shown in title case, and "New category…" is hidden when a category named "other" already exists.
36
+
37
+ ### Changed (copy)
38
+
39
+ - Neutral default wording: `flow.ledes.basics` "That is all it needs to go in the shop.", `flow.successFirst` "Your first one is in the shop!", `flow.backToList` "Back to the list", `flow.otherCategory` "New category…" (was "Other…"), `flow.altHelp` "Describe the photo for shoppers who can’t see it…", `flow.savedNew` "Saved as you go". Sheet: `featureOff` "Take off the home page", `hide` "Hide from the shop". Hosts that matched the old strings should update, or set their own labels.
40
+
41
+ ### Features
42
+
43
+ - Orders show the short reference from `formatOrderRef` in the list (the whole card/row is the link) and as the order page title; searching by the reference works.
44
+ - **Changed:** "Mark as shipped" is blocked when the carrier or tracking number is blank, with a **Ship without tracking** secondary button. New `requireTracking?: boolean` prop on `AdminOrderDetail` (default `true`; `false` restores the old behaviour). New labels `orders.actions.shipWithoutTracking` and `orders.actions.trackingRequired`.
45
+ - Ship, resolve and refund results appear under the control that triggered them; shipping-rate save results appear under Save changes. Error notices and the first invalid rate field scroll into view.
46
+ - The order page has bolder section headings and, from 900px, a two-column layout (details left, fulfillment, refunds and actions right). Phone order cards are compact (reference, status, buyer, total, date).
47
+ - **Changed:** the generic `orders.emptyHint` ("Orders appear here as soon as a customer checks out") shows only on the All tab; the other tabs show just their own `orders.empty.*` line.
48
+
49
+ ### Features
50
+
51
+ - `AdminLayout` marks the current section: new `currentHref?` prop (defaults to `window.location.pathname`), longest-prefix match, `aria-current="page"` plus an `is-current` class on sidebar, tab bar and More-sheet links. `LinkProps` gains optional `'aria-current'`; a custom `renderLink` should forward it. Nav badges now have an accessible name and tooltip through new labels `nav.badge(count)` ("2 waiting") and `nav.productsBadge(count)` ("3 drafts").
52
+
53
+ ### Polish
54
+
55
+ - Refund and Remove rate use a new quiet destructive button (`sfa-btn--quiet-danger`: outlined, danger colour on text and border only) and Refund sits apart from "Mark as shipped". The solid `sfa-btn--danger` remains for confirm dialogs.
56
+ - Ship inputs fill their column at a stable width, "Track shipment" is underlined with an external-link arrow, and the orders search placeholder is now "Search orders" (`orders.searchPlaceholder`; the accessible label is unchanged).
57
+ - Shipping-rate rows give the name a full-width field, prefix the price with the currency symbol (from `locale` and the rate's currency), drop the bare currency line, and use compact icon buttons (Move earlier, Move later, Remove) that keep their accessible names.
58
+ - Pieces grid: bottom padding clears the floating "New piece" button, the filter chips show a scroll cue at either edge and leave Search on the toolbar row on phones, card names wrap to two lines and left-align with prices, the sheet title is bolder, keyboard focus after closing the sheet outlines the whole card with a rounded ring, and the search clear icon follows the theme.
59
+
60
+ ### Fixes
61
+
62
+ - Corner radii stay in proportion for any host `--sf-radius`. New tokens `--sfa-radius-sm` / `-md` / `-lg` (derived from `--sf-radius`, capped at 12px / 16px / 22px; hosts can override each) replace the multipliers on the product flow (preview, photos, dropzone, tips, thumbs, success photo) and the sheet. Hosts with a large `--sf-radius` (for example `0.75rem`) now get capped radii instead of 36-84px curves. The Preview card has more padding and fills the frame on phones (max 280px, centered). The success photo's radius at the 4px default is 20px (was 28px).
63
+
64
+ - Product flow, Basics step: typing a new category no longer closes the input after the first letter. The layout now depends on the shop's known `categories`, not the value being typed, so with no categories the plain input stays mounted (and keeps the mobile keyboard open). Under "Other…", the half-typed word no longer appears as a chip on each keystroke. A saved category that isn't one of the shop's still shows as selected.
65
+
66
+ ### Changes
67
+
68
+ - The Live and Sold chips no longer overlap: a sold piece counts under Sold only (needs `commerce-server` with the matching counts change).
69
+
1
70
  ## 0.7.0 (2026-11-04)
2
71
 
3
72
  ### Features
package/README.md CHANGED
@@ -121,15 +121,17 @@ Override any copy through `AdminProvider`'s `labels` (`nav`, `statuses` and `err
121
121
  initialStep="basics" // optional: photos | basics | story | preview
122
122
  categories={['Necklaces', 'Rings']} // category chips; "Other…" allows any
123
123
  materialSuggestions={['Silver', 'Gold']} // optional chips
124
+ taxCodes={[{ code: 'txcd_20010000', label: 'Clothing' }]} // optional; shows a Tax category select
124
125
  onDone={() => router.push('/admin/products')}
125
126
  onClose={() => router.push('/admin/products')}
126
127
  />
127
128
  ```
128
129
 
129
130
  - `AdminProductList` — a photo grid of product cards (status pill, featured star, price or what a draft still needs), filter chips with counts from `getProductCounts` (All excludes archived; "Sold" is active with zero stock), name search (behind a header button on phones, inline from 900px), a "N drafts need a few details" banner, paging and a quick-action sheet when a card is tapped. An empty shop shows a welcome state with no filters. Props, all optional: `welcome` (brand art for the welcome state; a neutral illustration by default), `setup` (`SetupStep[]` for the checklist), `onAddMany` (shows the "Add several at once" button) and `onFinishDrafts` (shows the banner's Finish button), `onOpenProduct(product)` (replaces the sheet) and `onEditProduct(product)` (the sheet's Edit button; defaults to a link to `hrefs.product`).
130
- - `AdminProductSheet({ productId, onClose, onEdit?, onChanged? })` — quick actions for one product: a bottom sheet below 900px, a 420px side panel from 900px, focus-trapped. One primary action by state (finish, put in the shop, mark sold, back in stock, restore), plus Edit, Photos, Feature on home, Hide from shop, Copy shop link (needs `hrefs.storefrontProduct`) and Archive. Changes show a toast with Undo. Publishing an incomplete draft lists what is missing. Copy: `labels.products.sheet.*`.
131
+ - `AdminProductSheet({ productId, onClose, onEdit?, onChanged? })` — quick actions for one product: a bottom sheet below 900px, a 420px side panel from 900px, focus-trapped. One primary action by state (finish, put in the shop, mark sold, back in stock, restore), plus Edit, Photos, Feature on home, Hide from shop, Copy shop link (needs `hrefs.storefrontProduct`) and Archive (a draft that was never live offers Delete draft instead, with a confirm; `client.deleteProduct(id)`). Changes show a toast with Undo. Publishing an incomplete draft lists what is missing. Copy: `labels.products.sheet.*`.
131
132
  - `AdminSetupChecklist({ steps })` — a generic setup card with a progress bar. Each `SetupStep` is `{ id, title, detail?, done, href?, onSelect?, locked?, lockedDetail? }`; the host decides the steps. `AdminProductList` shows it in full while the shop is empty and as a compact pill once it has products; it renders nothing when every step is done. Pass `compact` to force the pill.
132
- - `AdminProductFlow({ productId?, initialStep?, onDone?, onClose?, categories?, materialSuggestions?, isFirstProduct?, imagesSlot? })` — photo-first add and edit. Below 900px it is a full-screen flow of four steps (Photos, Basics, Story, Preview) with a step bar (`aria-current="step"`) and a sticky footer action; from 900px it is one page with a sticky "In the shop" preview and the publish buttons on the right, and `initialStep` scrolls to that section. The first photo or name creates a draft (`createProduct`), later photos upload to it, and edits autosave by PATCH about 600ms after typing stops, with a "Saving…/Saved" note and an inline retry on failure. Tapping a thumbnail makes it the cover (`reorderProductImages`); ✕ removes it. Blank photo descriptions become "name, photo N" when the product is saved. "Put it in the shop" PATCHes `status: 'active'`; a 422 lists what is missing. Publishing a draft shows a success screen ("Add another", "Back to products"); it says "first product" when `isFirstProduct` is true, or, when that prop is omitted, when `getProductCounts().live` is 1. `onDone(product)` runs on Save as draft, Done and Back to products; `onClose(product | null)` runs from ✕, after a "Saved as a draft" toast when a new draft was kept. Without `onClose`, ✕ is a link to `hrefs.products`. Copy: `labels.products.flow.*`, including `photoTip` and `descriptionPrompts` (chips that append text to the description; none by default).
133
+ - `AdminProductFlow({ productId?, initialStep?, onDone?, onClose?, categories?, materialSuggestions?, taxCodes?, isFirstProduct?, imagesSlot? })` — photo-first add and edit. Below 900px it is a full-screen flow of four steps (Photos, Basics, Story, Preview) with a step bar (`aria-current="step"`) and a sticky footer action; from 900px it is one page with a sticky "In the shop" preview and the publish buttons on the right, and `initialStep` scrolls to that section. The first photo or name creates a draft (`createProduct`), later photos upload to it, and edits autosave by PATCH about 600ms after typing stops, with a "Saving…/Saved" note and an inline retry on failure. Tapping a thumbnail makes it the cover (`reorderProductImages`); ✕ removes it. Blank photo descriptions become "name, photo N" when the product is saved. "Put it in the shop" PATCHes `status: 'active'`; a 422 lists what is missing. Publishing a draft shows a success screen ("Add another", "Back to products"); it says "first product" when `isFirstProduct` is true, or, when that prop is omitted, when `getProductCounts().live` is 1. `onDone(product)` runs on Save as draft, Done and Back to products; `onClose(product | null)` runs from ✕, after a "Saved as a draft" toast when a new draft was kept. Without `onClose`, ✕ is a link to `hrefs.products`. Copy: `labels.products.flow.*`, including `photoTip` and `descriptionPrompts` (chips that append text to the description; none by default). The success screen shows "See it in the shop" when `hrefs.storefrontProduct` is set. `taxCodes` (`{ code, label }[]`, none by default) adds a "Tax category" select to the Basics step (`labels.products.flow.taxCode`, `taxCodeDefault`); the empty "Default" option clears the code so Stripe uses the account default. The kit ships no list; pick codes from [Stripe's tax code list](https://docs.stripe.com/tax/tax-categories). Placeholders: `descriptionPlaceholder`, `sizePlaceholder`, `altPlaceholder` (empty by default). Editing a live product uses `previewHeadingLive` and `saveChanges`; a disabled Continue explains itself via `continueNeeds`.
134
+ - `AdminProductImages({ productId, images })` — **deprecated**; the flow's Photos step replaces it, and it is removed in the next major.
133
135
  - `AdminProductEditor({ productId?, onSaved?, imagesSlot?, categories?, initialStep? })` — the previous editor's props, now a thin wrapper around `AdminProductFlow` (`onSaved` is `onDone`). `imagesSlot` replaces the built-in Photos section. Without `initialStep` it reads `?step=` from the URL, which is how the sheet's Edit, Photos and "Add the missing details" links open the right step. `onEditProduct`/`onEdit` receive the step as a second argument.
134
136
  - `Switch({ id, label, checked, onChange })` — a native checkbox with `role="switch"`, styled by `.sfa-switch`.
135
137
  - Prices are typed in dollars and parsed to integer cents from the string (`parseMoneyInput`, `formatMoneyInput`).
@@ -163,8 +165,8 @@ Override any copy through `AdminProvider`'s `labels` (`nav`, `statuses` and `err
163
165
  ```
164
166
 
165
167
  - `AdminOrderList` — tabs To ship (default), Shipped, Needs attention, Pending refund and All; debounced (300ms) search by email or order id; "Load more" cursor paging. Changing a tab or search restarts from the first page.
166
- - `AdminOrderDetail` — items and totals, buyer, shipping address, fulfillment (carrier and tracking link, or "Not shipped"), refund history with pending refunds marked distinctly, and the remaining balance. Dates use the provider `locale`.
167
- - `OrderActions({ order, reload })` — the ship form, refund dialog and resolve button that `AdminOrderDetail` shows, exported for hosts that build their own order page.
168
+ - `AdminOrderDetail` — items and totals, buyer, shipping address, fulfillment (carrier and tracking link, or "Not shipped"), refund history with pending refunds marked distinctly, and the remaining balance. Dates use the provider `locale`. Orders are titled with the short reference (`formatOrderRef`). Shipping is blocked without a carrier and tracking number unless the admin picks "Ship without tracking"; pass `requireTracking={false}` to turn that off.
169
+ - `OrderActions({ order, reload, requireTracking? })` — the ship form, refund dialog and resolve button that `AdminOrderDetail` shows, exported for hosts that build their own order page.
168
170
  - Rows link through `hrefs.order(id)` and `renderLink`. Copy lives under `labels.orders`.
169
171
 
170
172
  ## Discount screens
@@ -218,6 +220,8 @@ Optional tokens, each with a fallback derived from the tokens above, so existing
218
220
 
219
221
  - `extraNav`: host items, each `{ href, label, position?, icon?, badge? }`. `icon` defaults to a dot; `adminNavIcons` (`products`, `orders`, `shipping`, `discounts`, `more`, `dot`) are the kit's inline SVGs.
220
222
  - `navBadges`: `{ products?, orders?, shippingRates? }` counts for the kit items. The kit computes nothing; pass numbers from `client.getProductCounts()` and friends. `0` and `''` hide the badge.
223
+ - `currentHref`: the path of the page being shown. The longest matching nav item (`/admin/orders/123` marks Orders) gets `aria-current="page"` and an `is-current` class, in the sidebar, tab bar and More sheet. Without it the layout reads `window.location.pathname` on the client; pass it from your router for SSR or client-side navigation. A custom `renderLink` should forward `aria-current` to its anchor.
224
+ - Badges carry an accessible name and tooltip: `labels.nav.productsBadge(count)` (default "3 drafts") for Products and `labels.nav.badge(count)` ("2 waiting") for the rest. Pass `0` to hide a badge.
221
225
  - `header` and `footer`: the sidebar's brand and bottom slots (put Sign out in `footer`). On a phone they live in the "More" sheet, so they render twice in the DOM, once per layout, and CSS shows one. Avoid fixed `id`s in them.
222
226
  - With more than five nav items the fifth tab becomes "More" (`labels.nav.more`, `labels.nav.closeMore`) and lists the rest.
223
227
 
@@ -14,6 +14,7 @@ export interface AdminClient {
14
14
  getProduct(id: string): Promise<AdminProduct>;
15
15
  createProduct(input: AdminProductInput): Promise<AdminProduct>;
16
16
  updateProduct(id: string, patch: AdminProductPatch): Promise<AdminProduct>;
17
+ deleteProduct(id: string): Promise<void>;
17
18
  uploadProductImage(id: string, file: Blob, alt?: string): Promise<AdminProduct>;
18
19
  reorderProductImages(id: string, imageIds: string[]): Promise<AdminProduct>;
19
20
  updateProductImage(id: string, imageId: string, patch: ProductImagePatch): Promise<AdminProduct>;
@@ -53,3 +53,7 @@ export declare class ProductIncompleteError extends AdminApiError {
53
53
  readonly missing: ProductIncompleteField[];
54
54
  constructor(url: string, missing: ProductIncompleteField[], message?: string);
55
55
  }
56
+ export declare class ProductNotDeletableError extends AdminApiError {
57
+ readonly reason: 'published' | 'has_orders';
58
+ constructor(url: string, reason: 'published' | 'has_orders', message?: string);
59
+ }
@@ -1 +1 @@
1
- export { n as AdminApiError, r as AdminInvalidInputError, i as AdminNotFoundError, a as AdminUnauthorizedError, o as DiscountCodeTakenError, s as DiscountInUseError, c as ImageTooLargeError, l as InsufficientStockError, u as InvalidImageError, d as InvalidOrderStateError, f as MaxRedemptionsBelowUsageError, p as ProductIncompleteError, m as RefundExceedsBalanceError, h as RefundGatewayError, g as RefundOutcomeUnknownError, t as createAdminClient } from "../client-DCL5DSHP.js";
1
+ export { n as AdminApiError, r as AdminInvalidInputError, i as AdminNotFoundError, a as AdminUnauthorizedError, o as DiscountCodeTakenError, s as DiscountInUseError, c as ImageTooLargeError, l as InsufficientStockError, u as InvalidImageError, d as InvalidOrderStateError, f as MaxRedemptionsBelowUsageError, p as ProductIncompleteError, m as ProductNotDeletableError, h as RefundExceedsBalanceError, g as RefundGatewayError, _ as RefundOutcomeUnknownError, t as createAdminClient } from "../client-ClTCzt-p.js";
@@ -69,9 +69,14 @@ var c = class extends Error {
69
69
  constructor(e, t, n) {
70
70
  super(422, e, "PRODUCT_INCOMPLETE", n), this.missing = t, this.name = "ProductIncompleteError";
71
71
  }
72
+ }, C = class extends c {
73
+ reason;
74
+ constructor(e, t, n) {
75
+ super(409, e, "PRODUCT_NOT_DELETABLE", n), this.reason = t, this.name = "ProductNotDeletableError";
76
+ }
72
77
  };
73
78
  // src/client/map-error.ts
74
- async function C(e) {
79
+ async function w(e) {
75
80
  try {
76
81
  let t = await e.json();
77
82
  return t && typeof t == "object" ? t : {};
@@ -79,12 +84,12 @@ async function C(e) {
79
84
  return {};
80
85
  }
81
86
  }
82
- async function w(e, t) {
83
- let n = await C(t), { error: r, message: i } = n;
84
- return t.status === 401 ? new l(e) : t.status === 404 ? new u(e, r, i) : t.status === 413 ? new v(e) : r === "INVALID_INPUT" ? new d(e, n.issues ?? []) : r === "INVALID_ORDER_STATE" ? new f(e, n.status ?? "", i) : r === "INSUFFICIENT_STOCK" ? new p(e, n.productIds ?? [], i) : r === "REFUND_EXCEEDS_BALANCE" ? new m(e, n.balanceCents, i) : r === "REFUND_GATEWAY_ERROR" ? new h(e, i) : r === "REFUND_OUTCOME_UNKNOWN" ? new g(e, i) : r === "DISCOUNT_CODE_TAKEN" ? new y(e, i) : r === "MAX_REDEMPTIONS_BELOW_USAGE" ? new x(e, i) : r === "DISCOUNT_IN_USE" ? new b(e, i) : r === "PRODUCT_INCOMPLETE" ? new S(e, n.missing ?? [], i) : r === "INVALID_IMAGE" || r === "INVALID_IMAGE_ORDER" ? new _(e, r, i) : new c(t.status, e, r, i);
87
+ async function T(e, t) {
88
+ let n = await w(t), { error: r, message: i } = n;
89
+ return t.status === 401 ? new l(e) : t.status === 404 ? new u(e, r, i) : t.status === 413 ? new v(e) : r === "INVALID_INPUT" ? new d(e, n.issues ?? []) : r === "INVALID_ORDER_STATE" ? new f(e, n.status ?? "", i) : r === "INSUFFICIENT_STOCK" ? new p(e, n.productIds ?? [], i) : r === "REFUND_EXCEEDS_BALANCE" ? new m(e, n.balanceCents, i) : r === "REFUND_GATEWAY_ERROR" ? new h(e, i) : r === "REFUND_OUTCOME_UNKNOWN" ? new g(e, i) : r === "DISCOUNT_CODE_TAKEN" ? new y(e, i) : r === "MAX_REDEMPTIONS_BELOW_USAGE" ? new x(e, i) : r === "DISCOUNT_IN_USE" ? new b(e, i) : r === "PRODUCT_INCOMPLETE" ? new S(e, n.missing ?? [], i) : r === "PRODUCT_NOT_DELETABLE" ? new C(e, n.reason ?? "published", i) : r === "INVALID_IMAGE" || r === "INVALID_IMAGE_ORDER" ? new _(e, r, i) : new c(t.status, e, r, i);
85
90
  }
86
91
  // src/client/transport.ts
87
- function T({ baseUrl: e, fetch: t = (...e) => globalThis.fetch(...e), headers: n, onUnauthorized: r }) {
92
+ function E({ baseUrl: e, fetch: t = (...e) => globalThis.fetch(...e), headers: n, onUnauthorized: r }) {
88
93
  let i = e.replace(/\/+$/, "");
89
94
  return { async request(e, a, { method: o = "GET", body: s, form: c, headers: u } = {}) {
90
95
  let d = `${i}${e}`, f = new Headers(n ? await n() : void 0);
@@ -96,60 +101,61 @@ function T({ baseUrl: e, fetch: t = (...e) => globalThis.fetch(...e), headers: n
96
101
  body: c ?? (s === void 0 ? void 0 : JSON.stringify(s))
97
102
  });
98
103
  if (!p.ok) {
99
- let e = await w(d, p);
104
+ let e = await T(d, p);
100
105
  if (e instanceof l) try {
101
106
  r?.(e);
102
107
  } catch {}
103
108
  throw e;
104
109
  }
105
- return a.parse(await p.json());
110
+ return p.status === 204 ? a.parse(void 0) : a.parse(await p.json());
106
111
  } };
107
112
  }
108
113
  // src/client/admin-client.ts
109
- var E = encodeURIComponent;
110
- function D(e) {
114
+ var D = encodeURIComponent, O = { parse: () => void 0 };
115
+ function k(e) {
111
116
  let t = new URLSearchParams();
112
117
  for (let [n, r] of Object.entries(e)) r !== void 0 && t.set(n, String(r));
113
118
  let n = t.toString();
114
119
  return n ? `?${n}` : "";
115
120
  }
116
- function O(c) {
117
- let { request: l } = T(c), u = a, d = o, f = s.array();
121
+ function A(c) {
122
+ let { request: l } = E(c), u = a, d = o, f = s.array();
118
123
  return {
119
- listProducts: (e = {}) => l(`/admin/products${D(e)}`, i),
124
+ listProducts: (e = {}) => l(`/admin/products${k(e)}`, i),
120
125
  getProductCounts: () => l("/admin/products/counts", r),
121
- getProduct: (e) => l(`/admin/products/${E(e)}`, u),
126
+ getProduct: (e) => l(`/admin/products/${D(e)}`, u),
122
127
  createProduct: (e) => l("/admin/products", u, {
123
128
  method: "POST",
124
129
  body: e
125
130
  }),
126
- updateProduct: (e, t) => l(`/admin/products/${E(e)}`, u, {
131
+ updateProduct: (e, t) => l(`/admin/products/${D(e)}`, u, {
127
132
  method: "PATCH",
128
133
  body: t
129
134
  }),
135
+ deleteProduct: (e) => l(`/admin/products/${D(e)}`, O, { method: "DELETE" }),
130
136
  uploadProductImage(e, t, n) {
131
137
  let r = new FormData();
132
- return n !== void 0 && r.append("alt", n), r.append("file", t), l(`/admin/products/${E(e)}/images`, u, {
138
+ return n !== void 0 && r.append("alt", n), r.append("file", t), l(`/admin/products/${D(e)}/images`, u, {
133
139
  method: "POST",
134
140
  form: r
135
141
  });
136
142
  },
137
- reorderProductImages: (e, t) => l(`/admin/products/${E(e)}/images/order`, u, {
143
+ reorderProductImages: (e, t) => l(`/admin/products/${D(e)}/images/order`, u, {
138
144
  method: "PATCH",
139
145
  body: { imageIds: t }
140
146
  }),
141
- updateProductImage: (e, t, n) => l(`/admin/products/${E(e)}/images/${E(t)}`, u, {
147
+ updateProductImage: (e, t, n) => l(`/admin/products/${D(e)}/images/${D(t)}`, u, {
142
148
  method: "PATCH",
143
149
  body: n
144
150
  }),
145
- deleteProductImage: (e, t) => l(`/admin/products/${E(e)}/images/${E(t)}`, u, { method: "DELETE" }),
146
- listDiscounts: (t = {}) => l(`/admin/discounts${D(t)}`, e),
147
- getDiscount: (e) => l(`/admin/discounts/${E(e)}`, d),
151
+ deleteProductImage: (e, t) => l(`/admin/products/${D(e)}/images/${D(t)}`, u, { method: "DELETE" }),
152
+ listDiscounts: (t = {}) => l(`/admin/discounts${k(t)}`, e),
153
+ getDiscount: (e) => l(`/admin/discounts/${D(e)}`, d),
148
154
  createDiscount: (e) => l("/admin/discounts", d, {
149
155
  method: "POST",
150
156
  body: e
151
157
  }),
152
- updateDiscount: (e, t) => l(`/admin/discounts/${E(e)}`, d, {
158
+ updateDiscount: (e, t) => l(`/admin/discounts/${D(e)}`, d, {
153
159
  method: "PATCH",
154
160
  body: t
155
161
  }),
@@ -158,17 +164,17 @@ function O(c) {
158
164
  method: "PUT",
159
165
  body: e
160
166
  }),
161
- listOrders: (e = {}) => l(`/admin/orders${D(e)}`, n),
162
- getOrder: (e) => l(`/admin/orders/${E(e)}`, t),
163
- shipOrder: (e, n = {}) => l(`/admin/orders/${E(e)}/ship`, t, {
167
+ listOrders: (e = {}) => l(`/admin/orders${k(e)}`, n),
168
+ getOrder: (e) => l(`/admin/orders/${D(e)}`, t),
169
+ shipOrder: (e, n = {}) => l(`/admin/orders/${D(e)}/ship`, t, {
164
170
  method: "POST",
165
171
  body: n
166
172
  }),
167
- resolveOrder: (e) => l(`/admin/orders/${E(e)}/resolve`, t, { method: "POST" }),
173
+ resolveOrder: (e) => l(`/admin/orders/${D(e)}/resolve`, t, { method: "POST" }),
168
174
  async refundOrder(e, n = { restock: !1 }, r = {}) {
169
175
  let i = r.idempotencyKey ?? crypto.randomUUID();
170
176
  try {
171
- return await l(`/admin/orders/${E(e)}/refunds`, t, {
177
+ return await l(`/admin/orders/${D(e)}/refunds`, t, {
172
178
  method: "POST",
173
179
  body: n,
174
180
  headers: { "idempotency-key": i }
@@ -179,6 +185,6 @@ function O(c) {
179
185
  }
180
186
  };
181
187
  }
182
- export { l as a, v as c, f as d, x as f, g, h, u as i, p as l, m, c as n, y as o, S as p, d as r, b as s, O as t, _ as u };
188
+ export { g as _, l as a, v as c, f as d, x as f, h as g, m as h, u as i, p as l, C as m, c as n, y as o, S as p, d as r, b as s, A as t, _ as u };
183
189
 
184
- //# sourceMappingURL=client-DCL5DSHP.js.map
190
+ //# sourceMappingURL=client-ClTCzt-p.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-ClTCzt-p.js","names":["AdminApiError","message","status","code","url","AdminUnauthorizedError","AdminNotFoundError","AdminInvalidInputError","issues","InvalidOrderStateError","orderStatus","InsufficientStockError","productIds","RefundExceedsBalanceError","balanceCents","RefundGatewayError","RefundOutcomeUnknownError","InvalidImageError","ImageTooLargeError","DiscountCodeTakenError","DiscountInUseError","MaxRedemptionsBelowUsageError","ProductIncompleteError","missing","ProductNotDeletableError","reason","readBody","body","res","mapError","createTransport","fetchImpl","args","getHeaders","root","baseUrl","path","merged","name","value","headers","form","error","schema","enc","voidResponse","queryString","params","key","query","qs","createAdminClient","options","product","adminProductSchema","discount","discountSchema","rates","shippingRateSchema","request","adminProductPageSchema","adminProductCountsSchema","id","input","patch","alt","file","imageId","adminDiscountPageSchema","adminOrderPageSchema","adminOrderDetailSchema","refund","err"],"sources":["../src/client/errors.ts","../src/client/map-error.ts","../src/client/transport.ts","../src/client/admin-client.ts"],"sourcesContent":["import type { ProductIncompleteField } from '@develemit/commerce-contracts';\n\nexport class AdminApiError extends Error {\n idempotencyKey?: string;\n\n constructor(\n readonly status: number,\n readonly url: string,\n readonly code?: string,\n message?: string,\n ) {\n super(\n message ??\n `Admin API request failed (${status}${code ? ` ${code}` : ''}): ${url}`,\n );\n this.name = 'AdminApiError';\n }\n}\n\nexport class AdminUnauthorizedError extends AdminApiError {\n constructor(url: string) {\n super(401, url, 'UNAUTHORIZED', `Not authorized: ${url}`);\n this.name = 'AdminUnauthorizedError';\n }\n}\n\nexport class AdminNotFoundError extends AdminApiError {\n constructor(url: string, code?: string, message?: string) {\n super(404, url, code, message ?? `Not found: ${url}`);\n this.name = 'AdminNotFoundError';\n }\n}\n\nexport class AdminInvalidInputError extends AdminApiError {\n constructor(\n url: string,\n readonly issues: unknown[],\n ) {\n super(400, url, 'INVALID_INPUT', `Invalid input: ${url}`);\n this.name = 'AdminInvalidInputError';\n }\n}\n\nexport class InvalidOrderStateError extends AdminApiError {\n constructor(\n url: string,\n readonly orderStatus: string,\n message?: string,\n ) {\n super(409, url, 'INVALID_ORDER_STATE', message);\n this.name = 'InvalidOrderStateError';\n }\n}\n\nexport class InsufficientStockError extends AdminApiError {\n constructor(\n url: string,\n readonly productIds: string[],\n message?: string,\n ) {\n super(409, url, 'INSUFFICIENT_STOCK', message);\n this.name = 'InsufficientStockError';\n }\n}\n\nexport class RefundExceedsBalanceError extends AdminApiError {\n constructor(\n url: string,\n readonly balanceCents: number | undefined,\n message?: string,\n ) {\n super(400, url, 'REFUND_EXCEEDS_BALANCE', message);\n this.name = 'RefundExceedsBalanceError';\n }\n}\n\nexport class RefundGatewayError extends AdminApiError {\n constructor(url: string, message?: string) {\n super(502, url, 'REFUND_GATEWAY_ERROR', message);\n this.name = 'RefundGatewayError';\n }\n}\n\nexport class RefundOutcomeUnknownError extends AdminApiError {\n constructor(url: string, message?: string) {\n super(502, url, 'REFUND_OUTCOME_UNKNOWN', message);\n this.name = 'RefundOutcomeUnknownError';\n }\n}\n\nexport class InvalidImageError extends AdminApiError {\n constructor(url: string, code: string, message?: string) {\n super(400, url, code, message);\n this.name = 'InvalidImageError';\n }\n}\n\nexport class ImageTooLargeError extends AdminApiError {\n constructor(url: string) {\n super(413, url, 'IMAGE_TOO_LARGE', `Image is too large: ${url}`);\n this.name = 'ImageTooLargeError';\n }\n}\n\nexport class DiscountCodeTakenError extends AdminApiError {\n constructor(url: string, message?: string) {\n super(409, url, 'DISCOUNT_CODE_TAKEN', message);\n this.name = 'DiscountCodeTakenError';\n }\n}\n\nexport class DiscountInUseError extends AdminApiError {\n constructor(url: string, message?: string) {\n super(409, url, 'DISCOUNT_IN_USE', message);\n this.name = 'DiscountInUseError';\n }\n}\n\nexport class MaxRedemptionsBelowUsageError extends AdminApiError {\n constructor(url: string, message?: string) {\n super(400, url, 'MAX_REDEMPTIONS_BELOW_USAGE', message);\n this.name = 'MaxRedemptionsBelowUsageError';\n }\n}\n\nexport class ProductIncompleteError extends AdminApiError {\n constructor(\n url: string,\n readonly missing: ProductIncompleteField[],\n message?: string,\n ) {\n super(422, url, 'PRODUCT_INCOMPLETE', message);\n this.name = 'ProductIncompleteError';\n }\n}\n\nexport class ProductNotDeletableError extends AdminApiError {\n constructor(\n url: string,\n readonly reason: 'published' | 'has_orders',\n message?: string,\n ) {\n super(409, url, 'PRODUCT_NOT_DELETABLE', message);\n this.name = 'ProductNotDeletableError';\n }\n}\n","import type { ProductIncompleteField } from '@develemit/commerce-contracts';\nimport {\n AdminApiError,\n AdminInvalidInputError,\n AdminNotFoundError,\n AdminUnauthorizedError,\n DiscountCodeTakenError,\n DiscountInUseError,\n ImageTooLargeError,\n InsufficientStockError,\n InvalidImageError,\n InvalidOrderStateError,\n MaxRedemptionsBelowUsageError,\n ProductIncompleteError,\n ProductNotDeletableError,\n RefundExceedsBalanceError,\n RefundGatewayError,\n RefundOutcomeUnknownError,\n} from './errors';\n\ninterface ErrorBody {\n error?: string;\n message?: string;\n issues?: unknown[];\n status?: string;\n productIds?: string[];\n missing?: ProductIncompleteField[];\n balanceCents?: number;\n reason?: 'published' | 'has_orders';\n}\n\nasync function readBody(res: Response): Promise<ErrorBody> {\n try {\n const body: unknown = await res.json();\n return body && typeof body === 'object' ? (body as ErrorBody) : {};\n } catch {\n return {};\n }\n}\n\nexport async function mapError(\n url: string,\n res: Response,\n): Promise<AdminApiError> {\n const body = await readBody(res);\n const { error: code, message } = body;\n if (res.status === 401) return new AdminUnauthorizedError(url);\n if (res.status === 404) return new AdminNotFoundError(url, code, message);\n if (res.status === 413) return new ImageTooLargeError(url);\n if (code === 'INVALID_INPUT') {\n return new AdminInvalidInputError(url, body.issues ?? []);\n }\n if (code === 'INVALID_ORDER_STATE') {\n return new InvalidOrderStateError(url, body.status ?? '', message);\n }\n if (code === 'INSUFFICIENT_STOCK') {\n return new InsufficientStockError(url, body.productIds ?? [], message);\n }\n if (code === 'REFUND_EXCEEDS_BALANCE') {\n return new RefundExceedsBalanceError(url, body.balanceCents, message);\n }\n if (code === 'REFUND_GATEWAY_ERROR')\n return new RefundGatewayError(url, message);\n if (code === 'REFUND_OUTCOME_UNKNOWN') {\n return new RefundOutcomeUnknownError(url, message);\n }\n if (code === 'DISCOUNT_CODE_TAKEN') {\n return new DiscountCodeTakenError(url, message);\n }\n if (code === 'MAX_REDEMPTIONS_BELOW_USAGE') {\n return new MaxRedemptionsBelowUsageError(url, message);\n }\n if (code === 'DISCOUNT_IN_USE') return new DiscountInUseError(url, message);\n if (code === 'PRODUCT_INCOMPLETE') {\n return new ProductIncompleteError(url, body.missing ?? [], message);\n }\n if (code === 'PRODUCT_NOT_DELETABLE') {\n return new ProductNotDeletableError(\n url,\n body.reason ?? 'published',\n message,\n );\n }\n if (code === 'INVALID_IMAGE' || code === 'INVALID_IMAGE_ORDER') {\n return new InvalidImageError(url, code, message);\n }\n return new AdminApiError(res.status, url, code, message);\n}\n","import { AdminUnauthorizedError } from './errors';\nimport { mapError } from './map-error';\n\nexport interface AdminClientOptions {\n baseUrl: string;\n fetch?: typeof fetch;\n headers?: () => HeadersInit | Promise<HeadersInit>;\n onUnauthorized?: (error: AdminUnauthorizedError) => void;\n}\n\ntype Schema<T> = { parse(data: unknown): T };\n\nexport interface RequestOptions {\n method?: string;\n body?: unknown;\n form?: FormData;\n headers?: Record<string, string>;\n}\n\nexport interface Transport {\n request<T>(\n path: string,\n schema: Schema<T>,\n options?: RequestOptions,\n ): Promise<T>;\n}\n\nexport function createTransport({\n baseUrl,\n fetch: fetchImpl = (...args) => globalThis.fetch(...args),\n headers: getHeaders,\n onUnauthorized,\n}: AdminClientOptions): Transport {\n const root = baseUrl.replace(/\\/+$/, '');\n\n return {\n async request(path, schema, { method = 'GET', body, form, headers } = {}) {\n const url = `${root}${path}`;\n const merged = new Headers(getHeaders ? await getHeaders() : undefined);\n merged.set('accept', 'application/json');\n if (body !== undefined) merged.set('content-type', 'application/json');\n for (const [name, value] of Object.entries(headers ?? {})) {\n merged.set(name, value);\n }\n const res = await fetchImpl(url, {\n method,\n headers: merged,\n body: form ?? (body === undefined ? undefined : JSON.stringify(body)),\n });\n if (!res.ok) {\n const error = await mapError(url, res);\n if (error instanceof AdminUnauthorizedError) {\n try {\n onUnauthorized?.(error);\n } catch {\n // A host callback failure must not replace the 401 the caller expects.\n }\n }\n throw error;\n }\n if (res.status === 204) return schema.parse(undefined);\n return schema.parse(await res.json());\n },\n };\n}\n","import {\n adminDiscountPageSchema,\n adminOrderDetailSchema,\n adminOrderPageSchema,\n adminProductCountsSchema,\n adminProductPageSchema,\n adminProductSchema,\n discountSchema,\n shippingRateSchema,\n type AdminDiscountListQuery,\n type AdminDiscountPage,\n type AdminOrderDetail,\n type AdminOrderListQuery,\n type AdminProductCounts,\n type AdminOrderPage,\n type AdminProduct,\n type AdminProductListQuery,\n type AdminProductPage,\n type AdminProductInput,\n type AdminProductPatch,\n type Discount,\n type DiscountInput,\n type DiscountPatch,\n type RefundOrderInput,\n type ProductImagePatch,\n type ShippingRate,\n type ShippingRateInput,\n type ShipOrderInput,\n} from '@develemit/commerce-contracts';\nimport { createTransport, type AdminClientOptions } from './transport';\n\nexport type AdminOrderQuery = Partial<\n Pick<AdminOrderListQuery, 'status' | 'q' | 'hasPendingRefund' | 'cursor'>\n> & { limit?: number };\n\nexport type AdminProductQuery = Pick<\n AdminProductListQuery,\n | 'status'\n | 'excludeArchived'\n | 'incomplete'\n | 'stock'\n | 'q'\n | 'cursor'\n | 'limit'\n>;\n\nexport type AdminDiscountQuery = Partial<\n Pick<AdminDiscountListQuery, 'status' | 'q' | 'cursor' | 'limit'>\n>;\n\nexport interface RefundOptions {\n idempotencyKey?: string;\n}\n\nexport interface AdminClient {\n listProducts(query?: AdminProductQuery): Promise<AdminProductPage>;\n getProductCounts(): Promise<AdminProductCounts>;\n getProduct(id: string): Promise<AdminProduct>;\n createProduct(input: AdminProductInput): Promise<AdminProduct>;\n updateProduct(id: string, patch: AdminProductPatch): Promise<AdminProduct>;\n deleteProduct(id: string): Promise<void>;\n uploadProductImage(\n id: string,\n file: Blob,\n alt?: string,\n ): Promise<AdminProduct>;\n reorderProductImages(id: string, imageIds: string[]): Promise<AdminProduct>;\n updateProductImage(\n id: string,\n imageId: string,\n patch: ProductImagePatch,\n ): Promise<AdminProduct>;\n deleteProductImage(id: string, imageId: string): Promise<AdminProduct>;\n listDiscounts(query?: AdminDiscountQuery): Promise<AdminDiscountPage>;\n getDiscount(id: string): Promise<Discount>;\n createDiscount(input: DiscountInput): Promise<Discount>;\n updateDiscount(id: string, patch: DiscountPatch): Promise<Discount>;\n listShippingRates(): Promise<ShippingRate[]>;\n replaceShippingRates(rates: ShippingRateInput[]): Promise<ShippingRate[]>;\n listOrders(query?: AdminOrderQuery): Promise<AdminOrderPage>;\n getOrder(id: string): Promise<AdminOrderDetail>;\n shipOrder(id: string, input?: ShipOrderInput): Promise<AdminOrderDetail>;\n resolveOrder(id: string): Promise<AdminOrderDetail>;\n /**\n * Retrying the same intent must reuse the same key. When none is passed one\n * is generated, and it is set on any thrown error as `idempotencyKey` so the\n * caller can pass it back on retry.\n */\n refundOrder(\n id: string,\n input?: RefundOrderInput,\n options?: RefundOptions,\n ): Promise<AdminOrderDetail>;\n}\n\nconst enc = encodeURIComponent;\nconst voidResponse = { parse: (): void => undefined };\n\nfunction queryString(query: object): string {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) params.set(key, String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n\nexport function createAdminClient(options: AdminClientOptions): AdminClient {\n const { request } = createTransport(options);\n const product = adminProductSchema;\n const discount = discountSchema;\n const rates = shippingRateSchema.array();\n\n return {\n listProducts: (query = {}) =>\n request(`/admin/products${queryString(query)}`, adminProductPageSchema),\n getProductCounts: () =>\n request('/admin/products/counts', adminProductCountsSchema),\n getProduct: (id) => request(`/admin/products/${enc(id)}`, product),\n createProduct: (input) =>\n request('/admin/products', product, { method: 'POST', body: input }),\n updateProduct: (id, patch) =>\n request(`/admin/products/${enc(id)}`, product, {\n method: 'PATCH',\n body: patch,\n }),\n deleteProduct: (id) =>\n request(`/admin/products/${enc(id)}`, voidResponse, {\n method: 'DELETE',\n }),\n uploadProductImage(id, file, alt) {\n const form = new FormData();\n if (alt !== undefined) form.append('alt', alt);\n form.append('file', file);\n return request(`/admin/products/${enc(id)}/images`, product, {\n method: 'POST',\n form,\n });\n },\n reorderProductImages: (id, imageIds) =>\n request(`/admin/products/${enc(id)}/images/order`, product, {\n method: 'PATCH',\n body: { imageIds },\n }),\n updateProductImage: (id, imageId, patch) =>\n request(`/admin/products/${enc(id)}/images/${enc(imageId)}`, product, {\n method: 'PATCH',\n body: patch,\n }),\n deleteProductImage: (id, imageId) =>\n request(`/admin/products/${enc(id)}/images/${enc(imageId)}`, product, {\n method: 'DELETE',\n }),\n listDiscounts: (query = {}) =>\n request(`/admin/discounts${queryString(query)}`, adminDiscountPageSchema),\n getDiscount: (id) => request(`/admin/discounts/${enc(id)}`, discount),\n createDiscount: (input) =>\n request('/admin/discounts', discount, { method: 'POST', body: input }),\n updateDiscount: (id, patch) =>\n request(`/admin/discounts/${enc(id)}`, discount, {\n method: 'PATCH',\n body: patch,\n }),\n listShippingRates: () => request('/admin/shipping-rates', rates),\n replaceShippingRates: (input) =>\n request('/admin/shipping-rates', rates, { method: 'PUT', body: input }),\n listOrders: (query = {}) =>\n request(`/admin/orders${queryString(query)}`, adminOrderPageSchema),\n getOrder: (id) =>\n request(`/admin/orders/${enc(id)}`, adminOrderDetailSchema),\n shipOrder: (id, input = {}) =>\n request(`/admin/orders/${enc(id)}/ship`, adminOrderDetailSchema, {\n method: 'POST',\n body: input,\n }),\n resolveOrder: (id) =>\n request(`/admin/orders/${enc(id)}/resolve`, adminOrderDetailSchema, {\n method: 'POST',\n }),\n async refundOrder(id, input = { restock: false }, refund = {}) {\n const key = refund.idempotencyKey ?? crypto.randomUUID();\n try {\n return await request(\n `/admin/orders/${enc(id)}/refunds`,\n adminOrderDetailSchema,\n {\n method: 'POST',\n body: input,\n headers: { 'idempotency-key': key },\n },\n );\n } catch (err) {\n if (err instanceof Error) {\n (err as Error & { idempotencyKey?: string }).idempotencyKey = key;\n }\n throw err;\n }\n },\n };\n}\n"],"mappings":";;AAEA,IAAaA,IAAb,cAAmC,MAAM;CAI5B;CACA;CACA;CALX;CAEA,YACE,GACA,GACA,GACA,GACA;EAKA,AAJA,MACEC,KACE,6BAA6BC,IAASC,IAAO,IAAIA,MAAS,GAAG,KAAKC,GACtE,GARS,KAAA,SAAAF,GACA,KAAA,MAAAE,GACA,KAAA,OAAAD,GAOT,KAAK,OAAO;CACd;AACF,GAEaE,IAAb,cAA4CL,EAAc;CACxD,YAAY,GAAa;EAEvB,AADA,MAAM,KAAKI,GAAK,gBAAgB,mBAAmBA,GAAK,GACxD,KAAK,OAAO;CACd;AACF,GAEaE,IAAb,cAAwCN,EAAc;CACpD,YAAY,GAAa,GAAe,GAAkB;EAExD,AADA,MAAM,KAAKI,GAAKD,GAAMF,KAAW,cAAcG,GAAK,GACpD,KAAK,OAAO;CACd;AACF,GAEaG,IAAb,cAA4CP,EAAc;CAG7C;CAFX,YACE,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,iBAAiB,kBAAkBA,GAAK,GAF/C,KAAA,SAAAI,GAGT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAA4CT,EAAc;CAG7C;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,uBAAuBH,CAAO,GAHrC,KAAA,cAAAS,GAIT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAA4CX,EAAc;CAG7C;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,sBAAsBH,CAAO,GAHpC,KAAA,aAAAW,GAIT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAA+Cb,EAAc;CAGhD;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,0BAA0BH,CAAO,GAHxC,KAAA,eAAAa,GAIT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAAwCf,EAAc;CACpD,YAAY,GAAa,GAAkB;EAEzC,AADA,MAAM,KAAKI,GAAK,wBAAwBH,CAAO,GAC/C,KAAK,OAAO;CACd;AACF,GAEae,IAAb,cAA+ChB,EAAc;CAC3D,YAAY,GAAa,GAAkB;EAEzC,AADA,MAAM,KAAKI,GAAK,0BAA0BH,CAAO,GACjD,KAAK,OAAO;CACd;AACF,GAEagB,IAAb,cAAuCjB,EAAc;CACnD,YAAY,GAAa,GAAc,GAAkB;EAEvD,AADA,MAAM,KAAKI,GAAKD,GAAMF,CAAO,GAC7B,KAAK,OAAO;CACd;AACF,GAEaiB,IAAb,cAAwClB,EAAc;CACpD,YAAY,GAAa;EAEvB,AADA,MAAM,KAAKI,GAAK,mBAAmB,uBAAuBA,GAAK,GAC/D,KAAK,OAAO;CACd;AACF,GAEae,IAAb,cAA4CnB,EAAc;CACxD,YAAY,GAAa,GAAkB;EAEzC,AADA,MAAM,KAAKI,GAAK,uBAAuBH,CAAO,GAC9C,KAAK,OAAO;CACd;AACF,GAEamB,IAAb,cAAwCpB,EAAc;CACpD,YAAY,GAAa,GAAkB;EAEzC,AADA,MAAM,KAAKI,GAAK,mBAAmBH,CAAO,GAC1C,KAAK,OAAO;CACd;AACF,GAEaoB,IAAb,cAAmDrB,EAAc;CAC/D,YAAY,GAAa,GAAkB;EAEzC,AADA,MAAM,KAAKI,GAAK,+BAA+BH,CAAO,GACtD,KAAK,OAAO;CACd;AACF,GAEaqB,IAAb,cAA4CtB,EAAc;CAG7C;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,sBAAsBH,CAAO,GAHpC,KAAA,UAAAsB,GAIT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAA8CxB,EAAc;CAG/C;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAM,KAAKI,GAAK,yBAAyBH,CAAO,GAHvC,KAAA,SAAAwB,GAIT,KAAK,OAAO;CACd;AACF;;AClHA,eAAeC,EAAS,GAAmC;CACzD,IAAI;EACF,IAAMC,IAAgB,MAAMC,EAAI,KAAK;EACrC,OAAOD,KAAQ,OAAOA,KAAS,WAAYA,IAAqB,CAAC;CACnE,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,eAAsBE,EACpB,GACA,GACwB;CACxB,IAAMF,IAAO,MAAMD,EAASE,CAAG,GACzB,EAAE,OAAOzB,GAAM,eAAYwB;CAyCjC,OAxCIC,EAAI,WAAW,MAAY,IAAIvB,EAAuBD,CAAG,IACzDwB,EAAI,WAAW,MAAY,IAAItB,EAAmBF,GAAKD,GAAMF,CAAO,IACpE2B,EAAI,WAAW,MAAY,IAAIV,EAAmBd,CAAG,IACrDD,MAAS,kBACJ,IAAII,EAAuBH,GAAKuB,EAAK,UAAU,CAAC,CAAC,IAEtDxB,MAAS,wBACJ,IAAIM,EAAuBL,GAAKuB,EAAK,UAAU,IAAI1B,CAAO,IAE/DE,MAAS,uBACJ,IAAIQ,EAAuBP,GAAKuB,EAAK,cAAc,CAAC,GAAG1B,CAAO,IAEnEE,MAAS,2BACJ,IAAIU,EAA0BT,GAAKuB,EAAK,cAAc1B,CAAO,IAElEE,MAAS,yBACJ,IAAIY,EAAmBX,GAAKH,CAAO,IACxCE,MAAS,2BACJ,IAAIa,EAA0BZ,GAAKH,CAAO,IAE/CE,MAAS,wBACJ,IAAIgB,EAAuBf,GAAKH,CAAO,IAE5CE,MAAS,gCACJ,IAAIkB,EAA8BjB,GAAKH,CAAO,IAEnDE,MAAS,oBAA0B,IAAIiB,EAAmBhB,GAAKH,CAAO,IACtEE,MAAS,uBACJ,IAAImB,EAAuBlB,GAAKuB,EAAK,WAAW,CAAC,GAAG1B,CAAO,IAEhEE,MAAS,0BACJ,IAAIqB,EACTpB,GACAuB,EAAK,UAAU,aACf1B,CACF,IAEEE,MAAS,mBAAmBA,MAAS,wBAChC,IAAIc,EAAkBb,GAAKD,GAAMF,CAAO,IAE1C,IAAID,EAAc4B,EAAI,QAAQxB,GAAKD,GAAMF,CAAO;AACzD;;AC5DA,SAAgB6B,EAAgB,EAC9B,YACA,OAAOC,KAAa,GAAGC,MAAS,WAAW,MAAM,GAAGA,CAAI,GACxD,SAASC,GACT,qBACgC;CAChC,IAAMC,IAAOC,EAAQ,QAAQ,QAAQ,EAAE;CAEvC,OAAO,EACL,MAAM,QAAQ,GAAM,GAAQ,EAAE,YAAS,OAAO,SAAM,SAAM,eAAY,CAAC,GAAG;EACxE,IAAM/B,IAAM,GAAG8B,IAAOE,KAChBC,IAAS,IAAI,QAAQJ,IAAa,MAAMA,EAAW,IAAI,KAAA,CAAS;EAEtE,AADAI,EAAO,IAAI,UAAU,kBAAkB,GACnCV,MAAS,KAAA,KAAWU,EAAO,IAAI,gBAAgB,kBAAkB;EACrE,KAAK,IAAM,CAACC,GAAMC,MAAU,OAAO,QAAQC,KAAW,CAAC,CAAC,GACtD,EAAO,IAAIF,GAAMC,CAAK;EAExB,IAAMX,IAAM,MAAMG,EAAU3B,GAAK;GAC/B;GACA,SAASiC;GACT,MAAMI,MAASd,MAAS,KAAA,IAAY,KAAA,IAAY,KAAK,UAAUA,CAAI;EACrE,CAAC;EACD,IAAI,CAACC,EAAI,IAAI;GACX,IAAMc,IAAQ,MAAMb,EAASzB,GAAKwB,CAAG;GACrC,IAAIc,aAAiBrC,GACnB,IAAI;IACF,IAAiBqC,CAAK;GACxB,QAAQ,CAER;GAEF,MAAMA;EACR;EAEA,OADId,EAAI,WAAW,MAAYe,EAAO,MAAM,KAAA,CAAS,IAC9CA,EAAO,MAAM,MAAMf,EAAI,KAAK,CAAC;CACtC,EACF;AACF;;AC+BA,IAAMgB,IAAM,oBACNC,IAAe,EAAE,aAAmB,KAAA,EAAU;AAEpD,SAASC,EAAY,GAAuB;CAC1C,IAAMC,IAAS,IAAI,gBAAgB;CACnC,KAAK,IAAM,CAACC,GAAKT,MAAU,OAAO,QAAQU,CAAK,GAC7C,AAAIV,MAAU,KAAA,KAAWQ,EAAO,IAAIC,GAAK,OAAOT,CAAK,CAAC;CAExD,IAAMW,IAAKH,EAAO,SAAS;CAC3B,OAAOG,IAAK,IAAIA,MAAO;AACzB;AAEA,SAAgBC,EAAkB,GAA0C;CAC1E,IAAM,EAAE,eAAYrB,EAAgBsB,CAAO,GACrCC,IAAUC,GACVC,IAAWC,GACXC,IAAQC,EAAmB,MAAM;CAEvC,OAAO;EACL,eAAe,IAAQ,CAAC,MACtBC,EAAQ,kBAAkBb,EAAYG,CAAK,KAAKW,CAAsB;EACxE,wBACED,EAAQ,0BAA0BE,CAAwB;EAC5D,aAAa,MAAOF,EAAQ,mBAAmBf,EAAIkB,CAAE,KAAKT,CAAO;EACjE,gBAAgB,MACdM,EAAQ,mBAAmBN,GAAS;GAAE,QAAQ;GAAQ,MAAMU;EAAM,CAAC;EACrE,gBAAgB,GAAI,MAClBJ,EAAQ,mBAAmBf,EAAIkB,CAAE,KAAKT,GAAS;GAC7C,QAAQ;GACR,MAAMW;EACR,CAAC;EACH,gBAAgB,MACdL,EAAQ,mBAAmBf,EAAIkB,CAAE,KAAKjB,GAAc,EAClD,QAAQ,SACV,CAAC;EACH,mBAAmB,GAAI,GAAM,GAAK;GAChC,IAAMJ,IAAO,IAAI,SAAS;GAG1B,OAFIwB,MAAQ,KAAA,KAAWxB,EAAK,OAAO,OAAOwB,CAAG,GAC7CxB,EAAK,OAAO,QAAQyB,CAAI,GACjBP,EAAQ,mBAAmBf,EAAIkB,CAAE,EAAE,UAAUT,GAAS;IAC3D,QAAQ;IACR;GACF,CAAC;EACH;EACA,uBAAuB,GAAI,MACzBM,EAAQ,mBAAmBf,EAAIkB,CAAE,EAAE,gBAAgBT,GAAS;GAC1D,QAAQ;GACR,MAAM,EAAE,YAAS;EACnB,CAAC;EACH,qBAAqB,GAAI,GAAS,MAChCM,EAAQ,mBAAmBf,EAAIkB,CAAE,EAAE,UAAUlB,EAAIuB,CAAO,KAAKd,GAAS;GACpE,QAAQ;GACR,MAAMW;EACR,CAAC;EACH,qBAAqB,GAAI,MACvBL,EAAQ,mBAAmBf,EAAIkB,CAAE,EAAE,UAAUlB,EAAIuB,CAAO,KAAKd,GAAS,EACpE,QAAQ,SACV,CAAC;EACH,gBAAgB,IAAQ,CAAC,MACvBM,EAAQ,mBAAmBb,EAAYG,CAAK,KAAKmB,CAAuB;EAC1E,cAAc,MAAOT,EAAQ,oBAAoBf,EAAIkB,CAAE,KAAKP,CAAQ;EACpE,iBAAiB,MACfI,EAAQ,oBAAoBJ,GAAU;GAAE,QAAQ;GAAQ,MAAMQ;EAAM,CAAC;EACvE,iBAAiB,GAAI,MACnBJ,EAAQ,oBAAoBf,EAAIkB,CAAE,KAAKP,GAAU;GAC/C,QAAQ;GACR,MAAMS;EACR,CAAC;EACH,yBAAyBL,EAAQ,yBAAyBF,CAAK;EAC/D,uBAAuB,MACrBE,EAAQ,yBAAyBF,GAAO;GAAE,QAAQ;GAAO,MAAMM;EAAM,CAAC;EACxE,aAAa,IAAQ,CAAC,MACpBJ,EAAQ,gBAAgBb,EAAYG,CAAK,KAAKoB,CAAoB;EACpE,WAAW,MACTV,EAAQ,iBAAiBf,EAAIkB,CAAE,KAAKQ,CAAsB;EAC5D,YAAY,GAAI,IAAQ,CAAC,MACvBX,EAAQ,iBAAiBf,EAAIkB,CAAE,EAAE,QAAQQ,GAAwB;GAC/D,QAAQ;GACR,MAAMP;EACR,CAAC;EACH,eAAe,MACbJ,EAAQ,iBAAiBf,EAAIkB,CAAE,EAAE,WAAWQ,GAAwB,EAClE,QAAQ,OACV,CAAC;EACH,MAAM,YAAY,GAAI,IAAQ,EAAE,SAAS,GAAM,GAAG,IAAS,CAAC,GAAG;GAC7D,IAAMtB,IAAMuB,EAAO,kBAAkB,OAAO,WAAW;GACvD,IAAI;IACF,OAAO,MAAMZ,EACX,iBAAiBf,EAAIkB,CAAE,EAAE,WACzBQ,GACA;KACE,QAAQ;KACR,MAAMP;KACN,SAAS,EAAE,mBAAmBf,EAAI;IACpC,CACF;GACF,SAASwB,GAAK;IAIZ,MAHIA,aAAe,UACjB,EAA6C,iBAAiBxB,IAE1DwB;GACR;EACF;CACF;AACF"}
@@ -8,5 +8,6 @@ export interface AdminLayoutProps {
8
8
  navBadges?: NavBadges;
9
9
  header?: ReactNode;
10
10
  footer?: ReactNode;
11
+ currentHref?: string;
11
12
  }
12
- export declare function AdminLayout({ children, extraNav, navBadges, header, footer, }: AdminLayoutProps): import('react').JSX.Element;
13
+ export declare function AdminLayout({ children, extraNav, navBadges, header, footer, currentHref, }: AdminLayoutProps): import('react').JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function findCurrentHref(hrefs: string[], currentHref: string | undefined): string | undefined;
@@ -16,6 +16,7 @@ export interface NavItem {
16
16
  label: string;
17
17
  icon: ReactNode;
18
18
  badge?: number | string;
19
+ badgeLabel?: string;
19
20
  }
20
21
  export declare const TAB_BAR_SLOTS = 5;
21
22
  interface KitHrefs {
@@ -27,6 +28,8 @@ interface KitLabels {
27
28
  products: string;
28
29
  orders: string;
29
30
  shippingRates: string;
31
+ badge: (count: number | string) => string;
32
+ productsBadge: (count: number | string) => string;
30
33
  }
31
34
  export declare function buildNavItems(hrefs: KitHrefs, labels: KitLabels, extraNav: ExtraNavItem[], badges: NavBadges): NavItem[];
32
35
  export declare function splitForTabBar(items: NavItem[]): {
@@ -0,0 +1 @@
1
+ export declare function useCurrentHref(explicit: string | undefined): string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- export type EmptyStateIcon = 'orders' | 'discounts' | 'shipping' | 'search';
2
+ export type EmptyStateIcon = 'orders' | 'discounts' | 'shipping' | 'search' | 'products';
3
3
  export interface EmptyStateProps {
4
4
  icon: EmptyStateIcon;
5
5
  title: ReactNode;