Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b6c5b4b
feat(dashboard,js-sdk): product option redesign (client-side)
willbouch Oct 28, 2025
27bd873
support create
willbouch Oct 29, 2025
855f0e3
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Oct 29, 2025
0d08830
edit option and values rank
willbouch Oct 29, 2025
c7af4df
change bade
willbouch Oct 30, 2025
313d79c
select from options at product creation
willbouch Oct 31, 2025
62824f6
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Oct 31, 2025
019525c
create product with link to option
willbouch Oct 31, 2025
6fae878
edit option from product page
willbouch Oct 31, 2025
394de22
link and unlink option to product
willbouch Oct 31, 2025
49ce697
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Oct 31, 2025
0a0ec0b
missed the js sdk in last commit
willbouch Oct 31, 2025
e0b0aa7
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 5, 2025
c14b865
small fix when removing a value
willbouch Nov 5, 2025
dc20544
Create shaggy-parents-guess.md
willbouch Nov 5, 2025
4dd31a9
pr comments
willbouch Nov 6, 2025
9db6646
pr comments
willbouch Nov 6, 2025
c88433d
pr comments
willbouch Nov 6, 2025
7802bad
is string
willbouch Nov 6, 2025
b6e7b6d
pr comments
willbouch Nov 6, 2025
21cdc39
query graph
willbouch Nov 6, 2025
2fdf3ae
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 6, 2025
0ce0b3b
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 6, 2025
7d6bede
fix pipeline
willbouch Nov 6, 2025
a50b4d7
woops
willbouch Nov 6, 2025
30af643
todo
willbouch Nov 6, 2025
641e6c6
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 10, 2025
18af9a5
better table
willbouch Nov 10, 2025
1fab903
review
willbouch Nov 14, 2025
6bb8b6a
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 14, 2025
50859ed
Merge branch 'chore/add-many-to-many-between-product-and-option' into…
willbouch Nov 14, 2025
91de56f
changeset
willbouch Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/shaggy-parents-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/admin-shared": minor
"@medusajs/dashboard": minor
---

feat(dashboard,admin-shared): product option redesign (client-side)
10 changes: 10 additions & 0 deletions packages/admin/admin-shared/src/extensions/widgets/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ const PRODUCT_CATEGORY_INJECTION_ZONES = [
"product_category.list.after",
] as const

const PRODUCT_OPTION_INJECTION_ZONES = [
"product_option.details.before",
"product_option.details.after",
"product_option.details.side.before",
"product_option.details.side.after",
"product_option.list.before",
"product_option.list.after",
] as const

const SHIPPING_OPTION_TYPE_INJECTION_ZONES = [
"shipping_option_type.details.before",
"shipping_option_type.details.after",
Expand Down Expand Up @@ -216,6 +225,7 @@ export const INJECTION_ZONES = [
...PRODUCT_COLLECTION_INJECTION_ZONES,
...PRODUCT_CATEGORY_INJECTION_ZONES,
...PRODUCT_TYPE_INJECTION_ZONES,
...PRODUCT_OPTION_INJECTION_ZONES,
...SHIPPING_OPTION_TYPE_INJECTION_ZONES,
...PRODUCT_TAG_INJECTION_ZONES,
...PRICE_LIST_INJECTION_ZONES,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
BuildingStorefront,
Buildings,
BuildingStorefront,
ChevronDownMini,
CogSixTooth,
CurrencyDollar,
Expand All @@ -14,7 +14,7 @@ import {
Tag,
Users,
} from "@medusajs/icons"
import { Avatar, Divider, DropdownMenu, Text, clx } from "@medusajs/ui"
import { Avatar, clx, Divider, DropdownMenu, Text } from "@medusajs/ui"
import { Collapsible as RadixCollapsible } from "radix-ui"
import { useTranslation } from "react-i18next"

Expand Down Expand Up @@ -107,8 +107,7 @@ const Header = () => {

return (
<div className="w-full p-3">
<DropdownMenu
dir={direction}>
<DropdownMenu dir={direction}>
<DropdownMenu.Trigger
disabled={!isLoaded}
className={clx(
Expand Down Expand Up @@ -206,6 +205,10 @@ const useCoreRoutes = (): Omit<INavItem, "pathname">[] => {
label: t("categories.domain"),
to: "/categories",
},
{
label: t("productOptions.domain"),
to: "/product-options",
},
// TODO: Enable when domin is introduced
// {
// label: t("giftCards.domain"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,6 @@ export function getRouteMap({
lazy: () =>
import("../../routes/products/product-prices"),
},
{
path: "options/create",
lazy: () =>
import(
"../../routes/products/product-create-option"
),
},
{
path: "options/:option_id/edit",
lazy: () =>
import("../../routes/products/product-edit-option"),
},
{
path: "variants/create",
lazy: () =>
Expand All @@ -165,6 +153,13 @@ export function getRouteMap({
lazy: () =>
import("../../routes/products/product-metadata"),
},
{
path: "options/manage",
lazy: () =>
import(
"../../routes/products/product-options-manage"
),
},
],
},
{
Expand Down Expand Up @@ -225,6 +220,63 @@ export function getRouteMap({
},
],
},
{
path: "/product-options",
errorElement: <ErrorBoundary />,
handle: {
breadcrumb: () => t("productOptions.domain"),
},
children: [
{
path: "",
lazy: () =>
import("../../routes/product-options/product-option-list"),
children: [
{
path: "create",
lazy: () =>
import(
"../../routes/product-options/product-option-create"
),
},
],
},
{
path: ":id",
lazy: async () => {
const { Component, Breadcrumb, loader } = await import(
"../../routes/product-options/product-option-detail"
)

return {
Component,
loader,
handle: {
breadcrumb: (match: UIMatch<any>) => (
<Breadcrumb {...match} />
),
},
}
},
children: [
{
path: "edit",
lazy: () =>
import(
"../../routes/product-options/product-option-edit"
),
},
{
path: "metadata/edit",
lazy: () =>
import(
"../../routes/product-options/product-option-metadata"
),
},
],
},
],
},
{
path: "/categories",
errorElement: <ErrorBoundary />,
Expand Down
1 change: 1 addition & 0 deletions packages/admin/dashboard/src/hooks/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from "./payment-collections"
export * from "./payments"
export * from "./plugins"
export * from "./price-lists"
export * from "./product-options"
export * from "./product-types"
export * from "./product-variants"
export * from "./products"
Expand Down
151 changes: 151 additions & 0 deletions packages/admin/dashboard/src/hooks/api/product-options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { FetchError } from "@medusajs/js-sdk"
import {
QueryKey,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions,
} from "@tanstack/react-query"
import { sdk } from "../../lib/client"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { HttpTypes } from "@medusajs/types"
import { queryClient } from "../../lib/query-client.ts"

const PRODUCT_OPTIONS_QUERY_KEY = "product_options" as const
export const productOptionsQueryKeys = queryKeysFactory(
PRODUCT_OPTIONS_QUERY_KEY
)

export const useProductOption = (
id: string,
query?: HttpTypes.AdminProductOptionParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminProductOptionResponse,
FetchError,
HttpTypes.AdminProductOptionResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryKey: productOptionsQueryKeys.detail(id, query),
queryFn: () => sdk.admin.productOption.retrieve(id, query),
...options,
})

return { ...data, ...rest }
}

export const useProductOptions = (
query?: HttpTypes.AdminProductOptionListParams,
options?: Omit<
UseQueryOptions<
HttpTypes.AdminProductOptionListResponse,
FetchError,
HttpTypes.AdminProductOptionListResponse,
QueryKey
>,
"queryFn" | "queryKey"
>
) => {
const { data, ...rest } = useQuery({
queryFn: () => sdk.admin.productOption.list(query),
queryKey: productOptionsQueryKeys.list(query),
...options,
})

return { ...data, ...rest }
}

export const useCreateProductOption = (
options?: UseMutationOptions<
HttpTypes.AdminProductOptionResponse,
FetchError,
HttpTypes.AdminCreateProductOption
>
) => {
return useMutation({
mutationFn: (payload) => sdk.admin.productOption.create(payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.lists(),
})

options?.onSuccess?.(data, variables, context)
},
...options,
})
}

export const useUpdateProductOption = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminProductOptionResponse,
FetchError,
HttpTypes.AdminUpdateProductOption
>
) => {
return useMutation({
mutationFn: (payload) => sdk.admin.productOption.update(id, payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.detail(id),
})

options?.onSuccess?.(data, variables, context)
},
...options,
})
}

export const useDeleteProductOption = (
id: string,
options?: UseMutationOptions<
HttpTypes.AdminProductOptionDeleteResponse,
FetchError,
void
>
) => {
return useMutation({
mutationFn: () => sdk.admin.productOption.delete(id),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.detail(id),
})

options?.onSuccess?.(data, variables, context)
},
...options,
})
}

export const useDeleteProductOptionLazy = (
options?: UseMutationOptions<
HttpTypes.AdminProductOptionDeleteResponse,
FetchError,
string
>
) => {
return useMutation({
mutationFn: (id: string) => sdk.admin.productOption.delete(id),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.lists(),
})
queryClient.invalidateQueries({
queryKey: productOptionsQueryKeys.details(),
})

options?.onSuccess?.(data, variables, context)
},
...options,
})
}
Loading