feat(dashboard): variant details page (#7767)
* wip: setup * feat: finish inventory, prices section * feat: finish prices section pagination * fix: move edit variants to variants details, fix loader * fix: suggestion * feat: price editor flow
This commit is contained in:
@@ -174,6 +174,7 @@ export const useUpdateProductVariantsBatch = (
|
||||
}),
|
||||
onSuccess: (data: any, variables: any, context: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({ queryKey: variantsQueryKeys.details() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: productsQueryKeys.detail(productId),
|
||||
})
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"viewDetails": "View details",
|
||||
"back": "Back",
|
||||
"close": "Close",
|
||||
"showMore": "Show more",
|
||||
"continue": "Continue",
|
||||
"reset": "Reset",
|
||||
"confirm": "Confirm",
|
||||
@@ -325,6 +326,7 @@
|
||||
"create": {
|
||||
"header": "Create Variant"
|
||||
},
|
||||
"pricesPagination": "1 - {{current}} of {{total}} prices",
|
||||
"tableItemAvailable": "{{availableCount}} available",
|
||||
"tableItem_one": "{{availableCount}} available at {{locationCount}} location",
|
||||
"tableItem_other": "{{availableCount}} available at {{locationCount}} locations",
|
||||
@@ -1677,6 +1679,10 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"labels": {
|
||||
"productVariant": "Product Variant",
|
||||
"prices": "Prices"
|
||||
},
|
||||
"fields": {
|
||||
"amount": "Amount",
|
||||
"refundAmount": "Refund amount",
|
||||
@@ -1688,6 +1694,8 @@
|
||||
"customTitle": "Custom title",
|
||||
"manageInventory": "Manage inventory",
|
||||
"inventoryKit": "Inventory kit",
|
||||
"inventoryItems": "Inventory items",
|
||||
"requiredQuantity": "Required quantity",
|
||||
"description": "Description",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
|
||||
@@ -110,10 +110,25 @@ export const RouteMap: RouteObject[] = [
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-create-variant"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id/variants/:variant_id",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/product-variants/product-variant-detail"
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: "variants/:variant_id/edit",
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-edit-variant"),
|
||||
import(
|
||||
"../../routes/product-variants/product-variant-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "prices",
|
||||
lazy: () => import("../../routes/products/product-prices"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./variant-general-section"
|
||||
@@ -0,0 +1,97 @@
|
||||
import { ProductVariantDTO } from "@medusajs/types"
|
||||
import { Badge, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||
import { Component, PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { SectionRow } from "../../../../../components/common/section"
|
||||
import { useDeleteVariant } from "../../../../../hooks/api/products"
|
||||
|
||||
type VariantGeneralSectionProps = {
|
||||
variant: ProductVariantDTO
|
||||
}
|
||||
|
||||
export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const hasInventoryKit = variant.inventory?.length > 1
|
||||
|
||||
const { mutateAsync } = useDeleteVariant(variant.product_id, variant.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("products.variant.deleteWarning", {
|
||||
title: variant.title,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync(undefined, {
|
||||
onSuccess: () => {
|
||||
navigate("..", { replace: true })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Heading>{variant.title}</Heading>
|
||||
{hasInventoryKit && (
|
||||
<span className="text-ui-fg-muted font-normal">
|
||||
<Component />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-ui-fg-subtle txt-small mt-2">
|
||||
{t("labels.productVariant")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: "edit",
|
||||
icon: <PencilSquare />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
icon: <Trash />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SectionRow title={t("fields.sku")} value={variant.sku} />
|
||||
{variant.options.map((o) => (
|
||||
<SectionRow
|
||||
key={o.id}
|
||||
title={o.option?.title}
|
||||
value={<Badge size="2xsmall">{o.value}</Badge>}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./variant-inventory-section"
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Buildings } from "@medusajs/icons"
|
||||
import { InventoryItemDTO } from "@medusajs/types"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
|
||||
export const InventoryActions = ({ item }: { item: InventoryItemDTO }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Buildings />,
|
||||
label: t("product.variant.inventory.navigateToItem"),
|
||||
to: `/inventory/${item.id}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { InventoryNext, ProductVariantDTO } from "@medusajs/types"
|
||||
|
||||
import { InventoryActions } from "./inventory-actions"
|
||||
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface ExtendedInventoryItem extends InventoryNext.InventoryItemDTO {
|
||||
variants: ProductVariantDTO[]
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ExtendedInventoryItem>()
|
||||
|
||||
export const useInventoryTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("title", {
|
||||
header: t("fields.title"),
|
||||
cell: ({ getValue }) => {
|
||||
const title = getValue()
|
||||
|
||||
if (!title) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{title}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("sku", {
|
||||
header: t("fields.sku"),
|
||||
cell: ({ getValue }) => {
|
||||
const sku = getValue() as string
|
||||
|
||||
if (!sku) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{sku}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("required_quantity", {
|
||||
header: t("fields.requiredQuantity"),
|
||||
cell: ({ getValue }) => {
|
||||
const quantity = getValue()
|
||||
|
||||
if (Number.isNaN(quantity)) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{quantity}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "inventory_quantity",
|
||||
header: t("fields.inventory"),
|
||||
cell: ({ getValue, row: { original: inventory } }) => {
|
||||
if (!inventory.location_levels?.length) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
let quantity = 0
|
||||
let locations = 0
|
||||
|
||||
inventory.location_levels.forEach((level) => {
|
||||
quantity += level.available_quantity
|
||||
locations += 1
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">
|
||||
{t("products.variant.tableItem", {
|
||||
availableCount: quantity,
|
||||
locationCount: locations,
|
||||
count: locations,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <InventoryActions item={row.original} />,
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { InventoryItemDTO } from "@medusajs/types"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useInventoryTableColumns } from "./use-inventory-table-columns"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
type VariantInventorySectionProps = {
|
||||
inventoryItems: InventoryItemDTO[]
|
||||
}
|
||||
|
||||
export function VariantInventorySection({
|
||||
inventoryItems,
|
||||
}: VariantInventorySectionProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const columns = useInventoryTableColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: inventoryItems ?? [],
|
||||
columns,
|
||||
count: inventoryItems.length,
|
||||
enablePagination: true,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Heading level="h2">{t("fields.inventoryItems")}</Heading>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
{/*TODO: add inventory management*/}
|
||||
{/*<ActionMenu*/}
|
||||
{/* groups={[*/}
|
||||
{/* {*/}
|
||||
{/* actions: [*/}
|
||||
{/* {*/}
|
||||
{/* label: t("actions.manageInventoryItems"),*/}
|
||||
{/* to: "edit",*/}
|
||||
{/* icon: <Component />,*/}
|
||||
{/* },*/}
|
||||
{/* ],*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={inventoryItems.length}
|
||||
navigateTo={(row) => `/inventory/${row.id}`}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./variant-prices-section"
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useState } from "react"
|
||||
|
||||
import { CurrencyDollar } from "@medusajs/icons"
|
||||
import { Button, Container, Heading } from "@medusajs/ui"
|
||||
import { MoneyAmountDTO, ProductVariantDTO } from "@medusajs/types"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
|
||||
type VariantPricesSectionProps = {
|
||||
variant: ProductVariantDTO & { prices: MoneyAmountDTO[] }
|
||||
}
|
||||
|
||||
export function VariantPricesSection({ variant }: VariantPricesSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const prices = variant.prices
|
||||
.filter((p) => !p.rules?.length)
|
||||
.sort((p1, p2) => p1.currency_code?.localeCompare(p2.currency_code)) // display just currency prices
|
||||
|
||||
const [current, setCurrent] = useState(Math.min(prices.length, 3))
|
||||
|
||||
const hasPrices = !!prices.length
|
||||
|
||||
const displayPrices = prices.slice(0, current)
|
||||
|
||||
const onShowMore = () => {
|
||||
setCurrent(Math.min(current + 3, prices.length))
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("labels.prices")}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: `/products/${variant.product_id}/variants/${variant.id}/prices`,
|
||||
icon: <CurrencyDollar />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{!hasPrices && <NoRecords className="h-60" />}
|
||||
{displayPrices.map((price) => {
|
||||
return (
|
||||
<div className="txt-small text-ui-fg-subtle flex justify-between px-6 py-4">
|
||||
<span className="font-medium">
|
||||
{price.currency_code.toUpperCase()}
|
||||
</span>
|
||||
<span>{getLocaleAmount(price.amount, price.currency_code)}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{hasPrices && (
|
||||
<div className="txt-small text-ui-fg-subtle flex items-center justify-between px-6 py-4">
|
||||
<span className="font-medium">
|
||||
{t("products.variant.pricesPagination", {
|
||||
total: prices.length,
|
||||
current,
|
||||
})}
|
||||
</span>
|
||||
<Button
|
||||
onClick={onShowMore}
|
||||
disabled={current >= prices.length}
|
||||
className="-mr-3 text-blue-500"
|
||||
variant="transparent"
|
||||
>
|
||||
{t("actions.showMore")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const VARIANT_DETAIL_FIELDS =
|
||||
"*inventory_items,*inventory_items.inventory,*inventory_items.inventory.location_levels,*options,*options.option,*prices,*prices.price_rules"
|
||||
@@ -0,0 +1,2 @@
|
||||
export { variantLoader as loader } from "./loader"
|
||||
export { ProductVariantDetail as Component } from "./product-variant-detail"
|
||||
@@ -0,0 +1,26 @@
|
||||
import { LoaderFunctionArgs } from "react-router-dom"
|
||||
|
||||
import { variantsQueryKeys } from "../../../hooks/api/products"
|
||||
import { queryClient } from "../../../lib/query-client"
|
||||
import { VARIANT_DETAIL_FIELDS } from "./constants"
|
||||
import { sdk } from "../../../lib/client"
|
||||
|
||||
const variantDetailQuery = (productId: string, variantId: string) => ({
|
||||
queryKey: variantsQueryKeys.detail(variantId),
|
||||
queryFn: async () =>
|
||||
sdk.admin.product.retrieveVariant(productId, variantId, {
|
||||
fields: VARIANT_DETAIL_FIELDS,
|
||||
}),
|
||||
})
|
||||
|
||||
export const variantLoader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const productId = params.id
|
||||
const variantId = params.variant_id
|
||||
|
||||
const query = variantDetailQuery(productId!, variantId!)
|
||||
|
||||
return (
|
||||
queryClient.getQueryData<any>(query.queryKey) ??
|
||||
(await queryClient.fetchQuery(query))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { useProductVariant } from "../../../hooks/api/products"
|
||||
|
||||
import { variantLoader } from "./loader"
|
||||
import { VARIANT_DETAIL_FIELDS } from "./constants"
|
||||
import { VariantGeneralSection } from "./components/variant-general-section"
|
||||
import { VariantInventorySection } from "./components/variant-inventory-section"
|
||||
import { VariantPricesSection } from "./components/variant-prices-section"
|
||||
|
||||
export const ProductVariantDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof variantLoader>
|
||||
>
|
||||
|
||||
const { id, variant_id } = useParams()
|
||||
const { variant, isLoading, isError, error } = useProductVariant(
|
||||
id!,
|
||||
variant_id,
|
||||
{ fields: VARIANT_DETAIL_FIELDS },
|
||||
{
|
||||
initialData: initialData,
|
||||
}
|
||||
)
|
||||
|
||||
if (isLoading || !variant) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="flex flex-col gap-x-4 gap-y-3 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<VariantGeneralSection variant={variant} />
|
||||
<VariantInventorySection
|
||||
inventoryItems={variant.inventory_items.map((i) => {
|
||||
return {
|
||||
...i.inventory,
|
||||
required_quantity: i.required_quantity,
|
||||
variant,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="hidden xl:block">
|
||||
<JsonViewSection data={variant} root="product" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[400px]">
|
||||
<VariantPricesSection variant={variant} />
|
||||
|
||||
<div className="xl:hidden">
|
||||
<JsonViewSection data={variant} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ProductVariantEdit as Component } from "./product-variant-edit"
|
||||
export { editProductVariantLoader as loader } from "./loader"
|
||||
@@ -7,7 +7,7 @@ import { ProductEditVariantForm } from "./components/product-edit-variant-form"
|
||||
import { editProductVariantLoader } from "./loader"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export const ProductEditVariant = () => {
|
||||
export const ProductVariantEdit = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof editProductVariantLoader>
|
||||
>
|
||||
@@ -87,6 +87,9 @@ export const ProductVariantSection = ({
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
orderBy={["title", "created_at", "updated_at"]}
|
||||
navigateTo={(row) =>
|
||||
`/products/${row.original.product_id}/variants/${row.id}`
|
||||
}
|
||||
pagination
|
||||
search
|
||||
queryObject={raw}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { ProductEditVariant as Component } from "./product-edit-variant"
|
||||
@@ -3,11 +3,12 @@ import { Button } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
import { RouteFocusModal, useRouteModal } from "../../../components/route-modal"
|
||||
import { useUpdateProductVariantsBatch } from "../../../hooks/api/products"
|
||||
import { VariantPricingForm } from "../common/variant-pricing-form"
|
||||
import { castNumber } from "../../../lib/cast-number"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export const UpdateVariantPricesSchema = zod.object({
|
||||
variants: zod.array(
|
||||
@@ -25,14 +26,21 @@ export type UpdateVariantPricesSchemaType = zod.infer<
|
||||
|
||||
export const PricingEdit = ({
|
||||
product,
|
||||
variantId,
|
||||
}: {
|
||||
product: HttpTypes.AdminProduct
|
||||
variantId?: string
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const variants = variantId
|
||||
? product.variants.filter((v) => v.id === variantId)
|
||||
: product.variants
|
||||
|
||||
const form = useForm<UpdateVariantPricesSchemaType>({
|
||||
defaultValues: {
|
||||
variants: product.variants.map((variant: any) => ({
|
||||
variants: variants.map((variant: any) => ({
|
||||
title: variant.title,
|
||||
prices: variant.prices.reduce((acc: any, price: any) => {
|
||||
acc[price.currency_code] = price.amount
|
||||
@@ -49,10 +57,10 @@ export const PricingEdit = ({
|
||||
const handleSubmit = form.handleSubmit(
|
||||
async (values) => {
|
||||
const reqData = values.variants.map((variant, ind) => ({
|
||||
id: product.variants[ind].id,
|
||||
id: variants[ind].id,
|
||||
prices: Object.entries(variant.prices || {}).map(
|
||||
([currency_code, value]: any) => {
|
||||
const id = product.variants[ind].prices.find(
|
||||
const id = variants[ind].prices.find(
|
||||
(p) => p.currency_code === currency_code
|
||||
)?.id
|
||||
|
||||
@@ -66,7 +74,7 @@ export const PricingEdit = ({
|
||||
}))
|
||||
await mutateAsync(reqData, {
|
||||
onSuccess: () => {
|
||||
handleSuccess(`/products/${product.id}`)
|
||||
handleSuccess("..")
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PricingEdit } from "./pricing-edit"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
|
||||
export const ProductPrices = () => {
|
||||
const { id } = useParams()
|
||||
const { id, variant_id } = useParams()
|
||||
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
@@ -15,7 +15,9 @@ export const ProductPrices = () => {
|
||||
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
{!isLoading && product && <PricingEdit product={product} />}
|
||||
{!isLoading && product && (
|
||||
<PricingEdit product={product} variantId={variant_id} />
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user