@develemit/commerce-admin 0.4.0 → 0.7.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 (71) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +85 -9
  3. package/dist/client/admin-client.d.ts +3 -2
  4. package/dist/client/errors.d.ts +8 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/client/transport.d.ts +3 -1
  7. package/dist/client-DCL5DSHP.js +184 -0
  8. package/dist/client-DCL5DSHP.js.map +1 -0
  9. package/dist/components/admin-layout/admin-layout.d.ts +5 -6
  10. package/dist/components/admin-layout/nav-icons.d.ts +9 -0
  11. package/dist/components/admin-layout/nav-items.d.ts +36 -0
  12. package/dist/components/admin-layout/use-more-sheet.d.ts +5 -0
  13. package/dist/components/empty-state/empty-state.d.ts +10 -0
  14. package/dist/components/notice/notice.d.ts +5 -1
  15. package/dist/components/page-header/admin-page-header.d.ts +8 -0
  16. package/dist/components/setup-checklist/setup-checklist.d.ts +15 -0
  17. package/dist/components/switch/switch.d.ts +7 -0
  18. package/dist/index.d.ts +12 -0
  19. package/dist/index.js +3325 -1211
  20. package/dist/index.js.map +1 -1
  21. package/dist/lib/admin-labels.d.ts +2 -0
  22. package/dist/lib/discount-labels.d.ts +1 -0
  23. package/dist/lib/fill-alts.d.ts +3 -0
  24. package/dist/lib/order-labels.d.ts +1 -0
  25. package/dist/lib/product-draft.d.ts +11 -0
  26. package/dist/lib/product-flow-labels.d.ts +54 -0
  27. package/dist/lib/product-labels.d.ts +53 -0
  28. package/dist/lib/product-missing.d.ts +4 -0
  29. package/dist/lib/product-sheet-actions.d.ts +13 -0
  30. package/dist/lib/shipping-labels.d.ts +2 -0
  31. package/dist/lib/stock-labels.d.ts +41 -0
  32. package/dist/lib/use-batch-upload.d.ts +38 -0
  33. package/dist/lib/use-focus-trap.d.ts +2 -0
  34. package/dist/lib/use-image-upload.d.ts +3 -2
  35. package/dist/lib/use-order-pages.d.ts +2 -0
  36. package/dist/lib/use-paged-list.d.ts +15 -0
  37. package/dist/lib/use-product-autosave.d.ts +95 -0
  38. package/dist/lib/use-product-pages.d.ts +2 -2
  39. package/dist/styles.css +1300 -55
  40. package/dist/views/products/add-several/admin-add-several.d.ts +5 -0
  41. package/dist/views/products/add-several/photo-picker.d.ts +7 -0
  42. package/dist/views/products/admin-product-editor.d.ts +3 -1
  43. package/dist/views/products/admin-product-list.d.ts +13 -1
  44. package/dist/views/products/finish-drafts/admin-finish-drafts.d.ts +7 -0
  45. package/dist/views/products/finish-drafts/piece-card.d.ts +10 -0
  46. package/dist/views/products/finish-drafts/piece-fields.d.ts +7 -0
  47. package/dist/views/products/finish-drafts/piece-summary.d.ts +10 -0
  48. package/dist/views/products/finish-drafts/use-publish-pieces.d.ts +52 -0
  49. package/dist/views/products/product-card.d.ts +6 -0
  50. package/dist/views/products/product-flow/admin-product-flow.d.ts +15 -0
  51. package/dist/views/products/product-flow/basics-section.d.ts +6 -0
  52. package/dist/views/products/product-flow/flow-actions.d.ts +10 -0
  53. package/dist/views/products/product-flow/flow-frame.d.ts +28 -0
  54. package/dist/views/products/product-flow/flow-images.d.ts +7 -0
  55. package/dist/views/products/product-flow/flow-section-props.d.ts +5 -0
  56. package/dist/views/products/product-flow/flow-success.d.ts +8 -0
  57. package/dist/views/products/product-flow/flow-test-helpers.d.ts +12 -0
  58. package/dist/views/products/product-flow/photos-section.d.ts +9 -0
  59. package/dist/views/products/product-flow/preview-section.d.ts +10 -0
  60. package/dist/views/products/product-flow/story-section.d.ts +8 -0
  61. package/dist/views/products/product-flow/use-product-flow.d.ts +138 -0
  62. package/dist/views/products/product-sheet/admin-product-sheet.d.ts +8 -0
  63. package/dist/views/products/product-sheet/sheet-carousel.d.ts +4 -0
  64. package/dist/views/products/product-sheet/sheet-edit-link.d.ts +14 -0
  65. package/dist/views/products/product-sheet/use-product-change.d.ts +16 -0
  66. package/dist/views/products/product-toolbar.d.ts +10 -0
  67. package/dist/views/products/product-welcome.d.ts +8 -0
  68. package/dist/views/shipping/admin-shipping-rates.d.ts +6 -1
  69. package/package.json +11 -3
  70. package/dist/client--_j5r8ag.js +0 -168
  71. package/dist/client--_j5r8ag.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,87 @@
1
+ ## 0.7.0 (2026-11-04)
2
+
3
+ ### Features
4
+
5
+ - 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`.
6
+ - 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.").
7
+ - 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`).
8
+ - **Changed:** the products list shows newest first, and its "All" filter sends `excludeArchived` so the list matches the All count. `AdminProductQuery` gains `excludeArchived`.
9
+ - **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.
10
+ - 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.
11
+
12
+ - 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`.
13
+ - `AdminPageHeader` puts the page title and actions on one row; every kit screen uses it (the shipping page title is now an `h1`).
14
+ - 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`.
15
+ - Optional tokens `--sf-color-accent-soft`, `--sf-color-success(-soft)`, `--sf-color-warning(-soft)` and `--sf-shadow-raised`, all with fallbacks.
16
+ - 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.*`.
17
+ - `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.
18
+ - `Notice` accepts an optional `action: { label, onAction }`.
19
+ - `AdminClient.getProductCounts()` and `ProductIncompleteError` (422, `missing`).
20
+ - 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.
21
+
22
+ - `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.
23
+ - New export `AdminSetupChecklist` and `SetupStep`. New labels `products.welcome`, `products.card`, `products.draftsBanner`, `products.setup`, `products.openSearch` and `products.closeSearch`.
24
+ - The list now calls `getProductCounts()`, so a custom client must implement it.
25
+
26
+ ### Docs
27
+
28
+ - The package now ships `CHANGELOG.md` and declares `description`, `repository` and `homepage`. The README documents the client error classes, `errorKey`, `useAdminClient` and `OrderActions`.
29
+
30
+ ## 0.6.0 (2026-11-03)
31
+
32
+ ### Features
33
+
34
+ - `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.
35
+
36
+ ## 0.5.0 (2026-11-02)
37
+
38
+ ### Features
39
+
40
+ - `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).
41
+
42
+ ## 0.4.0 (2026-10-30)
43
+
44
+ ### Features
45
+
46
+ - `.sfa-badge--refund-pending` and `.sfa-badge--refund-succeeded` have their own colors, from `--sf-*` tokens with neutral fallbacks.
47
+ - 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`).
48
+ - `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.
49
+ - `AdminClient` gains `listDiscounts`, `getDiscount`, `createDiscount` and `updateDiscount`, plus `DiscountCodeTakenError` and `DiscountInUseError`. `AdminErrorKey` gains `discountCodeTaken` and `discountInUse`; `labels.statuses` gains `scheduled` and `inactive`.
50
+ - `hrefs` gains `discounts`, `discount(id)` and `newDiscount`; `labels` gains `discounts`.
51
+ - `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).
52
+ - `AdminLayout` does not add a Discounts link; pass it through `extraNav`.
53
+
54
+ ## 0.3.0 (2026-10-30)
55
+
56
+ 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`.
57
+
58
+ ### Changes for setups linked to pre-release builds
59
+
60
+ - `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`.
61
+
62
+ ### Fixes
63
+
64
+ - Published tarballs no longer include declaration maps (`*.d.ts.map`), which pointed at `src/` files that were not shipped. JS sourcemaps now embed `sourcesContent`.
65
+ - 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.
66
+ - 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.
67
+ - 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.
68
+
69
+ ### Features
70
+
71
+ - `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.
72
+ - `AdminProductEditor` accepts an optional `categories` prop; the Category input offers them as `<datalist>` suggestions and still takes free text.
73
+ - 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`.
74
+
75
+ - New package `@develemit/commerce-admin` with `AdminProvider` (link adapter, route hrefs, locale, API client slot, labels) and `useAdmin()`.
76
+
77
+ - Shared admin primitives (`AdminLayout`, `AdminTable`, `StatusBadge`, `Money`, `ConfirmDialog`, `Notice`, `ErrorMessage`) and the `useAdminQuery`/`useAdminMutation` hooks; `labels` gains `nav`, `statuses`, `errors`, `confirm`, `dismiss`, `navLabel`.
78
+
79
+ - `AdminProductList` and `AdminProductEditor` product screens, `parseMoneyInput`/`formatMoneyInput`, `labels.products`, `hrefs.storefrontProduct`, and a `sold` status label.
80
+
81
+ - `AdminOrderList` and `AdminOrderDetail` order screens (with an `actions` slot) and `labels.orders`.
82
+
83
+ - `OrderActions` (ship / re-ship, refund dialog with idempotent retry, resolve) is now the default `AdminOrderDetail` actions; `labels.orders.actions`.
84
+
85
+ - `AdminShippingRates` shipping-rate editor (add, edit, reorder, deactivate, remove; one `PUT` on save), `onDirtyChange`, and `labels.shipping`.
86
+
87
+ - `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
 
@@ -62,11 +64,36 @@ The client calls `${baseUrl}/admin/...`, matching the server's admin routes. `hr
62
64
  The server's `admin.authorize(req)` decides who may call the admin API. Never put an admin token in browser code. Two patterns:
63
65
 
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
- 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.
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.
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.
66
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,41 @@ 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. 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).
133
+ - `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
+ - `Switch({ id, label, checked, onChange })` — a native checkbox with `role="switch"`, styled by `.sfa-switch`.
102
135
  - Prices are typed in dollars and parsed to integer cents from the string (`parseMoneyInput`, `formatMoneyInput`).
103
136
  - Copy lives under `labels.products` and merges key by key.
104
137
 
138
+ ### Stocking a shop
139
+
140
+ ```tsx
141
+ <AdminAddSeveral onDone={(products) => setIds(products.map((p) => p.id))} />
142
+ <AdminFinishDrafts
143
+ productIds={ids} // omit to finish every incomplete draft
144
+ categories={['Mugs', 'Bowls']}
145
+ onDone={({ published, drafts }) => router.push('/admin/products')}
146
+ />
147
+ ```
148
+
149
+ - `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.
150
+ - `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.
151
+ - 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'`.
152
+ - 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`.
153
+
105
154
  ## Order screens
106
155
 
107
156
  ```tsx
@@ -115,6 +164,7 @@ Override any copy through `AdminProvider`'s `labels` (`nav`, `statuses` and `err
115
164
 
116
165
  - `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
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.
118
168
  - Rows link through `hrefs.order(id)` and `renderLink`. Copy lives under `labels.orders`.
119
169
 
120
170
  ## Discount screens
@@ -146,3 +196,29 @@ The default `renderImage` is a lazy, async `<img>` (`priority` makes it eager).
146
196
  ## Theming
147
197
 
148
198
  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.
199
+
200
+ ### Theme tokens
201
+
202
+ Optional tokens, each with a fallback derived from the tokens above, so existing hosts need nothing:
203
+
204
+ | Token | Fallback | Used for |
205
+ | ------------------------- | ------------------------------------------ | ---------------------------- |
206
+ | `--sf-color-accent-soft` | 12% `--sf-color-accent` on `--sf-color-bg` | Focus halo, hovered nav item |
207
+ | `--sf-color-success` | `#1b7a3d` | Success text and borders |
208
+ | `--sf-color-success-soft` | 14% success on `--sf-color-bg` | Success backgrounds |
209
+ | `--sf-color-warning` | `#b26a00` | Warning text and borders |
210
+ | `--sf-color-warning-soft` | 14% warning on `--sf-color-bg` | Warning backgrounds |
211
+ | `--sf-shadow-raised` | soft shadow from `--sf-color-fg` | More sheet, floating button |
212
+
213
+ `--sf-color-focus` (default: the accent) colors the focus ring. `--sf-font-display` styles page titles.
214
+
215
+ ## Layout
216
+
217
+ `AdminLayout` is a bottom tab bar below 900px and a sticky 240px left sidebar from 900px.
218
+
219
+ - `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
+ - `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.
221
+ - `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
+ - With more than five nav items the fifth tab becomes "More" (`labels.nav.more`, `labels.nav.closeMore`) and lists the rest.
223
+
224
+ `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,15 +1,16 @@
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>;
@@ -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;
@@ -45,3 +46,10 @@ export declare class DiscountCodeTakenError extends AdminApiError {
45
46
  export declare class DiscountInUseError extends AdminApiError {
46
47
  constructor(url: string, message?: string);
47
48
  }
49
+ export declare class MaxRedemptionsBelowUsageError extends AdminApiError {
50
+ constructor(url: string, message?: string);
51
+ }
52
+ export declare class ProductIncompleteError extends AdminApiError {
53
+ readonly missing: ProductIncompleteField[];
54
+ constructor(url: string, missing: ProductIncompleteField[], message?: string);
55
+ }
@@ -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 RefundExceedsBalanceError, p as RefundGatewayError, m as RefundOutcomeUnknownError, t as createAdminClient } from "../client--_j5r8ag.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 RefundExceedsBalanceError, h as RefundGatewayError, g as RefundOutcomeUnknownError, t as createAdminClient } from "../client-DCL5DSHP.js";
@@ -1,7 +1,9 @@
1
+ import { AdminUnauthorizedError } from './errors';
1
2
  export interface AdminClientOptions {
2
3
  baseUrl: string;
3
4
  fetch?: typeof fetch;
4
5
  headers?: () => HeadersInit | Promise<HeadersInit>;
6
+ onUnauthorized?: (error: AdminUnauthorizedError) => void;
5
7
  }
6
8
  type Schema<T> = {
7
9
  parse(data: unknown): T;
@@ -15,5 +17,5 @@ export interface RequestOptions {
15
17
  export interface Transport {
16
18
  request<T>(path: string, schema: Schema<T>, options?: RequestOptions): Promise<T>;
17
19
  }
18
- export declare function createTransport({ baseUrl, fetch: fetchImpl, headers: getHeaders, }: AdminClientOptions): Transport;
20
+ export declare function createTransport({ baseUrl, fetch: fetchImpl, headers: getHeaders, onUnauthorized, }: AdminClientOptions): Transport;
19
21
  export {};
@@ -0,0 +1,184 @@
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
+ };
73
+ // src/client/map-error.ts
74
+ async function C(e) {
75
+ try {
76
+ let t = await e.json();
77
+ return t && typeof t == "object" ? t : {};
78
+ } catch {
79
+ return {};
80
+ }
81
+ }
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);
85
+ }
86
+ // src/client/transport.ts
87
+ function T({ baseUrl: e, fetch: t = (...e) => globalThis.fetch(...e), headers: n, onUnauthorized: r }) {
88
+ let i = e.replace(/\/+$/, "");
89
+ return { async request(e, a, { method: o = "GET", body: s, form: c, headers: u } = {}) {
90
+ let d = `${i}${e}`, f = new Headers(n ? await n() : void 0);
91
+ f.set("accept", "application/json"), s !== void 0 && f.set("content-type", "application/json");
92
+ for (let [e, t] of Object.entries(u ?? {})) f.set(e, t);
93
+ let p = await t(d, {
94
+ method: o,
95
+ headers: f,
96
+ body: c ?? (s === void 0 ? void 0 : JSON.stringify(s))
97
+ });
98
+ if (!p.ok) {
99
+ let e = await w(d, p);
100
+ if (e instanceof l) try {
101
+ r?.(e);
102
+ } catch {}
103
+ throw e;
104
+ }
105
+ return a.parse(await p.json());
106
+ } };
107
+ }
108
+ // src/client/admin-client.ts
109
+ var E = encodeURIComponent;
110
+ function D(e) {
111
+ let t = new URLSearchParams();
112
+ for (let [n, r] of Object.entries(e)) r !== void 0 && t.set(n, String(r));
113
+ let n = t.toString();
114
+ return n ? `?${n}` : "";
115
+ }
116
+ function O(c) {
117
+ let { request: l } = T(c), u = a, d = o, f = s.array();
118
+ return {
119
+ listProducts: (e = {}) => l(`/admin/products${D(e)}`, i),
120
+ getProductCounts: () => l("/admin/products/counts", r),
121
+ getProduct: (e) => l(`/admin/products/${E(e)}`, u),
122
+ createProduct: (e) => l("/admin/products", u, {
123
+ method: "POST",
124
+ body: e
125
+ }),
126
+ updateProduct: (e, t) => l(`/admin/products/${E(e)}`, u, {
127
+ method: "PATCH",
128
+ body: t
129
+ }),
130
+ uploadProductImage(e, t, n) {
131
+ let r = new FormData();
132
+ return n !== void 0 && r.append("alt", n), r.append("file", t), l(`/admin/products/${E(e)}/images`, u, {
133
+ method: "POST",
134
+ form: r
135
+ });
136
+ },
137
+ reorderProductImages: (e, t) => l(`/admin/products/${E(e)}/images/order`, u, {
138
+ method: "PATCH",
139
+ body: { imageIds: t }
140
+ }),
141
+ updateProductImage: (e, t, n) => l(`/admin/products/${E(e)}/images/${E(t)}`, u, {
142
+ method: "PATCH",
143
+ body: n
144
+ }),
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),
148
+ createDiscount: (e) => l("/admin/discounts", d, {
149
+ method: "POST",
150
+ body: e
151
+ }),
152
+ updateDiscount: (e, t) => l(`/admin/discounts/${E(e)}`, d, {
153
+ method: "PATCH",
154
+ body: t
155
+ }),
156
+ listShippingRates: () => l("/admin/shipping-rates", f),
157
+ replaceShippingRates: (e) => l("/admin/shipping-rates", f, {
158
+ method: "PUT",
159
+ body: e
160
+ }),
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, {
164
+ method: "POST",
165
+ body: n
166
+ }),
167
+ resolveOrder: (e) => l(`/admin/orders/${E(e)}/resolve`, t, { method: "POST" }),
168
+ async refundOrder(e, n = { restock: !1 }, r = {}) {
169
+ let i = r.idempotencyKey ?? crypto.randomUUID();
170
+ try {
171
+ return await l(`/admin/orders/${E(e)}/refunds`, t, {
172
+ method: "POST",
173
+ body: n,
174
+ headers: { "idempotency-key": i }
175
+ });
176
+ } catch (e) {
177
+ throw e instanceof Error && (e.idempotencyKey = i), e;
178
+ }
179
+ }
180
+ };
181
+ }
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 };
183
+
184
+ //# sourceMappingURL=client-DCL5DSHP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-DCL5DSHP.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","readBody","body","res","mapError","createTransport","fetchImpl","args","getHeaders","root","baseUrl","path","merged","name","value","headers","form","error","schema","enc","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","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 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}\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 === '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 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 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;\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 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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"}
@@ -1,13 +1,12 @@
1
1
  import { ReactNode } from 'react';
2
- export interface ExtraNavItem {
3
- href: string;
4
- label: string;
5
- position?: 'start' | 'end';
6
- }
2
+ import { ExtraNavItem, NavBadges } from './nav-items';
3
+ export type { ExtraNavItem, NavBadges } from './nav-items';
4
+ export { adminNavIcons } from './nav-icons';
7
5
  export interface AdminLayoutProps {
8
6
  children: ReactNode;
9
7
  extraNav?: ExtraNavItem[];
8
+ navBadges?: NavBadges;
10
9
  header?: ReactNode;
11
10
  footer?: ReactNode;
12
11
  }
13
- export declare function AdminLayout({ children, extraNav, header, footer, }: AdminLayoutProps): import('react').JSX.Element;
12
+ export declare function AdminLayout({ children, extraNav, navBadges, header, footer, }: AdminLayoutProps): import('react').JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ export declare const adminNavIcons: {
3
+ products: ReactNode;
4
+ orders: ReactNode;
5
+ shipping: ReactNode;
6
+ discounts: ReactNode;
7
+ more: ReactNode;
8
+ dot: ReactNode;
9
+ };
@@ -0,0 +1,36 @@
1
+ import { ReactNode } from 'react';
2
+ export interface ExtraNavItem {
3
+ href: string;
4
+ label: string;
5
+ position?: 'start' | 'end';
6
+ icon?: ReactNode;
7
+ badge?: number | string;
8
+ }
9
+ export interface NavBadges {
10
+ products?: number | string;
11
+ orders?: number | string;
12
+ shippingRates?: number | string;
13
+ }
14
+ export interface NavItem {
15
+ href: string;
16
+ label: string;
17
+ icon: ReactNode;
18
+ badge?: number | string;
19
+ }
20
+ export declare const TAB_BAR_SLOTS = 5;
21
+ interface KitHrefs {
22
+ products: string;
23
+ orders: string;
24
+ shippingRates: string;
25
+ }
26
+ interface KitLabels {
27
+ products: string;
28
+ orders: string;
29
+ shippingRates: string;
30
+ }
31
+ export declare function buildNavItems(hrefs: KitHrefs, labels: KitLabels, extraNav: ExtraNavItem[], badges: NavBadges): NavItem[];
32
+ export declare function splitForTabBar(items: NavItem[]): {
33
+ bar: NavItem[];
34
+ overflow: NavItem[];
35
+ };
36
+ export {};
@@ -0,0 +1,5 @@
1
+ export declare function useMoreSheet(): {
2
+ open: boolean;
3
+ close: () => void;
4
+ toggle: () => void;
5
+ };
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ export type EmptyStateIcon = 'orders' | 'discounts' | 'shipping' | 'search';
3
+ export interface EmptyStateProps {
4
+ icon: EmptyStateIcon;
5
+ title: ReactNode;
6
+ body?: ReactNode;
7
+ action?: ReactNode;
8
+ children?: ReactNode;
9
+ }
10
+ export declare function EmptyState({ icon, title, body, action, children, }: EmptyStateProps): import('react').JSX.Element;