@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,53 @@
1
+ import { QueryKey, UseQueryOptions } from "@tanstack/react-query"
2
+
3
+ export type TQueryKey<TKey, TListQuery = any, TDetailQuery = string> = {
4
+ all: readonly [TKey]
5
+ lists: () => readonly [...TQueryKey<TKey>["all"], "list"]
6
+ list: (
7
+ query?: TListQuery
8
+ ) => readonly [...ReturnType<TQueryKey<TKey>["lists"]>, { query: TListQuery }]
9
+ details: () => readonly [...TQueryKey<TKey>["all"], "detail"]
10
+ detail: (
11
+ id: TDetailQuery,
12
+ query?: TListQuery
13
+ ) => readonly [
14
+ ...ReturnType<TQueryKey<TKey>["details"]>,
15
+ TDetailQuery,
16
+ { query: TListQuery },
17
+ ]
18
+ }
19
+
20
+ export type UseQueryOptionsWrapper<
21
+ // Return type of queryFn
22
+ TQueryFn = unknown,
23
+ // Type thrown in case the queryFn rejects
24
+ E = Error,
25
+ // Query key type
26
+ TQueryKey extends QueryKey = QueryKey,
27
+ > = Omit<
28
+ UseQueryOptions<TQueryFn, E, TQueryFn, TQueryKey>,
29
+ "queryKey" | "queryFn"
30
+ >
31
+
32
+ export const queryKeysFactory = <
33
+ T,
34
+ TListQueryType = any,
35
+ TDetailQueryType = string,
36
+ >(
37
+ globalKey: T
38
+ ) => {
39
+ const queryKeyFactory: TQueryKey<T, TListQueryType, TDetailQueryType> = {
40
+ all: [globalKey],
41
+ lists: () => [...queryKeyFactory.all, "list"],
42
+ list: (query?: TListQueryType) =>
43
+ [...queryKeyFactory.lists(), query ? { query } : undefined].filter(
44
+ (k) => !!k
45
+ ),
46
+ details: () => [...queryKeyFactory.all, "detail"],
47
+ detail: (id: TDetailQueryType, query?: TListQueryType) =>
48
+ [...queryKeyFactory.details(), id, query ? { query } : undefined].filter(
49
+ (k) => !!k
50
+ ),
51
+ }
52
+ return queryKeyFactory
53
+ }
@@ -0,0 +1,133 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import { FocusModal, Heading, Button, toast, ProgressTabs } from "@medusajs/ui";
3
+ import { useNavigate, useParams } from "react-router-dom";
4
+ import { medusaClient } from "../../../../lib/config";
5
+ import { useEffect, useState } from "react";
6
+ import { AdminProductCategory } from "@medusajs/types";
7
+ import {
8
+ AttributeForm,
9
+ CreateAttributeFormSchema,
10
+ } from "../../components/AttributeForm";
11
+ import { z } from "zod";
12
+ import {
13
+ attributeQueryKeys,
14
+ useAttribute,
15
+ useUpdateAttribute,
16
+ } from "../../../../hooks/api/attributes";
17
+ import { useQueryClient } from "@tanstack/react-query";
18
+
19
+ const EditAttributePage = () => {
20
+ const navigate = useNavigate();
21
+ const { id } = useParams();
22
+ const [categories, setCategories] = useState<AdminProductCategory[]>([]);
23
+ const [activeTab, setActiveTab] = useState<"details" | "type">("details");
24
+ const [tabStatuses, setTabStatuses] = useState<{
25
+ detailsStatus: "not-started" | "in-progress" | "completed";
26
+ typeStatus: "not-started" | "in-progress" | "completed";
27
+ }>({
28
+ detailsStatus: "completed", // Edit mode starts with completed status
29
+ typeStatus: "completed", // Edit mode starts with completed status
30
+ });
31
+ const queryClient = useQueryClient();
32
+
33
+ const { attribute, isLoading } = useAttribute(
34
+ id ?? "",
35
+ {
36
+ fields:
37
+ "name,description,handle,is_variant_defining,is_filterable,ui_component,product_categories.name,possible_values.*",
38
+ },
39
+ { enabled: !!id }
40
+ );
41
+
42
+ const { mutateAsync } = useUpdateAttribute(id!);
43
+
44
+ useEffect(() => {
45
+ const fetchCategories = async () => {
46
+ try {
47
+ const response = await medusaClient.admin.productCategory.list();
48
+ setCategories(response.product_categories);
49
+ } catch (error) {
50
+ console.error("Failed to fetch categories:", error);
51
+ }
52
+ };
53
+ fetchCategories();
54
+ }, []);
55
+
56
+ const handleSave = async (
57
+ data: z.infer<typeof CreateAttributeFormSchema>
58
+ ) => {
59
+ const { ...payload } = data;
60
+ await mutateAsync(payload, {
61
+ onSuccess: () => {
62
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.lists() });
63
+ toast.success("Attribute updated!");
64
+ navigate(-1);
65
+ },
66
+ onError: (error) => {
67
+ toast.error((error as Error).message);
68
+ console.error(error);
69
+ },
70
+ });
71
+ };
72
+
73
+ const handleClose = () => {
74
+ navigate(-1);
75
+ };
76
+
77
+ if (isLoading || !attribute) {
78
+ return null;
79
+ }
80
+
81
+ return (
82
+ <FocusModal
83
+ open={true}
84
+ onOpenChange={(open) => {
85
+ if (!open) handleClose();
86
+ }}
87
+ >
88
+ <FocusModal.Content>
89
+ <ProgressTabs value={activeTab} onValueChange={(value) => setActiveTab(value as "details" | "type")} className="w-full h-full">
90
+ <FocusModal.Header className="flex items-center justify-between w-full py-0 h-fit">
91
+ <div className="w-full border-l h-full">
92
+ <ProgressTabs.List className="justify-start flex w-full items-center">
93
+ <ProgressTabs.Trigger value="details" status={tabStatuses.detailsStatus}>
94
+ Details
95
+ </ProgressTabs.Trigger>
96
+ <ProgressTabs.Trigger value="type" status={tabStatuses.typeStatus}>
97
+ Type
98
+ </ProgressTabs.Trigger>
99
+ </ProgressTabs.List>
100
+ </div>
101
+ </FocusModal.Header>
102
+ <FocusModal.Body className="flex flex-col items-center py-16">
103
+ <div>
104
+ <AttributeForm
105
+ initialData={attribute}
106
+ //@ts-expect-error correct data type will be received here
107
+ onSubmit={handleSave}
108
+ onCancel={handleClose}
109
+ categories={categories}
110
+ activeTab={activeTab}
111
+ onFormStateChange={setTabStatuses}
112
+ />
113
+ </div>
114
+ </FocusModal.Body>
115
+ </ProgressTabs>
116
+ <FocusModal.Footer>
117
+ <Button variant="secondary" onClick={handleClose}>
118
+ Cancel
119
+ </Button>
120
+ <Button type="submit" form="attribute-form">
121
+ Save
122
+ </Button>
123
+ </FocusModal.Footer>
124
+ </FocusModal.Content>
125
+ </FocusModal>
126
+ );
127
+ };
128
+
129
+ export const config = defineRouteConfig({
130
+ label: "Edit Attribute",
131
+ });
132
+
133
+ export default EditAttributePage;
@@ -0,0 +1,174 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk"
2
+ import { Drawer, Heading, Text, Button, Input, toast, Label } from "@medusajs/ui"
3
+ import { useQuery } from "@tanstack/react-query"
4
+ import { useParams, useSearchParams, useNavigate } from "react-router-dom"
5
+ import { medusaClient } from "../../../../lib/config"
6
+ import { Attribute } from "../../../../../types/attribute/http/attribute"
7
+ import { useEffect } from "react"
8
+ import { useForm } from "react-hook-form"
9
+ import { zodResolver } from "@hookform/resolvers/zod"
10
+ import { z } from "zod"
11
+ import { MetadataEditor } from "../../../../components/metadata-editor"
12
+ import { useAttribute, useUpdateAttributePossibleValue } from "../../../../hooks/api/attributes"
13
+
14
+ const formSchema = z.object({
15
+ value: z.string().min(1, "Value is required"),
16
+ rank: z.preprocess(
17
+ (val) => (val === "" ? undefined : Number(val)),
18
+ z.number().min(0, "Rank must be non-negative").optional()
19
+ ),
20
+ metadata: z.array(z.object({
21
+ key: z.string(),
22
+ value: z.string(),
23
+ })).default([]),
24
+ })
25
+
26
+ type FormValues = z.infer<typeof formSchema>
27
+
28
+ const EditPossibleValuePage = () => {
29
+ const { id: attributeId } = useParams()
30
+ const [searchParams] = useSearchParams()
31
+ const navigate = useNavigate()
32
+ const possibleValueId = searchParams.get("possible_value_id")
33
+
34
+ const { attribute, isLoading: isAttributeLoading } = useAttribute(attributeId!, {
35
+ fields: 'possible_values.*'
36
+ }, {
37
+ enabled: !!attributeId,
38
+ })
39
+
40
+ const { mutateAsync, isPending } = useUpdateAttributePossibleValue(attributeId!, possibleValueId!)
41
+
42
+ const possibleValue = attribute?.possible_values?.find((pv: { id: string }) => pv.id === possibleValueId)
43
+
44
+ const form = useForm<FormValues>({
45
+ resolver: zodResolver(formSchema),
46
+ defaultValues: {
47
+ value: "",
48
+ rank: undefined,
49
+ metadata: [],
50
+ },
51
+ })
52
+
53
+ useEffect(() => {
54
+ if (possibleValue) {
55
+ const metadataArray = Object.entries(possibleValue.metadata || {}).map(([key, value]) => ({ key, value: String(value) }))
56
+ form.reset({
57
+ value: possibleValue.value,
58
+ rank: possibleValue.rank,
59
+ metadata: metadataArray.length > 0 ? metadataArray : [{ key: "", value: "" }],
60
+ })
61
+ }
62
+ }, [possibleValue, form])
63
+
64
+ const handleSave = form.handleSubmit(async (data) => {
65
+ const transformedMetadata = data.metadata.reduce((acc, item) => {
66
+ // Only include valid key-value pairs where both key and value are non-empty
67
+ if (item.key.trim() !== "" && item.value.trim() !== "") {
68
+ acc[item.key] = item.value;
69
+ }
70
+ return acc;
71
+ }, {} as Record<string, unknown>);
72
+
73
+ await mutateAsync(
74
+ {
75
+ value: data.value,
76
+ rank: data.rank,
77
+ metadata:
78
+ Object.keys(transformedMetadata).length > 0
79
+ ? transformedMetadata
80
+ : null,
81
+ },
82
+ {
83
+ onSuccess: () => {
84
+ toast.success("Possible value updated!");
85
+ navigate(-1);
86
+ },
87
+ onError: (error) => {
88
+ toast.error("Failed to update possible value");
89
+ console.error(error);
90
+ },
91
+ }
92
+ );
93
+ });
94
+
95
+ const handleClose = () => {
96
+ navigate(`/attributes/${attributeId}`)
97
+ }
98
+
99
+ return (
100
+ <Drawer open={true} onOpenChange={(open) => { if (!open) handleClose() }}>
101
+ <Drawer.Content>
102
+ {isAttributeLoading ? (
103
+ <>
104
+ <Drawer.Header>
105
+ <Heading>Loading...</Heading>
106
+ </Drawer.Header>
107
+ <Drawer.Body>
108
+ <Text>Fetching possible value details...</Text>
109
+ </Drawer.Body>
110
+ </>
111
+ ) : !possibleValue ? (
112
+ <>
113
+ <Drawer.Header>
114
+ <Heading>Possible Value Not Found</Heading>
115
+ </Drawer.Header>
116
+ <Drawer.Body>
117
+ <Text>The requested possible value could not be found.</Text>
118
+ <Button onClick={handleClose}>Close</Button>
119
+ </Drawer.Body>
120
+ </>
121
+ ) : (
122
+ <>
123
+ <Drawer.Header>
124
+ <Drawer.Title>Edit Possible Value</Drawer.Title>
125
+ </Drawer.Header>
126
+ <Drawer.Body>
127
+ <form id="edit-possible-value-form" onSubmit={handleSave}>
128
+ <div className="grid gap-4">
129
+ <div>
130
+ <Label htmlFor="value">Value</Label>
131
+ <Input
132
+ id="value"
133
+ {...form.register("value")}
134
+ />
135
+ {form.formState.errors.value && (
136
+ <Text className="text-red-500 text-sm mt-1">
137
+ {form.formState.errors.value.message}
138
+ </Text>
139
+ )}
140
+ </div>
141
+ <div>
142
+ <Label htmlFor="rank">Rank</Label>
143
+ <Input
144
+ id="rank"
145
+ type="number"
146
+ {...form.register("rank", { valueAsNumber: true })}
147
+ />
148
+ {form.formState.errors.rank && (
149
+ <Text className="text-red-500 text-sm mt-1">
150
+ {form.formState.errors.rank.message}
151
+ </Text>
152
+ )}
153
+ </div>
154
+
155
+ <MetadataEditor form={form} />
156
+ </div>
157
+ </form>
158
+ </Drawer.Body>
159
+ <Drawer.Footer>
160
+ <Button variant="secondary" onClick={handleClose} disabled={!!isPending}>Cancel</Button>
161
+ <Button type="submit" form="edit-possible-value-form" disabled={!!isPending}>Save</Button>
162
+ </Drawer.Footer>
163
+ </>
164
+ )}
165
+ </Drawer.Content>
166
+ </Drawer>
167
+ )
168
+ }
169
+
170
+ export const config = defineRouteConfig({
171
+
172
+ })
173
+
174
+ export default EditPossibleValuePage
@@ -0,0 +1,127 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import {
3
+ Container,
4
+ Heading,
5
+ Text,
6
+ toast,
7
+ DropdownMenu,
8
+ Button,
9
+ Badge,
10
+ } from "@medusajs/ui";
11
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
12
+ import { useParams, useNavigate } from "react-router-dom";
13
+ import { medusaClient } from "../../../lib/config";
14
+ import { Attribute } from "../../../../types/attribute/http/attribute";
15
+ import { EllipsisHorizontal } from "@medusajs/icons";
16
+ import { SectionRow } from "../../../components/section-row";
17
+ import { PossibleValuesTable } from "../components/possible-values-table";
18
+ import { SingleColumnLayout } from "../../../layouts/single-column";
19
+ import { attributeQueryKeys, useAttribute } from "../../../hooks/api/attributes";
20
+
21
+ const AttributeDetailPage = () => {
22
+ const { id } = useParams();
23
+ const navigate = useNavigate();
24
+
25
+ const queryClient = useQueryClient()
26
+
27
+ const { attribute, isLoading } = useAttribute(id ?? "", {
28
+ fields: 'name, description, handle, product_categories.name, possible_values.*, is_filterable, ui_component'
29
+ }, { enabled: !!id })
30
+
31
+ if (isLoading) {
32
+ return (
33
+ <Container>
34
+ <div className="flex items-center justify-center h-[200px]">
35
+ <Text>Loading...</Text>
36
+ </div>
37
+ </Container>
38
+ );
39
+ }
40
+
41
+ if (!attribute) {
42
+ return (
43
+ <Container>
44
+ <div className="flex items-center justify-center h-[200px]">
45
+ <Text>Attribute not found</Text>
46
+ </div>
47
+ </Container>
48
+ );
49
+ }
50
+
51
+ const handleEdit = () => {
52
+ navigate(`/attributes/${id}/edit`);
53
+ };
54
+
55
+ const handleDelete = async () => {
56
+ try {
57
+ await medusaClient.client.fetch(`/admin/plugin/attributes/${id}`, {
58
+ method: "DELETE",
59
+ });
60
+ toast.success("Attribute deleted!");
61
+ queryClient.invalidateQueries({ queryKey: attributeQueryKeys.list() });
62
+ navigate("/attributes");
63
+ } catch (error) {
64
+ toast.error((error as Error).message);
65
+ }
66
+ };
67
+
68
+ return (
69
+ <SingleColumnLayout>
70
+ <Container className="divide-y p-0">
71
+ <div className="flex items-center justify-between px-6 py-4">
72
+ <Heading level="h2">{attribute.name}</Heading>
73
+ <div className="flex items-center gap-2">
74
+ <DropdownMenu>
75
+ <DropdownMenu.Trigger asChild>
76
+ <Button variant="transparent" size="small">
77
+ <EllipsisHorizontal />
78
+ </Button>
79
+ </DropdownMenu.Trigger>
80
+ <DropdownMenu.Content align="end">
81
+ <DropdownMenu.Item onClick={handleEdit}>
82
+ Edit
83
+ </DropdownMenu.Item>
84
+ <DropdownMenu.Item onClick={handleDelete}>
85
+ Delete
86
+ </DropdownMenu.Item>
87
+ </DropdownMenu.Content>
88
+ </DropdownMenu>
89
+ </div>
90
+ </div>
91
+
92
+ <SectionRow title="Description" value={attribute.description} />
93
+ <SectionRow title="Handle" value={attribute.handle} />
94
+ <SectionRow title="UI Component" value={attribute.ui_component} />
95
+ <SectionRow title="Filterable" value={attribute.is_filterable ? "True" : "False"} />
96
+ <SectionRow
97
+ title="Global"
98
+ value={!attribute.product_categories?.length ? "True" : "False"}
99
+ />
100
+
101
+ {attribute.product_categories &&
102
+ attribute.product_categories.length > 0 && (
103
+ <SectionRow
104
+ title="Product Categories"
105
+ value={
106
+ <>
107
+ {attribute.product_categories.map((category) => (
108
+ <Badge size="xsmall" key={category.id}>
109
+ {category.name}
110
+ </Badge>
111
+ ))}
112
+ </>
113
+ }
114
+ />
115
+ )}
116
+ </Container>
117
+
118
+ <PossibleValuesTable attribute={attribute} isLoading={isLoading} />
119
+ </SingleColumnLayout>
120
+ );
121
+ };
122
+
123
+ export const config = defineRouteConfig({
124
+ label: "Attribute Details",
125
+ });
126
+
127
+ export default AttributeDetailPage;