@csbeker/medusa-product-attributes 2.2.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 (237) hide show
  1. package/.medusa/server/src/admin/index.js +9012 -0
  2. package/.medusa/server/src/admin/index.mjs +9010 -0
  3. package/.medusa/server/src/api/admin/middlewares.js +10 -0
  4. package/.medusa/server/src/api/admin/plugin/attribute-set/[id]/attributes/route.js +17 -0
  5. package/.medusa/server/src/api/admin/plugin/attribute-set/[id]/route.js +33 -0
  6. package/.medusa/server/src/api/admin/plugin/attribute-set/middlewares.js +44 -0
  7. package/.medusa/server/src/api/admin/plugin/attribute-set/query-config.js +22 -0
  8. package/.medusa/server/src/api/admin/plugin/attribute-set/route.js +25 -0
  9. package/.medusa/server/src/api/admin/plugin/attribute-set/validators.js +37 -0
  10. package/.medusa/server/src/api/admin/plugin/attributes/[id]/route.js +68 -0
  11. package/.medusa/server/src/api/admin/plugin/attributes/[id]/values/[valueId]/route.js +37 -0
  12. package/.medusa/server/src/api/admin/plugin/attributes/[id]/values/route.js +31 -0
  13. package/.medusa/server/src/api/admin/plugin/attributes/middlewares.js +103 -0
  14. package/.medusa/server/src/api/admin/plugin/attributes/query-config.js +41 -0
  15. package/.medusa/server/src/api/admin/plugin/attributes/route.js +28 -0
  16. package/.medusa/server/src/api/admin/plugin/attributes/validators.js +69 -0
  17. package/.medusa/server/src/api/admin/plugin/route.js +7 -0
  18. package/.medusa/server/src/api/middlewares.js +12 -0
  19. package/.medusa/server/src/api/store/middlewares.js +8 -0
  20. package/.medusa/server/src/api/store/plugin/attributes/middlewares.js +64 -0
  21. package/.medusa/server/src/api/store/plugin/attributes/products/middlewares.js +100 -0
  22. package/.medusa/server/src/api/store/plugin/attributes/products/query-config.js +20 -0
  23. package/.medusa/server/src/api/store/plugin/attributes/products/route.js +48 -0
  24. package/.medusa/server/src/api/store/plugin/attributes/products/validators.js +39 -0
  25. package/.medusa/server/src/api/store/plugin/attributes/query-config.js +21 -0
  26. package/.medusa/server/src/api/store/plugin/attributes/route.js +15 -0
  27. package/.medusa/server/src/api/store/plugin/attributes/validators.js +14 -0
  28. package/.medusa/server/src/api/store/plugin/route.js +7 -0
  29. package/.medusa/server/src/api/utils/common-validators.js +23 -0
  30. package/.medusa/server/src/api/utils/constants.js +6 -0
  31. package/.medusa/server/src/api/utils/middlewares.js +34 -0
  32. package/.medusa/server/src/links/attribute-product-category.js +16 -0
  33. package/.medusa/server/src/links/attribute-value-product.js +16 -0
  34. package/.medusa/server/src/modules/attribute/events/index.js +8 -0
  35. package/.medusa/server/src/modules/attribute/index.js +13 -0
  36. package/.medusa/server/src/modules/attribute/migrations/Migration20250319161229.js +24 -0
  37. package/.medusa/server/src/modules/attribute/migrations/Migration20250320182643.js +16 -0
  38. package/.medusa/server/src/modules/attribute/migrations/Migration20250321162638.js +14 -0
  39. package/.medusa/server/src/modules/attribute/migrations/Migration20250505144933.js +23 -0
  40. package/.medusa/server/src/modules/attribute/migrations/Migration20250505201747.js +21 -0
  41. package/.medusa/server/src/modules/attribute/migrations/Migration20250506162300.js +14 -0
  42. package/.medusa/server/src/modules/attribute/migrations/Migration20250611160552.js +14 -0
  43. package/.medusa/server/src/modules/attribute/migrations/Migration20250611173345.js +16 -0
  44. package/.medusa/server/src/modules/attribute/migrations/Migration20250612192857.js +16 -0
  45. package/.medusa/server/src/modules/attribute/models/attribute-possible-value.js +24 -0
  46. package/.medusa/server/src/modules/attribute/models/attribute-set.js +22 -0
  47. package/.medusa/server/src/modules/attribute/models/attribute-value.js +17 -0
  48. package/.medusa/server/src/modules/attribute/models/attribute.js +27 -0
  49. package/.medusa/server/src/modules/attribute/service.js +84 -0
  50. package/.medusa/server/src/modules/attribute/types/attribute/common.js +13 -0
  51. package/.medusa/server/src/modules/attribute/types/attribute/index.js +18 -0
  52. package/.medusa/server/src/modules/attribute/types/attribute-set/index.js +18 -0
  53. package/.medusa/server/src/modules/attribute/types/attribute-set/mutations.js +3 -0
  54. package/.medusa/server/src/modules/attribute/types/attribute-value/index.js +18 -0
  55. package/.medusa/server/src/modules/attribute/types/attribute-value/mutations.js +3 -0
  56. package/.medusa/server/src/modules/attribute/types/index.js +20 -0
  57. package/.medusa/server/src/types/attribute/common.js +3 -0
  58. package/.medusa/server/src/types/attribute/http/attribute/admin/index.js +3 -0
  59. package/.medusa/server/src/types/attribute/http/attribute/index.js +18 -0
  60. package/.medusa/server/src/types/attribute/http/attribute-set/index.js +3 -0
  61. package/.medusa/server/src/types/attribute/http/index.js +19 -0
  62. package/.medusa/server/src/types/attribute/index.js +19 -0
  63. package/.medusa/server/src/utils/index.js +18 -0
  64. package/.medusa/server/src/utils/products-created-handler.js +22 -0
  65. package/.medusa/server/src/utils/products-updated-handler.js +58 -0
  66. package/.medusa/server/src/utils/validate-attribute-values-to-link.js +43 -0
  67. package/.medusa/server/src/workflows/attribute/index.js +19 -0
  68. package/.medusa/server/src/workflows/attribute/steps/create-attribute-possible-values.js +18 -0
  69. package/.medusa/server/src/workflows/attribute/steps/create-attributes.js +27 -0
  70. package/.medusa/server/src/workflows/attribute/steps/delete-attribute.js +31 -0
  71. package/.medusa/server/src/workflows/attribute/steps/index.js +21 -0
  72. package/.medusa/server/src/workflows/attribute/steps/update-attributes.js +34 -0
  73. package/.medusa/server/src/workflows/attribute/workflows/create-attribute-possible-values.js +11 -0
  74. package/.medusa/server/src/workflows/attribute/workflows/create-attributes.js +46 -0
  75. package/.medusa/server/src/workflows/attribute/workflows/delete-attribute.js +10 -0
  76. package/.medusa/server/src/workflows/attribute/workflows/index.js +20 -0
  77. package/.medusa/server/src/workflows/attribute/workflows/update-attributes.js +73 -0
  78. package/.medusa/server/src/workflows/attribute-set/steps/batch-link-attribute-set-attributes.js +66 -0
  79. package/.medusa/server/src/workflows/attribute-set/steps/create-attribute-set.js +24 -0
  80. package/.medusa/server/src/workflows/attribute-set/steps/index.js +20 -0
  81. package/.medusa/server/src/workflows/attribute-set/steps/update-attribute-set.js +22 -0
  82. package/.medusa/server/src/workflows/attribute-set/workflows/batch-link-attribute-set-attributes.js +10 -0
  83. package/.medusa/server/src/workflows/attribute-set/workflows/create-attribute-set.js +11 -0
  84. package/.medusa/server/src/workflows/attribute-set/workflows/index.js +20 -0
  85. package/.medusa/server/src/workflows/attribute-set/workflows/update-attribute-set.js +10 -0
  86. package/.medusa/server/src/workflows/attribute-value/steps/create-attribute-value.js +18 -0
  87. package/.medusa/server/src/workflows/attribute-value/steps/delete-attribute-value.js +18 -0
  88. package/.medusa/server/src/workflows/attribute-value/steps/index.js +20 -0
  89. package/.medusa/server/src/workflows/attribute-value/steps/validate-attribute-value.js +53 -0
  90. package/.medusa/server/src/workflows/attribute-value/workflow/create-attribute-value.js +28 -0
  91. package/.medusa/server/src/workflows/attribute-value/workflow/delete-attribute-value.js +38 -0
  92. package/.medusa/server/src/workflows/attribute-value/workflow/index.js +19 -0
  93. package/.medusa/server/src/workflows/attribute_possible_value/index.js +19 -0
  94. package/.medusa/server/src/workflows/attribute_possible_value/steps/index.js +18 -0
  95. package/.medusa/server/src/workflows/attribute_possible_value/steps/update-attribute-possible-value.js +18 -0
  96. package/.medusa/server/src/workflows/attribute_possible_value/workflows/index.js +18 -0
  97. package/.medusa/server/src/workflows/attribute_possible_value/workflows/update-attribute-possible-value.js +11 -0
  98. package/.medusa/server/src/workflows/index.js +18 -0
  99. package/CHANGELOG.md +104 -0
  100. package/README.md +86 -0
  101. package/package.json +90 -0
  102. package/src/admin/README.md +31 -0
  103. package/src/admin/components/metadata-editor/index.tsx +101 -0
  104. package/src/admin/components/section-row.tsx +41 -0
  105. package/src/admin/hooks/api/attribute-set.ts +122 -0
  106. package/src/admin/hooks/api/attributes.ts +126 -0
  107. package/src/admin/hooks/table/columns/index.ts +1 -0
  108. package/src/admin/hooks/table/columns/use-attribute-table-columns.tsx +280 -0
  109. package/src/admin/layouts/single-column.tsx +11 -0
  110. package/src/admin/lib/config.ts +8 -0
  111. package/src/admin/lib/query-key-factory.ts +53 -0
  112. package/src/admin/routes/attributes/[id]/edit/page.tsx +133 -0
  113. package/src/admin/routes/attributes/[id]/edit-possible-value/page.tsx +174 -0
  114. package/src/admin/routes/attributes/[id]/page.tsx +127 -0
  115. package/src/admin/routes/attributes/components/AttributeForm.tsx +301 -0
  116. package/src/admin/routes/attributes/components/AttributeSetTable.tsx +108 -0
  117. package/src/admin/routes/attributes/components/category-selection-modal.tsx +82 -0
  118. package/src/admin/routes/attributes/components/possible-values-table.tsx +119 -0
  119. package/src/admin/routes/attributes/create/components/MultiSelectCategory.tsx +148 -0
  120. package/src/admin/routes/attributes/create/components/PossibleValuesList.tsx +151 -0
  121. package/src/admin/routes/attributes/create/page.tsx +123 -0
  122. package/src/admin/routes/attributes/create-set/page.tsx +110 -0
  123. package/src/admin/routes/attributes/page.tsx +346 -0
  124. package/src/admin/routes/attributes/set/[id]/attributes/page.tsx +35 -0
  125. package/src/admin/routes/attributes/set/[id]/components/AttributeSetAttributesSection.tsx +114 -0
  126. package/src/admin/routes/attributes/set/[id]/components/AttributeSetGeneralSection.tsx +42 -0
  127. package/src/admin/routes/attributes/set/[id]/components/attribute-set-attributes-form.tsx +143 -0
  128. package/src/admin/routes/attributes/set/[id]/components/index.ts +2 -0
  129. package/src/admin/routes/attributes/set/[id]/edit/page.tsx +119 -0
  130. package/src/admin/routes/attributes/set/[id]/page.tsx +45 -0
  131. package/src/admin/tsconfig.json +27 -0
  132. package/src/admin/types/global.d.ts +3 -0
  133. package/src/admin/vite-env.d.ts +1 -0
  134. package/src/api/README.md +133 -0
  135. package/src/api/admin/middlewares.ts +8 -0
  136. package/src/api/admin/plugin/attribute-set/[id]/attributes/route.ts +17 -0
  137. package/src/api/admin/plugin/attribute-set/[id]/route.ts +41 -0
  138. package/src/api/admin/plugin/attribute-set/middlewares.ts +42 -0
  139. package/src/api/admin/plugin/attribute-set/query-config.ts +20 -0
  140. package/src/api/admin/plugin/attribute-set/route.ts +34 -0
  141. package/src/api/admin/plugin/attribute-set/validators.ts +45 -0
  142. package/src/api/admin/plugin/attributes/[id]/route.ts +85 -0
  143. package/src/api/admin/plugin/attributes/[id]/values/[valueId]/route.ts +41 -0
  144. package/src/api/admin/plugin/attributes/[id]/values/route.ts +39 -0
  145. package/src/api/admin/plugin/attributes/middlewares.ts +91 -0
  146. package/src/api/admin/plugin/attributes/query-config.ts +42 -0
  147. package/src/api/admin/plugin/attributes/route.ts +33 -0
  148. package/src/api/admin/plugin/attributes/validators.ts +91 -0
  149. package/src/api/admin/plugin/route.ts +8 -0
  150. package/src/api/middlewares.ts +10 -0
  151. package/src/api/store/middlewares.ts +6 -0
  152. package/src/api/store/plugin/attributes/middlewares.ts +33 -0
  153. package/src/api/store/plugin/attributes/products/middlewares.ts +73 -0
  154. package/src/api/store/plugin/attributes/products/query-config.ts +19 -0
  155. package/src/api/store/plugin/attributes/products/route.ts +68 -0
  156. package/src/api/store/plugin/attributes/products/validators.ts +55 -0
  157. package/src/api/store/plugin/attributes/query-config.ts +19 -0
  158. package/src/api/store/plugin/attributes/route.ts +13 -0
  159. package/src/api/store/plugin/attributes/validators.ts +14 -0
  160. package/src/api/store/plugin/route.ts +8 -0
  161. package/src/api/utils/common-validators.ts +24 -0
  162. package/src/api/utils/constants.ts +2 -0
  163. package/src/api/utils/middlewares.ts +31 -0
  164. package/src/jobs/README.md +36 -0
  165. package/src/links/README.md +26 -0
  166. package/src/links/attribute-product-category.ts +14 -0
  167. package/src/links/attribute-value-product.ts +14 -0
  168. package/src/modules/README.md +116 -0
  169. package/src/modules/attribute/events/index.ts +4 -0
  170. package/src/modules/attribute/index.ts +8 -0
  171. package/src/modules/attribute/migrations/.snapshot-medusa-attribute.json +624 -0
  172. package/src/modules/attribute/migrations/Migration20250319161229.ts +27 -0
  173. package/src/modules/attribute/migrations/Migration20250320182643.ts +15 -0
  174. package/src/modules/attribute/migrations/Migration20250321162638.ts +13 -0
  175. package/src/modules/attribute/migrations/Migration20250505144933.ts +26 -0
  176. package/src/modules/attribute/migrations/Migration20250505201747.ts +23 -0
  177. package/src/modules/attribute/migrations/Migration20250506162300.ts +13 -0
  178. package/src/modules/attribute/migrations/Migration20250611160552.ts +13 -0
  179. package/src/modules/attribute/migrations/Migration20250611173345.ts +17 -0
  180. package/src/modules/attribute/migrations/Migration20250612192857.ts +17 -0
  181. package/src/modules/attribute/models/attribute-possible-value.ts +20 -0
  182. package/src/modules/attribute/models/attribute-set.ts +18 -0
  183. package/src/modules/attribute/models/attribute-value.ts +13 -0
  184. package/src/modules/attribute/models/attribute.ts +23 -0
  185. package/src/modules/attribute/service.ts +102 -0
  186. package/src/modules/attribute/types/attribute/common.ts +94 -0
  187. package/src/modules/attribute/types/attribute/index.ts +1 -0
  188. package/src/modules/attribute/types/attribute-set/index.ts +1 -0
  189. package/src/modules/attribute/types/attribute-set/mutations.ts +7 -0
  190. package/src/modules/attribute/types/attribute-value/index.ts +1 -0
  191. package/src/modules/attribute/types/attribute-value/mutations.ts +5 -0
  192. package/src/modules/attribute/types/index.ts +3 -0
  193. package/src/providers/README.md +30 -0
  194. package/src/subscribers/README.md +59 -0
  195. package/src/types/attribute/common.ts +173 -0
  196. package/src/types/attribute/http/attribute/admin/index.ts +0 -0
  197. package/src/types/attribute/http/attribute/index.ts +42 -0
  198. package/src/types/attribute/http/attribute-set/index.ts +10 -0
  199. package/src/types/attribute/http/index.ts +2 -0
  200. package/src/types/attribute/index.ts +2 -0
  201. package/src/utils/index.ts +1 -0
  202. package/src/utils/products-created-handler.ts +35 -0
  203. package/src/utils/products-updated-handler.ts +74 -0
  204. package/src/utils/validate-attribute-values-to-link.ts +67 -0
  205. package/src/workflows/README.md +79 -0
  206. package/src/workflows/attribute/index.ts +2 -0
  207. package/src/workflows/attribute/steps/create-attribute-possible-values.ts +29 -0
  208. package/src/workflows/attribute/steps/create-attributes.ts +35 -0
  209. package/src/workflows/attribute/steps/delete-attribute.ts +41 -0
  210. package/src/workflows/attribute/steps/index.ts +4 -0
  211. package/src/workflows/attribute/steps/update-attributes.ts +45 -0
  212. package/src/workflows/attribute/workflows/create-attribute-possible-values.ts +17 -0
  213. package/src/workflows/attribute/workflows/create-attributes.ts +56 -0
  214. package/src/workflows/attribute/workflows/delete-attribute.ts +15 -0
  215. package/src/workflows/attribute/workflows/index.ts +3 -0
  216. package/src/workflows/attribute/workflows/update-attributes.ts +103 -0
  217. package/src/workflows/attribute-set/steps/batch-link-attribute-set-attributes.ts +82 -0
  218. package/src/workflows/attribute-set/steps/create-attribute-set.ts +34 -0
  219. package/src/workflows/attribute-set/steps/index.ts +3 -0
  220. package/src/workflows/attribute-set/steps/update-attribute-set.ts +32 -0
  221. package/src/workflows/attribute-set/workflows/batch-link-attribute-set-attributes.ts +12 -0
  222. package/src/workflows/attribute-set/workflows/create-attribute-set.ts +17 -0
  223. package/src/workflows/attribute-set/workflows/index.ts +3 -0
  224. package/src/workflows/attribute-set/workflows/update-attribute-set.ts +14 -0
  225. package/src/workflows/attribute-value/steps/create-attribute-value.ts +26 -0
  226. package/src/workflows/attribute-value/steps/delete-attribute-value.ts +26 -0
  227. package/src/workflows/attribute-value/steps/index.ts +3 -0
  228. package/src/workflows/attribute-value/steps/validate-attribute-value.ts +95 -0
  229. package/src/workflows/attribute-value/workflow/create-attribute-value.ts +36 -0
  230. package/src/workflows/attribute-value/workflow/delete-attribute-value.ts +46 -0
  231. package/src/workflows/attribute-value/workflow/index.ts +2 -0
  232. package/src/workflows/attribute_possible_value/index.ts +2 -0
  233. package/src/workflows/attribute_possible_value/steps/index.ts +1 -0
  234. package/src/workflows/attribute_possible_value/steps/update-attribute-possible-value.ts +25 -0
  235. package/src/workflows/attribute_possible_value/workflows/index.ts +1 -0
  236. package/src/workflows/attribute_possible_value/workflows/update-attribute-possible-value.ts +15 -0
  237. package/src/workflows/index.ts +1 -0
@@ -0,0 +1,148 @@
1
+ import { Text, Badge, IconButton } from "@medusajs/ui"
2
+ import { AdminProductCategory } from "@medusajs/types"
3
+ import React, { useState, useRef, useEffect } from "react"
4
+ import { TrianglesMini, XMarkMini, ChevronRightMini, ChevronLeftMini, ArrowUturnLeft, PlayMiniSolid, TriangleRightMiniHover } from "@medusajs/icons"
5
+
6
+ type MultiSelectCategoryProps = {
7
+ categories: AdminProductCategory[]
8
+ value: string[]
9
+ onChange: (value: string[]) => void
10
+ }
11
+
12
+ const MultiSelectCategory: React.FC<MultiSelectCategoryProps> = ({
13
+ categories,
14
+ value,
15
+ onChange,
16
+ }) => {
17
+ const [isOpen, setIsOpen] = useState(false)
18
+ const dropdownRef = useRef<HTMLDivElement>(null)
19
+ const triggerRef = useRef<HTMLDivElement>(null)
20
+ const [currentParentId, setCurrentParentId] = useState<string | null>(null)
21
+ const [pathHistory, setPathHistory] = useState<(string | null)[]>([])
22
+
23
+ useEffect(() => {
24
+ const handleClickOutside = (event: MouseEvent) => {
25
+ if (
26
+ dropdownRef.current &&
27
+ !dropdownRef.current.contains(event.target as Node) &&
28
+ triggerRef.current &&
29
+ !triggerRef.current.contains(event.target as Node)
30
+ ) {
31
+ setIsOpen(false)
32
+ }
33
+ }
34
+ document.addEventListener("mousedown", handleClickOutside)
35
+ return () => {
36
+ document.removeEventListener("mousedown", handleClickOutside)
37
+ }
38
+ }, [])
39
+
40
+ const handleToggle = () => {
41
+ setIsOpen((prev) => !prev)
42
+ }
43
+
44
+ const hasChildren = (categoryId: string) => {
45
+ return categories.some(cat => cat.parent_category_id === categoryId)
46
+ }
47
+
48
+ const handleDrillDown = (category: AdminProductCategory, event: React.MouseEvent) => {
49
+ event.stopPropagation() // Prevent selection when drilling down
50
+ setPathHistory([...pathHistory, currentParentId])
51
+ setCurrentParentId(category.id)
52
+ }
53
+
54
+ const handleGoBack = () => {
55
+ const newPathHistory = [...pathHistory]
56
+ const previousParentId = newPathHistory.pop()
57
+ setPathHistory(newPathHistory)
58
+ setCurrentParentId(previousParentId || null)
59
+ }
60
+
61
+ const handleItemClick = (categoryId: string) => {
62
+ const isSelected = value.includes(categoryId)
63
+ if (isSelected) {
64
+ onChange(value.filter((id) => id !== categoryId))
65
+ } else {
66
+ onChange([...value, categoryId])
67
+ }
68
+ }
69
+
70
+ const currentCategories = categories.filter(cat => cat.parent_category_id === currentParentId)
71
+
72
+ const getBackButtonText = (): string => {
73
+ const parentCategory = categories.find(cat => cat.id === currentParentId);
74
+ return parentCategory?.name || "";
75
+ }
76
+
77
+ return (
78
+ <div className="relative">
79
+ <div
80
+ ref={triggerRef}
81
+ className="relative flex h-10 w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-ui-border-base bg-ui-bg-field text-ui-fg-base shadow-sm transition-colors duration-150 ease-in-out hover:bg-ui-bg-field-hover focus-within:border-ui-border-interactive focus-within:ring-1 focus-within:ring-ui-ring-interactive"
82
+ onClick={handleToggle}
83
+ >
84
+ <div className="flex items-center gap-2 px-3 py-2">
85
+ {value.length > 0 ? (
86
+ <>
87
+ <button type="button" onClick={(e) => { e.stopPropagation(); onChange([]) }}>
88
+ <Badge size="small" className="w-fit">
89
+ {value.length}
90
+ <XMarkMini></XMarkMini>
91
+ </Badge>
92
+ </button>
93
+ </>
94
+ ) : (
95
+ <Text className="text-ui-fg-subtle">Select categories</Text>
96
+ )}
97
+ </div>
98
+ <span className="flex h-full w-10 items-center justify-center border-l border-ui-border-base">
99
+ <TrianglesMini></TrianglesMini>
100
+ </span>
101
+ </div>
102
+
103
+ {isOpen && (
104
+ <div
105
+ ref={dropdownRef}
106
+ className="absolute z-10 mt-1 w-full max-h-60 overflow-y-auto rounded-md border border-ui-border-base bg-ui-bg-base shadow-lg"
107
+ >
108
+ {currentParentId !== null && (
109
+ <div
110
+ className="flex cursor-pointer items-center gap-3 px-3 py-2 text-ui-fg-subtle hover:bg-ui-bg-base-hover border-b border-ui-border-base"
111
+ onClick={handleGoBack}
112
+ >
113
+ <ArrowUturnLeft />
114
+ <Text>{getBackButtonText()}</Text>
115
+ </div>
116
+ )}
117
+ {currentCategories.length === 0 ? (
118
+ <div className="p-3 text-ui-fg-subtle">No categories found.</div>
119
+ ) : (
120
+ currentCategories.map((category) => {
121
+ const isSelected = value.includes(category.id);
122
+ const hasChildrenNode = hasChildren(category.id)
123
+ return (
124
+ <div
125
+ key={category.id}
126
+ className={`flex cursor-pointer items-center justify-between px-1 py-1`}
127
+ onClick={() => handleItemClick(category.id)}
128
+ >
129
+ <div className="flex items-center hover:bg-ui-bg-base-hover flex-1 px-2 mr-2 py-1.5 rounded-md relative">
130
+ {isSelected && <span className="absolute left-3 top-1/2 -translate-y-1/2 w-1 h-1 bg-white rounded-full" />}
131
+ <Text className="ml-6">{category.name}</Text>
132
+ </div>
133
+ {hasChildrenNode && (
134
+ <div onClick={(e) => handleDrillDown(category, e)} className="p-2 rounded-md hover:bg-ui-bg-base-hover">
135
+ <TriangleRightMiniHover className="mr-1" />
136
+ </div>
137
+ )}
138
+ </div>
139
+ );
140
+ })
141
+ )}
142
+ </div>
143
+ )}
144
+ </div>
145
+ );
146
+ }
147
+
148
+ export default MultiSelectCategory
@@ -0,0 +1,151 @@
1
+ import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core'
2
+ import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'
3
+ import { CSS } from '@dnd-kit/utilities'
4
+ import { Button, IconButton, Input, Text } from '@medusajs/ui'
5
+ import { Plus, X, EllipsisHorizontal, XMark } from '@medusajs/icons'
6
+ import { useFieldArray, useFormContext } from 'react-hook-form'
7
+ import { AdminCreateAttributeValueType } from '../../../../../api/admin/plugin/attributes/validators'
8
+ import { CreateAttributeFormSchema } from '../page'
9
+ import { z } from 'zod'
10
+
11
+ type FormValues = z.infer<typeof CreateAttributeFormSchema>
12
+
13
+ interface SortableItemProps {
14
+ id: string
15
+ index: number
16
+ onRemove: () => void
17
+ }
18
+
19
+ const SortableItem = ({ id, index, onRemove }: SortableItemProps) => {
20
+ const { register, formState: { errors } } = useFormContext<FormValues>()
21
+ const {
22
+ attributes,
23
+ listeners,
24
+ setNodeRef,
25
+ transform,
26
+ transition,
27
+ } = useSortable({ id })
28
+
29
+ const style = {
30
+ transform: CSS.Transform.toString(transform),
31
+ transition,
32
+ }
33
+
34
+ const fieldError = errors.possible_values?.[index]?.value
35
+
36
+ return (
37
+ <div
38
+ ref={setNodeRef}
39
+ style={style}
40
+ className="flex items-center gap-2 p-2 bg-ui-bg-component border border-ui-border-base rounded-xl mb-2"
41
+ >
42
+ <button
43
+ className="cursor-grab active:cursor-grabbing"
44
+ {...attributes}
45
+ {...listeners}
46
+ >
47
+ <EllipsisHorizontal className="text-ui-fg-subtle" />
48
+ </button>
49
+ <div className="flex-1">
50
+ <Input
51
+ className="flex-1"
52
+ aria-invalid={!!fieldError}
53
+ placeholder="Enter value"
54
+ {...register(`possible_values.${index}.value`)}
55
+ />
56
+ </div>
57
+ <IconButton
58
+ variant="transparent"
59
+ size="small"
60
+ onClick={onRemove}
61
+ >
62
+ <XMark />
63
+ </IconButton>
64
+ </div>
65
+ )
66
+ }
67
+
68
+ const PossibleValuesList = () => {
69
+ const { control, getValues } = useFormContext<FormValues>()
70
+ const { fields, append, remove, update } = useFieldArray({
71
+ control,
72
+ name: 'possible_values',
73
+ })
74
+
75
+ const sensors = useSensors(
76
+ useSensor(PointerSensor),
77
+ useSensor(KeyboardSensor, {
78
+ coordinateGetter: sortableKeyboardCoordinates,
79
+ })
80
+ )
81
+
82
+ const handleDragEnd = (event: DragEndEvent) => {
83
+ const { active, over } = event
84
+
85
+ if (over && active.id !== over.id) {
86
+ const oldIndex = fields.findIndex((field) => field.id === active.id)
87
+ const newIndex = fields.findIndex((field) => field.id === over.id)
88
+
89
+ // Get current form values
90
+ const currentValues = getValues('possible_values') as AdminCreateAttributeValueType[]
91
+
92
+ // Create new array with reordered items
93
+ const reorderedValues = arrayMove(currentValues, oldIndex, newIndex)
94
+
95
+ // Update all fields with their new positions and ranks
96
+ reorderedValues.forEach((value, index) => {
97
+ update(index, {
98
+ value: value.value,
99
+ rank: index,
100
+ metadata: value.metadata || {},
101
+ })
102
+ })
103
+ }
104
+ }
105
+
106
+ const handleAddValue = () => {
107
+ append({
108
+ value: '',
109
+ rank: fields.length,
110
+ metadata: {},
111
+ })
112
+ }
113
+
114
+ return (
115
+ <div className="space-y-2">
116
+ <div className="flex items-center justify-between pb-1">
117
+ <Text className="text-ui-fg-subtle">Possible Values</Text>
118
+ <Button
119
+ type="button"
120
+ variant="secondary"
121
+ size="small"
122
+ onClick={handleAddValue}
123
+ >
124
+ Add
125
+ </Button>
126
+ </div>
127
+
128
+ <DndContext
129
+ sensors={sensors}
130
+ collisionDetection={closestCenter}
131
+ onDragEnd={handleDragEnd}
132
+ >
133
+ <SortableContext
134
+ items={fields.map((field) => field.id)}
135
+ strategy={verticalListSortingStrategy}
136
+ >
137
+ {fields.map((field, index) => (
138
+ <SortableItem
139
+ key={field.id}
140
+ id={field.id}
141
+ index={index}
142
+ onRemove={() => remove(index)}
143
+ />
144
+ ))}
145
+ </SortableContext>
146
+ </DndContext>
147
+ </div>
148
+ )
149
+ }
150
+
151
+ export default PossibleValuesList
@@ -0,0 +1,123 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import {
3
+ FocusModal,
4
+ Button,
5
+ toast,
6
+ ProgressTabs,
7
+ } from "@medusajs/ui";
8
+ import { useNavigate } from "react-router-dom";
9
+ import { medusaClient } from "../../../lib/config";
10
+ import { useEffect, useState } from "react";
11
+ import { AdminProductCategory } from "@medusajs/types";
12
+ import { AttributeForm, CreateAttributeFormSchema } from "../components/AttributeForm";
13
+ import { z } from "zod";
14
+ import { useQueryClient } from "@tanstack/react-query";
15
+ import { attributeQueryKeys } from "../../../hooks/api/attributes";
16
+
17
+ const CreateAttributePage = () => {
18
+ const navigate = useNavigate();
19
+ const [categories, setCategories] = useState<AdminProductCategory[]>([]);
20
+ const [activeTab, setActiveTab] = useState<"details" | "type">("details");
21
+ const [tabStatuses, setTabStatuses] = useState<{
22
+ detailsStatus: "not-started" | "in-progress" | "completed";
23
+ typeStatus: "not-started" | "in-progress" | "completed";
24
+ }>({
25
+ detailsStatus: "not-started",
26
+ typeStatus: "not-started",
27
+ });
28
+ const queryClient = useQueryClient();
29
+
30
+ useEffect(() => {
31
+ const fetchCategories = async () => {
32
+ try {
33
+ const response = await medusaClient.admin.productCategory.list();
34
+ setCategories(response.product_categories);
35
+ } catch (error) {
36
+ console.error("Failed to fetch categories:", error);
37
+ }
38
+ };
39
+ fetchCategories();
40
+ }, []);
41
+
42
+ const handleSave = async (data: z.infer<typeof CreateAttributeFormSchema>) => {
43
+ try {
44
+ const { ...payload } = data;
45
+ await medusaClient.client.fetch("/admin/plugin/attributes", {
46
+ method: "POST",
47
+ body: payload,
48
+ });
49
+
50
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
51
+
52
+ toast.success("Attribute created!");
53
+ navigate(-1);
54
+ } catch (error) {
55
+ toast.error((error as Error).message);
56
+ console.error(error);
57
+ }
58
+ };
59
+
60
+ const handleClose = () => {
61
+ navigate(-1);
62
+ };
63
+
64
+ return (
65
+ <FocusModal
66
+ open={true}
67
+ onOpenChange={(open) => {
68
+ if (!open) handleClose();
69
+ }}
70
+ >
71
+ <FocusModal.Content>
72
+ <ProgressTabs
73
+ value={activeTab}
74
+ onValueChange={(value) => setActiveTab(value as "details" | "type")}
75
+ className="w-full h-full overflow-y-auto"
76
+ >
77
+ <FocusModal.Header className="flex items-center justify-between bg-ui-bg-base w-full py-0 h-fit sticky top-0 z-10">
78
+ <div className="w-full border-l h-full">
79
+ <ProgressTabs.List className="justify-start flex w-full items-center">
80
+ <ProgressTabs.Trigger
81
+ value="details"
82
+ status={tabStatuses.detailsStatus}
83
+ >
84
+ Details
85
+ </ProgressTabs.Trigger>
86
+ <ProgressTabs.Trigger
87
+ value="type"
88
+ status={tabStatuses.typeStatus}
89
+ >
90
+ Type
91
+ </ProgressTabs.Trigger>
92
+ </ProgressTabs.List>
93
+ </div>
94
+ </FocusModal.Header>
95
+ <FocusModal.Body className="flex flex-col items-center py-16">
96
+ <div>
97
+ <AttributeForm
98
+ //@ts-expect-error The received values will be for create form
99
+ onSubmit={handleSave}
100
+ onCancel={handleClose}
101
+ categories={categories}
102
+ activeTab={activeTab}
103
+ onFormStateChange={setTabStatuses}
104
+ />
105
+ </div>
106
+ </FocusModal.Body>
107
+ </ProgressTabs>
108
+ <FocusModal.Footer>
109
+ <Button variant="secondary" onClick={handleClose}>
110
+ Cancel
111
+ </Button>
112
+ <Button type="submit" form="attribute-form">
113
+ Save
114
+ </Button>
115
+ </FocusModal.Footer>
116
+ </FocusModal.Content>
117
+ </FocusModal>
118
+ );
119
+ };
120
+
121
+ export const config = defineRouteConfig({});
122
+
123
+ export default CreateAttributePage;
@@ -0,0 +1,110 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import {
3
+ FocusModal,
4
+ Heading,
5
+ Button,
6
+ toast,
7
+ Text,
8
+ Label,
9
+ Input,
10
+ Textarea,
11
+ } from "@medusajs/ui";
12
+ import { useNavigate } from "react-router-dom";
13
+ import { medusaClient } from "../../../lib/config";
14
+ import { z } from "zod";
15
+ import { AdminCreateAttributeSet } from "../../../../api/admin/plugin/attribute-set/validators";
16
+ import { useForm } from "react-hook-form";
17
+ import { zodResolver } from "@hookform/resolvers/zod";
18
+
19
+ type CreateAttributeSetFormValues = z.infer<typeof AdminCreateAttributeSet>;
20
+
21
+ const CreateAttributeSetPage = () => {
22
+ const navigate = useNavigate();
23
+ const form = useForm<CreateAttributeSetFormValues>({
24
+ resolver: zodResolver(AdminCreateAttributeSet),
25
+ defaultValues: {
26
+ name: "",
27
+ handle: "",
28
+ description: "",
29
+ },
30
+ });
31
+
32
+ const handleSubmit = form.handleSubmit(async (data) => {
33
+ try {
34
+ await medusaClient.client.fetch('/admin/plugin/attribute-set', {
35
+ method: 'POST',
36
+ body: data
37
+ })
38
+ toast.success('Attribute set created!')
39
+ navigate(-1)
40
+ } catch (error) {
41
+ toast.error((error as Error).message);
42
+ console.error(error);
43
+ }
44
+ });
45
+
46
+ const handleClose = () => {
47
+ navigate(-1);
48
+ };
49
+
50
+ return (
51
+ <FocusModal
52
+ open={true}
53
+ onOpenChange={(open) => {
54
+ if (!open) handleClose();
55
+ }}
56
+ >
57
+ <FocusModal.Content>
58
+ <FocusModal.Header>
59
+ <Heading>Create Attribute Set</Heading>
60
+ </FocusModal.Header>
61
+ <FocusModal.Body className="flex flex-col items-center py-16">
62
+ <div className="flex w-full max-w-lg flex-col gap-y-8">
63
+ <div className="flex flex-col gap-y-1">
64
+ <Heading>Create attribute set</Heading>
65
+ <Text className="text-ui-fg-subtle">
66
+ Create attribute sets to group together related attributes.
67
+ </Text>
68
+ </div>
69
+ <form id="attribute-set-form" onSubmit={handleSubmit}>
70
+ <div className="grid gap-4">
71
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
72
+ <div>
73
+ <Label size="xsmall" htmlFor="name">
74
+ Name
75
+ </Label>
76
+ <Input aria-invalid={!!form.formState.errors.name} {...form.register("name")} />
77
+ </div>
78
+ <div>
79
+ <Label size="xsmall" htmlFor="handle">
80
+ Handle
81
+ </Label>
82
+ <Input {...form.register("handle")} />
83
+ </div>
84
+ </div>
85
+ <div>
86
+ <Label size="xsmall" htmlFor="description">
87
+ Description
88
+ </Label>
89
+ <Textarea {...form.register("description")} />
90
+ </div>
91
+ </div>
92
+ </form>
93
+ </div>
94
+ </FocusModal.Body>
95
+ <FocusModal.Footer>
96
+ <Button variant="secondary" onClick={handleClose}>
97
+ Cancel
98
+ </Button>
99
+ <Button type="submit" form="attribute-set-form">
100
+ Create
101
+ </Button>
102
+ </FocusModal.Footer>
103
+ </FocusModal.Content>
104
+ </FocusModal>
105
+ );
106
+ };
107
+
108
+ export const config = defineRouteConfig({});
109
+
110
+ export default CreateAttributeSetPage;