feat: Add product routes and components to v2 in admin-next (#6958)
This commit is contained in:
1 parent
d333db0842
commit
07fb058d96
81 files changed
+597
-144
No files matched your search
@@ -236,6 +236,22 @@
|
||||
},
|
||||
"weight": {
|
||||
"label": "Weight"
|
||||
},
|
||||
"options": {
|
||||
"label": "Product options",
|
||||
"hint": "Options are used to define the color, size, etc. of the product",
|
||||
"optionTitle": "Option title",
|
||||
"variations": "Variations (comma-separated)"
|
||||
},
|
||||
"variants": {
|
||||
"label": "Product variants",
|
||||
"hint": "Variants left unchecked won't be created, This ranking will affect how the variants are ranked in your frontend."
|
||||
},
|
||||
"mid_code": {
|
||||
"label": "Mid code"
|
||||
},
|
||||
"hs_code": {
|
||||
"label": "HS code"
|
||||
}
|
||||
},
|
||||
"variant": {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./list"
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Checkbox, Text } from "@medusajs/ui"
|
||||
|
||||
export interface ListProps<T> {
|
||||
options: { title: string; value: T }[]
|
||||
value?: T[]
|
||||
onChange?: (value: T[]) => void
|
||||
compare?: (a: T, b: T) => boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const List = <T extends any>({
|
||||
options,
|
||||
onChange,
|
||||
value,
|
||||
compare,
|
||||
disabled,
|
||||
}: ListProps<T>) => {
|
||||
if (options.length === 0) {
|
||||
return <div>No options</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-row justify-center border divide-y rounded-lg">
|
||||
{options.map((option) => {
|
||||
return (
|
||||
<div className="flex p-4 gap-x-4">
|
||||
{onChange && value !== undefined && (
|
||||
<Checkbox
|
||||
disabled={disabled}
|
||||
checked={value.some(
|
||||
(v) => compare?.(v, option.value) ?? v === option.value
|
||||
)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onChange([...value, option.value])
|
||||
} else {
|
||||
onChange(
|
||||
value.filter(
|
||||
(v) =>
|
||||
!(compare?.(v, option.value) ?? v === option.value)
|
||||
)
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Text key={option.title}>{option.title}</Text>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { CategoriesListRes, CategoryRes } from "../../types/api-responses"
|
||||
|
||||
const CATEGORIES_QUERY_KEY = "categories" as const
|
||||
export const categoriesQueryKeys = queryKeysFactory(CATEGORIES_QUERY_KEY)
|
||||
|
||||
export const useCategory = (
|
||||
id: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<CategoryRes, Error, CategoryRes, QueryKey>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: categoriesQueryKeys.detail(id),
|
||||
queryFn: async () => client.categories.retrieve(id),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useCategories = (
|
||||
query?: Record<string, any>,
|
||||
options?: Omit<
|
||||
UseQueryOptions<CategoriesListRes, Error, CategoriesListRes, QueryKey>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: categoriesQueryKeys.list(query),
|
||||
queryFn: async () => client.categories.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
@@ -1,7 +1,18 @@
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import {
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { ProductListRes, ProductRes } from "../../types/api-responses"
|
||||
import {
|
||||
ProductDeleteRes,
|
||||
ProductListRes,
|
||||
ProductRes,
|
||||
} from "../../types/api-responses"
|
||||
import { queryClient } from "../../lib/medusa"
|
||||
|
||||
const PRODUCTS_QUERY_KEY = "products" as const
|
||||
export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY)
|
||||
@@ -38,3 +49,48 @@ export const useProducts = (
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useCreateProduct = (
|
||||
options?: UseMutationOptions<ProductRes, Error, any>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: any) => client.products.create(payload),
|
||||
onSuccess: (data: any, variables: any, context: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() })
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateProduct = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<ProductRes, Error, any>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload: any) => client.products.update(id, payload),
|
||||
onSuccess: (data: any, variables: any, context: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({ queryKey: productsQueryKeys.detail(id) })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteProduct = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<ProductDeleteRes, Error, void>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => client.products.delete(id),
|
||||
onSuccess: (data: any, variables: any, context: any) => {
|
||||
queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({ queryKey: productsQueryKeys.detail(id) })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { TagsListRes, TagRes } from "../../types/api-responses"
|
||||
|
||||
const TAGS_QUERY_KEY = "tags" as const
|
||||
export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
||||
|
||||
export const useTag = (
|
||||
id: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<TagRes, Error, TagRes, QueryKey>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: tagsQueryKeys.detail(id),
|
||||
queryFn: async () => client.tags.retrieve(id),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useTags = (
|
||||
query?: Record<string, any>,
|
||||
options?: Omit<
|
||||
UseQueryOptions<TagsListRes, Error, TagsListRes, QueryKey>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: tagsQueryKeys.list(query),
|
||||
queryFn: async () => client.tags.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
ProductCollectionListRes,
|
||||
ProductCollectionRes,
|
||||
} from "../../types/api-responses"
|
||||
import { getRequest } from "./common"
|
||||
|
||||
async function listProductCategories(query?: Record<string, any>) {
|
||||
return getRequest<ProductCollectionListRes>(`/admin/categories`, query)
|
||||
}
|
||||
|
||||
async function retrieveProductCategory(
|
||||
id: string,
|
||||
query?: Record<string, any>
|
||||
) {
|
||||
return getRequest<ProductCollectionRes>(`/admin/categories/${id}`, query)
|
||||
}
|
||||
|
||||
export const categories = {
|
||||
list: listProductCategories,
|
||||
retrieve: retrieveProductCategory,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiKeys } from "./api-keys"
|
||||
import { auth } from "./auth"
|
||||
import { categories } from "./categories"
|
||||
import { collections } from "./collections"
|
||||
import { currencies } from "./currencies"
|
||||
import { customers } from "./customers"
|
||||
@@ -11,18 +12,21 @@ import { regions } from "./regions"
|
||||
import { salesChannels } from "./sales-channels"
|
||||
import { stockLocations } from "./stock-locations"
|
||||
import { stores } from "./stores"
|
||||
import { tags } from "./tags"
|
||||
import { users } from "./users"
|
||||
import { workflowExecutions } from "./workflow-executions"
|
||||
|
||||
export const client = {
|
||||
auth: auth,
|
||||
apiKeys: apiKeys,
|
||||
categories: categories,
|
||||
customers: customers,
|
||||
currencies: currencies,
|
||||
collections: collections,
|
||||
promotions: promotions,
|
||||
stores: stores,
|
||||
salesChannels: salesChannels,
|
||||
tags: tags,
|
||||
users: users,
|
||||
regions: regions,
|
||||
invites: invites,
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
import { ProductListRes, ProductRes } from "../../types/api-responses"
|
||||
import { getRequest } from "./common"
|
||||
import {
|
||||
ProductDeleteRes,
|
||||
ProductListRes,
|
||||
ProductRes,
|
||||
} from "../../types/api-responses"
|
||||
import { deleteRequest, getRequest, postRequest } from "./common"
|
||||
|
||||
async function retrieveProduct(id: string, query?: Record<string, any>) {
|
||||
return getRequest<ProductRes>(`/admin/products/${id}`, query)
|
||||
}
|
||||
|
||||
async function createProduct(payload: any) {
|
||||
return postRequest<ProductRes>(`/admin/products`, payload)
|
||||
}
|
||||
|
||||
async function listProducts(query?: Record<string, any>) {
|
||||
return getRequest<ProductListRes>(`/admin/products`, query)
|
||||
}
|
||||
|
||||
async function updateProduct(id: string, payload: any) {
|
||||
return postRequest<ProductRes>(`/admin/products/${id}`, payload)
|
||||
}
|
||||
|
||||
async function deleteProduct(id: string) {
|
||||
return deleteRequest<ProductDeleteRes>(`/admin/products/${id}`)
|
||||
}
|
||||
|
||||
export const products = {
|
||||
retrieve: retrieveProduct,
|
||||
list: listProducts,
|
||||
create: createProduct,
|
||||
update: updateProduct,
|
||||
delete: deleteProduct,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
ProductCollectionListRes,
|
||||
ProductCollectionRes,
|
||||
} from "../../types/api-responses"
|
||||
import { getRequest } from "./common"
|
||||
|
||||
async function listProductTags(query?: Record<string, any>) {
|
||||
return getRequest<ProductCollectionListRes>(`/admin/tags`, query)
|
||||
}
|
||||
|
||||
async function retrieveProductTag(id: string, query?: Record<string, any>) {
|
||||
return getRequest<ProductCollectionRes>(`/admin/tags/${id}`, query)
|
||||
}
|
||||
|
||||
export const tags = {
|
||||
list: listProductTags,
|
||||
retrieve: retrieveProductTag,
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import type {
|
||||
AdminDraftOrdersRes,
|
||||
AdminGiftCardsRes,
|
||||
AdminOrdersRes,
|
||||
AdminProductsRes,
|
||||
AdminRegionsRes,
|
||||
AdminSalesChannelsRes,
|
||||
AdminUserRes,
|
||||
} from "@medusajs/medusa"
|
||||
import { Outlet, RouteObject } from "react-router-dom"
|
||||
|
||||
@@ -188,66 +190,6 @@ export const v1Routes: RouteObject[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/products",
|
||||
handle: {
|
||||
crumb: () => "Products",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () => import("../../routes/products/product-list"),
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
lazy: () => import("../../routes/products/product-create"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/products/product-detail"),
|
||||
handle: {
|
||||
crumb: (data: AdminProductsRes) => data.product.title,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () => import("../../routes/products/product-edit"),
|
||||
},
|
||||
{
|
||||
path: "sales-channels",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-sales-channels"),
|
||||
},
|
||||
{
|
||||
path: "attributes",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-attributes"),
|
||||
},
|
||||
{
|
||||
path: "options/create",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-create-option"),
|
||||
},
|
||||
{
|
||||
path: "options/:option_id/edit",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-edit-option"),
|
||||
},
|
||||
{
|
||||
path: "media",
|
||||
lazy: () => import("../../routes/products/product-media"),
|
||||
},
|
||||
{
|
||||
path: "variants/:variant_id/edit",
|
||||
lazy: () =>
|
||||
import("../../routes/products/product-edit-variant"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/categories",
|
||||
handle: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SalesChannelDTO, UserDTO } from "@medusajs/types"
|
||||
import { Navigate, Outlet, RouteObject, useLocation } from "react-router-dom"
|
||||
|
||||
import { Spinner } from "@medusajs/icons"
|
||||
import { AdminCollectionsRes } from "@medusajs/medusa"
|
||||
import { AdminCollectionsRes, AdminProductsRes } from "@medusajs/medusa"
|
||||
import { ErrorBoundary } from "../../components/error/error-boundary"
|
||||
import { MainLayout } from "../../components/layout-v2/main-layout"
|
||||
import { SettingsLayout } from "../../components/layout/settings-layout"
|
||||
@@ -66,6 +66,68 @@ export const v2Routes: RouteObject[] = [
|
||||
path: "/",
|
||||
element: <MainLayout />,
|
||||
children: [
|
||||
{
|
||||
path: "/products",
|
||||
handle: {
|
||||
crumb: () => "Products",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () => import("../../v2-routes/products/product-list"),
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-create"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../v2-routes/products/product-detail"),
|
||||
handle: {
|
||||
crumb: (data: AdminProductsRes) => data.product.title,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () => import("../../v2-routes/products/product-edit"),
|
||||
},
|
||||
{
|
||||
path: "sales-channels",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-sales-channels"),
|
||||
},
|
||||
{
|
||||
path: "attributes",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-attributes"),
|
||||
},
|
||||
{
|
||||
path: "options/create",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-create-option"),
|
||||
},
|
||||
{
|
||||
path: "options/:option_id/edit",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-edit-option"),
|
||||
},
|
||||
{
|
||||
path: "media",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-media"),
|
||||
},
|
||||
{
|
||||
path: "variants/:variant_id/edit",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/products/product-edit-variant"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/orders",
|
||||
handle: {
|
||||
@@ -364,7 +426,7 @@ export const v2Routes: RouteObject[] = [
|
||||
handle: {
|
||||
crumb: (data: ApiKeyRes) => {
|
||||
console.log("data", data)
|
||||
return data.apiKey.title
|
||||
return data.api_key.title
|
||||
},
|
||||
},
|
||||
children: [
|
||||
|
||||
+3
-3
@@ -15,10 +15,10 @@ import { MoneyAmountCell } from "../../../../../../components/table/table-cells/
|
||||
import { PlaceholderCell } from "../../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { ProductCell } from "../../../../../../components/table/table-cells/product/product-cell"
|
||||
import { useDataTable } from "../../../../../../hooks/use-data-table"
|
||||
import { useProductVariantTableFilters } from "../../../../../products/product-detail/components/product-variant-section/use-variant-table-filters"
|
||||
import { useProductVariantTableQuery } from "../../../../../products/product-detail/components/product-variant-section/use-variant-table-query"
|
||||
import { useCreateDraftOrder } from "../hooks"
|
||||
import { ExistingItem } from "../types"
|
||||
import { useProductVariantTableQuery } from "../../../../../../v2-routes/products/product-detail/components/product-variant-section/use-variant-table-query"
|
||||
import { useProductVariantTableFilters } from "../../../../../../v2-routes/products/product-detail/components/product-variant-section/use-variant-table-filters"
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
const PREFIX = "av"
|
||||
@@ -193,7 +193,7 @@ const useVariantTableColumns = () => {
|
||||
cell: ({ getValue }) => {
|
||||
const options = getValue()
|
||||
|
||||
const displayValue = options?.map((o) => o.value).join(" · ")
|
||||
const displayValue = options?.map((o: any) => o.value).join(" · ")
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
UserDTO,
|
||||
} from "@medusajs/types"
|
||||
import { WorkflowExecutionDTO } from "../v2-routes/workflow-executions/types"
|
||||
import { ProductTagDTO } from "@medusajs/types/dist/product"
|
||||
|
||||
type ListRes = {
|
||||
count: number
|
||||
@@ -99,6 +100,14 @@ export type ProductRes = { product: ExtendedProductDTO }
|
||||
export type ProductListRes = { products: ExtendedProductDTO[] } & ListRes
|
||||
export type ProductDeleteRes = DeleteRes
|
||||
|
||||
// Categories
|
||||
export type CategoryRes = { category: ProductCategoryDTO }
|
||||
export type CategoriesListRes = { categories: ProductCategoryDTO[] } & ListRes
|
||||
|
||||
// Tags
|
||||
export type TagRes = { tag: ProductTagDTO }
|
||||
export type TagsListRes = { tags: ProductTagDTO[] } & ListRes
|
||||
|
||||
// Product Types
|
||||
export type ProductTypeRes = { product_type: ProductTypeDTO }
|
||||
export type ProductTypeListRes = { product_types: ProductTypeDTO[] } & ListRes
|
||||
|
||||
File renamed without changes.
+2
-3
@@ -1,17 +1,16 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { CountrySelect } from "../../../../../components/common/country-select"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
type ProductAttributesFormProps = {
|
||||
product: Product
|
||||
@@ -57,7 +56,7 @@ export const ProductAttributesForm = ({
|
||||
resolver: zodResolver(ProductAttributesSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id)
|
||||
const { mutateAsync, isLoading } = useUpdateProduct(product.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
File renamed without changes.
+2
-2
@@ -1,16 +1,16 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { ProductAttributesForm } from "./components/product-attributes-form"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductAttributes = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,15 +1,15 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { CreateProductOptionForm } from "./components/create-product-option-form"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductCreateOption = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
+173
-18
@@ -13,13 +13,6 @@ import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"
|
||||
|
||||
import { SalesChannel } from "@medusajs/medusa"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import {
|
||||
useAdminCollections,
|
||||
useAdminProductCategories,
|
||||
useAdminProductTags,
|
||||
useAdminProductTypes,
|
||||
useAdminSalesChannels,
|
||||
} from "medusa-react"
|
||||
import { Fragment, useMemo, useState } from "react"
|
||||
import { CountrySelect } from "../../../../../components/common/country-select"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
@@ -32,6 +25,12 @@ import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { CreateProductFormReturn } from "./create-product-form"
|
||||
import { Combobox } from "../../../../../components/common/combobox"
|
||||
import { FileUpload } from "../../../../../components/common/file-upload"
|
||||
import { List } from "../../../../../components/common/list"
|
||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
||||
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"
|
||||
|
||||
type CreateProductPropsProps = {
|
||||
form: CreateProductFormReturn
|
||||
@@ -46,16 +45,48 @@ const SUPPORTED_FORMATS = [
|
||||
"image/svg+xml",
|
||||
]
|
||||
|
||||
const permutations = (
|
||||
data: { title: string; values: string[] }[]
|
||||
): { [key: string]: string }[] => {
|
||||
if (data.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (data.length === 1) {
|
||||
return data[0].values.map((value) => ({ [data[0].title]: value }))
|
||||
}
|
||||
|
||||
const toProcess = data[0]
|
||||
const rest = data.slice(1)
|
||||
|
||||
return toProcess.values.flatMap((value) => {
|
||||
return permutations(rest).map((permutation) => {
|
||||
return {
|
||||
[toProcess.title]: value,
|
||||
...permutation,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const generateNameFromPermutation = (permutation: {
|
||||
[key: string]: string
|
||||
}) => {
|
||||
return Object.values(permutation).join(" / ")
|
||||
}
|
||||
|
||||
export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, onOpenChange] = useState(false)
|
||||
const { product_types, isLoading: isLoadingTypes } = useAdminProductTypes()
|
||||
const { product_tags, isLoading: isLoadingTags } = useAdminProductTags()
|
||||
const { collections, isLoading: isLoadingCollections } = useAdminCollections()
|
||||
const { product_types, isLoading: isLoadingTypes } = useProductTypes()
|
||||
const { product_tags, isLoading: isLoadingTags } = useTags()
|
||||
const { collections, isLoading: isLoadingCollections } = useCollections()
|
||||
const { sales_channels, isLoading: isLoadingSalesChannels } =
|
||||
useAdminSalesChannels()
|
||||
const { product_categories, isLoading: isLoadingCategories } =
|
||||
useAdminProductCategories()
|
||||
useSalesChannels()
|
||||
const { product_categories, isLoading: isLoadingCategories } = useCategories()
|
||||
|
||||
const options = form.watch("options")
|
||||
const optionPermutations = permutations(options)
|
||||
|
||||
// const { append } = useFieldArray({
|
||||
// name: "images",
|
||||
@@ -202,14 +233,18 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="type_id"
|
||||
render={({ field }) => {
|
||||
render={({ field: { onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.type.label")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select disabled={isLoadingTypes} {...field}>
|
||||
<Select
|
||||
disabled={isLoadingTypes}
|
||||
{...field}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<Select.Trigger ref={field.ref}>
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
@@ -229,14 +264,18 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="collection_id"
|
||||
render={({ field }) => {
|
||||
render={({ field: { onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.collection.label")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select disabled={isLoadingCollections} {...field}>
|
||||
<Select
|
||||
disabled={isLoadingCollections}
|
||||
{...field}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<Select.Trigger ref={field.ref}>
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
@@ -351,6 +390,91 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
|
||||
</div>
|
||||
<div id="variants" className="flex flex-col gap-y-8">
|
||||
<Heading level="h2">{t("products.variants")}</Heading>
|
||||
<div className="grid grid-cols-1 gap-x-4 gap-y-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="options"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.options.label")}
|
||||
</Form.Label>
|
||||
<Form.Hint>
|
||||
{t("products.fields.options.hint")}
|
||||
</Form.Hint>
|
||||
<Form.Control>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
field.onChange([
|
||||
{
|
||||
title: "Color",
|
||||
values: ["Red", "Blue", "Green"],
|
||||
},
|
||||
{ title: "Size", values: ["S", "M", "L"] },
|
||||
])
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</button>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-x-4 gap-y-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="variants"
|
||||
render={({ field: { value, onChange, ...field } }) => {
|
||||
const selectedOptions = value.map((v) => v.options)
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.variants.label")}
|
||||
</Form.Label>
|
||||
<Form.Hint>
|
||||
{t("products.fields.variants.hint")}
|
||||
</Form.Hint>
|
||||
<Form.Control>
|
||||
<List
|
||||
{...field}
|
||||
value={selectedOptions}
|
||||
onChange={(v) => {
|
||||
onChange(
|
||||
v.map((options, i) => {
|
||||
return {
|
||||
title:
|
||||
generateNameFromPermutation(options),
|
||||
variant_rank: i,
|
||||
options,
|
||||
prices: [],
|
||||
}
|
||||
})
|
||||
)
|
||||
}}
|
||||
compare={(a, b) => {
|
||||
return (
|
||||
generateNameFromPermutation(a) ===
|
||||
generateNameFromPermutation(b)
|
||||
)
|
||||
}}
|
||||
options={optionPermutations.map((opt) => {
|
||||
return {
|
||||
title: generateNameFromPermutation(opt),
|
||||
value: opt,
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="attributes" className="flex flex-col gap-y-8">
|
||||
@@ -454,8 +578,39 @@ export const CreateProductDetails = ({ form }: CreateProductPropsProps) => {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="mid_code"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.mid_code.label")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="hs_code"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label optional>
|
||||
{t("products.fields.hs_code.label")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* TODO: Add missing attribute fields */}
|
||||
</div>
|
||||
<div id="media" className="flex flex-col gap-y-8">
|
||||
<Heading level="h2">{t("products.media.label")}</Heading>
|
||||
+26
-11
@@ -1,6 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { useAdminCreateProduct } from "medusa-react"
|
||||
import { UseFormReturn, useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { CreateProductDetails } from "./create-product-details"
|
||||
import { useCreateProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
const CreateProductSchema = zod.object({
|
||||
title: zod.string(),
|
||||
@@ -30,8 +30,16 @@ const CreateProductSchema = zod.object({
|
||||
weight: zod.string().optional(),
|
||||
mid_code: zod.string().optional(),
|
||||
hs_code: zod.string().optional(),
|
||||
options: zod.array(
|
||||
zod.object({
|
||||
title: zod.string(),
|
||||
values: zod.array(zod.string()),
|
||||
})
|
||||
),
|
||||
variants: zod.array(
|
||||
zod.object({
|
||||
title: zod.string(),
|
||||
options: zod.record(zod.string(), zod.string()),
|
||||
variant_rank: zod.number(),
|
||||
})
|
||||
),
|
||||
@@ -55,17 +63,18 @@ export const CreateProductForm = () => {
|
||||
discountable: true,
|
||||
tags: [],
|
||||
sales_channels: [],
|
||||
options: [],
|
||||
variants: [],
|
||||
images: [],
|
||||
},
|
||||
resolver: zodResolver(CreateProductSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateProduct()
|
||||
const { mutateAsync, isLoading } = useCreateProduct()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
const handleSubmit = form.handleSubmit(
|
||||
async (values) => {
|
||||
const reqData = {
|
||||
...values,
|
||||
is_giftcard: false,
|
||||
tags: values.tags?.map((tag) => ({ value: tag })),
|
||||
@@ -74,15 +83,21 @@ 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.map((v) => ({ title: "", prices: [] })),
|
||||
},
|
||||
{
|
||||
variants: values.variants,
|
||||
} as any
|
||||
|
||||
delete reqData.sales_channels
|
||||
|
||||
await mutateAsync(reqData, {
|
||||
onSuccess: ({ product }) => {
|
||||
handleSuccess(`../${product.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
(err) => {
|
||||
console.log(err)
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { Container, Heading, StatusBadge, Text, usePrompt } from "@medusajs/ui"
|
||||
import { useAdminDeleteProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useDeleteProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
type ProductGeneralSectionProps = {
|
||||
product: Product
|
||||
@@ -17,7 +17,7 @@ export const ProductGeneralSection = ({
|
||||
const prompt = usePrompt()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { mutateAsync } = useAdminDeleteProduct(product.id)
|
||||
const { mutateAsync } = useDeleteProduct(product.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
File renamed without changes.
+2
-2
@@ -9,11 +9,11 @@ import {
|
||||
clx,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
type ProductMedisaSectionProps = {
|
||||
product: Product
|
||||
@@ -37,7 +37,7 @@ export const ProductMediaSection = ({ product }: ProductMedisaSectionProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
const { mutateAsync } = useAdminUpdateProduct(product.id)
|
||||
const { mutateAsync } = useUpdateProduct(product.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const ids = Object.keys(selection)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-8
@@ -1,4 +1,3 @@
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
@@ -15,6 +14,7 @@ import before from "medusa-admin:widgets/product/details/before"
|
||||
import sideAfter from "medusa-admin:widgets/product/details/side/after"
|
||||
import sideBefore from "medusa-admin:widgets/product/details/side/before"
|
||||
import { ProductOrganizationSection } from "./components/product-organization-section"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -22,13 +22,9 @@ export const ProductDetail = () => {
|
||||
>
|
||||
|
||||
const { id } = useParams()
|
||||
const { product, isLoading, isError, error } = useAdminProduct(
|
||||
id!,
|
||||
undefined,
|
||||
{
|
||||
initialData: initialData,
|
||||
}
|
||||
)
|
||||
const { product, isLoading, isError, error } = useProduct(id!, undefined, {
|
||||
initialData: initialData,
|
||||
})
|
||||
|
||||
if (isLoading || !product) {
|
||||
return <div>Loading...</div>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,15 +1,15 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { json, useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { CreateProductOptionForm } from "./components/edit-product-option-form"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductEditOption = () => {
|
||||
const { id, option_id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
const option = product?.options.find((o) => o.id === option_id)
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-8
@@ -1,11 +1,11 @@
|
||||
import { ProductVariant } from "@medusajs/medusa"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { json, useLoaderData, useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { ProductEditVariantForm } from "./components/product-edit-variant-form"
|
||||
import { editProductVariantLoader } from "./loader"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductEditVariant = () => {
|
||||
const loaderData = useLoaderData() as Awaited<
|
||||
@@ -15,13 +15,9 @@ export const ProductEditVariant = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id, variant_id } = useParams()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(
|
||||
id!,
|
||||
undefined,
|
||||
{
|
||||
initialData: loaderData?.initialData,
|
||||
}
|
||||
)
|
||||
const { product, isLoading, isError, error } = useProduct(id!, undefined, {
|
||||
initialData: loaderData?.initialData,
|
||||
})
|
||||
|
||||
const variant = product?.variants.find(
|
||||
(v: ProductVariant) => v.id === variant_id
|
||||
+2
-2
@@ -2,7 +2,6 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { ProductStatus } from "@medusajs/types"
|
||||
import { Button, Input, Select, Switch, Text, Textarea } from "@medusajs/ui"
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
type EditProductFormProps = {
|
||||
product: Product
|
||||
@@ -44,7 +44,7 @@ export const EditProductForm = ({ product }: EditProductFormProps) => {
|
||||
resolver: zodResolver(EditProductSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id)
|
||||
const { mutateAsync, isLoading } = useUpdateProduct(product.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,16 +1,16 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditProductForm } from "./components/edit-product-form"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductEdit = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
File renamed without changes.
+6
-3
@@ -2,7 +2,6 @@ import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import type { Product } from "@medusajs/medusa"
|
||||
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useAdminDeleteProduct, useAdminProducts } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, Outlet, useLoaderData } from "react-router-dom"
|
||||
@@ -14,6 +13,10 @@ import { useProductTableFilters } from "../../../../../hooks/table/filters/use-p
|
||||
import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { productsLoader } from "../../loader"
|
||||
import {
|
||||
useDeleteProduct,
|
||||
useProducts,
|
||||
} from "../../../../../hooks/api/products"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -25,7 +28,7 @@ export const ProductListTable = () => {
|
||||
>
|
||||
|
||||
const { searchParams, raw } = useProductTableQuery({ pageSize: PAGE_SIZE })
|
||||
const { products, count, isLoading, isError, error } = useAdminProducts(
|
||||
const { products, count, isLoading, isError, error } = useProducts(
|
||||
{
|
||||
...searchParams,
|
||||
},
|
||||
@@ -80,7 +83,7 @@ export const ProductListTable = () => {
|
||||
const ProductActions = ({ product }: { product: Product }) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
const { mutateAsync } = useAdminDeleteProduct(product.id)
|
||||
const { mutateAsync } = useDeleteProduct(product.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -12,8 +12,8 @@ import { useCallback, useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { RouteFocusModal } from "../../../../../components/route-modal"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
|
||||
type ProductMediaGalleryProps = {
|
||||
product: Product
|
||||
@@ -25,7 +25,7 @@ export const ProductMediaGallery = ({ product }: ProductMediaGalleryProps) => {
|
||||
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id)
|
||||
const { mutateAsync, isLoading } = useUpdateProduct(product.id)
|
||||
|
||||
const media = getMedia(product.images, product.thumbnail)
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,12 +1,12 @@
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { ProductMediaView } from "./components/product-media-view"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductMedia = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
const ready = !isLoading && product
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -1,12 +1,12 @@
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { EditSalesChannelsForm } from "./components/edit-sales-channels-form"
|
||||
import { useProduct } from "../../../hooks/api/products"
|
||||
|
||||
export const ProductSalesChannels = () => {
|
||||
const { id } = useParams()
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
const { product, isLoading, isError, error } = useProduct(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
@@ -45,7 +45,7 @@ export const createProductsWorkflow = createWorkflow(
|
||||
const inputProduct = data.input.products[i]
|
||||
return p.variants?.map((v, j) => ({
|
||||
...v,
|
||||
prices: inputProduct?.variants?.[j]?.prices,
|
||||
prices: inputProduct?.variants?.[j]?.prices ?? [],
|
||||
}))
|
||||
})
|
||||
.flat()
|
||||
@@ -54,7 +54,7 @@ export const createProductsWorkflow = createWorkflow(
|
||||
|
||||
const pricesToCreate = transform({ variantsWithAssociatedPrices }, (data) =>
|
||||
data.variantsWithAssociatedPrices.map((v) => ({
|
||||
prices: v.prices,
|
||||
prices: v.prices ?? [],
|
||||
}))
|
||||
)
|
||||
|
||||
|
||||
@@ -509,6 +509,10 @@ export class AdminPostProductsProductVariantsReq {
|
||||
@IsOptional()
|
||||
manage_inventory?: boolean = true
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
variant_rank?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
@@ -538,9 +542,10 @@ export class AdminPostProductsProductVariantsReq {
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ProductVariantPricesCreateReq)
|
||||
prices: ProductVariantPricesCreateReq[]
|
||||
prices?: ProductVariantPricesCreateReq[]
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@@ -588,6 +593,10 @@ export class AdminPostProductsProductVariantsVariantReq {
|
||||
@IsOptional()
|
||||
manage_inventory?: boolean
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
variant_rank?: number
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weight?: number
|
||||
|
||||
Reference in new issue
Block a user