@86d-app/products 0.0.3 → 0.0.6

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 (195) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/AGENTS.md +41 -41
  3. package/README.md +267 -7
  4. package/dist/__tests__/controllers.test.d.ts +2 -0
  5. package/dist/__tests__/controllers.test.d.ts.map +1 -0
  6. package/dist/__tests__/endpoint-security.test.d.ts +2 -0
  7. package/dist/__tests__/endpoint-security.test.d.ts.map +1 -0
  8. package/dist/__tests__/service-impl.test.d.ts +2 -0
  9. package/dist/__tests__/service-impl.test.d.ts.map +1 -0
  10. package/dist/__tests__/state.test.d.ts +2 -0
  11. package/dist/__tests__/state.test.d.ts.map +1 -0
  12. package/dist/admin/components/categories-admin.d.ts +2 -0
  13. package/dist/admin/components/categories-admin.d.ts.map +1 -0
  14. package/dist/admin/components/category-form.d.ts +7 -0
  15. package/dist/admin/components/category-form.d.ts.map +1 -0
  16. package/dist/admin/components/category-list.d.ts +7 -0
  17. package/dist/admin/components/category-list.d.ts.map +1 -0
  18. package/dist/admin/components/collections-admin.d.ts +2 -0
  19. package/dist/admin/components/collections-admin.d.ts.map +1 -0
  20. package/dist/admin/components/index.d.ts +9 -0
  21. package/dist/admin/components/index.d.ts.map +1 -0
  22. package/dist/admin/components/product-detail.d.ts +7 -0
  23. package/dist/admin/components/product-detail.d.ts.map +1 -0
  24. package/dist/admin/components/product-edit.d.ts +6 -0
  25. package/dist/admin/components/product-edit.d.ts.map +1 -0
  26. package/dist/admin/components/product-form.d.ts +7 -0
  27. package/dist/admin/components/product-form.d.ts.map +1 -0
  28. package/dist/admin/components/product-list.d.ts +2 -0
  29. package/dist/admin/components/product-list.d.ts.map +1 -0
  30. package/dist/admin/components/product-new.d.ts +2 -0
  31. package/dist/admin/components/product-new.d.ts.map +1 -0
  32. package/dist/admin/endpoints/add-collection-product.d.ts +15 -0
  33. package/dist/admin/endpoints/add-collection-product.d.ts.map +1 -0
  34. package/dist/admin/endpoints/bulk-action.d.ts +17 -0
  35. package/dist/admin/endpoints/bulk-action.d.ts.map +1 -0
  36. package/dist/admin/endpoints/create-category.d.ts +23 -0
  37. package/dist/admin/endpoints/create-category.d.ts.map +1 -0
  38. package/dist/admin/endpoints/create-collection.d.ts +22 -0
  39. package/dist/admin/endpoints/create-collection.d.ts.map +1 -0
  40. package/dist/admin/endpoints/create-product.d.ts +44 -0
  41. package/dist/admin/endpoints/create-product.d.ts.map +1 -0
  42. package/dist/admin/endpoints/create-variant.d.ts +35 -0
  43. package/dist/admin/endpoints/create-variant.d.ts.map +1 -0
  44. package/dist/admin/endpoints/delete-category.d.ts +18 -0
  45. package/dist/admin/endpoints/delete-category.d.ts.map +1 -0
  46. package/dist/admin/endpoints/delete-collection.d.ts +8 -0
  47. package/dist/admin/endpoints/delete-collection.d.ts.map +1 -0
  48. package/dist/admin/endpoints/delete-product.d.ts +18 -0
  49. package/dist/admin/endpoints/delete-product.d.ts.map +1 -0
  50. package/dist/admin/endpoints/delete-variant.d.ts +18 -0
  51. package/dist/admin/endpoints/delete-variant.d.ts.map +1 -0
  52. package/dist/admin/endpoints/get-product.d.ts +16 -0
  53. package/dist/admin/endpoints/get-product.d.ts.map +1 -0
  54. package/dist/admin/endpoints/import-products.d.ts +36 -0
  55. package/dist/admin/endpoints/import-products.d.ts.map +1 -0
  56. package/dist/admin/endpoints/index.d.ts +418 -0
  57. package/dist/admin/endpoints/index.d.ts.map +1 -0
  58. package/dist/admin/endpoints/list-categories.d.ts +11 -0
  59. package/dist/admin/endpoints/list-categories.d.ts.map +1 -0
  60. package/dist/admin/endpoints/list-collections.d.ts +11 -0
  61. package/dist/admin/endpoints/list-collections.d.ts.map +1 -0
  62. package/dist/admin/endpoints/list-products.d.ts +27 -0
  63. package/dist/admin/endpoints/list-products.d.ts.map +1 -0
  64. package/dist/admin/endpoints/remove-collection-product.d.ts +9 -0
  65. package/dist/admin/endpoints/remove-collection-product.d.ts.map +1 -0
  66. package/dist/admin/endpoints/update-category.d.ts +26 -0
  67. package/dist/admin/endpoints/update-category.d.ts.map +1 -0
  68. package/dist/admin/endpoints/update-collection.d.ts +19 -0
  69. package/dist/admin/endpoints/update-collection.d.ts.map +1 -0
  70. package/dist/admin/endpoints/update-product.d.ts +47 -0
  71. package/dist/admin/endpoints/update-product.d.ts.map +1 -0
  72. package/dist/admin/endpoints/update-variant.d.ts +35 -0
  73. package/dist/admin/endpoints/update-variant.d.ts.map +1 -0
  74. package/dist/controllers.d.ts +130 -0
  75. package/dist/controllers.d.ts.map +1 -0
  76. package/dist/index.d.ts +20 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/markdown.d.ts +6 -0
  79. package/dist/markdown.d.ts.map +1 -0
  80. package/dist/schema.d.ts +351 -0
  81. package/dist/schema.d.ts.map +1 -0
  82. package/dist/service-impl.d.ts +4 -0
  83. package/dist/service-impl.d.ts.map +1 -0
  84. package/dist/service.d.ts +280 -0
  85. package/dist/service.d.ts.map +1 -0
  86. package/dist/state.d.ts +38 -0
  87. package/dist/state.d.ts.map +1 -0
  88. package/dist/store/components/_hooks.d.ts +88 -0
  89. package/dist/store/components/_hooks.d.ts.map +1 -0
  90. package/dist/store/components/_types.d.ts +70 -0
  91. package/dist/store/components/_types.d.ts.map +1 -0
  92. package/dist/store/components/_utils.d.ts +5 -0
  93. package/dist/store/components/_utils.d.ts.map +1 -0
  94. package/dist/store/components/back-in-stock-notify.d.ts +8 -0
  95. package/dist/store/components/back-in-stock-notify.d.ts.map +1 -0
  96. package/dist/store/components/collection-card.d.ts +6 -0
  97. package/dist/store/components/collection-card.d.ts.map +1 -0
  98. package/dist/store/components/collection-detail.d.ts +6 -0
  99. package/dist/store/components/collection-detail.d.ts.map +1 -0
  100. package/dist/store/components/collection-grid.d.ts +6 -0
  101. package/dist/store/components/collection-grid.d.ts.map +1 -0
  102. package/dist/store/components/featured-products.d.ts +6 -0
  103. package/dist/store/components/featured-products.d.ts.map +1 -0
  104. package/dist/store/components/filter-chip.d.ts +6 -0
  105. package/dist/store/components/filter-chip.d.ts.map +1 -0
  106. package/dist/store/components/index.d.ts +37 -0
  107. package/dist/store/components/index.d.ts.map +1 -0
  108. package/dist/store/components/product-card.d.ts +7 -0
  109. package/dist/store/components/product-card.d.ts.map +1 -0
  110. package/dist/store/components/product-detail.d.ts +6 -0
  111. package/dist/store/components/product-detail.d.ts.map +1 -0
  112. package/dist/store/components/product-listing.d.ts +7 -0
  113. package/dist/store/components/product-listing.d.ts.map +1 -0
  114. package/dist/store/components/product-qa-section.d.ts +5 -0
  115. package/dist/store/components/product-qa-section.d.ts.map +1 -0
  116. package/dist/store/components/product-reviews-section.d.ts +5 -0
  117. package/dist/store/components/product-reviews-section.d.ts.map +1 -0
  118. package/dist/store/components/recently-viewed.d.ts +12 -0
  119. package/dist/store/components/recently-viewed.d.ts.map +1 -0
  120. package/dist/store/components/recommended-products.d.ts +7 -0
  121. package/dist/store/components/recommended-products.d.ts.map +1 -0
  122. package/dist/store/components/related-products.d.ts +7 -0
  123. package/dist/store/components/related-products.d.ts.map +1 -0
  124. package/dist/store/components/star-display.d.ts +6 -0
  125. package/dist/store/components/star-display.d.ts.map +1 -0
  126. package/dist/store/components/star-picker.d.ts +6 -0
  127. package/dist/store/components/star-picker.d.ts.map +1 -0
  128. package/dist/store/components/stock-badge.d.ts +5 -0
  129. package/dist/store/components/stock-badge.d.ts.map +1 -0
  130. package/dist/store/endpoints/get-category.d.ts +22 -0
  131. package/dist/store/endpoints/get-category.d.ts.map +1 -0
  132. package/dist/store/endpoints/get-collection.d.ts +17 -0
  133. package/dist/store/endpoints/get-collection.d.ts.map +1 -0
  134. package/dist/store/endpoints/get-featured.d.ts +10 -0
  135. package/dist/store/endpoints/get-featured.d.ts.map +1 -0
  136. package/dist/store/endpoints/get-product.d.ts +17 -0
  137. package/dist/store/endpoints/get-product.d.ts.map +1 -0
  138. package/dist/store/endpoints/get-related.d.ts +11 -0
  139. package/dist/store/endpoints/get-related.d.ts.map +1 -0
  140. package/dist/store/endpoints/index.d.ts +130 -0
  141. package/dist/store/endpoints/index.d.ts.map +1 -0
  142. package/dist/store/endpoints/list-categories.d.ts +6 -0
  143. package/dist/store/endpoints/list-categories.d.ts.map +1 -0
  144. package/dist/store/endpoints/list-collections.d.ts +10 -0
  145. package/dist/store/endpoints/list-collections.d.ts.map +1 -0
  146. package/dist/store/endpoints/list-products.d.ts +26 -0
  147. package/dist/store/endpoints/list-products.d.ts.map +1 -0
  148. package/dist/store/endpoints/search-products.d.ts +12 -0
  149. package/dist/store/endpoints/search-products.d.ts.map +1 -0
  150. package/dist/store/endpoints/store-search.d.ts +18 -0
  151. package/dist/store/endpoints/store-search.d.ts.map +1 -0
  152. package/package.json +3 -3
  153. package/src/__tests__/endpoint-security.test.ts +457 -0
  154. package/src/__tests__/service-impl.test.ts +1745 -0
  155. package/src/admin/endpoints/create-category.ts +5 -2
  156. package/src/admin/endpoints/create-collection.ts +1 -1
  157. package/src/admin/endpoints/create-product.ts +5 -2
  158. package/src/admin/endpoints/delete-category.ts +1 -1
  159. package/src/admin/endpoints/delete-collection.ts +1 -1
  160. package/src/admin/endpoints/delete-product.ts +1 -1
  161. package/src/admin/endpoints/delete-variant.ts +1 -1
  162. package/src/admin/endpoints/list-categories.ts +1 -1
  163. package/src/admin/endpoints/list-collections.ts +1 -1
  164. package/src/admin/endpoints/list-products.ts +1 -1
  165. package/src/admin/endpoints/remove-collection-product.ts +1 -1
  166. package/src/admin/endpoints/update-category.ts +5 -2
  167. package/src/admin/endpoints/update-collection.ts +1 -1
  168. package/src/admin/endpoints/update-product.ts +5 -2
  169. package/src/admin/endpoints/update-variant.ts +1 -1
  170. package/src/service-impl.ts +1139 -0
  171. package/src/service.ts +312 -0
  172. package/src/store/components/_hooks.ts +81 -0
  173. package/src/store/components/_utils.ts +8 -0
  174. package/src/store/components/collection-detail.tsx +21 -1
  175. package/src/store/components/collection-grid.tsx +5 -1
  176. package/src/store/components/featured-products.tsx +5 -1
  177. package/src/store/components/index.tsx +2 -0
  178. package/src/store/components/product-card.mdx +1 -1
  179. package/src/store/components/product-card.tsx +25 -5
  180. package/src/store/components/product-detail.mdx +2 -0
  181. package/src/store/components/product-detail.tsx +55 -8
  182. package/src/store/components/product-listing.tsx +25 -4
  183. package/src/store/components/product-qa-section.mdx +21 -0
  184. package/src/store/components/product-qa-section.tsx +503 -0
  185. package/src/store/components/recommended-products.mdx +6 -0
  186. package/src/store/components/recommended-products.tsx +119 -0
  187. package/src/store/endpoints/get-category.ts +2 -2
  188. package/src/store/endpoints/get-collection.ts +1 -1
  189. package/src/store/endpoints/get-featured.ts +1 -1
  190. package/src/store/endpoints/get-product.ts +1 -1
  191. package/src/store/endpoints/get-related.ts +2 -2
  192. package/src/store/endpoints/list-collections.ts +3 -3
  193. package/src/store/endpoints/list-products.ts +9 -9
  194. package/src/store/endpoints/store-search.ts +1 -1
  195. package/COMPONENTS.md +0 -231
@@ -0,0 +1 @@
1
+ $ tsc
package/AGENTS.md CHANGED
@@ -1,42 +1,28 @@
1
1
  # Products Module
2
2
 
3
- Product catalog with variants and hierarchical categories. Full CRUD for admin, read-only browsing and search for storefront.
3
+ Product catalog with variants, hierarchical categories, and collections. Full CRUD for admin, read-only browsing and search for storefront. Includes CSV import, bulk operations, and inventory management.
4
4
 
5
5
  ## Structure
6
6
 
7
7
  ```
8
8
  src/
9
- index.ts Factory: products(options?) => Module + admin nav registration
10
- schema.ts Zod models: product, productVariant, category
11
- controllers.ts All business logic (product, variant, category controllers)
12
- components/
13
- store/ Customer-facing MDX components
14
- index.tsx ProductCard, ProductGrid, CategoryList, CategoryItem (.tsx logic)
15
- *.mdx Store template variants
16
- admin/ Store admin MDX components
17
- index.tsx Product table, product editor, category manager (.tsx logic)
18
- *.mdx Admin template variants
19
- endpoints/
20
- store/ Customer-facing (6 endpoints)
21
- list-products.ts GET /products
22
- get-product.ts GET /products/:id (by ID or slug)
23
- get-featured.ts GET /products/featured
24
- search-products.ts GET /products/search?q=
25
- list-categories.ts GET /categories
26
- get-category.ts GET /categories/:id
27
- admin/ Protected (12 endpoints)
28
- create-product.ts POST /admin/products
29
- list-products.ts GET /admin/products/list
30
- get-product.ts GET /admin/products/:id
31
- update-product.ts PUT /admin/products/:id
32
- delete-product.ts DELETE /admin/products/:id
33
- create-variant.ts POST /admin/products/:productId/variants
34
- update-variant.ts PUT /admin/variants/:id
35
- delete-variant.ts DELETE /admin/variants/:id
36
- create-category.ts POST /admin/categories
37
- list-categories.ts GET /admin/categories/list
38
- update-category.ts PUT /admin/categories/:id
39
- delete-category.ts DELETE /admin/categories/:id
9
+ index.ts Factory: products(options?) => Module + admin nav
10
+ schema.ts Zod models: product, productVariant, category, collection, collectionProduct
11
+ controllers.ts Raw module controllers (ctx pattern for endpoint system)
12
+ service.ts TypeScript interface (ProductController)
13
+ service-impl.ts Clean typed implementation (createProductController)
14
+ state.ts MobX UI state (filters, sort, view mode)
15
+ store/
16
+ endpoints/ Customer-facing (9 endpoints)
17
+ components/ Store MDX components
18
+ admin/
19
+ endpoints/ Protected (23 endpoints)
20
+ components/ Admin UI components
21
+ __tests__/
22
+ controllers.test.ts Raw controller tests (135 tests)
23
+ service-impl.test.ts Service layer tests (134 tests)
24
+ endpoint-security.test.ts Data integrity invariants
25
+ state.test.ts MobX state tests
40
26
  ```
41
27
 
42
28
  ## Options
@@ -51,15 +37,29 @@ ProductsOptions {
51
37
 
52
38
  ## Data models
53
39
 
54
- - **product**: id, name, slug (unique), price, compareAtPrice, costPrice, sku?, inventory, status (draft|active|archived), categoryId?, images[], tags[], isFeatured, weight/weightUnit
55
- - **productVariant**: id, productId (FK cascade), name, sku?, price, inventory, options (Record<string,string> for size/color/etc.), position
56
- - **category**: id, name, slug (unique), parentId? (self-referential for hierarchy), position, isVisible
40
+ - **product**: id, name, slug (unique), price, compareAtPrice, costPrice, sku?, inventory, trackInventory, allowBackorder, status (draft|active|archived), categoryId?, images[], tags[], metadata, weight/weightUnit, isFeatured
41
+ - **productVariant**: id, productId (FK cascade), name, sku?, price, inventory, options (Record<string,string>), images[], position
42
+ - **category**: id, name, slug (unique), parentId? (self-referential), position, isVisible, metadata
43
+ - **collection**: id, name, slug (unique), isFeatured, isVisible, position, metadata
44
+ - **collectionProduct**: id, collectionId (FK cascade), productId (FK cascade), position
57
45
 
58
- ## Patterns
46
+ ## Key patterns
59
47
 
60
- - Uses core data layer directly (no adapter) `data.get()`, `data.findMany()`, `data.upsert()`, `data.delete()`
61
- - Product IDs: `prod_${timestamp}`, variant IDs: `var_${timestamp}`
48
+ - Two controller layers: `controllers.ts` (raw ctx pattern for endpoints) and `service-impl.ts` (clean typed API with `createProductController(data)`)
49
+ - Service-impl uses `crypto.randomUUID()` for IDs; raw controllers use `Date.now()`
62
50
  - Store endpoints only return active products; admin endpoints return all statuses
63
- - Variant writes also update parent product's `updatedAt`
64
- - Category deletion orphans children and products (sets their categoryId to null)
65
- - `getTree()` builds hierarchical category structure from flat list
51
+ - Variant writes update parent product's `updatedAt`
52
+ - Category deletion orphans children and products (sets categoryId/parentId to undefined)
53
+ - Collection `getWithProducts` returns only active products; `listCollectionProducts` returns all
54
+ - `addProductToCollection` prevents duplicates (returns existing link)
55
+ - Import resolves categories by name (case-insensitive), deduplicates slugs, updates existing products by SKU
56
+ - Inventory decrement has NO floor — can go negative (documented behavior)
57
+ - Untracked products (`trackInventory: false`) skip inventory operations
58
+ - Related products scored: same category (+10), shared tags (+1 each)
59
+
60
+ ## Gotchas
61
+
62
+ - `exactOptionalPropertyTypes` is on — use `undefined` carefully for optional fields
63
+ - Price fields in import are multiplied by 100 (dollars to cents)
64
+ - Category tree only includes visible categories
65
+ - Search is case-insensitive across name, description, and tags
package/README.md CHANGED
@@ -9,16 +9,15 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://vercel.com/changelog"><strong>npm</strong></a> ·
13
12
  <a href="https://x.com/86d_app"><strong>X</strong></a> ·
14
- <a href="https://vercel.com/templates"><strong>LinkedIn</strong></a>
13
+ <a href="https://www.linkedin.com/company/86d"><strong>LinkedIn</strong></a>
15
14
  </p>
16
15
  <br/>
17
16
 
18
17
  > [!WARNING]
19
18
  > This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.
20
19
 
21
- # @86d-app/products
20
+ # Products Module
22
21
 
23
22
  Product catalog module with variants and hierarchical categories. Full CRUD for the admin panel and read-only browsing with search and filtering for the storefront.
24
23
 
@@ -55,9 +54,13 @@ const module = products({
55
54
  | `GET` | `/products` | List active products (paginated, filterable) |
56
55
  | `GET` | `/products/featured` | Get featured products |
57
56
  | `GET` | `/products/:slug` | Get a single product by slug (includes variants) |
58
- | `GET` | `/products/search?q=` | Search products by name, description, or SKU |
57
+ | `GET` | `/products/search?q=` | Search products by name, description, or tags |
58
+ | `GET` | `/products/store-search` | Full-text product search |
59
+ | `GET` | `/products/related/:id` | Get related products by category/tag scoring |
59
60
  | `GET` | `/categories` | List visible categories |
60
61
  | `GET` | `/categories/:slug` | Get a single category by slug |
62
+ | `GET` | `/collections` | List visible collections |
63
+ | `GET` | `/collections/:slug` | Get a collection with its active products |
61
64
 
62
65
  Query parameters for `GET /products`:
63
66
 
@@ -85,10 +88,32 @@ Query parameters for `GET /products`:
85
88
  | `GET` | `/admin/categories/list` | List all categories |
86
89
  | `PUT` | `/admin/categories/:id` | Update a category |
87
90
  | `DELETE` | `/admin/categories/:id` | Delete a category |
91
+ | `POST` | `/admin/collections` | Create a collection |
92
+ | `GET` | `/admin/collections/list` | List all collections |
93
+ | `PUT` | `/admin/collections/:id` | Update a collection |
94
+ | `DELETE` | `/admin/collections/:id` | Delete a collection |
95
+ | `POST` | `/admin/collections/:id/products` | Add product to collection |
96
+ | `DELETE` | `/admin/collections/:id/products/:productId` | Remove product from collection |
97
+ | `POST` | `/admin/products/bulk-action` | Bulk update status or delete |
98
+ | `POST` | `/admin/products/import` | Import products from CSV data |
99
+
100
+ ## Service API
101
+
102
+ A typed service layer is available via `createProductController(data)` from `service-impl.ts`:
103
+
104
+ ```ts
105
+ import { createProductController } from "@86d-app/products/service-impl";
106
+
107
+ const ctrl = createProductController(dataService);
108
+ const product = await ctrl.createProduct({ name: "Widget", slug: "widget", price: 2999 });
109
+ const variants = await ctrl.getVariantsByProduct(product.id);
110
+ await ctrl.addProductToCollection(collectionId, product.id);
111
+ const result = await ctrl.importProducts([{ name: "Gadget", price: 19.99 }]);
112
+ ```
88
113
 
89
114
  ## Controller API
90
115
 
91
- Controllers are accessed via the runtime context. Three sub-controllers are available: `product`, `variant`, and `category`.
116
+ Controllers are accessed via the runtime context. Five sub-controllers are available: `product`, `variant`, `category`, `bulk`, `collection`, and `import`.
92
117
 
93
118
  ```ts
94
119
  // product controller
@@ -193,9 +218,244 @@ interface ProductWithVariants extends Product {
193
218
  }
194
219
  ```
195
220
 
221
+ ## Store Components
222
+
223
+ ### ProductCard
224
+
225
+ Displays a single product card with image, name, price, discount badge, and optional "Add to Cart" button.
226
+
227
+ #### Props
228
+
229
+ | Prop | Type | Default | Description |
230
+ |------|------|---------|-------------|
231
+ | `product` | `Product` | — | Product object with id, name, slug, price, images, etc. |
232
+ | `showAddToCart` | `boolean` | `true` | Show the "Add to Cart" button |
233
+
234
+ #### Usage in MDX
235
+
236
+ ```mdx
237
+ <ProductCard product={product} />
238
+
239
+ <ProductCard product={product} showAddToCart={false} />
240
+ ```
241
+
242
+ ### FeaturedProducts
243
+
244
+ Displays a responsive grid of featured products. Fetches its own data.
245
+
246
+ #### Props
247
+
248
+ | Prop | Type | Default | Description |
249
+ |------|------|---------|-------------|
250
+ | `limit` | `number` | — | Max number of featured products to display |
251
+ | `title` | `string` | — | Section heading |
252
+
253
+ #### Usage in MDX
254
+
255
+ ```mdx
256
+ <FeaturedProducts />
257
+
258
+ <FeaturedProducts limit={4} title="Staff Picks" />
259
+ ```
260
+
261
+ ### ProductListing
262
+
263
+ Full product listing with search, category/price/stock/tag filters, sorting, and pagination. Fetches its own data.
264
+
265
+ #### Props
266
+
267
+ | Prop | Type | Default | Description |
268
+ |------|------|---------|-------------|
269
+ | `initialCategory` | `string` | — | Pre-select a category filter |
270
+ | `initialSearch` | `string` | — | Pre-fill the search query |
271
+ | `pageSize` | `number` | — | Products per page |
272
+
273
+ #### Usage in MDX
274
+
275
+ ```mdx
276
+ <ProductListing />
277
+
278
+ <ProductListing initialCategory="shoes" pageSize={12} />
279
+ ```
280
+
281
+ ### ProductDetail
282
+
283
+ Full product detail page with image gallery, variant selector, pricing, inventory status, reviews, and related products. Fetches its own data.
284
+
285
+ #### Props
286
+
287
+ | Prop | Type | Description |
288
+ |------|------|-------------|
289
+ | `slug` | `string` | Product slug (from URL) |
290
+ | `params` | `Record<string, string>` | Route params (params.slug) |
291
+
292
+ #### Usage
293
+
294
+ Loaded dynamically by the store catch-all route for `/products/:slug`.
295
+
296
+ ### RelatedProducts
297
+
298
+ Horizontal grid of related products for a given product. Fetches its own data.
299
+
300
+ #### Props
301
+
302
+ | Prop | Type | Default | Description |
303
+ |------|------|---------|-------------|
304
+ | `productId` | `string` | — | Product ID to find related products for |
305
+ | `limit` | `number` | — | Max related products to show |
306
+ | `title` | `string` | — | Section heading |
307
+
308
+ #### Usage in MDX
309
+
310
+ ```mdx
311
+ <RelatedProducts productId={product.id} />
312
+
313
+ <RelatedProducts productId={product.id} limit={4} title="You may also like" />
314
+ ```
315
+
316
+ ### CollectionCard
317
+
318
+ Displays a single collection card with image, name, and description.
319
+
320
+ #### Props
321
+
322
+ | Prop | Type | Description |
323
+ |------|------|-------------|
324
+ | `collection` | `CollectionCardData` | Collection object with id, name, slug, description, image |
325
+
326
+ #### Usage in MDX
327
+
328
+ ```mdx
329
+ <CollectionCard collection={collection} />
330
+ ```
331
+
332
+ ### CollectionGrid
333
+
334
+ Grid of collections with optional featured-only filtering. Fetches its own data.
335
+
336
+ #### Props
337
+
338
+ | Prop | Type | Default | Description |
339
+ |------|------|---------|-------------|
340
+ | `title` | `string` | — | Section heading |
341
+ | `featured` | `boolean` | — | Only show featured collections |
342
+
343
+ #### Usage in MDX
344
+
345
+ ```mdx
346
+ <CollectionGrid />
347
+
348
+ <CollectionGrid title="Shop by Category" featured={true} />
349
+ ```
350
+
351
+ ### CollectionDetail
352
+
353
+ Full collection page with image, description, product count, and products grid. Fetches its own data.
354
+
355
+ #### Props
356
+
357
+ | Prop | Type | Description |
358
+ |------|------|-------------|
359
+ | `slug` | `string` | Collection slug (from URL) |
360
+ | `params` | `Record<string, string>` | Route params (params.slug) |
361
+
362
+ #### Usage
363
+
364
+ Loaded dynamically by the store catch-all route for `/collections/:slug`.
365
+
366
+ ### FilterChip
367
+
368
+ Small removable tag displaying an active filter. Used internally by ProductListing.
369
+
370
+ #### Props
371
+
372
+ | Prop | Type | Description |
373
+ |------|------|-------------|
374
+ | `label` | `string` | Filter display text |
375
+ | `onRemove` | `() => void` | Callback when the chip is dismissed |
376
+
377
+ #### Usage in MDX
378
+
379
+ ```mdx
380
+ <FilterChip label="Shoes" onRemove={handleRemove} />
381
+ ```
382
+
383
+ ### StarDisplay
384
+
385
+ Read-only star rating display.
386
+
387
+ #### Props
388
+
389
+ | Prop | Type | Default | Description |
390
+ |------|------|---------|-------------|
391
+ | `rating` | `number` | — | Rating value (0–5) |
392
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Star size |
393
+
394
+ #### Usage in MDX
395
+
396
+ ```mdx
397
+ <StarDisplay rating={4.5} />
398
+
399
+ <StarDisplay rating={product.averageRating} size="sm" />
400
+ ```
401
+
402
+ ### StarPicker
403
+
404
+ Interactive star rating input for review submission.
405
+
406
+ #### Props
407
+
408
+ | Prop | Type | Description |
409
+ |------|------|-------------|
410
+ | `value` | `number` | Current rating value |
411
+ | `onChange` | `(n: number) => void` | Callback when user selects a rating |
412
+
413
+ #### Usage in MDX
414
+
415
+ ```mdx
416
+ <StarPicker value={rating} onChange={setRating} />
417
+ ```
418
+
419
+ ### StockBadge
420
+
421
+ Inventory status badge. Shows "Out of stock", "Only X left", or "In stock".
422
+
423
+ #### Props
424
+
425
+ | Prop | Type | Description |
426
+ |------|------|-------------|
427
+ | `inventory` | `number` | Available inventory count |
428
+
429
+ #### Usage in MDX
430
+
431
+ ```mdx
432
+ <StockBadge inventory={product.inventory} />
433
+ ```
434
+
435
+ ### ProductReviewsSection
436
+
437
+ Complete review section with rating summary, review list with pagination, and review submission form. Fetches its own data.
438
+
439
+ #### Props
440
+
441
+ | Prop | Type | Description |
442
+ |------|------|-------------|
443
+ | `productId` | `string` | Product ID to show reviews for |
444
+
445
+ #### Usage in MDX
446
+
447
+ ```mdx
448
+ <ProductReviewsSection productId={product.id} />
449
+ ```
450
+
196
451
  ## Notes
197
452
 
198
453
  - Store endpoints return only `active` products; admin endpoints return all statuses (`draft`, `active`, `archived`).
199
- - Product IDs are prefixed: `prod_${timestamp}`. Variant IDs: `var_${timestamp}`.
200
- - Deleting a category orphans its child categories and products (sets `categoryId` to `null`) rather than cascading.
454
+ - Product IDs are prefixed: `prod_` (UUID in service-impl, timestamp in raw controllers). Variant: `var_`, Category: `cat_`, Collection: `col_`.
455
+ - Deleting a category orphans its child categories and products (sets `categoryId`/`parentId` to `undefined`) rather than cascading.
456
+ - Deleting a product cascades to all its variants. Deleting a collection cascades to collection-product links.
201
457
  - `getTree()` builds a hierarchical category tree from the flat list using `parentId` references.
458
+ - `addProductToCollection` prevents duplicates — returns existing link if product is already in the collection.
459
+ - Import converts dollar prices to cents (`price * 100`), resolves categories by name (case-insensitive), and deduplicates slugs.
460
+ - Inventory decrement has no floor — inventory can go negative (documented behavior).
461
+ - Products with `trackInventory: false` skip all inventory decrement/increment operations.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=controllers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllers.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/controllers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=endpoint-security.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-security.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/endpoint-security.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-impl.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-impl.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/service-impl.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/state.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare function CategoriesAdmin(): import("react").JSX.Element;
2
+ //# sourceMappingURL=categories-admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"categories-admin.d.ts","sourceRoot":"","sources":["../../../src/admin/components/categories-admin.tsx"],"names":[],"mappings":"AAsDA,wBAAgB,eAAe,gCA0Y9B"}
@@ -0,0 +1,7 @@
1
+ interface CategoryFormProps {
2
+ categoryId?: string;
3
+ onSuccess?: () => void;
4
+ }
5
+ export declare function CategoryForm({ categoryId, onSuccess }: CategoryFormProps): import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=category-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category-form.d.ts","sourceRoot":"","sources":["../../../src/admin/components/category-form.tsx"],"names":[],"mappings":"AA6BA,UAAU,iBAAiB;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAkDD,wBAAgB,YAAY,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,iBAAiB,+BAuRxE"}
@@ -0,0 +1,7 @@
1
+ interface CategoryListProps {
2
+ onCreateNew?: () => void;
3
+ onEdit?: (categoryId: string) => void;
4
+ }
5
+ export declare function CategoryList({ onCreateNew, onEdit }: CategoryListProps): import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=category-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category-list.d.ts","sourceRoot":"","sources":["../../../src/admin/components/category-list.tsx"],"names":[],"mappings":"AAyCA,UAAU,iBAAiB;IAC1B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,iBAAiB,+BAyHtE"}
@@ -0,0 +1,2 @@
1
+ export declare function CollectionsAdmin(): import("react").JSX.Element;
2
+ //# sourceMappingURL=collections-admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections-admin.d.ts","sourceRoot":"","sources":["../../../src/admin/components/collections-admin.tsx"],"names":[],"mappings":"AAkFA,wBAAgB,gBAAgB,gCAgjB/B"}
@@ -0,0 +1,9 @@
1
+ export { CategoriesAdmin } from "./categories-admin";
2
+ export { CategoryForm } from "./category-form";
3
+ export { CategoryList } from "./category-list";
4
+ export { CollectionsAdmin } from "./collections-admin";
5
+ export { ProductDetail } from "./product-detail";
6
+ export { ProductEdit } from "./product-edit";
7
+ export { ProductList } from "./product-list";
8
+ export { ProductNew } from "./product-new";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/admin/components/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface ProductDetailProps {
2
+ productId?: string;
3
+ params?: Record<string, string>;
4
+ }
5
+ export declare function ProductDetail(props: ProductDetailProps): import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=product-detail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-detail.d.ts","sourceRoot":"","sources":["../../../src/admin/components/product-detail.tsx"],"names":[],"mappings":"AA2RA,UAAU,kBAAkB;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BAqftD"}
@@ -0,0 +1,6 @@
1
+ interface ProductEditProps {
2
+ params?: Record<string, string>;
3
+ }
4
+ export declare function ProductEdit({ params }: ProductEditProps): import("react").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=product-edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-edit.d.ts","sourceRoot":"","sources":["../../../src/admin/components/product-edit.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,gBAAgB,+BAmDvD"}
@@ -0,0 +1,7 @@
1
+ interface ProductFormProps {
2
+ productId?: string;
3
+ onNavigate: (path: string) => void;
4
+ }
5
+ export declare function ProductForm({ productId, onNavigate }: ProductFormProps): import("react").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=product-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-form.d.ts","sourceRoot":"","sources":["../../../src/admin/components/product-form.tsx"],"names":[],"mappings":"AAgDA,UAAU,gBAAgB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAuPD,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,gBAAgB,+BA8etE"}
@@ -0,0 +1,2 @@
1
+ export declare function ProductList(): import("react").JSX.Element;
2
+ //# sourceMappingURL=product-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-list.d.ts","sourceRoot":"","sources":["../../../src/admin/components/product-list.tsx"],"names":[],"mappings":"AAkjBA,wBAAgB,WAAW,gCAkjB1B"}
@@ -0,0 +1,2 @@
1
+ export declare function ProductNew(): import("react").JSX.Element;
2
+ //# sourceMappingURL=product-new.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-new.d.ts","sourceRoot":"","sources":["../../../src/admin/components/product-new.tsx"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,gCAiCzB"}
@@ -0,0 +1,15 @@
1
+ import { z } from "@86d-app/core";
2
+ export declare const addCollectionProduct: import("better-call").StrictEndpoint<"/admin/collections/:id/products", {
3
+ method: "POST";
4
+ params: z.ZodObject<{
5
+ id: z.ZodString;
6
+ }, z.core.$strip>;
7
+ body: z.ZodObject<{
8
+ productId: z.ZodString;
9
+ position: z.ZodOptional<z.ZodNumber>;
10
+ }, z.core.$strip>;
11
+ }, {
12
+ link: unknown;
13
+ status: number;
14
+ }>;
15
+ //# sourceMappingURL=add-collection-product.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-collection-product.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/add-collection-product.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,CAAC,EAAE,MAAM,eAAe,CAAC;AAEvD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAchC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { z } from "@86d-app/core";
2
+ export declare const bulkAction: import("better-call").StrictEndpoint<"/admin/products/bulk", {
3
+ method: "POST";
4
+ body: z.ZodObject<{
5
+ action: z.ZodEnum<{
6
+ delete: "delete";
7
+ updateStatus: "updateStatus";
8
+ }>;
9
+ ids: z.ZodArray<z.ZodString>;
10
+ status: z.ZodOptional<z.ZodEnum<{
11
+ draft: "draft";
12
+ active: "active";
13
+ archived: "archived";
14
+ }>>;
15
+ }, z.core.$strip>;
16
+ }, unknown>;
17
+ //# sourceMappingURL=bulk-action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-action.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/bulk-action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,CAAC,EAAE,MAAM,eAAe,CAAC;AAEvD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;WAwCtB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { z } from "@86d-app/core";
2
+ export declare const createCategory: import("better-call").StrictEndpoint<"/admin/categories/create", {
3
+ method: "POST";
4
+ body: z.ZodObject<{
5
+ name: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
6
+ slug: z.ZodString;
7
+ description: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
8
+ parentId: z.ZodOptional<z.ZodString>;
9
+ image: z.ZodOptional<z.ZodString>;
10
+ position: z.ZodOptional<z.ZodNumber>;
11
+ isVisible: z.ZodOptional<z.ZodBoolean>;
12
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
13
+ }, z.core.$strip>;
14
+ }, {
15
+ error: string;
16
+ status: number;
17
+ category?: never;
18
+ } | {
19
+ category: unknown;
20
+ status: number;
21
+ error?: never;
22
+ }>;
23
+ //# sourceMappingURL=create-category.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-category.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/create-category.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqC,CAAC,EAAE,MAAM,eAAe,CAAC;AAErE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;EAoD1B,CAAC"}