diff --git a/packages/admin-next/dashboard/src/components/common/section/index.ts b/packages/admin-next/dashboard/src/components/common/section/index.ts new file mode 100644 index 0000000000000..88b246692a210 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/section/index.ts @@ -0,0 +1 @@ +export * from "./section-row" diff --git a/packages/admin-next/dashboard/src/components/common/section/section-row.tsx b/packages/admin-next/dashboard/src/components/common/section/section-row.tsx new file mode 100644 index 0000000000000..aefc164b9a4af --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/section/section-row.tsx @@ -0,0 +1,33 @@ +import { Text } from "@medusajs/ui" + +export type SectionRowProps = { + title: string + value?: React.ReactNode | string | null + actions?: React.ReactNode +} + +export const SectionRow = ({ title, value, actions }: SectionRowProps) => { + const isValueString = typeof value === "string" || !value + + return ( +
+ + {title} + + + {isValueString ? ( + + {value ?? "-"} + + ) : ( +
{value}
+ )} + + {actions &&
{actions}
} +
+ ) +} diff --git a/packages/admin-next/dashboard/src/hooks/api/products.tsx b/packages/admin-next/dashboard/src/hooks/api/products.tsx index 00cb49b1b41ad..6f4fa7be8023e 100644 --- a/packages/admin-next/dashboard/src/hooks/api/products.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/products.tsx @@ -7,11 +7,7 @@ import { } from "@tanstack/react-query" import { client } from "../../lib/client" import { queryKeysFactory } from "../../lib/query-key-factory" -import { - ProductDeleteRes, - ProductListRes, - ProductRes, -} from "../../types/api-responses" +import { ProductDeleteRes, ProductRes } from "../../types/api-responses" import { queryClient } from "../../lib/medusa" const PRODUCTS_QUERY_KEY = "products" as const @@ -20,6 +16,63 @@ export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY) const VARIANTS_QUERY_KEY = "product_variants" as const export const variantsQueryKeys = queryKeysFactory(VARIANTS_QUERY_KEY) +const OPTIONS_QUERY_KEY = "product_options" as const +export const optionsQueryKeys = queryKeysFactory(OPTIONS_QUERY_KEY) + +export const useCreateProductOption = ( + productId: string, + options?: UseMutationOptions +) => { + return useMutation({ + mutationFn: (payload: any) => + client.products.createOption(productId, payload), + onSuccess: (data: any, variables: any, context: any) => { + queryClient.invalidateQueries({ queryKey: optionsQueryKeys.lists() }) + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} + +export const useUpdateProductOption = ( + productId: string, + optionId: string, + options?: UseMutationOptions +) => { + return useMutation({ + mutationFn: (payload: any) => + client.products.updateOption(productId, optionId, payload), + onSuccess: (data: any, variables: any, context: any) => { + queryClient.invalidateQueries({ queryKey: optionsQueryKeys.lists() }) + queryClient.invalidateQueries({ + queryKey: optionsQueryKeys.detail(optionId), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} + +export const useDeleteProductOption = ( + productId: string, + optionId: string, + options?: UseMutationOptions +) => { + return useMutation({ + mutationFn: () => client.products.deleteOption(productId, optionId), + onSuccess: (data: any, variables: any, context: any) => { + queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() }) + queryClient.invalidateQueries({ + queryKey: productsQueryKeys.detail(optionId), + }) + + options?.onSuccess?.(data, variables, context) + }, + ...options, + }) +} + export const useProductVariant = ( productId: string, variantId: string, @@ -55,6 +108,26 @@ export const useProductVariants = ( return { ...data, ...rest } } +export const useUpdateProductVariant = ( + productId: string, + variantId: string, + options?: UseMutationOptions +) => { + return useMutation({ + mutationFn: (payload: any) => + client.products.updateVariant(productId, variantId, payload), + 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 useDeleteVariant = ( productId: string, variantId: string, @@ -78,7 +151,7 @@ export const useProduct = ( id: string, query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions, "queryFn" | "queryKey" > ) => { @@ -94,7 +167,7 @@ export const useProduct = ( export const useProducts = ( query?: Record, options?: Omit< - UseQueryOptions, + UseQueryOptions, "queryFn" | "queryKey" > ) => { diff --git a/packages/admin-next/dashboard/src/lib/addresses.ts b/packages/admin-next/dashboard/src/lib/addresses.ts index ce82464f01485..3933524f9da01 100644 --- a/packages/admin-next/dashboard/src/lib/addresses.ts +++ b/packages/admin-next/dashboard/src/lib/addresses.ts @@ -1,4 +1,5 @@ import { Address } from "@medusajs/medusa" +import { countries } from "./countries" export const isSameAddress = (a: Address | null, b: Address | null) => { if (!a || !b) { @@ -75,3 +76,12 @@ export const getFormattedAddress = ({ return formattedAddress } + +export const getFormattedCountry = (countryCode: string | null | undefined) => { + if (!countryCode) { + return "" + } + + const country = countries.find((c) => c.iso_2 === countryCode) + return country ? country.display_name : countryCode +} diff --git a/packages/admin-next/dashboard/src/lib/client/products.ts b/packages/admin-next/dashboard/src/lib/client/products.ts index 6341954f9d398..e29a52b150fe0 100644 --- a/packages/admin-next/dashboard/src/lib/client/products.ts +++ b/packages/admin-next/dashboard/src/lib/client/products.ts @@ -1,16 +1,12 @@ -import { - ProductDeleteRes, - ProductListRes, - ProductRes, -} from "../../types/api-responses" +import { ProductDeleteRes, ProductListRes } from "../../types/api-responses" import { deleteRequest, getRequest, postRequest } from "./common" async function retrieveProduct(id: string, query?: Record) { - return getRequest(`/admin/products/${id}`, query) + return getRequest(`/admin/products/${id}`, query) } async function createProduct(payload: any) { - return postRequest(`/admin/products`, payload) + return postRequest(`/admin/products`, payload) } async function listProducts(query?: Record) { @@ -18,7 +14,7 @@ async function listProducts(query?: Record) { } async function updateProduct(id: string, payload: any) { - return postRequest(`/admin/products/${id}`, payload) + return postRequest(`/admin/products/${id}`, payload) } async function deleteProduct(id: string) { @@ -40,12 +36,38 @@ async function listVariants(productId: string, query?: Record) { return getRequest(`/admin/products/${productId}/variants`, query) } +async function updateVariant( + productId: string, + variantId: string, + payload: any +) { + return postRequest( + `/admin/products/${productId}/variants/${variantId}`, + payload + ) +} + async function deleteVariant(productId: string, variantId: string) { return deleteRequest( `/admin/products/${productId}/variants/${variantId}` ) } +async function createOption(productId: string, payload: any) { + return postRequest(`/admin/products/${productId}/options`, payload) +} + +async function updateOption(productId: string, optionId: string, payload: any) { + return postRequest( + `/admin/products/${productId}/options/${optionId}`, + payload + ) +} + +async function deleteOption(productId: string, optionId: string) { + return deleteRequest(`/admin/products/${productId}/options/${optionId}`) +} + export const products = { retrieve: retrieveProduct, list: listProducts, @@ -54,5 +76,9 @@ export const products = { delete: deleteProduct, retrieveVariant, listVariants, + updateVariant, deleteVariant, + createOption, + updateOption, + deleteOption, } diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-create-option/components/create-product-option-form/create-product-option-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-create-option/components/create-product-option-form/create-product-option-form.tsx index f99dbdb5416ee..219256bf35142 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-create-option/components/create-product-option-form/create-product-option-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-create-option/components/create-product-option-form/create-product-option-form.tsx @@ -1,7 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Product } from "@medusajs/medusa" import { Button, Input } from "@medusajs/ui" -import { useAdminCreateProductOption } from "medusa-react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { z } from "zod" @@ -10,6 +9,7 @@ import { RouteDrawer, useRouteModal, } from "../../../../../components/route-modal" +import { useCreateProductOption } from "../../../../../hooks/api/products" type EditProductOptionsFormProps = { product: Product @@ -32,7 +32,7 @@ export const CreateProductOptionForm = ({ resolver: zodResolver(CreateProductOptionSchema), }) - const { mutateAsync, isLoading } = useAdminCreateProductOption(product.id) + const { mutateAsync, isLoading } = useCreateProductOption(product.id) const handleSubmit = form.handleSubmit(async (values) => { mutateAsync(values, { 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 07a3374c8467c..ef242e94a9c23 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 @@ -87,7 +87,7 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => { const { product_categories, isLoading: isLoadingCategories } = useCategories() const options = form.watch("options") - const optionPermutations = permutations(options) + const optionPermutations = permutations(options ?? []) // const { append } = useFieldArray({ // name: "images", @@ -396,7 +396,7 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => { control={form.control} name="options" render={({ field: { onChange, value } }) => { - const normalizedValue = value.map((v) => { + const normalizedValue = (value ?? []).map((v) => { return { key: v.title, value: v.values.join(","), @@ -443,7 +443,9 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => { control={form.control} name="variants" render={({ field: { value, onChange, ...field } }) => { - const selectedOptions = value.map((v) => v.options) + const selectedOptions = (value ?? []).map( + (v) => v.options + ) return ( diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-form.tsx index e04116b3e3124..b251bcff25a03 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-create/components/create-product-form/create-product-form.tsx @@ -56,10 +56,6 @@ export const CreateProductForm = () => { const form = useForm({ defaultValues: { - title: "", - subtitle: "", - handle: "", - description: "", discountable: true, tags: [], sales_channels: [], @@ -83,11 +79,12 @@ export const CreateProductForm = () => { length: values.length ? parseFloat(values.length) : undefined, height: values.height ? parseFloat(values.height) : undefined, weight: values.weight ? parseFloat(values.weight) : undefined, - variants: values.variants, + variants: values.variants.map((variant) => ({ + ...variant, + prices: [], + })), } as any - delete reqData.sales_channels - await mutateAsync(reqData, { onSuccess: ({ product }) => { handleSuccess(`../${product.id}`) diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-attribute-section/product-attribute-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-attribute-section/product-attribute-section.tsx index c16843d7ea53c..b13dd2c7092c7 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-attribute-section/product-attribute-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-attribute-section/product-attribute-section.tsx @@ -3,6 +3,8 @@ import { Product } from "@medusajs/medusa" import { Container, Heading, Text } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" +import { SectionRow } from "../../../../../components/common/section" +import { getFormattedCountry } from "../../../../../lib/addresses" type ProductAttributeSectionProps = { product: Product @@ -31,62 +33,16 @@ export const ProductAttributeSection = ({ ]} /> -
- - {t("fields.height")} - - - {product.height ?? "-"} - -
-
- - {t("fields.width")} - - - {product.width ?? "-"} - -
-
- - {t("fields.length")} - - - {product.length ?? "-"} - -
-
- - {t("fields.weight")} - - - {product.weight ?? "-"} - -
-
- - {t("fields.midCode")} - - - {product.mid_code ?? "-"} - -
-
- - {t("fields.hsCode")} - - - {product.hs_code ?? "-"} - -
-
- - {t("fields.countryOfOrigin")} - - - {product.origin_country ?? "-"} - -
+ + + + + + + ) } diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-general-section/product-general-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-general-section/product-general-section.tsx index be31377a06546..cf154eb3f6a0f 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-general-section/product-general-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-general-section/product-general-section.tsx @@ -5,6 +5,22 @@ import { useTranslation } from "react-i18next" import { useNavigate } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" import { useDeleteProduct } from "../../../../../hooks/api/products" +import { SectionRow } from "../../../../../components/common/section" + +const productStatusColor = (status: string) => { + switch (status) { + case "draft": + return "purple" + case "proposed": + return "orange" + case "published": + return "green" + case "rejected": + return "red" + default: + return "purple" + } +} type ProductGeneralSectionProps = { product: Product @@ -45,7 +61,9 @@ export const ProductGeneralSection = ({
{product.title}
- Published + + {t(`products.productStatus.${product.status}`)} +
-
- - {t("fields.description")} - - - {product.description} - -
-
- - {t("fields.subtitle")} - - - {product.subtitle ?? "-"} - -
-
- - {t("fields.handle")} - - - /{product.handle} - -
-
- - {t("fields.discountable")} - - - {product.discountable ? t("fields.true") : t("fields.false")} - -
+ + + + + ) } diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-option-section/product-option-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-option-section/product-option-section.tsx index f2ff2783cb831..ea9b4f38bcbf1 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-option-section/product-option-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-option-section/product-option-section.tsx @@ -1,8 +1,64 @@ -import { PencilSquare, Plus } from "@medusajs/icons" -import { Product, ProductOption } from "@medusajs/medusa" -import { Badge, Container, Heading, Text } from "@medusajs/ui" +import { PencilSquare, Plus, Trash } from "@medusajs/icons" +import { Product } from "@medusajs/medusa" +import { Badge, Container, Heading, usePrompt } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" +import { SectionRow } from "../../../../../components/common/section" +import { useDeleteProductOption } from "../../../../../hooks/api/products" + +const OptionActions = ({ + product, + option, +}: { + product: Product + option: any +}) => { + const { t } = useTranslation() + const { mutateAsync } = useDeleteProductOption(product.id, option.id) + const prompt = usePrompt() + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("products.deleteWarning", { + title: product.title, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + , + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + onClick: handleDelete, + icon: , + }, + ], + }, + ]} + /> + ) +} type ProductOptionSectionProps = { product: Product @@ -31,50 +87,27 @@ export const ProductOptionSection = ({ ]} /> + {product.options.map((option) => { return ( -
- - {option.title} - -
- {getUnqiueValues(option).map((value) => { - return ( - - {value} - - ) - })} -
- , - }, - ], - }, - ]} - /> -
+ value={option.values.map((val) => { + return ( + + {val.value} + + ) + })} + actions={} + /> ) })} ) } - -const getUnqiueValues = (option: ProductOption) => { - const values = option.values.map((v) => v.value) - - return Array.from(new Set(values)) -} diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-organization-section/product-organization-section.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-organization-section/product-organization-section.tsx index 8fd9768975754..0ed59bd62b2dd 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-organization-section/product-organization-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/components/product-organization-section/product-organization-section.tsx @@ -1,9 +1,10 @@ import { PencilSquare } from "@medusajs/icons" import { Product } from "@medusajs/medusa" -import { Badge, Container, Heading, Text } from "@medusajs/ui" +import { Badge, Container, Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" +import { SectionRow } from "../../../../../components/common/section" type ProductOrganizationSectionProps = { product: Product @@ -32,58 +33,49 @@ export const ProductOrganizationSection = ({ ]} /> -
- - {t("fields.tags")} - -
- {product.tags.length > 0 + + 0 ? product.tags.map((tag) => ( {tag.value} )) - : "-"} -
-
-
- - {t("fields.type")} - - {product.type ? ( - - - {product.type.value} - - - ) : ( - - - - - )} -
-
- - {t("fields.collection")} - - {product.collection ? ( - - - {product.collection.title} - - - ) : ( - - - - - )} -
-
- - {t("fields.categories")} - -
- {product.categories?.length > 0 + : undefined + } + /> + + + {product.type.value} + + + ) : undefined + } + /> + + + + {product.collection.title} + + + ) : undefined + } + /> + + 0 ? product.categories.map((pcat) => ( {pcat.name} )) - : "-"} -
-
+ : undefined + } + /> ) } 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 55d6faf38153f..e9252f1d9fade 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,19 +1,19 @@ 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 { Trans, useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" +import { useSalesChannels } from "../../../../../hooks/api/sales-channels" type ProductSalesChannelSectionProps = { product: Product } +// TODO: The fetched sales channel doesn't contain all necessary info export const ProductSalesChannelSection = ({ product, }: ProductSalesChannelSectionProps) => { - // const { count } = useAdminSalesChannels() - const count = 0 + const { count } = useSalesChannels() 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 9bf91cd15cc57..2470f90650621 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 @@ -29,6 +29,9 @@ export const ProductVariantSection = ({ product.id, { ...searchParams, + }, + { + keepPreviousData: true, } ) 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 790196524d2e0..7ad26232d0112 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 @@ -69,38 +69,38 @@ export const useProductVariantTableColumns = (product?: Product) => { const { t } = useTranslation() const optionColumns = useMemo(() => { - return product - ? product.options?.map((o) => { - return columnHelper.display({ - id: o.id, - header: () => ( -
- {o.title} -
- ), - cell: ({ row }) => { - const value = row.original.options.find( - (op) => op.option_id === o.id - ) - - if (!value) { - return - } + if (!product) { + return [] + } + return product.options?.map((option) => { + return columnHelper.display({ + id: option.id, + header: () => ( +
+ {option.title} +
+ ), + cell: ({ row }) => { + const variantOpt: any = row.original.options.find( + (opt: any) => opt.option_value.option_id === option.id + ) + if (!variantOpt) { + return + } - return ( -
- - {value.value} - -
- ) - }, - }) - }) - : [] + return ( +
+ + {variantOpt.option_value.value} + +
+ ) + }, + }) + }) }, [product]) return useMemo( diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/loader.ts b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/loader.ts index 7f5fad255e6f0..58a77829c7375 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/loader.ts @@ -1,13 +1,14 @@ import { AdminProductsRes } from "@medusajs/medusa" import { Response } from "@medusajs/medusa-js" -import { adminProductKeys } from "medusa-react" import { LoaderFunctionArgs } from "react-router-dom" -import { medusa, queryClient } from "../../../lib/medusa" +import { queryClient } from "../../../lib/medusa" +import { productsQueryKeys } from "../../../hooks/api/products" +import { client } from "../../../lib/client" const productDetailQuery = (id: string) => ({ - queryKey: adminProductKeys.detail(id), - queryFn: async () => medusa.admin.products.retrieve(id), + queryKey: productsQueryKeys.detail(id), + queryFn: async () => client.products.retrieve(id), }) export const productLoader = async ({ params }: LoaderFunctionArgs) => { diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/product-detail.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/product-detail.tsx index 551e8f5324d5d..7dd0488e9008e 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-detail/product-detail.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-detail/product-detail.tsx @@ -16,6 +16,7 @@ import sideBefore from "medusa-admin:widgets/product/details/side/before" import { ProductOrganizationSection } from "./components/product-organization-section" import { useProduct } from "../../../hooks/api/products" +// TODO: Use product domain translations only export const ProductDetail = () => { const initialData = useLoaderData() as Awaited< ReturnType @@ -43,31 +44,13 @@ export const ProductDetail = () => { ) })} -
-
+ +
+
-
- {sideBefore.widgets.map((w, i) => { - return ( -
- -
- ) - })} - - - - {sideAfter.widgets.map((w, i) => { - return ( -
- -
- ) - })} -
{after.widgets.map((w, i) => { return (
@@ -75,9 +58,12 @@ export const ProductDetail = () => {
) })} - + +
+ +
-
+
{sideBefore.widgets.map((w, i) => { return (
@@ -95,6 +81,10 @@ export const ProductDetail = () => {
) })} + +
+ +
diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-option/components/edit-product-option-form/edit-product-option-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-option/components/edit-product-option-form/edit-product-option-form.tsx index 9c5e91b3b8378..55048807d55f0 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-option/components/edit-product-option-form/edit-product-option-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-option/components/edit-product-option-form/edit-product-option-form.tsx @@ -1,7 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod" import { ProductOption } from "@medusajs/medusa" import { Button, Input } from "@medusajs/ui" -import { useAdminUpdateProductOption } from "medusa-react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { z } from "zod" @@ -10,6 +9,7 @@ import { RouteDrawer, useRouteModal, } from "../../../../../components/route-modal" +import { useUpdateProductOption } from "../../../../../hooks/api/products" type EditProductOptionFormProps = { option: ProductOption @@ -32,8 +32,9 @@ export const CreateProductOptionForm = ({ resolver: zodResolver(CreateProductOptionSchema), }) - const { mutateAsync, isLoading } = useAdminUpdateProductOption( - option.product_id + const { mutateAsync, isLoading } = useUpdateProductOption( + option.product_id, + option.id ) const handleSubmit = form.handleSubmit(async (values) => { diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/components/product-edit-variant-form/product-edit-variant-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/components/product-edit-variant-form/product-edit-variant-form.tsx index 603051d09e331..fa310bbfb1a40 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/components/product-edit-variant-form/product-edit-variant-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/components/product-edit-variant-form/product-edit-variant-form.tsx @@ -1,7 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Product, ProductOption, ProductVariant } from "@medusajs/medusa" import { Button, Heading, Input, Switch } from "@medusajs/ui" -import { useAdminUpdateVariant } from "medusa-react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { z } from "zod" @@ -17,6 +16,7 @@ import { } from "../../../../../components/route-modal" import { castNumber } from "../../../../../lib/cast-number" import { optionalInt } from "../../../../../lib/validation" +import { useUpdateProductVariant } from "../../../../../hooks/api/products" type ProductEditVariantFormProps = { product: Product @@ -83,7 +83,10 @@ export const ProductEditVariantForm = ({ resolver: zodResolver(ProductEditVariantSchema), }) - const { mutateAsync, isLoading } = useAdminUpdateVariant(product.id) + const { mutateAsync, isLoading } = useUpdateProductVariant( + product.id, + variant.id + ) const handleSubmit = form.handleSubmit(async (data) => { const parseNumber = (value?: string | number) => { diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/loader.ts b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/loader.ts index 321ace2e9ba99..6563e610d5142 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-edit-variant/loader.ts @@ -1,10 +1,10 @@ -import { adminProductKeys, adminStoreKeys } from "medusa-react" import { LoaderFunctionArgs } from "react-router-dom" import { medusa, queryClient } from "../../../lib/medusa" +import { productsQueryKeys } from "../../../hooks/api/products" const queryKey = (id: string) => { - return [adminProductKeys.detail(id), adminStoreKeys.details()] + return [productsQueryKeys.detail(id)] } const queryFn = async (id: string) => { diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-list/loader.ts b/packages/admin-next/dashboard/src/v2-routes/products/product-list/loader.ts index 9ad18e2dc5545..806dae1e6d463 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-list/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-list/loader.ts @@ -1,12 +1,12 @@ import { AdminProductsListRes } from "@medusajs/medusa" import { Response } from "@medusajs/medusa-js" import { QueryClient } from "@tanstack/react-query" -import { adminProductKeys } from "medusa-react" import { medusa, queryClient } from "../../../lib/medusa" +import { productsQueryKeys } from "../../../hooks/api/products" const productsListQuery = () => ({ - queryKey: adminProductKeys.list({ limit: 20, offset: 0 }), + queryKey: productsQueryKeys.list({ limit: 20, offset: 0 }), queryFn: async () => medusa.admin.products.list({ limit: 20, offset: 0 }), }) diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx index c686e26cd8064..bd7e71d80ffd1 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx @@ -3,7 +3,6 @@ import { CheckMini, Spinner, ThumbnailBadge } from "@medusajs/icons" import { Image, Product } from "@medusajs/medusa" import { Button, CommandBar, Tooltip, clx } from "@medusajs/ui" import { AnimatePresence, motion } from "framer-motion" -import { useAdminUpdateProduct, useMedusa } from "medusa-react" import { Fragment, useCallback, useState } from "react" import { useFieldArray, useForm } from "react-hook-form" import { useTranslation } from "react-i18next" @@ -19,6 +18,7 @@ import { FileType, FileUpload, } from "../../../../../components/common/file-upload" +import { useUpdateProduct } from "../../../../../hooks/api/products" type ProductMediaViewProps = { product: Product @@ -73,8 +73,7 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { keyName: "field_id", }) - const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id) - const { client } = useMedusa() + const { mutateAsync, isLoading } = useUpdateProduct(product.id) const handleSubmit = form.handleSubmit(async ({ media }) => { const urls = media.map((m) => m.url) @@ -86,15 +85,20 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { if (filesToUpload.length) { const files = filesToUpload.map((m) => m.file) as File[] - const uploads = await client.admin.uploads - .create(files) - .then((res) => { - return res.uploads - }) - .catch((_err) => { - // Show error message - return null - }) + // TODO: Implement upload to Medusa + // const uploads = await client.admin.uploads + // .create(files) + // .then((res) => { + // return res.uploads + // }) + // .catch((_err) => { + // // Show error message + // return null + // }) + + const uploads = files.map((file) => ({ + url: URL.createObjectURL(file), + })) if (!uploads) { return diff --git a/packages/admin-next/dashboard/src/v2-routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx b/packages/admin-next/dashboard/src/v2-routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx index 4935d217b4d81..a08a16b1d0638 100644 --- a/packages/admin-next/dashboard/src/v2-routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx @@ -1,7 +1,6 @@ import { Product, SalesChannel } from "@medusajs/medusa" import { Button, Checkbox } from "@medusajs/ui" import { RowSelectionState, createColumnHelper } from "@tanstack/react-table" -import { useAdminSalesChannels, useAdminUpdateProduct } from "medusa-react" import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import * as zod from "zod" @@ -17,6 +16,8 @@ import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/ import { useSalesChannelTableFilters } from "../../../../../hooks/table/filters/use-sales-channel-table-filters" import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query" import { useDataTable } from "../../../../../hooks/use-data-table" +import { useSalesChannels } from "../../../../../hooks/api/sales-channels" +import { useUpdateProduct } from "../../../../../hooks/api/products" type EditSalesChannelsFormProps = { product: Product @@ -63,15 +64,14 @@ export const EditSalesChannelsForm = ({ const { searchParams, raw } = useSalesChannelTableQuery({ pageSize: PAGE_SIZE, }) - const { sales_channels, count, isLoading, isError, error } = - useAdminSalesChannels( - { - ...searchParams, - }, - { - keepPreviousData: true, - } - ) + const { sales_channels, count, isLoading, isError, error } = useSalesChannels( + { + ...searchParams, + }, + { + keepPreviousData: true, + } + ) const filters = useSalesChannelTableFilters() const columns = useColumns() @@ -90,9 +90,7 @@ export const EditSalesChannelsForm = ({ pageSize: PAGE_SIZE, }) - const { mutateAsync, isLoading: isMutating } = useAdminUpdateProduct( - product.id - ) + const { mutateAsync, isLoading: isMutating } = useUpdateProduct(product.id) const handleSubmit = form.handleSubmit(async (data) => { const arr = data.sales_channels ?? [] diff --git a/packages/medusa/src/api-v2/admin/products/query-config.ts b/packages/medusa/src/api-v2/admin/products/query-config.ts index d49664ec6b991..3b72e34cbd58f 100644 --- a/packages/medusa/src/api-v2/admin/products/query-config.ts +++ b/packages/medusa/src/api-v2/admin/products/query-config.ts @@ -24,6 +24,7 @@ export const defaultAdminProductsVariantFields = [ "barcode", "*prices", "*options", + "*options.option_value", ] export const retrieveVariantConfig = { @@ -81,10 +82,10 @@ export const defaultAdminProductFields = [ "*options.values", "*tags", "*images", - "*sales_channels", "*variants", "*variants.prices", "*variants.options", + "*variants.options.option_value", "*sales_channels", ]