diff --git a/.changeset/modern-walls-appear.md b/.changeset/modern-walls-appear.md new file mode 100644 index 0000000000000..62869d2b7652b --- /dev/null +++ b/.changeset/modern-walls-appear.md @@ -0,0 +1,5 @@ +--- +"@medusajs/dashboard": patch +--- + +fix(dashboard): Fix query key for product details and load product variant data correctly diff --git a/packages/admin/dashboard/src/hooks/api/products.tsx b/packages/admin/dashboard/src/hooks/api/products.tsx index 2ee357a93a4aa..72e2508125a5f 100644 --- a/packages/admin/dashboard/src/hooks/api/products.tsx +++ b/packages/admin/dashboard/src/hooks/api/products.tsx @@ -87,16 +87,21 @@ export const useDeleteProductOption = ( export const useProductVariant = ( productId: string, variantId: string, - query?: Record, + query?: HttpTypes.AdminProductVariantParams, options?: Omit< - UseQueryOptions, + UseQueryOptions< + HttpTypes.AdminProductVariantResponse, + FetchError, + HttpTypes.AdminProductVariantResponse, + QueryKey + >, "queryFn" | "queryKey" > ) => { const { data, ...rest } = useQuery({ queryFn: () => sdk.admin.product.retrieveVariant(productId, variantId, query), - queryKey: variantsQueryKeys.detail(variantId), + queryKey: variantsQueryKeys.detail(variantId, query), ...options, }) @@ -238,7 +243,7 @@ export const useProduct = ( ) => { const { data, ...rest } = useQuery({ queryFn: () => sdk.admin.product.retrieve(id, query), - queryKey: productsQueryKeys.detail(id), + queryKey: productsQueryKeys.detail(id, query), ...options, }) diff --git a/packages/admin/dashboard/src/lib/query-key-factory.ts b/packages/admin/dashboard/src/lib/query-key-factory.ts index cf477d387a74a..8510217ea2b14 100644 --- a/packages/admin/dashboard/src/lib/query-key-factory.ts +++ b/packages/admin/dashboard/src/lib/query-key-factory.ts @@ -5,10 +5,7 @@ export type TQueryKey = { lists: () => readonly [...TQueryKey["all"], "list"] list: ( query?: TListQuery - ) => readonly [ - ...ReturnType["lists"]>, - { query: TListQuery | undefined }, - ] + ) => readonly [...ReturnType["lists"]>, { query: TListQuery }] details: () => readonly [...TQueryKey["all"], "detail"] detail: ( id: TDetailQuery, @@ -16,7 +13,7 @@ export type TQueryKey = { ) => readonly [ ...ReturnType["details"]>, TDetailQuery, - { query: TListQuery | undefined }, + { query: TListQuery } ] } @@ -26,7 +23,7 @@ export type UseQueryOptionsWrapper< // Type thrown in case the queryFn rejects E = Error, // Query key type - TQueryKey extends QueryKey = QueryKey, + TQueryKey extends QueryKey = QueryKey > = Omit< UseQueryOptions, "queryKey" | "queryFn" @@ -35,20 +32,22 @@ export type UseQueryOptionsWrapper< export const queryKeysFactory = < T, TListQueryType = any, - TDetailQueryType = string, + TDetailQueryType = string >( globalKey: T ) => { const queryKeyFactory: TQueryKey = { all: [globalKey], lists: () => [...queryKeyFactory.all, "list"], - list: (query?: TListQueryType) => [...queryKeyFactory.lists(), { query }], + list: (query?: TListQueryType) => + [...queryKeyFactory.lists(), query ? { query } : undefined].filter( + (k) => !!k + ), details: () => [...queryKeyFactory.all, "detail"], - detail: (id: TDetailQueryType, query?: TListQueryType) => [ - ...queryKeyFactory.details(), - id, - { query }, - ], + detail: (id: TDetailQueryType, query?: TListQueryType) => + [...queryKeyFactory.details(), id, query ? { query } : undefined].filter( + (k) => !!k + ), } return queryKeyFactory } diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-general-section/variant-general-section.tsx b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-general-section/variant-general-section.tsx index 297ef0df6cbe5..b2000ddce5bf7 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-general-section/variant-general-section.tsx +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-general-section/variant-general-section.tsx @@ -1,6 +1,6 @@ -import { ProductVariantDTO } from "@medusajs/types" -import { Badge, Container, Heading, usePrompt } from "@medusajs/ui" import { Component, PencilSquare, Trash } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { Badge, Container, Heading, usePrompt } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" @@ -9,7 +9,7 @@ import { SectionRow } from "../../../../../components/common/section" import { useDeleteVariant } from "../../../../../hooks/api/products" type VariantGeneralSectionProps = { - variant: ProductVariantDTO + variant: HttpTypes.AdminProductVariant } export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) { @@ -19,7 +19,7 @@ export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) { const hasInventoryKit = variant.inventory?.length > 1 - const { mutateAsync } = useDeleteVariant(variant.product_id, variant.id) + const { mutateAsync } = useDeleteVariant(variant.product_id!, variant.id) const handleDelete = async () => { const res = await prompt({ @@ -85,10 +85,10 @@ export function VariantGeneralSection({ variant }: VariantGeneralSectionProps) { - {variant.options.map((o) => ( + {variant.options?.map((o) => ( {o.value}} /> ))} diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx index 5615365f645f6..1c2c85af1b74a 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx @@ -1,27 +1,27 @@ -import { useTranslation } from "react-i18next" import { useState } from "react" +import { useTranslation } from "react-i18next" import { CurrencyDollar } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" 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" +import { getLocaleAmount } from "../../../../../lib/money-amount-helpers" type VariantPricesSectionProps = { - variant: ProductVariantDTO & { prices: MoneyAmountDTO[] } + variant: HttpTypes.AdminProductVariant } export function VariantPricesSection({ variant }: VariantPricesSectionProps) { const { t } = useTranslation() const prices = variant.prices - .filter((p) => !Object.keys(p.rules || {}).length) // display just currency prices + ?.filter((p) => !Object.keys(p.rules || {}).length) .sort((p1, p2) => p1.currency_code?.localeCompare(p2.currency_code)) - const hasPrices = !!prices.length + const hasPrices = !!prices?.length const [pageSize, setPageSize] = useState(3) - const displayPrices = prices.slice(0, pageSize) + const displayPrices = prices?.slice(0, pageSize) const onShowMore = () => { setPageSize(pageSize + 3) @@ -46,7 +46,7 @@ export function VariantPricesSection({ variant }: VariantPricesSectionProps) { /> {!hasPrices && } - {displayPrices.map((price) => { + {displayPrices?.map((price) => { return (
({ - queryKey: variantsQueryKeys.detail(variantId), + queryKey: variantsQueryKeys.detail(variantId, { + fields: VARIANT_DETAIL_FIELDS, + }), queryFn: async () => sdk.admin.product.retrieveVariant(productId, variantId, { fields: VARIANT_DETAIL_FIELDS, diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/product-variant-detail.tsx b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/product-variant-detail.tsx index a1b5cb556986a..95fcb802b209e 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/product-variant-detail.tsx +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-detail/product-variant-detail.tsx @@ -1,16 +1,17 @@ -import { Outlet, useLoaderData, useParams } from "react-router-dom" +import { 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 { TwoColumnPageSkeleton } from "../../../components/common/skeleton" +import { TwoColumnPage } from "../../../components/layout/pages" import { VariantGeneralSection } from "./components/variant-general-section" import { InventorySectionPlaceholder, VariantInventorySection, } from "./components/variant-inventory-section" import { VariantPricesSection } from "./components/variant-prices-section" +import { VARIANT_DETAIL_FIELDS } from "./constants" +import { variantLoader } from "./loader" export const ProductVariantDetail = () => { const initialData = useLoaderData() as Awaited< @@ -20,15 +21,22 @@ export const ProductVariantDetail = () => { const { id, variant_id } = useParams() const { variant, isLoading, isError, error } = useProductVariant( id!, - variant_id, + variant_id!, { fields: VARIANT_DETAIL_FIELDS }, { - initialData: initialData, + initialData, } ) if (isLoading || !variant) { - return
Loading...
+ return ( + + ) } if (isError) { @@ -36,38 +44,38 @@ export const ProductVariantDetail = () => { } return ( -
-
-
- - {!variant.manage_inventory ? ( - - ) : ( - { - return { - ...i.inventory, - required_quantity: i.required_quantity, - variant, - } - })} - /> - )} - -
- -
-
- -
- - -
- -
-
-
- -
+ + + + {!variant.manage_inventory ? ( + + ) : ( + { + return { + ...i.inventory, + required_quantity: i.required_quantity, + variant, + } + })} + /> + )} + + + + + ) } diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/components/product-edit-variant-form/product-edit-variant-form.tsx b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/components/product-edit-variant-form/product-edit-variant-form.tsx index 89faed6beea17..79f902e747b90 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/components/product-edit-variant-form/product-edit-variant-form.tsx +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/components/product-edit-variant-form/product-edit-variant-form.tsx @@ -44,8 +44,8 @@ const ProductEditVariantSchema = z.object({ // TODO: Either pass option ID or make the backend handle options constraints differently to handle the lack of IDs export const ProductEditVariantForm = ({ - product, variant, + product, }: ProductEditVariantFormProps) => { const { t } = useTranslation() const { handleSuccess } = useRouteModal() @@ -63,7 +63,6 @@ export const ProductEditVariantForm = ({ ean: variant.ean || "", upc: variant.upc || "", barcode: variant.barcode || "", - inventory_quantity: variant.inventory_quantity || "", manage_inventory: variant.manage_inventory || false, allow_backorder: variant.allow_backorder || false, weight: variant.weight || "", @@ -79,7 +78,7 @@ export const ProductEditVariantForm = ({ }) const { mutateAsync, isPending } = useUpdateProductVariant( - product.id, + variant.product_id!, variant.id ) diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/loader.ts b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/loader.ts index 55c48ca0b7be0..c42c76428cc52 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/loader.ts +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/loader.ts @@ -1,27 +1,30 @@ import { LoaderFunctionArgs } from "react-router-dom" -import { productsQueryKeys } from "../../../hooks/api/products" +import { productVariantQueryKeys } from "../../../hooks/api" import { sdk } from "../../../lib/client" import { queryClient } from "../../../lib/query-client" -const queryKey = (id: string) => { - return [productsQueryKeys.detail(id)] +const queryFn = async (id: string, variantId: string) => { + return await sdk.admin.product.retrieveVariant(id, variantId) } -const queryFn = async (id: string) => { - return await sdk.admin.product.retrieve(id) -} - -const editProductVariantQuery = (id: string) => ({ - queryKey: queryKey(id), - queryFn: async () => queryFn(id), +const editProductVariantQuery = (id: string, variantId: string) => ({ + queryKey: productVariantQueryKeys.detail(variantId), + queryFn: async () => queryFn(id, variantId), }) export const editProductVariantLoader = async ({ params, + request, }: LoaderFunctionArgs) => { const id = params.id - const query = editProductVariantQuery(id!) + + const searchParams = new URL(request.url).searchParams + const searchVariantId = searchParams.get("variant_id") + + const variantId = params.variant_id || searchVariantId + + const query = editProductVariantQuery(id!, variantId || searchVariantId!) return ( queryClient.getQueryData>(query.queryKey) ?? diff --git a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/product-variant-edit.tsx b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/product-variant-edit.tsx index bc3fb67724b49..61394ea49c3fb 100644 --- a/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/product-variant-edit.tsx +++ b/packages/admin/dashboard/src/routes/product-variants/product-variant-edit/product-variant-edit.tsx @@ -1,14 +1,8 @@ -import { HttpTypes } from "@medusajs/types" import { Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" -import { - json, - useLoaderData, - useParams, - useSearchParams, -} from "react-router-dom" +import { useLoaderData, useParams, useSearchParams } from "react-router-dom" import { RouteDrawer } from "../../../components/modals" -import { useProduct } from "../../../hooks/api/products" +import { useProduct, useProductVariant } from "../../../hooks/api/products" import { ProductEditVariantForm } from "./components/product-edit-variant-form" import { editProductVariantLoader } from "./loader" @@ -22,41 +16,46 @@ export const ProductVariantEdit = () => { const [URLSearchParms] = useSearchParams() const searchVariantId = URLSearchParms.get("variant_id") - const { product, isPending, isFetching, isError, error } = useProduct( + const { variant, isPending, isError, error } = useProductVariant( id!, + variant_id || searchVariantId!, undefined, { initialData, } ) - const variant = product?.variants.find( - (v: HttpTypes.AdminProductVariant) => - v.id === (variant_id || searchVariantId) + const { + product, + isPending: isProductPending, + isError: isProductError, + error: productError, + } = useProduct( + variant?.product_id!, + { + fields: "-variants", + }, + { + enabled: !!variant?.product_id, + } ) - if (!isPending && !isFetching && !variant) { - throw json({ - status: 404, - message: `Variant with ID ${variant_id || searchVariantId} was not found.`, - }) - } + const ready = !isPending && !!variant && !isProductPending && !!product if (isError) { throw error } + if (isProductError) { + throw productError + } + return ( {t("products.variant.edit.header")} - {variant && ( - - )} + {ready && } ) } diff --git a/packages/admin/dashboard/src/routes/products/product-prices/pricing-edit.tsx b/packages/admin/dashboard/src/routes/products/product-prices/pricing-edit.tsx index b4f757f9abd34..dd269541fcb64 100644 --- a/packages/admin/dashboard/src/routes/products/product-prices/pricing-edit.tsx +++ b/packages/admin/dashboard/src/routes/products/product-prices/pricing-edit.tsx @@ -51,12 +51,12 @@ export const PricingEdit = ({ }, [regions]) const variants = variantId - ? product.variants.filter((v) => v.id === variantId) + ? product.variants?.filter((v) => v.id === variantId) : product.variants const form = useForm({ defaultValues: { - variants: variants.map((variant: any) => ({ + variants: variants?.map((variant: any) => ({ title: variant.title, prices: variant.prices.reduce((acc: any, price: any) => { if (price.rules?.region_id) { @@ -90,11 +90,11 @@ export const PricingEdit = ({ let existingId = undefined if (regionId) { - existingId = variants[ind].prices.find( + existingId = variants?.[ind]?.prices?.find( (p) => p.rules["region_id"] === regionId )?.id } else { - existingId = variants[ind].prices.find( + existingId = variants?.[ind]?.prices?.find( (p) => p.currency_code === currencyCode && Object.keys(p.rules ?? {}).length === 0 diff --git a/packages/admin/dashboard/src/routes/products/product-prices/product-prices.tsx b/packages/admin/dashboard/src/routes/products/product-prices/product-prices.tsx index 90ea3d888d3b9..85efaac29502f 100644 --- a/packages/admin/dashboard/src/routes/products/product-prices/product-prices.tsx +++ b/packages/admin/dashboard/src/routes/products/product-prices/product-prices.tsx @@ -7,7 +7,9 @@ import { PricingEdit } from "./pricing-edit" export const ProductPrices = () => { const { id, variant_id } = useParams() - const { product, isLoading, isError, error } = useProduct(id!) + const { product, isLoading, isError, error } = useProduct(id!, { + fields: "+variants,+variants.prices", + }) if (isError) { throw error