@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,101 @@
1
+ import { Button, Input, Text, Heading, DropdownMenu } from "@medusajs/ui"
2
+ import { useFieldArray, UseFormReturn, FieldValues, Path, FieldError, FieldErrors, ArrayPath, FieldArray } from "react-hook-form"
3
+ import { EllipsisHorizontal, Trash } from "@medusajs/icons"
4
+
5
+ interface MetadataField {
6
+ key: string
7
+ value: string
8
+ }
9
+
10
+ interface MetadataEditorProps<T extends FieldValues & { metadata: MetadataField[] }> {
11
+ form: UseFormReturn<T>
12
+ name?: ArrayPath<T>
13
+ title?: string
14
+ }
15
+
16
+ export const MetadataEditor = <T extends FieldValues & { metadata: MetadataField[] }>({
17
+ form,
18
+ name = "metadata" as ArrayPath<T>,
19
+ title = "Metadata"
20
+ }: MetadataEditorProps<T>) => {
21
+ const { fields, append, remove } = useFieldArray({
22
+ control: form.control,
23
+ name,
24
+ })
25
+
26
+ const getErrorMessage = (error: FieldError | undefined) => {
27
+ return error?.message ? String(error.message) : ""
28
+ }
29
+
30
+ const getFieldErrors = (index: number) => {
31
+ const errors = form.formState.errors[name] as FieldErrors<MetadataField[]> | undefined
32
+ return {
33
+ key: errors?.[index]?.key,
34
+ value: errors?.[index]?.value
35
+ }
36
+ }
37
+
38
+ return (
39
+ <div className="col-span-2 mt-4">
40
+ <Heading level="h3" className="inter-small-semibold mb-2">{title}</Heading>
41
+ <div className="border rounded-lg overflow-hidden">
42
+ <div className="grid grid-cols-[1fr_1fr_40px] bg-ui-bg-subtle border-b py-2 px-3 text-ui-fg-subtle text-sm font-semibold">
43
+ <span className="border-r pr-2">Key</span>
44
+ <span>Value</span>
45
+ <span></span>
46
+ </div>
47
+ {fields.map((field, index) => {
48
+ const fieldErrors = getFieldErrors(index)
49
+ return (
50
+ <div key={field.id} className="grid grid-cols-[1fr_1fr_40px] items-center border-b last:border-b-0">
51
+ <div className="py-2 pl-3 pr-2 border-r">
52
+ <Input
53
+ placeholder="Key"
54
+ className="!shadow-none !border-none focus-visible:!outline-none bg-transparent"
55
+ {...form.register(`${name}.${index}.key` as Path<T>)}
56
+ />
57
+ {fieldErrors.key && (
58
+ <Text className="text-red-500 text-sm mt-1">
59
+ {getErrorMessage(fieldErrors.key)}
60
+ </Text>
61
+ )}
62
+ </div>
63
+ <div className="py-2 pl-3 pr-2">
64
+ <Input
65
+ placeholder="Value"
66
+ className="!shadow-none !border-none focus-visible:!outline-none bg-transparent"
67
+ {...form.register(`${name}.${index}.value` as Path<T>)}
68
+ />
69
+ {fieldErrors.value && (
70
+ <Text className="text-red-500 text-sm mt-1">
71
+ {getErrorMessage(fieldErrors.value)}
72
+ </Text>
73
+ )}
74
+ </div>
75
+ <div className="flex justify-end pr-3">
76
+ <DropdownMenu>
77
+ <DropdownMenu.Trigger asChild>
78
+ <Button variant="transparent" size="small">
79
+ <EllipsisHorizontal />
80
+ </Button>
81
+ </DropdownMenu.Trigger>
82
+ <DropdownMenu.Content align="end">
83
+ <DropdownMenu.Item onClick={() => remove(index)} className="gap-x-2">
84
+ <Trash className="text-ui-fg-subtle" />
85
+ Remove
86
+ </DropdownMenu.Item>
87
+ </DropdownMenu.Content>
88
+ </DropdownMenu>
89
+ </div>
90
+ </div>
91
+ )
92
+ })}
93
+ <div className="p-3">
94
+ <Button type="button" variant="secondary" size="small" onClick={() => append({ key: "", value: "" } as FieldArray<T, ArrayPath<T>>)} className="w-full">
95
+ + Add Row
96
+ </Button>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ )
101
+ }
@@ -0,0 +1,41 @@
1
+ import { Text, clx } from "@medusajs/ui";
2
+ import { ReactNode } from "react";
3
+
4
+ export type SectionRowProps = {
5
+ title: string;
6
+ value?: ReactNode | string | null;
7
+ actions?: ReactNode;
8
+ };
9
+
10
+ export const SectionRow = ({ title, value, actions }: SectionRowProps) => {
11
+ const isValueString = typeof value === "string" || !value;
12
+
13
+ return (
14
+ <div
15
+ className={clx(
16
+ `text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4`,
17
+ {
18
+ "grid-cols-[1fr_1fr_28px]": !!actions,
19
+ },
20
+ )}
21
+ >
22
+ <Text size="small" weight="plus" leading="compact">
23
+ {title}
24
+ </Text>
25
+
26
+ {isValueString ? (
27
+ <Text
28
+ size="small"
29
+ leading="compact"
30
+ className="whitespace-pre-line text-pretty"
31
+ >
32
+ {value ?? "-"}
33
+ </Text>
34
+ ) : (
35
+ <div className="flex flex-wrap gap-1">{value}</div>
36
+ )}
37
+
38
+ {actions && <div>{actions}</div>}
39
+ </div>
40
+ );
41
+ };
@@ -0,0 +1,122 @@
1
+ import { QueryKey, useMutation, UseMutationOptions, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
2
+ import { medusaClient } from "../../lib/config";
3
+ import { queryKeysFactory } from "../../lib/query-key-factory";
4
+ import { LinkWorkflowInput, PaginatedResponse } from "@medusajs/types";
5
+ import { AttributeSet } from "../../../types/attribute/http/attribute-set";
6
+ import { FetchError } from "@medusajs/js-sdk";
7
+ import { attributeQueryKeys } from "./attributes";
8
+
9
+ const ATRIBUTE_SET_QUERY_KEY = "attribute-sets" as const;
10
+ export const attributeSetQueryKeys = queryKeysFactory(ATRIBUTE_SET_QUERY_KEY);
11
+
12
+ export const useAttributeSets = (
13
+ query?: any,
14
+ options?: Omit<
15
+ UseQueryOptions<
16
+ PaginatedResponse<{ attributeSets: AttributeSet[] }>,
17
+ FetchError,
18
+ PaginatedResponse<{ attributeSets: AttributeSet[] }>,
19
+ QueryKey
20
+ >,
21
+ "queryFn" | "queryKey"
22
+ >
23
+ ) => {
24
+ const { data, ...rest } = useQuery({
25
+ queryKey: attributeSetQueryKeys.list(query),
26
+ queryFn: () =>
27
+ medusaClient.client.fetch<
28
+ PaginatedResponse<{ attributeSets: AttributeSet[] }>
29
+ >("/admin/plugin/attribute-set", {
30
+ method: "GET",
31
+ }),
32
+ ...options,
33
+ });
34
+ return { ...data, ...rest };
35
+ };
36
+
37
+ export const useAttributeSet = (
38
+ id: string,
39
+ query?: any,
40
+ options?: Omit<
41
+ UseQueryOptions<
42
+ { attributeSet: AttributeSet },
43
+ FetchError,
44
+ { attributeSet: AttributeSet },
45
+ QueryKey
46
+ >,
47
+ "queryFn" | "queryKey"
48
+ >
49
+ ) => {
50
+ const { data, ...rest } = useQuery({
51
+ queryKey: attributeSetQueryKeys.detail(id, query),
52
+ queryFn: () =>
53
+ medusaClient.client.fetch<{ attributeSet: AttributeSet }>(
54
+ `/admin/plugin/attribute-set/${id}`,
55
+ {
56
+ method: "GET",
57
+ }
58
+ ),
59
+ ...options,
60
+ });
61
+
62
+ return { ...data, ...rest };
63
+ };
64
+
65
+ export const useUpdateAttributeSet = (
66
+ id: string,
67
+ options?:
68
+ UseMutationOptions<
69
+ { attributeSet: AttributeSet },
70
+ FetchError,
71
+ Partial<Pick<AttributeSet, 'name' | 'handle' | 'description' | 'metadata'>>
72
+ >
73
+ ) => {
74
+ const queryClient = useQueryClient()
75
+ return useMutation({
76
+ mutationFn: (payload) => medusaClient.client.fetch(`/admin/plugin/attribute-set/${id}`, {
77
+ method: 'POST',
78
+ body: payload
79
+ }),
80
+ onSuccess: (data, variables, context) => {
81
+ queryClient.invalidateQueries({
82
+ queryKey: attributeSetQueryKeys.detail(id)
83
+ })
84
+ queryClient.invalidateQueries({
85
+ queryKey: attributeSetQueryKeys.list()
86
+ })
87
+
88
+ options?.onSuccess?.(data, variables, context)
89
+ },
90
+ ...options
91
+ })
92
+ }
93
+
94
+ export const useBatchAttributesToSets = (
95
+ attributeSetId: string,
96
+ options?:
97
+ UseMutationOptions<
98
+ {},
99
+ FetchError,
100
+ Omit<LinkWorkflowInput, 'id'>
101
+ >
102
+ ) => {
103
+ const queryClient = useQueryClient()
104
+ return useMutation({
105
+ mutationFn: (payload) => medusaClient.client.fetch(`/admin/plugin/attribute-set/${attributeSetId}/attributes`, {
106
+ method: 'POST',
107
+ body: payload,
108
+ }),
109
+ onSuccess: (data, variables, context) => {
110
+ queryClient.invalidateQueries({
111
+ queryKey: attributeSetQueryKeys.detail(attributeSetId)
112
+ })
113
+ queryClient.invalidateQueries({
114
+ queryKey: attributeQueryKeys.lists()
115
+ })
116
+ console.log('Invalidated query keys')
117
+
118
+ options?.onSuccess?.(data, variables, context)
119
+ },
120
+ ...options
121
+ })
122
+ }
@@ -0,0 +1,126 @@
1
+ import { QueryKey, useMutation, UseMutationOptions, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query"
2
+ import { queryKeysFactory } from "../../lib/query-key-factory"
3
+ import { PaginatedResponse } from "@medusajs/types"
4
+ import { Attribute, AttributePossibleValue } from "../../../types/attribute/http/attribute"
5
+ import { FetchError } from "@medusajs/js-sdk"
6
+ import { medusaClient } from "../../lib/config"
7
+ import { attributeSetQueryKeys } from "./attribute-set"
8
+
9
+ const ATTRIBUTE_QUERY_KEY = 'attribute' as const
10
+ export const attributeQueryKeys = queryKeysFactory(ATTRIBUTE_QUERY_KEY)
11
+
12
+ export const useAttributes = (
13
+ query?: any,
14
+ options?: Omit<
15
+ UseQueryOptions<
16
+ PaginatedResponse<{ attributes: Attribute[] }>,
17
+ FetchError,
18
+ PaginatedResponse<{ attributes: Attribute[] }>,
19
+ QueryKey
20
+ >,
21
+ "queryFn" | "queryKey"
22
+ >
23
+ ) => {
24
+ const { data, ...rest } = useQuery({
25
+ queryKey: attributeQueryKeys.list(query),
26
+ queryFn: () =>
27
+ medusaClient.client.fetch<PaginatedResponse<{ attributes: Attribute[] }>>('/admin/plugin/attributes', {
28
+ method: 'GET',
29
+ query,
30
+ }),
31
+ ...options,
32
+ })
33
+ return { ...data, ...rest }
34
+ }
35
+
36
+ export const useAttribute = (
37
+ id: string,
38
+ query?: Record<string, unknown>,
39
+ options?: Omit<
40
+ UseQueryOptions<
41
+ { attribute: Attribute },
42
+ FetchError,
43
+ { attribute: Attribute },
44
+ QueryKey
45
+ >,
46
+ "queryFn" | "queryKey"
47
+ >
48
+ ) => {
49
+ const { data, ...rest } = useQuery({
50
+ queryKey: attributeQueryKeys.detail(id, query),
51
+ queryFn: () =>
52
+ medusaClient.client.fetch<{ attribute: Attribute }>(`/admin/plugin/attributes/${id}`, {
53
+ method: 'GET',
54
+ query,
55
+ }),
56
+ ...options,
57
+ })
58
+ return { ...data, ...rest }
59
+ }
60
+
61
+ export const useUpdateAttribute = (
62
+ id: string,
63
+ options?:
64
+ UseMutationOptions<
65
+ { attribute: Attribute },
66
+ FetchError,
67
+ Partial<Pick<Attribute, 'name' | 'handle' | 'description' | 'metadata'>>
68
+ >
69
+ ) => {
70
+ const queryClient = useQueryClient()
71
+
72
+ return useMutation({
73
+ mutationFn: (payload) => medusaClient.client.fetch(`/admin/plugin/attributes/${id}`, {
74
+ method: 'POST',
75
+ body: payload
76
+ }),
77
+ onSuccess: (data, variables, context) => {
78
+ queryClient.invalidateQueries({
79
+ queryKey: attributeQueryKeys.detail(id)
80
+ })
81
+ queryClient.invalidateQueries({
82
+ queryKey: attributeQueryKeys.list()
83
+ })
84
+
85
+ options?.onSuccess?.(data, variables, context)
86
+ },
87
+ ...options
88
+ })
89
+ }
90
+
91
+ const ATTRIBUTE_POSSIBLE_VALUE_QUERY_KEY = 'attribute-possible-value' as const
92
+ export const attributePossibleValueQueryKeys = queryKeysFactory(ATTRIBUTE_POSSIBLE_VALUE_QUERY_KEY)
93
+
94
+ export const useUpdateAttributePossibleValue = (
95
+ attributeId: string,
96
+ possibleValueId: string,
97
+ options?:
98
+ UseMutationOptions<
99
+ { possible_value: AttributePossibleValue },
100
+ FetchError,
101
+ Partial<Pick<AttributePossibleValue, 'value' | 'rank' | 'metadata'>>
102
+ >
103
+ ) => {
104
+ const queryClient = useQueryClient()
105
+
106
+ return useMutation({
107
+ mutationFn: (payload) => medusaClient.client.fetch(`/admin/plugin/attributes/${attributeId}/values/${possibleValueId}`, {
108
+ method: 'POST',
109
+ body: payload
110
+ }),
111
+ onSuccess: (data, variables, context) => {
112
+ queryClient.invalidateQueries({
113
+ queryKey: attributeQueryKeys.detail(attributeId)
114
+ })
115
+ queryClient.invalidateQueries({
116
+ queryKey: attributeQueryKeys.list()
117
+ })
118
+ queryClient.invalidateQueries({
119
+ queryKey: attributePossibleValueQueryKeys.detail(possibleValueId)
120
+ })
121
+
122
+ options?.onSuccess?.(data, variables, context)
123
+ },
124
+ ...options
125
+ })
126
+ }
@@ -0,0 +1 @@
1
+ export * from './use-attribute-table-columns'
@@ -0,0 +1,280 @@
1
+ import { Badge, createDataTableColumnHelper, DropdownMenu, IconButton, Switch, toast, usePrompt } from "@medusajs/ui";
2
+ import { useMemo, useState } from "react";
3
+ import { useNavigate } from "react-router-dom";
4
+ import { Attribute } from "../../../../types/attribute";
5
+ import { useQueryClient } from "@tanstack/react-query";
6
+ import { medusaClient } from "../../../lib/config";
7
+ import { attributeQueryKeys } from "../../api/attributes";
8
+ import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons";
9
+ import { CategorySelectionModal } from "../../../routes/attributes/components/category-selection-modal";
10
+
11
+ const columnHelper = createDataTableColumnHelper<Attribute>();
12
+
13
+ export const useAttributeTableColumns = () => {
14
+ const navigate = useNavigate();
15
+ const queryClient = useQueryClient();
16
+ const prompt = usePrompt();
17
+
18
+ const [isCategoryModalOpen, setIsCategoryModalOpen] = useState(false);
19
+ const [currentAttribute, setCurrentAttribute] = useState<Attribute | null>(null);
20
+
21
+ const handleEdit = (attributeId: string) => {
22
+ navigate(`/attributes/${attributeId}/edit`);
23
+ };
24
+
25
+ const handleToggleFilterable = async (attributeId: string, currentValue: boolean, attribute: Attribute) => {
26
+ try {
27
+ await medusaClient.client.fetch(`/admin/plugin/attributes/${attributeId}`, {
28
+ method: "POST",
29
+ body: {
30
+ name: attribute.name,
31
+ description: attribute.description,
32
+ handle: attribute.handle,
33
+ is_filterable: !currentValue,
34
+ // is_required: attribute.is_required,
35
+ ui_component: attribute.ui_component,
36
+ // metadata: {},
37
+ product_category_ids: attribute.product_categories?.map((category) => category.id),
38
+ possible_values: attribute.possible_values?.map((value) => ({
39
+ id: value.id,
40
+ value: value.value,
41
+ rank: value.rank,
42
+ metadata: value.metadata,
43
+ })),
44
+ },
45
+ });
46
+
47
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
48
+ toast.success("Attribute updated!");
49
+ } catch (error) {
50
+ toast.error((error as Error).message);
51
+ }
52
+ };
53
+
54
+ const handleToggleGlobal = async (attributeId: string, newValue: boolean, attribute: Attribute) => {
55
+ const isCurrentlyGlobal = !attribute.product_categories?.length;
56
+
57
+ if (isCurrentlyGlobal && !newValue) {
58
+ setCurrentAttribute(attribute);
59
+ setIsCategoryModalOpen(true);
60
+ return;
61
+ }
62
+
63
+ if (!isCurrentlyGlobal && newValue) {
64
+ try {
65
+ await medusaClient.client.fetch(`/admin/plugin/attributes/${attributeId}`, {
66
+ method: "POST",
67
+ body: {
68
+ name: attribute.name,
69
+ description: attribute.description,
70
+ handle: attribute.handle,
71
+ is_filterable: attribute.is_filterable,
72
+ // is_required: attribute.is_required,
73
+ ui_component: attribute.ui_component,
74
+ product_category_ids: [],
75
+ possible_values: attribute.possible_values?.map((value) => ({
76
+ id: value.id,
77
+ value: value.value,
78
+ rank: value.rank,
79
+ metadata: value.metadata ?? {},
80
+ })),
81
+ },
82
+ });
83
+
84
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
85
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.detail(attributeId) });
86
+ toast.success("Attribute updated!");
87
+ } catch (error) {
88
+ toast.error((error as Error).message);
89
+ }
90
+ return;
91
+ }
92
+ };
93
+
94
+ const handleCategorySelection = async (selectedCategories: string[]) => {
95
+ if (!currentAttribute) return;
96
+
97
+ try {
98
+ await medusaClient.client.fetch(`/admin/plugin/attributes/${currentAttribute.id}`, {
99
+ method: "POST",
100
+ body: {
101
+ name: currentAttribute.name,
102
+ description: currentAttribute.description,
103
+ handle: currentAttribute.handle,
104
+ is_filterable: currentAttribute.is_filterable,
105
+ // is_required: currentAttribute.is_required,
106
+ ui_component: currentAttribute.ui_component,
107
+ product_category_ids: selectedCategories,
108
+ possible_values: currentAttribute.possible_values?.map((value) => ({
109
+ id: value.id,
110
+ value: value.value,
111
+ rank: value.rank,
112
+ metadata: value.metadata,
113
+ })),
114
+ },
115
+ });
116
+
117
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
118
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.detail(currentAttribute.id) });
119
+ toast.success("Attribute updated!");
120
+ } catch (error) {
121
+ toast.error((error as Error).message);
122
+ } finally {
123
+ setCurrentAttribute(null);
124
+ setIsCategoryModalOpen(false);
125
+ }
126
+ };
127
+
128
+ const handleDelete = async (attributeId: string, attributeName: string) => {
129
+ const confirmed = await prompt({
130
+ title: "Delete Attribute",
131
+ description: `You are about to delete attribute ${attributeName}. This action cannot be undone.`,
132
+ confirmText: "Delete",
133
+ cancelText: "Cancel",
134
+ });
135
+
136
+ if (!confirmed) {
137
+ return;
138
+ }
139
+
140
+ try {
141
+ await medusaClient.client.fetch(`/admin/plugin/attributes/${attributeId}`, {
142
+ method: "DELETE",
143
+ });
144
+ toast.success("Attribute deleted!");
145
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
146
+ } catch (error) {
147
+ toast.error((error as Error).message);
148
+ }
149
+ };
150
+
151
+ const columns = useMemo(
152
+ () => [
153
+ columnHelper.accessor("name", {
154
+ header: "Name",
155
+ }),
156
+ columnHelper.accessor("handle", {
157
+ header: "Handle",
158
+ }),
159
+ columnHelper.accessor("is_filterable", {
160
+ header: "Filterable",
161
+ cell: (info) => {
162
+ const attribute = info.row.original;
163
+ return (
164
+ <div onClick={(e) => e.stopPropagation()}>
165
+ <Switch
166
+ checked={info.getValue()}
167
+ onCheckedChange={() =>
168
+ handleToggleFilterable(
169
+ attribute.id,
170
+ info.getValue(),
171
+ attribute
172
+ )
173
+ }
174
+ />
175
+ </div>
176
+ );
177
+ },
178
+ }),
179
+ columnHelper.accessor("product_categories", {
180
+ header: "Global",
181
+ cell: (info) => {
182
+ const attribute = info.row.original;
183
+ const isGlobal = !info.getValue()?.length;
184
+ return (
185
+ <Switch
186
+ checked={isGlobal}
187
+ onCheckedChange={(newValue) =>
188
+ handleToggleGlobal(attribute.id, newValue, attribute)
189
+ }
190
+ onClick={(e) => e.stopPropagation()}
191
+ />
192
+ );
193
+ },
194
+ }),
195
+ columnHelper.accessor("possible_values", {
196
+ header: "Possible Values",
197
+ cell: (info) => {
198
+ const values = info.getValue();
199
+ if (!values || values.length === 0) {
200
+ return "-";
201
+ }
202
+
203
+ if (values.length <= 3) {
204
+ return (
205
+ <div className="flex flex-wrap gap-2">
206
+ {values.map((value) => (
207
+ <Badge size="xsmall" key={value.id}>
208
+ {value.value}
209
+ </Badge>
210
+ ))}
211
+ </div>
212
+ );
213
+ }
214
+
215
+ const remainingCount = values.length - 2;
216
+ return (
217
+ <div className="flex flex-wrap gap-2">
218
+ {values.slice(0, 2).map((value) => (
219
+ <Badge size="xsmall" key={value.id}>
220
+ {value.value}
221
+ </Badge>
222
+ ))}
223
+ <Badge size="xsmall" color="grey">
224
+ +{remainingCount}
225
+ </Badge>
226
+ </div>
227
+ );
228
+ },
229
+ }),
230
+ columnHelper.display({
231
+ id: "actions",
232
+ header: "",
233
+ cell: (info) => {
234
+ const attribute = info.row.original;
235
+ return (
236
+ <div className="flex items-center justify-end">
237
+ <DropdownMenu>
238
+ <DropdownMenu.Trigger asChild>
239
+ <IconButton variant="transparent" size="small">
240
+ <EllipsisHorizontal />
241
+ </IconButton>
242
+ </DropdownMenu.Trigger>
243
+ <DropdownMenu.Content align="end">
244
+ <DropdownMenu.Item onClick={(e) => {
245
+ e.stopPropagation()
246
+ handleEdit(attribute.id)
247
+ }}>
248
+ <span className="flex items-center gap-2">
249
+ <PencilSquare /> Edit
250
+ </span>
251
+ </DropdownMenu.Item>
252
+ <DropdownMenu.Item onClick={(e) => {
253
+ e.stopPropagation(),
254
+ handleDelete(attribute.id, attribute.name)
255
+ }}>
256
+ <span className="flex items-center gap-2">
257
+ <Trash /> Delete
258
+ </span>
259
+ </DropdownMenu.Item>
260
+ </DropdownMenu.Content>
261
+ </DropdownMenu>
262
+ </div>
263
+ );
264
+ },
265
+ }),
266
+ ],
267
+ [navigate, queryClient, prompt, handleToggleFilterable, handleToggleGlobal]
268
+ );
269
+
270
+ return {
271
+ columns,
272
+ modal: (
273
+ <CategorySelectionModal
274
+ open={isCategoryModalOpen}
275
+ onOpenChange={setIsCategoryModalOpen}
276
+ onConfirm={handleCategorySelection}
277
+ />
278
+ )
279
+ };
280
+ };
@@ -0,0 +1,11 @@
1
+ export type SingleColumnLayoutProps = {
2
+ children: React.ReactNode
3
+ }
4
+
5
+ export const SingleColumnLayout = ({ children }: SingleColumnLayoutProps) => {
6
+ return (
7
+ <div className="flex flex-col gap-y-3">
8
+ {children}
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,8 @@
1
+ import Medusa from "@medusajs/js-sdk"
2
+
3
+ export const medusaClient = new Medusa({
4
+ baseUrl: __BACKEND_URL__,
5
+ auth: {
6
+ type: 'session'
7
+ }
8
+ })