diff --git a/packages/admin-next/dashboard/public/locales/en-US/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json
index cfc472c920..a6cfc68e25 100644
--- a/packages/admin-next/dashboard/public/locales/en-US/translation.json
+++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json
@@ -241,6 +241,7 @@
"options": {
"label": "Product options",
"hint": "Options are used to define the color, size, etc. of the product",
+ "add": "Add option",
"optionTitle": "Option title",
"variations": "Variations (comma-separated)"
},
diff --git a/packages/admin-next/dashboard/src/components/common/keypair/index.tsx b/packages/admin-next/dashboard/src/components/common/keypair/index.tsx
new file mode 100644
index 0000000000..5215aa1988
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/common/keypair/index.tsx
@@ -0,0 +1 @@
+export * from "./keypair"
diff --git a/packages/admin-next/dashboard/src/components/common/keypair/keypair.tsx b/packages/admin-next/dashboard/src/components/common/keypair/keypair.tsx
new file mode 100644
index 0000000000..160b02000d
--- /dev/null
+++ b/packages/admin-next/dashboard/src/components/common/keypair/keypair.tsx
@@ -0,0 +1,149 @@
+import { Plus, Trash } from "@medusajs/icons"
+import { Button, Input, Table } from "@medusajs/ui"
+import { useState } from "react"
+
+interface KeyPair {
+ key: string
+ value: string
+}
+
+export interface KeypairProps {
+ labels: {
+ add: string
+ key?: string
+ value?: string
+ }
+ value: KeyPair[]
+ onChange: (value: KeyPair[]) => void
+ disabled?: boolean
+}
+
+export const Keypair = ({ labels, onChange, value }: KeypairProps) => {
+ const addKeyPair = () => {
+ onChange([...value, { key: ``, value: `` }])
+ }
+
+ const deleteKeyPair = (index: number) => {
+ return () => {
+ onChange(value.filter((_, i) => i !== index))
+ }
+ }
+
+ const onKeyChange = (index: number) => {
+ return (key: string) => {
+ const newArr = value.map((pair, i) => {
+ if (i === index) {
+ return { key, value: pair.value }
+ }
+ return pair
+ })
+
+ onChange(newArr)
+ }
+ }
+
+ const onValueChange = (index: number) => {
+ return (val: string) => {
+ const newArr = value.map((pair, i) => {
+ if (i === index) {
+ return { key: pair.key, value: val }
+ }
+ return pair
+ })
+
+ onChange(newArr)
+ }
+ }
+
+ return (
+
+
+
+
+ {labels.key}
+ {labels.value}
+
+
+
+ {value.map((pair, index) => {
+ return (
+
+ )
+ })}
+
+
+
+
+ )
+}
+
+type FieldProps = {
+ field: KeyPair
+ labels: {
+ key?: string
+ value?: string
+ }
+ updateKey: (key: string) => void
+ updateValue: (value: string) => void
+ onDelete: () => void
+}
+
+const Field: React.FC = ({
+ field,
+ updateKey,
+ updateValue,
+ onDelete,
+}) => {
+ const [key, setKey] = useState(field.key)
+ const [value, setValue] = useState(field.value)
+
+ return (
+
+
+ updateKey(key)}
+ value={key}
+ onChange={(e) => {
+ setKey(e.currentTarget.value)
+ }}
+ />
+
+
+ updateValue(value)}
+ value={value}
+ onChange={(e) => {
+ setValue(e.currentTarget.value)
+ }}
+ />
+
+
+
+
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/components/common/list/list.tsx b/packages/admin-next/dashboard/src/components/common/list/list.tsx
index 4817c4cb8c..a058177d05 100644
--- a/packages/admin-next/dashboard/src/components/common/list/list.tsx
+++ b/packages/admin-next/dashboard/src/components/common/list/list.tsx
@@ -16,7 +16,7 @@ export const List = ({
disabled,
}: ListProps) => {
if (options.length === 0) {
- return No options
+ return null
}
return (
diff --git a/packages/admin-next/dashboard/src/hooks/api/products.tsx b/packages/admin-next/dashboard/src/hooks/api/products.tsx
index a84209c591..00cb49b1b4 100644
--- a/packages/admin-next/dashboard/src/hooks/api/products.tsx
+++ b/packages/admin-next/dashboard/src/hooks/api/products.tsx
@@ -17,6 +17,63 @@ import { queryClient } from "../../lib/medusa"
const PRODUCTS_QUERY_KEY = "products" as const
export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY)
+const VARIANTS_QUERY_KEY = "product_variants" as const
+export const variantsQueryKeys = queryKeysFactory(VARIANTS_QUERY_KEY)
+
+export const useProductVariant = (
+ productId: string,
+ variantId: string,
+ query?: Record,
+ options?: Omit<
+ UseQueryOptions,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: () => client.products.retrieveVariant(productId, variantId, query),
+ queryKey: variantsQueryKeys.detail(variantId),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useProductVariants = (
+ productId: string,
+ query?: Record,
+ options?: Omit<
+ UseQueryOptions,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: () => client.products.listVariants(productId, query),
+ queryKey: variantsQueryKeys.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useDeleteVariant = (
+ productId: string,
+ variantId: string,
+ options?: UseMutationOptions
+) => {
+ return useMutation({
+ mutationFn: () => client.products.deleteVariant(productId, variantId),
+ onSuccess: (data: any, variables: any, context: any) => {
+ queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() })
+ queryClient.invalidateQueries({
+ queryKey: variantsQueryKeys.detail(variantId),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
export const useProduct = (
id: string,
query?: Record,
diff --git a/packages/admin-next/dashboard/src/lib/client/products.ts b/packages/admin-next/dashboard/src/lib/client/products.ts
index 8871fffdd7..6341954f9d 100644
--- a/packages/admin-next/dashboard/src/lib/client/products.ts
+++ b/packages/admin-next/dashboard/src/lib/client/products.ts
@@ -25,10 +25,34 @@ async function deleteProduct(id: string) {
return deleteRequest(`/admin/products/${id}`)
}
+async function retrieveVariant(
+ productId: string,
+ variantId: string,
+ query?: Record
+) {
+ return getRequest(
+ `/admin/products/${productId}/variants/${variantId}`,
+ query
+ )
+}
+
+async function listVariants(productId: string, query?: Record) {
+ return getRequest(`/admin/products/${productId}/variants`, query)
+}
+
+async function deleteVariant(productId: string, variantId: string) {
+ return deleteRequest(
+ `/admin/products/${productId}/variants/${variantId}`
+ )
+}
+
export const products = {
retrieve: retrieveProduct,
list: listProducts,
create: createProduct,
update: updateProduct,
delete: deleteProduct,
+ retrieveVariant,
+ listVariants,
+ deleteVariant,
}
diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-details.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-details.tsx
index b3b657677e..07a3374c84 100644
--- a/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-details.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-details.tsx
@@ -31,6 +31,7 @@ import { useCollections } from "../../../../../hooks/api/collections"
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
import { useCategories } from "../../../../../hooks/api/categories"
import { useTags } from "../../../../../hooks/api/tags"
+import { Keypair } from "../../../../../components/common/keypair"
type CreateProductPropsProps = {
form: CreateProductFormReturn
@@ -394,7 +395,14 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
{
+ render={({ field: { onChange, value } }) => {
+ const normalizedValue = value.map((v) => {
+ return {
+ key: v.title,
+ value: v.values.join(","),
+ }
+ })
+
return (
@@ -404,20 +412,26 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
{t("products.fields.options.hint")}
-
+ value={normalizedValue}
+ onChange={(newVal) =>
+ onChange(
+ newVal.map((v) => {
+ return {
+ title: v.key,
+ values: v.value
+ .split(",")
+ .map((v) => v.trim()),
+ }
+ })
+ )
+ }
+ />
)
diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-sales-channel-section/product-sales-channel-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-sales-channel-section/product-sales-channel-section.tsx
index e67ee16ffc..55d6faf381 100644
--- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-sales-channel-section/product-sales-channel-section.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-sales-channel-section/product-sales-channel-section.tsx
@@ -1,7 +1,7 @@
import { Channels, PencilSquare } from "@medusajs/icons"
import { Product } from "@medusajs/medusa"
import { Container, Heading, Text, Tooltip } from "@medusajs/ui"
-import { useAdminSalesChannels } from "medusa-react"
+// import { useAdminSalesChannels } from "medusa-react"
import { Trans, useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
@@ -12,7 +12,8 @@ type ProductSalesChannelSectionProps = {
export const ProductSalesChannelSection = ({
product,
}: ProductSalesChannelSectionProps) => {
- const { count } = useAdminSalesChannels()
+ // const { count } = useAdminSalesChannels()
+ const count = 0
const { t } = useTranslation()
const availableInSalesChannels =
diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/product-variant-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/product-variant-section.tsx
index f2e11ee1c2..9bf91cd15c 100644
--- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/product-variant-section.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/product-variant-section.tsx
@@ -1,7 +1,6 @@
import { Plus } from "@medusajs/icons"
import { Product } from "@medusajs/medusa"
import { Container, Heading } from "@medusajs/ui"
-import { useAdminProductVariants } from "medusa-react"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
@@ -10,6 +9,7 @@ import { useDataTable } from "../../../../../hooks/use-data-table"
import { useProductVariantTableColumns } from "./use-variant-table-columns"
import { useProductVariantTableFilters } from "./use-variant-table-filters"
import { useProductVariantTableQuery } from "./use-variant-table-query"
+import { useProductVariants } from "../../../../../hooks/api/products"
type ProductVariantSectionProps = {
product: Product
@@ -25,16 +25,12 @@ export const ProductVariantSection = ({
const { searchParams, raw } = useProductVariantTableQuery({
pageSize: PAGE_SIZE,
})
- const { variants, count, isLoading, isError, error } =
- useAdminProductVariants(
- product.id,
- {
- ...searchParams,
- },
- {
- keepPreviousData: true,
- }
- )
+ const { variants, count, isLoading, isError, error } = useProductVariants(
+ product.id,
+ {
+ ...searchParams,
+ }
+ )
const filters = useProductVariantTableFilters()
const columns = useProductVariantTableColumns(product)
diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx
index 836c87fe2d..790196524d 100644
--- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx
@@ -2,12 +2,12 @@ import { PencilSquare, Trash } from "@medusajs/icons"
import { Product, ProductVariant } from "@medusajs/medusa"
import { Badge, usePrompt } from "@medusajs/ui"
import { createColumnHelper } from "@tanstack/react-table"
-import { useAdminDeleteVariant } from "medusa-react"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
+import { useDeleteVariant } from "../../../../../hooks/api/products"
const VariantActions = ({
variant,
@@ -16,7 +16,7 @@ const VariantActions = ({
variant: ProductVariant
product: Product
}) => {
- const { mutateAsync } = useAdminDeleteVariant(product.id)
+ const { mutateAsync } = useDeleteVariant(product.id, variant.id)
const { t } = useTranslation()
const prompt = usePrompt()
@@ -34,7 +34,7 @@ const VariantActions = ({
return
}
- await mutateAsync(variant.id)
+ await mutateAsync()
}
return (