@develemit/commerce-admin 0.6.0 → 0.8.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 (81) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +88 -9
  3. package/dist/client/admin-client.d.ts +4 -2
  4. package/dist/client/errors.d.ts +9 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/client-ClTCzt-p.js +190 -0
  7. package/dist/client-ClTCzt-p.js.map +1 -0
  8. package/dist/components/admin-layout/admin-layout.d.ts +6 -6
  9. package/dist/components/admin-layout/current-item.d.ts +1 -0
  10. package/dist/components/admin-layout/nav-icons.d.ts +9 -0
  11. package/dist/components/admin-layout/nav-items.d.ts +39 -0
  12. package/dist/components/admin-layout/use-current-href.d.ts +1 -0
  13. package/dist/components/admin-layout/use-more-sheet.d.ts +5 -0
  14. package/dist/components/empty-state/empty-state.d.ts +10 -0
  15. package/dist/components/notice/notice.d.ts +5 -1
  16. package/dist/components/page-header/admin-page-header.d.ts +8 -0
  17. package/dist/components/setup-checklist/setup-checklist.d.ts +15 -0
  18. package/dist/components/switch/switch.d.ts +7 -0
  19. package/dist/index.d.ts +12 -0
  20. package/dist/index.js +3758 -1276
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/admin-labels.d.ts +5 -0
  23. package/dist/lib/currency-symbol.d.ts +1 -0
  24. package/dist/lib/discount-labels.d.ts +1 -0
  25. package/dist/lib/fill-alts.d.ts +3 -0
  26. package/dist/lib/order-action-labels.d.ts +2 -0
  27. package/dist/lib/order-labels.d.ts +1 -0
  28. package/dist/lib/product-draft.d.ts +14 -0
  29. package/dist/lib/product-flow-labels.d.ts +63 -0
  30. package/dist/lib/product-labels.d.ts +61 -0
  31. package/dist/lib/product-missing.d.ts +4 -0
  32. package/dist/lib/product-sheet-actions.d.ts +13 -0
  33. package/dist/lib/provider.d.ts +2 -0
  34. package/dist/lib/shipping-labels.d.ts +2 -0
  35. package/dist/lib/stock-labels.d.ts +43 -0
  36. package/dist/lib/use-batch-upload.d.ts +39 -0
  37. package/dist/lib/use-focus-trap.d.ts +2 -0
  38. package/dist/lib/use-image-upload.d.ts +3 -2
  39. package/dist/lib/use-product-autosave.d.ts +99 -0
  40. package/dist/lib/use-product-pages.d.ts +3 -2
  41. package/dist/styles.css +1743 -57
  42. package/dist/views/orders/admin-order-detail.d.ts +2 -1
  43. package/dist/views/orders/order-actions.d.ts +2 -1
  44. package/dist/views/orders/resolve-button.d.ts +3 -1
  45. package/dist/views/orders/ship-form.d.ts +4 -1
  46. package/dist/views/products/add-several/admin-add-several.d.ts +5 -0
  47. package/dist/views/products/add-several/photo-picker.d.ts +9 -0
  48. package/dist/views/products/admin-product-editor.d.ts +3 -1
  49. package/dist/views/products/admin-product-images.d.ts +2 -0
  50. package/dist/views/products/admin-product-list.d.ts +13 -1
  51. package/dist/views/products/finish-drafts/admin-finish-drafts.d.ts +7 -0
  52. package/dist/views/products/finish-drafts/piece-card.d.ts +10 -0
  53. package/dist/views/products/finish-drafts/piece-fields.d.ts +7 -0
  54. package/dist/views/products/finish-drafts/piece-summary.d.ts +11 -0
  55. package/dist/views/products/finish-drafts/use-publish-pieces.d.ts +54 -0
  56. package/dist/views/products/product-card.d.ts +6 -0
  57. package/dist/views/products/product-flow/admin-product-flow.d.ts +15 -0
  58. package/dist/views/products/product-flow/basics-section.d.ts +6 -0
  59. package/dist/views/products/product-flow/flow-actions.d.ts +13 -0
  60. package/dist/views/products/product-flow/flow-frame.d.ts +30 -0
  61. package/dist/views/products/product-flow/flow-images.d.ts +7 -0
  62. package/dist/views/products/product-flow/flow-section-props.d.ts +5 -0
  63. package/dist/views/products/product-flow/flow-success.d.ts +8 -0
  64. package/dist/views/products/product-flow/flow-test-helpers.d.ts +12 -0
  65. package/dist/views/products/product-flow/materials-field.d.ts +6 -0
  66. package/dist/views/products/product-flow/photos-section.d.ts +9 -0
  67. package/dist/views/products/product-flow/preview-section.d.ts +10 -0
  68. package/dist/views/products/product-flow/story-section.d.ts +8 -0
  69. package/dist/views/products/product-flow/use-product-flow.d.ts +143 -0
  70. package/dist/views/products/product-sheet/admin-product-sheet.d.ts +8 -0
  71. package/dist/views/products/product-sheet/delete-draft-button.d.ts +9 -0
  72. package/dist/views/products/product-sheet/sheet-carousel.d.ts +4 -0
  73. package/dist/views/products/product-sheet/sheet-edit-link.d.ts +14 -0
  74. package/dist/views/products/product-sheet/use-product-change.d.ts +16 -0
  75. package/dist/views/products/product-toolbar.d.ts +10 -0
  76. package/dist/views/products/product-welcome.d.ts +8 -0
  77. package/dist/views/shipping/admin-shipping-rates.d.ts +6 -1
  78. package/dist/views/shipping/shipping-rate-row.d.ts +2 -1
  79. package/package.json +11 -3
  80. package/dist/client-CSjuKdIE.js +0 -178
  81. package/dist/client-CSjuKdIE.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,148 @@
1
+ ## 0.8.0 (2026-11-12)
2
+
3
+ ### Deprecated
4
+
5
+ - `AdminProductImages` and `AdminProductImagesProps`: the product flow has its own Photos step. They stay exported until the next major.
6
+
7
+ ### Fixes
8
+
9
+ - 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.
10
+
11
+ ### Features
12
+
13
+ - `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`.
14
+ - 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.
15
+ - Add several opens with a large photo drop zone (new label `stock.addSeveral.dropHint`); `PhotoPicker` is internal, no API change.
16
+ - 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'`.
17
+ - Setup checklist: actionable rows have a chevron, border and pressed state, and the compact list is expanded by default (the header still collapses it).
18
+ - The More sheet has a visible close button and a **View shop** link when the new optional `hrefs.storefront` is set (label `nav.viewShop`).
19
+ - The welcome screen, Add several and Finish drafts align left with the page heading and cap at 720px.
20
+
21
+ ### Features
22
+
23
+ - 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`.
24
+ - 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.
25
+ - Once photos exist, the Photos step's drop zone becomes an "Add photo" tile in the thumbnail grid.
26
+ - Materials: chip picks and the free-text box are separate; the box holds only custom text and the saved value merges both.
27
+ - Category chips are shown in title case, and "New category…" is hidden when a category named "other" already exists.
28
+
29
+ ### Changed (copy)
30
+
31
+ - 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.
32
+
33
+ ### Features
34
+
35
+ - 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.
36
+ - **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`.
37
+ - 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.
38
+ - 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).
39
+ - **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.
40
+
41
+ ### Features
42
+
43
+ - `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").
44
+
45
+ ### Polish
46
+
47
+ - 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.
48
+ - 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).
49
+ - 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.
50
+ - 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.
51
+
52
+ ### Fixes
53
+
54
+ - 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).
55
+
56
+ - 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.
57
+
58
+ ### Changes
59
+
60
+ - The Live and Sold chips no longer overlap: a sold piece counts under Sold only (needs `commerce-server` with the matching counts change).
61
+
62
+ ## 0.7.0 (2026-11-04)
63
+
64
+ ### Features
65
+
66
+ - Stocking a shop: new `AdminAddSeveral({ onDone })` (pick many photos, one draft per photo, progress, retry for just the failed ones) and `AdminFinishDrafts({ productIds?, categories?, onDone })` (a one-piece-at-a-time queue ending in a summary with "Put N in the shop" or "Keep as drafts"). Without `productIds` it finds drafts through `listProducts({ incomplete: 'true' })`, which `AdminProductQuery` now accepts. Copy lives in `labels.products.stock.*`; new exports `StockLabels` and `PublishOutcome`.
67
+ - New `EmptyState` component. Orders, Discounts and Shipping rates show an icon, a title and a sentence on what happens there. `AdminShippingRates` gains `suggestedRates?` (chips in its empty state) and an "Add an option" button. New labels `orders.emptyHint`, `discounts.emptyHint`, `shipping.emptyTitle`, `shipping.addOption`. **Changed:** `shipping.empty` is now the sentence under the title ("Add one so customers can check out.").
68
+ - Photo-first add and edit: new `AdminProductFlow({ productId?, initialStep?, onDone?, onClose?, categories?, materialSuggestions?, isFirstProduct?, imagesSlot? })`. Phones get four steps (Photos, Basics, Story, Preview) with a step bar and sticky footer; desktops get one two-column page with a sticky live preview. The first photo or name creates a draft and edits autosave (about 600ms) with an inline retry. Publishing a draft ends on a success screen. Copy lives in `labels.products.flow.*` (`photoTip`, `descriptionPrompts`, …). New exports `ProductFlowStep`, `ProductFlowLabels`, `EditProduct` and `Switch` (`.sfa-switch`).
69
+ - **Changed:** the products list shows newest first, and its "All" filter sends `excludeArchived` so the list matches the All count. `AdminProductQuery` gains `excludeArchived`.
70
+ - **Changed:** `AdminProductEditor` keeps its props but now renders `AdminProductFlow`, so its look and flow are new: there is no Status select or Save button (edits autosave, and "Put it in the shop" publishes), `onSaved` runs when the user finishes (Save as draft, Done, Back to products) instead of after every save, and `imagesSlot` replaces the built-in Photos section. It reads `?step=` from the URL when `initialStep` isn't passed.
71
+ - The sheet's Edit details, Photos and "Add the missing details" links now point at `hrefs.product(id)` plus `?step=basics` or `?step=photos`, and `onEdit`/`onEditProduct` receive that step as a second argument.
72
+
73
+ - Responsive shell: `AdminLayout` is a bottom tab bar with a "More" sheet below 900px and a left sidebar from 900px. New props `navBadges`; `extraNav` items accept `icon` and `badge`. New export `adminNavIcons`, labels `nav.more` and `nav.closeMore`.
74
+ - `AdminPageHeader` puts the page title and actions on one row; every kit screen uses it (the shipping page title is now an `h1`).
75
+ - Unified controls: pill `sfa-btn` (48px, plus `--ghost`), `sfa-chip` filters (44px) and `sfa-input` (52px). Filters changed from `sfa-btn sfa-filter` to `sfa-chip`.
76
+ - Optional tokens `--sf-color-accent-soft`, `--sf-color-success(-soft)`, `--sf-color-warning(-soft)` and `--sf-shadow-raised`, all with fallbacks.
77
+ - Product quick actions: tapping a card in `AdminProductList` opens `AdminProductSheet({ productId, onClose, onEdit?, onChanged? })` — a bottom sheet below 900px and a 420px side panel from 900px, focus-trapped, closing on Escape. It shows a photo carousel, price, status and category, one primary action by state (finish / put in the shop / mark sold / back in stock / restore), and Edit, Photos, Feature on home, Hide from shop, Copy shop link and Archive. Reversible changes offer Undo. Copy lives in `labels.products.sheet.*`.
78
+ - `AdminProductList` props `onOpenProduct` (replaces the sheet) and `onEditProduct` (the sheet's Edit button). **Changed:** the per-card Mark sold / Archive / View in shop buttons and their confirm dialog are gone (the sheet replaces them), and the card's name is now a `button`, not a link.
79
+ - `Notice` accepts an optional `action: { label, onAction }`.
80
+ - `AdminClient.getProductCounts()` and `ProductIncompleteError` (422, `missing`).
81
+ - Admin product responses can have `priceCents` and `category` of `null` (drafts); the list shows an em dash for a missing price. The product form still requires a name and category until the editor is redesigned.
82
+
83
+ - `AdminProductList` is now a photo grid with status pills, filter chips with counts, a drafts banner, a welcome state for an empty shop and a floating add button on phones. New optional props `welcome`, `setup`, `onAddMany`, `onFinishDrafts`. The product name link no longer sits in a table row; the whole card is clickable.
84
+ - New export `AdminSetupChecklist` and `SetupStep`. New labels `products.welcome`, `products.card`, `products.draftsBanner`, `products.setup`, `products.openSearch` and `products.closeSearch`.
85
+ - The list now calls `getProductCounts()`, so a custom client must implement it.
86
+
87
+ ### Docs
88
+
89
+ - The package now ships `CHANGELOG.md` and declares `description`, `repository` and `homepage`. The README documents the client error classes, `errorKey`, `useAdminClient` and `OrderActions`.
90
+
91
+ ## 0.6.0 (2026-11-03)
92
+
93
+ ### Features
94
+
95
+ - `createAdminClient` accepts `onUnauthorized(error)`, called whenever a request answers 401, before the request rejects with the usual `AdminUnauthorizedError`. Errors thrown by the callback are ignored. Additive.
96
+
97
+ ## 0.5.0 (2026-11-02)
98
+
99
+ ### Features
100
+
101
+ - `MaxRedemptionsBelowUsageError` (400 `MAX_REDEMPTIONS_BELOW_USAGE`) is mapped by the admin client, and `AdminDiscountEditor` shows it on the usage-limit field instead of as a generic error (e.g. when a redemption lands between page load and save).
102
+
103
+ ## 0.4.0 (2026-10-30)
104
+
105
+ ### Features
106
+
107
+ - `.sfa-badge--refund-pending` and `.sfa-badge--refund-succeeded` have their own colors, from `--sf-*` tokens with neutral fallbacks.
108
+ - Each image tile has an inline "Edit description" control (save, cancel, error shown). `AdminClient` gains `updateProductImage(productId, imageId, { alt })`; `labels.products.images` gains `editAlt`, `saveAlt` and `cancelAlt`. Needs a server with the image alt API (`@develemit/commerce-server`).
109
+ - `AdminDiscountList` (Active, Scheduled, Expired and Inactive tabs, debounced code search, Load more, "12 / 50 used") and `AdminDiscountEditor` (create or edit, percent or fixed, optional minimum subtotal, dates and usage limit, deactivate with confirmation). The code and value are locked once a discount has been used.
110
+ - `AdminClient` gains `listDiscounts`, `getDiscount`, `createDiscount` and `updateDiscount`, plus `DiscountCodeTakenError` and `DiscountInUseError`. `AdminErrorKey` gains `discountCodeTaken` and `discountInUse`; `labels.statuses` gains `scheduled` and `inactive`.
111
+ - `hrefs` gains `discounts`, `discount(id)` and `newDiscount`; `labels` gains `discounts`.
112
+ - `AdminOrderDetail` totals show a "Discount (CODE) −$X" row when the order has a discount. Needs a server with discounts (`@develemit/commerce-server` admin discounts API).
113
+ - `AdminLayout` does not add a Discounts link; pass it through `extraNav`.
114
+
115
+ ## 0.3.0 (2026-10-30)
116
+
117
+ First published release of `@develemit/commerce-admin`: the React admin UI (products, images, orders with ship/refund/resolve, shipping rates) for `@develemit/commerce-server`'s admin API. Peers: `react`, `react-dom` `^19`.
118
+
119
+ ### Changes for setups linked to pre-release builds
120
+
121
+ - `AdminClient.listProducts(query?)` returns a page, `{ items, nextCursor }`, instead of an array, and accepts `status`, `q`, `cursor` and `limit`. `AdminProductList` filters and searches on the server (debounced 300ms) and has a "Load more" button; `labels.products` gains `loadMore`.
122
+
123
+ ### Fixes
124
+
125
+ - Published tarballs no longer include declaration maps (`*.d.ts.map`), which pointed at `src/` files that were not shipped. JS sourcemaps now embed `sourcesContent`.
126
+ - Admin row links now meet the 44px tap target (fix): the order-id and product-name links, the back link, the tracking link and the restock links use the new `sfa-link--tap` class.
127
+ - The status filters in `AdminProductList` and `AdminOrderList` stay on one row that scrolls sideways on narrow screens instead of wrapping (fix). Both groups use the new `sfa-filter-group` class.
128
+ - Built `dist` type resolution: the `client` (and storefront `format`) entries now emit to `dist/<name>/index.js` beside their declarations, so `allowJs` consumers (Next) get real types for `useAdminClient()` instead of `any`. No API change; the `exports` subpaths are unchanged.
129
+
130
+ ### Features
131
+
132
+ - `AdminClient.listProducts` accepts `stock`. The Live tab sends `status=active&stock=in` and the Sold tab `status=active&stock=sold`, so pages are exact; the client-side stock split is gone.
133
+ - `AdminProductEditor` accepts an optional `categories` prop; the Category input offers them as `<datalist>` suggestions and still takes free text.
134
+ - The default `renderImage` now renders `<img loading="lazy" decoding="async">`, or `loading="eager"` with `fetchpriority="high"` when `priority` is set (non-breaking behavior change). `AdminImageProps` gains an optional `priority`.
135
+
136
+ - New package `@develemit/commerce-admin` with `AdminProvider` (link adapter, route hrefs, locale, API client slot, labels) and `useAdmin()`.
137
+
138
+ - Shared admin primitives (`AdminLayout`, `AdminTable`, `StatusBadge`, `Money`, `ConfirmDialog`, `Notice`, `ErrorMessage`) and the `useAdminQuery`/`useAdminMutation` hooks; `labels` gains `nav`, `statuses`, `errors`, `confirm`, `dismiss`, `navLabel`.
139
+
140
+ - `AdminProductList` and `AdminProductEditor` product screens, `parseMoneyInput`/`formatMoneyInput`, `labels.products`, `hrefs.storefrontProduct`, and a `sold` status label.
141
+
142
+ - `AdminOrderList` and `AdminOrderDetail` order screens (with an `actions` slot) and `labels.orders`.
143
+
144
+ - `OrderActions` (ship / re-ship, refund dialog with idempotent retry, resolve) is now the default `AdminOrderDetail` actions; `labels.orders.actions`.
145
+
146
+ - `AdminShippingRates` shipping-rate editor (add, edit, reorder, deactivate, remove; one `PUT` on save), `onDirtyChange`, and `labels.shipping`.
147
+
148
+ - `createAdminClient` from `@develemit/commerce-admin/client`, plus a README covering install, the provider, the server-side proxy auth pattern, each view and theming.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @develemit/commerce-admin
2
2
 
3
- Admin UI for the develemit commerce kit: product, order and shipping-rate screens for the `@develemit/commerce-server` admin API.
3
+ Admin UI for the develemit commerce kit: product, order, discount and shipping-rate screens for the `@develemit/commerce-server` admin API.
4
+
5
+ New here? The [admin guide](https://github.com/develemit/develemit-commerce/blob/main/docs/getting-started/admin.md) wires it into a Next app step by step. What changed in each version is in [CHANGELOG.md](CHANGELOG.md).
4
6
 
5
7
  Depends on `@develemit/commerce-contracts` only. Themed through the same `--sf-*` CSS variables as `@develemit/storefront`; import `@develemit/commerce-admin/styles.css` for the admin-only `sfa-` classes.
6
8
 
@@ -64,9 +66,34 @@ The server's `admin.authorize(req)` decides who may call the admin API. Never pu
64
66
  1. **Server-side proxy (recommended).** A route handler in your web app checks your own session (for example your login cookie), then forwards the request to the commerce API with the admin token from a server-only env var. The browser client points at the proxy (`baseUrl: '/api'`). See `apps/playground-web/src/app/api/admin/[...path]/route.ts`; it forwards method, query, body (including multipart image uploads) and `idempotency-key`.
65
67
  2. **Cookie session.** Serve the commerce API on the same site and have `authorize` read your session cookie. The client then needs no token; use `headers` or a custom `fetch` with `credentials: 'include'` if the API is on another origin. Pass `onUnauthorized(error)` to `createAdminClient` to react when any request answers 401 (for example redirect to your login page when the session expires); the request still rejects with `AdminUnauthorizedError`, and errors thrown by the callback are ignored.
66
68
 
69
+ ## Client errors
70
+
71
+ Every client method rejects with an `AdminApiError` (`status`, `url`, `code`) or one of its subclasses, so you can branch with `instanceof`:
72
+
73
+ | Error | When |
74
+ | ------------------------------- | ------------------------------------------------------------------------------- |
75
+ | `AdminUnauthorizedError` | 401: `authorize` refused (see `onUnauthorized` above) |
76
+ | `AdminNotFoundError` | 404: product, order, image or discount not found |
77
+ | `AdminInvalidInputError` | 400 `INVALID_INPUT`, with Zod `issues` |
78
+ | `InvalidOrderStateError` | the order's `status` doesn't allow the action (ship, refund, resolve) |
79
+ | `InsufficientStockError` | resolving a `needs_attention` order without stock (`productIds`) |
80
+ | `RefundExceedsBalanceError` | refund larger than what's left (`balanceCents`) |
81
+ | `RefundGatewayError` | the payment gateway rejected the refund |
82
+ | `RefundOutcomeUnknownError` | the gateway timed out; retry with the same `idempotencyKey` |
83
+ | `ProductIncompleteError` | 422: publishing a draft that lacks a name, price, category or photo (`missing`) |
84
+ | `InvalidImageError` | upload isn't a supported image |
85
+ | `ImageTooLargeError` | 413: upload over the server's limit |
86
+ | `DiscountCodeTakenError` | 409: the code already exists |
87
+ | `DiscountInUseError` | 409: a redeemed discount's code or value can't change |
88
+ | `MaxRedemptionsBelowUsageError` | 400: `maxRedemptions` lower than redemptions so far |
89
+
90
+ `client.getProductCounts()` returns `{ all, live, draft, sold, archived, incompleteDrafts }` for list filter chips.
91
+
92
+ `ErrorMessage` and `errorKey(error)` turn any of these into a `labels.errors` key, so screens show consistent copy.
93
+
67
94
  ## Primitives
68
95
 
69
- This version ships `AdminProvider`, `useAdmin()` and the shared primitives:
96
+ `AdminProvider` supplies the client, adapters and labels. `useAdmin()` reads all of it, and `useAdminClient()` just the client, for host screens that call the admin API. The shared primitives:
70
97
 
71
98
  - `AdminLayout` — nav from `hrefs`, plus `extraNav` (`{ href, label, position? }`) and `header`/`footer` slots for host content.
72
99
  - `AdminTable` — stacks into labeled cards below 640px.
@@ -89,19 +116,42 @@ Override any copy through `AdminProvider`'s `labels` (`nav`, `statuses` and `err
89
116
  <AdminProductList />
90
117
  </AdminProvider>
91
118
 
92
- <AdminProductEditor
119
+ <AdminProductFlow
93
120
  productId={id} // omit to create
94
- onSaved={(product) => router.push(hrefs.product(product.id))}
95
- imagesSlot={null}
96
- categories={['Necklaces', 'Rings']} // optional suggestions
121
+ initialStep="basics" // optional: photos | basics | story | preview
122
+ categories={['Necklaces', 'Rings']} // category chips; "Other…" allows any
123
+ materialSuggestions={['Silver', 'Gold']} // optional chips
124
+ onDone={() => router.push('/admin/products')}
125
+ onClose={() => router.push('/admin/products')}
97
126
  />
98
127
  ```
99
128
 
100
- - `AdminProductList` — status filters (All, Live, Sold, Drafts, Archived; "Sold" is active with zero stock), name search over the loaded list, a single row of filter buttons that scrolls sideways on narrow screens, thumbnails, "View in shop" when `hrefs.storefrontProduct` is set, and confirmed Mark sold / Archive actions.
101
- - `AdminProductEditor` — create or edit every product field. Validates with the contract schema, shows inline errors with `aria-invalid`/`aria-describedby`, focuses the first invalid field, maps a server `INVALID_INPUT` onto fields, and PATCHes only changed fields. `imagesSlot` renders in edit mode only. Pass `categories` (the categories already in use) to offer them as `<datalist>` suggestions on the Category input; free text is still allowed, and without it the input is a plain text field.
129
+ - `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 (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
+ - `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). The success screen shows "See it in the shop" when `hrefs.storefrontProduct` is set. Placeholders: `descriptionPlaceholder`, `sizePlaceholder`, `altPlaceholder` (empty by default). Editing a live product uses `previewHeadingLive` and `saveChanges`; a disabled Continue explains itself via `continueNeeds`.
133
+ - `AdminProductImages({ productId, images })` — **deprecated**; the flow's Photos step replaces it, and it is removed in the next major.
134
+ - `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.
135
+ - `Switch({ id, label, checked, onChange })` — a native checkbox with `role="switch"`, styled by `.sfa-switch`.
102
136
  - Prices are typed in dollars and parsed to integer cents from the string (`parseMoneyInput`, `formatMoneyInput`).
103
137
  - Copy lives under `labels.products` and merges key by key.
104
138
 
139
+ ### Stocking a shop
140
+
141
+ ```tsx
142
+ <AdminAddSeveral onDone={(products) => setIds(products.map((p) => p.id))} />
143
+ <AdminFinishDrafts
144
+ productIds={ids} // omit to finish every incomplete draft
145
+ categories={['Mugs', 'Bowls']}
146
+ onDone={({ published, drafts }) => router.push('/admin/products')}
147
+ />
148
+ ```
149
+
150
+ - `AdminAddSeveral({ onDone })` — an intro ("One photo, one piece") and a multi-photo picker. Each photo creates an image-only draft and uploads to it, one at a time, with a progress bar. If some fail, it lists those photos and offers **Try again** for just them (a draft that was already created is reused, never duplicated); unsupported or oversized files are listed without a retry. `onDone(products)` receives the drafts that got a photo.
151
+ - `AdminFinishDrafts({ productIds?, categories?, onDone })` — a queue that shows one piece at a time ("Piece N of M"): its cover photo (or an "Add a photo" picker), name, price and category chips, autosaved. **Next piece** is enabled once name, price and category are set; **Skip for now** moves on. The last step is a summary with each piece's price or "Needs details", and **Put N in the shop** (publishes only the complete ones; a 422 leaves that piece a draft) or **Keep as drafts**. Without `productIds` it loads `listProducts({ incomplete: 'true' })`, so `AdminProductList`'s `onFinishDrafts` can just navigate to a page that renders it. `onDone({ published, drafts })` runs when the user leaves.
152
+ - Both render a centered card of at most 640px; the queue puts the photo and the fields side by side from 900px. Copy: `labels.products.stock.{addSeveral,queue,summary}.*`. `AdminClient.listProducts` accepts `incomplete: 'true'`.
153
+ - Orders, Discounts and Shipping rates use `AdminPageHeader` and a shared `EmptyState({ icon, title, body?, action?, children? })` (icons `orders`, `discounts`, `shipping`, `search`). `AdminShippingRates` takes `suggestedRates?: { label, amountCents }[]`, shown as chips in its empty state (none by default); new labels `orders.emptyHint`, `discounts.emptyHint`, `shipping.emptyTitle` and `shipping.addOption`.
154
+
105
155
  ## Order screens
106
156
 
107
157
  ```tsx
@@ -114,7 +164,8 @@ Override any copy through `AdminProvider`'s `labels` (`nav`, `statuses` and `err
114
164
  ```
115
165
 
116
166
  - `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.
117
- - `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
+ - `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.
168
+ - `OrderActions({ order, reload, requireTracking? })` — the ship form, refund dialog and resolve button that `AdminOrderDetail` shows, exported for hosts that build their own order page.
118
169
  - Rows link through `hrefs.order(id)` and `renderLink`. Copy lives under `labels.orders`.
119
170
 
120
171
  ## Discount screens
@@ -146,3 +197,31 @@ The default `renderImage` is a lazy, async `<img>` (`priority` makes it eager).
146
197
  ## Theming
147
198
 
148
199
  The admin reads the same `--sf-*` variables as the storefront (`--sf-color-accent`, spacing, radius, typography), so a host that already themes its shop gets a matching admin. Admin-only classes carry the `sfa-` prefix.
200
+
201
+ ### Theme tokens
202
+
203
+ Optional tokens, each with a fallback derived from the tokens above, so existing hosts need nothing:
204
+
205
+ | Token | Fallback | Used for |
206
+ | ------------------------- | ------------------------------------------ | ---------------------------- |
207
+ | `--sf-color-accent-soft` | 12% `--sf-color-accent` on `--sf-color-bg` | Focus halo, hovered nav item |
208
+ | `--sf-color-success` | `#1b7a3d` | Success text and borders |
209
+ | `--sf-color-success-soft` | 14% success on `--sf-color-bg` | Success backgrounds |
210
+ | `--sf-color-warning` | `#b26a00` | Warning text and borders |
211
+ | `--sf-color-warning-soft` | 14% warning on `--sf-color-bg` | Warning backgrounds |
212
+ | `--sf-shadow-raised` | soft shadow from `--sf-color-fg` | More sheet, floating button |
213
+
214
+ `--sf-color-focus` (default: the accent) colors the focus ring. `--sf-font-display` styles page titles.
215
+
216
+ ## Layout
217
+
218
+ `AdminLayout` is a bottom tab bar below 900px and a sticky 240px left sidebar from 900px.
219
+
220
+ - `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.
221
+ - `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.
222
+ - `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.
223
+ - 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.
224
+ - `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.
225
+ - With more than five nav items the fifth tab becomes "More" (`labels.nav.more`, `labels.nav.closeMore`) and lists the rest.
226
+
227
+ `AdminPageHeader({ title, actions?, floatingAction?, children? })` renders the `h1` and its actions on one row; every kit screen uses it. A `floatingAction` floats above the tab bar on a phone and sits inline with the actions on desktop. Buttons are `sfa-btn` (`--primary`, `--ghost`, `--danger`), filters are `sfa-chip` with `aria-pressed`, and inputs are `sfa-input`.
@@ -1,18 +1,20 @@
1
- import { AdminDiscountListQuery, AdminDiscountPage, AdminOrderDetail, AdminOrderListQuery, AdminOrderPage, AdminProduct, AdminProductListQuery, AdminProductPage, AdminProductInput, AdminProductPatch, Discount, DiscountInput, DiscountPatch, RefundOrderInput, ProductImagePatch, ShippingRate, ShippingRateInput, ShipOrderInput } from '@develemit/commerce-contracts';
1
+ import { AdminDiscountListQuery, AdminDiscountPage, AdminOrderDetail, AdminOrderListQuery, AdminProductCounts, AdminOrderPage, AdminProduct, AdminProductListQuery, AdminProductPage, AdminProductInput, AdminProductPatch, Discount, DiscountInput, DiscountPatch, RefundOrderInput, ProductImagePatch, ShippingRate, ShippingRateInput, ShipOrderInput } from '@develemit/commerce-contracts';
2
2
  import { AdminClientOptions } from './transport';
3
3
  export type AdminOrderQuery = Partial<Pick<AdminOrderListQuery, 'status' | 'q' | 'hasPendingRefund' | 'cursor'>> & {
4
4
  limit?: number;
5
5
  };
6
- export type AdminProductQuery = Pick<AdminProductListQuery, 'status' | 'stock' | 'q' | 'cursor' | 'limit'>;
6
+ export type AdminProductQuery = Pick<AdminProductListQuery, 'status' | 'excludeArchived' | 'incomplete' | 'stock' | 'q' | 'cursor' | 'limit'>;
7
7
  export type AdminDiscountQuery = Partial<Pick<AdminDiscountListQuery, 'status' | 'q' | 'cursor' | 'limit'>>;
8
8
  export interface RefundOptions {
9
9
  idempotencyKey?: string;
10
10
  }
11
11
  export interface AdminClient {
12
12
  listProducts(query?: AdminProductQuery): Promise<AdminProductPage>;
13
+ getProductCounts(): Promise<AdminProductCounts>;
13
14
  getProduct(id: string): Promise<AdminProduct>;
14
15
  createProduct(input: AdminProductInput): Promise<AdminProduct>;
15
16
  updateProduct(id: string, patch: AdminProductPatch): Promise<AdminProduct>;
17
+ deleteProduct(id: string): Promise<void>;
16
18
  uploadProductImage(id: string, file: Blob, alt?: string): Promise<AdminProduct>;
17
19
  reorderProductImages(id: string, imageIds: string[]): Promise<AdminProduct>;
18
20
  updateProductImage(id: string, imageId: string, patch: ProductImagePatch): Promise<AdminProduct>;
@@ -1,3 +1,4 @@
1
+ import { ProductIncompleteField } from '@develemit/commerce-contracts';
1
2
  export declare class AdminApiError extends Error {
2
3
  readonly status: number;
3
4
  readonly url: string;
@@ -48,3 +49,11 @@ export declare class DiscountInUseError extends AdminApiError {
48
49
  export declare class MaxRedemptionsBelowUsageError extends AdminApiError {
49
50
  constructor(url: string, message?: string);
50
51
  }
52
+ export declare class ProductIncompleteError extends AdminApiError {
53
+ readonly missing: ProductIncompleteField[];
54
+ constructor(url: string, missing: ProductIncompleteField[], message?: string);
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 RefundExceedsBalanceError, m as RefundGatewayError, h as RefundOutcomeUnknownError, t as createAdminClient } from "../client-CSjuKdIE.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";
@@ -0,0 +1,190 @@
1
+ import { adminDiscountPageSchema as e, adminOrderDetailSchema as t, adminOrderPageSchema as n, adminProductCountsSchema as r, adminProductPageSchema as i, adminProductSchema as a, discountSchema as o, shippingRateSchema as s } from "@develemit/commerce-contracts";
2
+ // src/client/errors.ts
3
+ var c = class extends Error {
4
+ status;
5
+ url;
6
+ code;
7
+ idempotencyKey;
8
+ constructor(e, t, n, r) {
9
+ super(r ?? `Admin API request failed (${e}${n ? ` ${n}` : ""}): ${t}`), this.status = e, this.url = t, this.code = n, this.name = "AdminApiError";
10
+ }
11
+ }, l = class extends c {
12
+ constructor(e) {
13
+ super(401, e, "UNAUTHORIZED", `Not authorized: ${e}`), this.name = "AdminUnauthorizedError";
14
+ }
15
+ }, u = class extends c {
16
+ constructor(e, t, n) {
17
+ super(404, e, t, n ?? `Not found: ${e}`), this.name = "AdminNotFoundError";
18
+ }
19
+ }, d = class extends c {
20
+ issues;
21
+ constructor(e, t) {
22
+ super(400, e, "INVALID_INPUT", `Invalid input: ${e}`), this.issues = t, this.name = "AdminInvalidInputError";
23
+ }
24
+ }, f = class extends c {
25
+ orderStatus;
26
+ constructor(e, t, n) {
27
+ super(409, e, "INVALID_ORDER_STATE", n), this.orderStatus = t, this.name = "InvalidOrderStateError";
28
+ }
29
+ }, p = class extends c {
30
+ productIds;
31
+ constructor(e, t, n) {
32
+ super(409, e, "INSUFFICIENT_STOCK", n), this.productIds = t, this.name = "InsufficientStockError";
33
+ }
34
+ }, m = class extends c {
35
+ balanceCents;
36
+ constructor(e, t, n) {
37
+ super(400, e, "REFUND_EXCEEDS_BALANCE", n), this.balanceCents = t, this.name = "RefundExceedsBalanceError";
38
+ }
39
+ }, h = class extends c {
40
+ constructor(e, t) {
41
+ super(502, e, "REFUND_GATEWAY_ERROR", t), this.name = "RefundGatewayError";
42
+ }
43
+ }, g = class extends c {
44
+ constructor(e, t) {
45
+ super(502, e, "REFUND_OUTCOME_UNKNOWN", t), this.name = "RefundOutcomeUnknownError";
46
+ }
47
+ }, _ = class extends c {
48
+ constructor(e, t, n) {
49
+ super(400, e, t, n), this.name = "InvalidImageError";
50
+ }
51
+ }, v = class extends c {
52
+ constructor(e) {
53
+ super(413, e, "IMAGE_TOO_LARGE", `Image is too large: ${e}`), this.name = "ImageTooLargeError";
54
+ }
55
+ }, y = class extends c {
56
+ constructor(e, t) {
57
+ super(409, e, "DISCOUNT_CODE_TAKEN", t), this.name = "DiscountCodeTakenError";
58
+ }
59
+ }, b = class extends c {
60
+ constructor(e, t) {
61
+ super(409, e, "DISCOUNT_IN_USE", t), this.name = "DiscountInUseError";
62
+ }
63
+ }, x = class extends c {
64
+ constructor(e, t) {
65
+ super(400, e, "MAX_REDEMPTIONS_BELOW_USAGE", t), this.name = "MaxRedemptionsBelowUsageError";
66
+ }
67
+ }, S = class extends c {
68
+ missing;
69
+ constructor(e, t, n) {
70
+ super(422, e, "PRODUCT_INCOMPLETE", n), this.missing = t, this.name = "ProductIncompleteError";
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
+ }
77
+ };
78
+ // src/client/map-error.ts
79
+ async function w(e) {
80
+ try {
81
+ let t = await e.json();
82
+ return t && typeof t == "object" ? t : {};
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
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);
90
+ }
91
+ // src/client/transport.ts
92
+ function E({ baseUrl: e, fetch: t = (...e) => globalThis.fetch(...e), headers: n, onUnauthorized: r }) {
93
+ let i = e.replace(/\/+$/, "");
94
+ return { async request(e, a, { method: o = "GET", body: s, form: c, headers: u } = {}) {
95
+ let d = `${i}${e}`, f = new Headers(n ? await n() : void 0);
96
+ f.set("accept", "application/json"), s !== void 0 && f.set("content-type", "application/json");
97
+ for (let [e, t] of Object.entries(u ?? {})) f.set(e, t);
98
+ let p = await t(d, {
99
+ method: o,
100
+ headers: f,
101
+ body: c ?? (s === void 0 ? void 0 : JSON.stringify(s))
102
+ });
103
+ if (!p.ok) {
104
+ let e = await T(d, p);
105
+ if (e instanceof l) try {
106
+ r?.(e);
107
+ } catch {}
108
+ throw e;
109
+ }
110
+ return p.status === 204 ? a.parse(void 0) : a.parse(await p.json());
111
+ } };
112
+ }
113
+ // src/client/admin-client.ts
114
+ var D = encodeURIComponent, O = { parse: () => void 0 };
115
+ function k(e) {
116
+ let t = new URLSearchParams();
117
+ for (let [n, r] of Object.entries(e)) r !== void 0 && t.set(n, String(r));
118
+ let n = t.toString();
119
+ return n ? `?${n}` : "";
120
+ }
121
+ function A(c) {
122
+ let { request: l } = E(c), u = a, d = o, f = s.array();
123
+ return {
124
+ listProducts: (e = {}) => l(`/admin/products${k(e)}`, i),
125
+ getProductCounts: () => l("/admin/products/counts", r),
126
+ getProduct: (e) => l(`/admin/products/${D(e)}`, u),
127
+ createProduct: (e) => l("/admin/products", u, {
128
+ method: "POST",
129
+ body: e
130
+ }),
131
+ updateProduct: (e, t) => l(`/admin/products/${D(e)}`, u, {
132
+ method: "PATCH",
133
+ body: t
134
+ }),
135
+ deleteProduct: (e) => l(`/admin/products/${D(e)}`, O, { method: "DELETE" }),
136
+ uploadProductImage(e, t, n) {
137
+ let r = new FormData();
138
+ return n !== void 0 && r.append("alt", n), r.append("file", t), l(`/admin/products/${D(e)}/images`, u, {
139
+ method: "POST",
140
+ form: r
141
+ });
142
+ },
143
+ reorderProductImages: (e, t) => l(`/admin/products/${D(e)}/images/order`, u, {
144
+ method: "PATCH",
145
+ body: { imageIds: t }
146
+ }),
147
+ updateProductImage: (e, t, n) => l(`/admin/products/${D(e)}/images/${D(t)}`, u, {
148
+ method: "PATCH",
149
+ body: n
150
+ }),
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),
154
+ createDiscount: (e) => l("/admin/discounts", d, {
155
+ method: "POST",
156
+ body: e
157
+ }),
158
+ updateDiscount: (e, t) => l(`/admin/discounts/${D(e)}`, d, {
159
+ method: "PATCH",
160
+ body: t
161
+ }),
162
+ listShippingRates: () => l("/admin/shipping-rates", f),
163
+ replaceShippingRates: (e) => l("/admin/shipping-rates", f, {
164
+ method: "PUT",
165
+ body: e
166
+ }),
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, {
170
+ method: "POST",
171
+ body: n
172
+ }),
173
+ resolveOrder: (e) => l(`/admin/orders/${D(e)}/resolve`, t, { method: "POST" }),
174
+ async refundOrder(e, n = { restock: !1 }, r = {}) {
175
+ let i = r.idempotencyKey ?? crypto.randomUUID();
176
+ try {
177
+ return await l(`/admin/orders/${D(e)}/refunds`, t, {
178
+ method: "POST",
179
+ body: n,
180
+ headers: { "idempotency-key": i }
181
+ });
182
+ } catch (e) {
183
+ throw e instanceof Error && (e.idempotencyKey = i), e;
184
+ }
185
+ }
186
+ };
187
+ }
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 };
189
+
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":"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"}