feat(dashboard,js-sdk,types,admin-shared): Add Product Types domain (#7732)
This commit is contained in:
committed by
GitHub
parent
70a72ce2df
commit
2d8d2c4255
@@ -55,6 +55,13 @@ const PRODUCT_CATEGORY_INJECTION_ZONES = [
|
||||
"product_category.list.after",
|
||||
] as const
|
||||
|
||||
const PRODUCT_TYPE_INJECTION_ZONES = [
|
||||
"product_type.details.before",
|
||||
"product_type.details.after",
|
||||
"product_type.list.before",
|
||||
"product_type.list.after",
|
||||
] as const
|
||||
|
||||
const PRICE_LIST_INJECTION_ZONES = [
|
||||
"price_list.details.before",
|
||||
"price_list.details.after",
|
||||
@@ -198,4 +205,5 @@ export const INJECTION_ZONES = [
|
||||
...WORKFLOW_INJECTION_ZONES,
|
||||
...CAMPAIGN_INJECTION_ZONES,
|
||||
...TAX_INJECTION_ZONES,
|
||||
...PRODUCT_TYPE_INJECTION_ZONES,
|
||||
] as const
|
||||
|
||||
@@ -49,6 +49,10 @@ const useSettingRoutes = (): NavItemProps[] => {
|
||||
label: t("salesChannels.domain"),
|
||||
to: "/settings/sales-channels",
|
||||
},
|
||||
{
|
||||
label: t("productTypes.domain"),
|
||||
to: "/settings/product-types",
|
||||
},
|
||||
{
|
||||
label: t("shippingProfile.domain"),
|
||||
to: "/settings/shipping-profiles",
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
|
||||
const PRODUCT_TYPES_QUERY_KEY = "product_types" as const
|
||||
const productTypesQueryKeys = queryKeysFactory(PRODUCT_TYPES_QUERY_KEY)
|
||||
export const productTypesQueryKeys = queryKeysFactory(PRODUCT_TYPES_QUERY_KEY)
|
||||
|
||||
export const useProductType = (
|
||||
id: string,
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminProductTypeParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
{ product_type: HttpTypes.AdminProductType },
|
||||
Error,
|
||||
{ product_type: HttpTypes.AdminProductType },
|
||||
HttpTypes.AdminProductTypeResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductTypeResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => client.productTypes.retrieve(id, query),
|
||||
queryFn: () => sdk.admin.productType.retrieve(id, query),
|
||||
queryKey: productTypesQueryKeys.detail(id),
|
||||
...options,
|
||||
})
|
||||
@@ -29,22 +37,84 @@ export const useProductType = (
|
||||
}
|
||||
|
||||
export const useProductTypes = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminProductTypeListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
{ product_types: HttpTypes.AdminProductType[] },
|
||||
Error,
|
||||
{ product_types: HttpTypes.AdminProductType[] },
|
||||
HttpTypes.AdminProductTypeListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductTypeListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => client.productTypes.list(query),
|
||||
queryFn: () => sdk.admin.productType.list(query),
|
||||
queryKey: productTypesQueryKeys.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useCreateProductType = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductTypeResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminCreateProductType
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => sdk.admin.productType.create(payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateProductType = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductTypeResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminUpdateProductType
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => sdk.admin.productType.update(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: productTypesQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteProductType = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductTypeDeleteResponse,
|
||||
FetchError,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => sdk.admin.productType.delete(id),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: productTypesQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: productTypesQueryKeys.lists() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
@@ -223,11 +224,11 @@ export const useProduct = (
|
||||
}
|
||||
|
||||
export const useProducts = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminProductListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminProductListResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
export const useDateTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const dateFilters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||
].map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: "date",
|
||||
}))
|
||||
|
||||
return dateFilters
|
||||
}
|
||||
@@ -7,6 +7,7 @@ const excludeableFields = [
|
||||
"sales_channel_id",
|
||||
"collections",
|
||||
"categories",
|
||||
"product_types",
|
||||
] as const
|
||||
|
||||
export const useProductTableFilters = (
|
||||
@@ -14,10 +15,17 @@ export const useProductTableFilters = (
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product_types } = useProductTypes({
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
})
|
||||
const isProductTypeExcluded = exclude?.includes("product_types")
|
||||
|
||||
const { product_types } = useProductTypes(
|
||||
{
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
enabled: !isProductTypeExcluded,
|
||||
}
|
||||
)
|
||||
|
||||
// const { product_tags } = useAdminProductTags({
|
||||
// limit: 1000,
|
||||
@@ -61,7 +69,7 @@ export const useProductTableFilters = (
|
||||
|
||||
let filters: Filter[] = []
|
||||
|
||||
if (product_types) {
|
||||
if (product_types && !isProductTypeExcluded) {
|
||||
const typeFilter: Filter = {
|
||||
key: "type_id",
|
||||
label: t("fields.type"),
|
||||
|
||||
@@ -44,7 +44,7 @@ export const useProductTableQuery = ({
|
||||
q,
|
||||
} = queryObject
|
||||
|
||||
const searchParams: HttpTypes.AdminProductParams = {
|
||||
const searchParams: HttpTypes.AdminProductListParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
sales_channel_id: sales_channel_id?.split(","),
|
||||
|
||||
@@ -1647,6 +1647,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"productTypes": {
|
||||
"domain": "Product Types",
|
||||
"create": {
|
||||
"header": "Create Product Type",
|
||||
"hint": "Create a new product type to categorize your products.",
|
||||
"successToast": "Product type {{value}} was successfully created."
|
||||
},
|
||||
"edit": {
|
||||
"header": "Edit Product Type",
|
||||
"successToast": "Product type {{value}} was successfully updated."
|
||||
},
|
||||
"delete": {
|
||||
"confirmation": "You are about to delete the product type {{value}}. This action cannot be undone.",
|
||||
"successToast": "Product type {{value}} was successfully deleted."
|
||||
},
|
||||
"fields": {
|
||||
"value": "Value"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"serverError": "Server error - Try again later.",
|
||||
"invalidCredentials": "Wrong email or password"
|
||||
@@ -1758,8 +1777,8 @@
|
||||
"sent": "Sent",
|
||||
"salesChannels": "Sales Channels",
|
||||
"product": "Product",
|
||||
"createdAt": "Created at",
|
||||
"updatedAt": "Updated at",
|
||||
"createdAt": "Created",
|
||||
"updatedAt": "Updated",
|
||||
"revokedAt": "Revoked at",
|
||||
"true": "True",
|
||||
"false": "False",
|
||||
|
||||
@@ -848,6 +848,43 @@ export const RouteMap: RouteObject[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "product-types",
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
crumb: () => "Product Types",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () =>
|
||||
import("../../routes/product-types/product-type-list"),
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
lazy: () =>
|
||||
import("../../routes/product-types/product-type-create"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () =>
|
||||
import("../../routes/product-types/product-type-detail"),
|
||||
handle: {
|
||||
crumb: (data: HttpTypes.AdminProductTypeResponse) =>
|
||||
data.product_type.value,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import("../../routes/product-types/product-type-edit"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "shipping-profiles",
|
||||
element: <Outlet />,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { toast, usePrompt } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useDeleteProductType } from "../../../../hooks/api/product-types"
|
||||
|
||||
export const useDeleteProductTypeAction = (id: string) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useDeleteProductType(id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const result = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("productTypes.delete.confirmation"),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("productTypes.delete.successToast"),
|
||||
dismissLabel: t("actions.close"),
|
||||
dismissable: true,
|
||||
})
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissLabel: t("actions.close"),
|
||||
dismissable: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return handleDelete
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { z } from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useCreateProductType } from "../../../../../hooks/api/product-types"
|
||||
|
||||
const CreateProductTypeSchema = z.object({
|
||||
value: z.string().min(1),
|
||||
})
|
||||
|
||||
export const CreateProductTypeForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<z.infer<typeof CreateProductTypeSchema>>({
|
||||
defaultValues: {
|
||||
value: "",
|
||||
},
|
||||
resolver: zodResolver(CreateProductTypeSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isPending } = useCreateProductType()
|
||||
|
||||
const handleSubmit = form.handleSubmit(
|
||||
async (values: z.infer<typeof CreateProductTypeSchema>) => {
|
||||
await mutateAsync(values, {
|
||||
onSuccess: ({ product_type }) => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("productTypes.create.successToast", {
|
||||
value: product_type.value,
|
||||
}),
|
||||
dismissLabel: t("actions.close"),
|
||||
dismissable: true,
|
||||
})
|
||||
|
||||
handleSuccess(`/settings/product-types/${product_type.id}`)
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissLabel: t("actions.close"),
|
||||
dismissable: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
isLoading={isPending}
|
||||
>
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-col items-center overflow-y-auto p-16">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("productTypes.create.header")}</Heading>
|
||||
<Text size="small" className="text-ui-fg-subtle">
|
||||
{t("productTypes.create.hint")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="value"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("productTypes.fields.value")}</Form.Label>
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./create-product-type-form"
|
||||
@@ -0,0 +1 @@
|
||||
export { ProductTypeCreate as Component } from "./product-type-create"
|
||||
@@ -0,0 +1,10 @@
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateProductTypeForm } from "./components/create-product-type-form"
|
||||
|
||||
export const ProductTypeCreate = () => {
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
<CreateProductTypeForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./product-type-general-section"
|
||||
@@ -0,0 +1,45 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useDeleteProductTypeAction } from "../../../common/hooks/use-delete-product-type-action"
|
||||
|
||||
type ProductTypeGeneralSectionProps = {
|
||||
productType: HttpTypes.AdminProductType
|
||||
}
|
||||
|
||||
export const ProductTypeGeneralSection = ({
|
||||
productType,
|
||||
}: ProductTypeGeneralSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const handleDelete = useDeleteProductTypeAction(productType.id)
|
||||
|
||||
return (
|
||||
<Container className="flex items-center justify-between">
|
||||
<Heading>{productType.value}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: "edit",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
icon: <Trash />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./product-type-product-section"
|
||||
@@ -0,0 +1,66 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters"
|
||||
import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
|
||||
type ProductTypeProductSectionProps = {
|
||||
productType: HttpTypes.AdminProductType
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export const ProductTypeProductSection = ({
|
||||
productType,
|
||||
}: ProductTypeProductSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { searchParams, raw } = useProductTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { products, count, isPending, isError, error } = useProducts({
|
||||
...searchParams,
|
||||
type_id: [productType.id],
|
||||
})
|
||||
|
||||
const filters = useProductTableFilters(["product_types"])
|
||||
const columns = useProductTableColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
columns,
|
||||
data: products,
|
||||
count: products?.length || 0,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="px-6 py-4">
|
||||
<Heading level="h2">{t("products.domain")}</Heading>
|
||||
</div>
|
||||
<DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
isLoading={isPending}
|
||||
columns={columns}
|
||||
count={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
navigateTo={({ original }) => `/products/${original.id}`}
|
||||
orderBy={["title", "created_at", "updated_at"]}
|
||||
queryObject={raw}
|
||||
search
|
||||
pagination
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { productTypeLoader as loader } from "./loader"
|
||||
export { ProductTypeDetail as Component } from "./product-type-detail"
|
||||
@@ -0,0 +1,22 @@
|
||||
import { LoaderFunctionArgs } from "react-router-dom"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { productTypesQueryKeys } from "../../../hooks/api/product-types"
|
||||
import { sdk } from "../../../lib/client"
|
||||
import { queryClient } from "../../../lib/query-client"
|
||||
|
||||
const productTypeDetailQuery = (id: string) => ({
|
||||
queryKey: productTypesQueryKeys.detail(id),
|
||||
queryFn: async () => sdk.admin.productType.retrieve(id),
|
||||
})
|
||||
|
||||
export const productTypeLoader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const id = params.id
|
||||
const query = productTypeDetailQuery(id!)
|
||||
|
||||
return (
|
||||
queryClient.getQueryData<HttpTypes.AdminProductTypeResponse>(
|
||||
query.queryKey
|
||||
) ?? (await queryClient.fetchQuery(query))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { useProductType } from "../../../hooks/api/product-types"
|
||||
import { ProductTypeGeneralSection } from "./components/product-type-general-section"
|
||||
import { ProductTypeProductSection } from "./components/product-type-product-section"
|
||||
import { productTypeLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/product_type/details/after"
|
||||
import before from "virtual:medusa/widgets/product_type/details/before"
|
||||
|
||||
export const ProductTypeDetail = () => {
|
||||
const { id } = useParams()
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof productTypeLoader>
|
||||
>
|
||||
|
||||
const { product_type, isPending, isError, error } = useProductType(
|
||||
id!,
|
||||
undefined,
|
||||
{
|
||||
initialData,
|
||||
}
|
||||
)
|
||||
|
||||
if (isPending || !product_type) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={product_type} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<ProductTypeGeneralSection productType={product_type} />
|
||||
<ProductTypeProductSection productType={product_type} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={product_type} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<JsonViewSection data={product_type} />
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, Input, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { z } from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useUpdateProductType } from "../../../../../hooks/api/product-types"
|
||||
|
||||
const EditProductTypeSchema = z.object({
|
||||
value: z.string().min(1),
|
||||
})
|
||||
|
||||
type EditProductTypeFormProps = {
|
||||
productType: HttpTypes.AdminProductType
|
||||
}
|
||||
|
||||
export const EditProductTypeForm = ({
|
||||
productType,
|
||||
}: EditProductTypeFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<z.infer<typeof EditProductTypeSchema>>({
|
||||
defaultValues: {
|
||||
value: productType.value,
|
||||
},
|
||||
resolver: zodResolver(EditProductTypeSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isPending } = useUpdateProductType(productType.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
value: data.value,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ product_type }) => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("productTypes.edit.successToast", {
|
||||
value: product_type.value,
|
||||
}),
|
||||
dismissable: true,
|
||||
dismissLabel: t("general.close"),
|
||||
})
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: error.message,
|
||||
dismissable: true,
|
||||
dismissLabel: t("general.close"),
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="value"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("productTypes.fields.value")}</Form.Label>
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isPending}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./edit-product-type-form"
|
||||
@@ -0,0 +1 @@
|
||||
export { ProductTypeEdit as Component } from "./product-type-edit"
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { useProductType } from "../../../hooks/api/product-types"
|
||||
import { EditProductTypeForm } from "./components/edit-product-type-form"
|
||||
|
||||
export const ProductTypeEdit = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product_type, isPending, isError, error } = useProductType(id!)
|
||||
|
||||
const ready = !isPending && !!product_type
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("productTypes.edit.header")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{ready && <EditProductTypeForm productType={product_type} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./product-type-list-table"
|
||||
@@ -0,0 +1,41 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useDeleteProductTypeAction } from "../../../common/hooks/use-delete-product-type-action"
|
||||
|
||||
type ProductTypeRowActionsProps = {
|
||||
productType: HttpTypes.AdminProductType
|
||||
}
|
||||
|
||||
export const ProductTypeRowActions = ({
|
||||
productType,
|
||||
}: ProductTypeRowActionsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const handleDelete = useDeleteProductTypeAction(productType.id)
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: `/settings/product-types/${productType.id}/edit`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
icon: <Trash />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Button, Container, Heading } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useProductTypeTableColumns } from "./use-product-type-table-columns"
|
||||
import { useProductTypeTableFilters } from "./use-product-type-table-filters"
|
||||
import { useProductTypeTableQuery } from "./use-product-type-table-query"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export const ProductTypeListTable = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { searchParams, raw } = useProductTypeTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { product_types, count, isLoading, isError, error } = useProductTypes(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const filters = useProductTypeTableFilters()
|
||||
const columns = useProductTypeTableColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
columns,
|
||||
data: product_types,
|
||||
count,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading>{t("productTypes.domain")}</Heading>
|
||||
<Button size="small" variant="secondary" asChild>
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
isLoading={isLoading}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
orderBy={["value", "created_at", "updated_at"]}
|
||||
navigateTo={({ original }) => original.id}
|
||||
queryObject={raw}
|
||||
pagination
|
||||
search
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ProductTypeRowActions } from "./product-table-row-actions"
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminProductType>()
|
||||
|
||||
export const useProductTypeTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("value", {
|
||||
header: () => t("productTypes.fields.value"),
|
||||
cell: ({ getValue }) => getValue(),
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: () => t("fields.createdAt"),
|
||||
cell: ({ getValue }) => getValue(),
|
||||
}),
|
||||
columnHelper.accessor("updated_at", {
|
||||
header: () => t("fields.updatedAt"),
|
||||
cell: ({ getValue }) => getValue(),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return <ProductTypeRowActions productType={row.original} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useDateTableFilters } from "../../../../../hooks/table/filters/use-date-table-filters"
|
||||
|
||||
export const useProductTypeTableFilters = () => {
|
||||
const dateFilters = useDateTableFilters()
|
||||
|
||||
return dateFilters
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
type UseProductTypeTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useProductTypeTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseProductTypeTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
["offset", "q", "order", "created_at", "updated_at"],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, q, order, created_at, updated_at } = queryObject
|
||||
const searchParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ProductTypeList as Component } from "./product-type-list"
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { ProductTypeListTable } from "./components/product-type-list-table"
|
||||
|
||||
import after from "virtual:medusa/widgets/product_type/list/after"
|
||||
import before from "virtual:medusa/widgets/product_type/list/before"
|
||||
|
||||
export const ProductTypeList = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<ProductTypeListTable />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { PriceList } from "./price-list"
|
||||
import { Product } from "./product"
|
||||
import { ProductCategory } from "./product-category"
|
||||
import { ProductCollection } from "./product-collection"
|
||||
import { ProductType } from "./product-type"
|
||||
import { Region } from "./region"
|
||||
import { SalesChannel } from "./sales-channel"
|
||||
import { ShippingOption } from "./shipping-option"
|
||||
@@ -26,6 +27,7 @@ export class Admin {
|
||||
public productCategory: ProductCategory
|
||||
public priceList: PriceList
|
||||
public product: Product
|
||||
public productType: ProductType
|
||||
public upload: Upload
|
||||
public region: Region
|
||||
public stockLocation: StockLocation
|
||||
@@ -47,6 +49,7 @@ export class Admin {
|
||||
this.productCategory = new ProductCategory(client)
|
||||
this.priceList = new PriceList(client)
|
||||
this.product = new Product(client)
|
||||
this.productType = new ProductType(client)
|
||||
this.upload = new Upload(client)
|
||||
this.region = new Region(client)
|
||||
this.stockLocation = new StockLocation(client)
|
||||
|
||||
80
packages/core/js-sdk/src/admin/product-type.ts
Normal file
80
packages/core/js-sdk/src/admin/product-type.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Client } from "../client"
|
||||
import { ClientHeaders } from "../types"
|
||||
|
||||
export class ProductType {
|
||||
private client: Client
|
||||
constructor(client: Client) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
async create(
|
||||
body: HttpTypes.AdminCreateProductType,
|
||||
query?: HttpTypes.SelectParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminProductTypeResponse>(
|
||||
`/admin/product-types`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
body: HttpTypes.AdminUpdateProductType,
|
||||
query?: HttpTypes.SelectParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminProductTypeResponse>(
|
||||
`/admin/product-types/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async list(
|
||||
query?: HttpTypes.AdminProductTypeListParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminProductTypeListResponse>(
|
||||
`/admin/product-types`,
|
||||
{
|
||||
headers,
|
||||
query: query,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
id: string,
|
||||
query?: HttpTypes.AdminProductTypeParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return this.client.fetch<HttpTypes.AdminProductTypeResponse>(
|
||||
`/admin/product-types/${id}`,
|
||||
{
|
||||
query,
|
||||
headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async delete(id: string, headers?: ClientHeaders) {
|
||||
return this.client.fetch<HttpTypes.AdminProductTypeDeleteResponse>(
|
||||
`/admin/product-types/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export class Product {
|
||||
)
|
||||
}
|
||||
async list(
|
||||
queryParams?: HttpTypes.AdminProductParams,
|
||||
queryParams?: HttpTypes.AdminProductListParams,
|
||||
headers?: ClientHeaders
|
||||
) {
|
||||
return await this.client.fetch<HttpTypes.AdminProductListResponse>(
|
||||
|
||||
@@ -16,6 +16,7 @@ export * from "./payment"
|
||||
export * from "./pricing"
|
||||
export * from "./product"
|
||||
export * from "./product-category"
|
||||
export * from "./product-type"
|
||||
export * from "./promotion"
|
||||
export * from "./region"
|
||||
export * from "./reservation"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BaseProductType } from "../common"
|
||||
|
||||
export interface AdminProductType extends BaseProductType {}
|
||||
4
packages/core/types/src/http/product-type/admin/index.ts
Normal file
4
packages/core/types/src/http/product-type/admin/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./entities"
|
||||
export * from "./payloads"
|
||||
export * from "./queries"
|
||||
export * from "./responses"
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface AdminCreateProductType {
|
||||
value: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface AdminUpdateProductType {
|
||||
value?: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
15
packages/core/types/src/http/product-type/admin/queries.ts
Normal file
15
packages/core/types/src/http/product-type/admin/queries.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BaseFilterable, OperatorMap } from "../../../dal"
|
||||
import { FindParams, SelectParams } from "../../common"
|
||||
|
||||
export interface AdminProductTypeListParams
|
||||
extends FindParams,
|
||||
BaseFilterable<AdminProductTypeListParams> {
|
||||
q?: string
|
||||
id?: string | string[]
|
||||
value?: string | string[]
|
||||
created_at?: OperatorMap<string>
|
||||
updated_at?: OperatorMap<string>
|
||||
deleted_at?: OperatorMap<string>
|
||||
}
|
||||
|
||||
export interface AdminProductTypeParams extends SelectParams {}
|
||||
14
packages/core/types/src/http/product-type/admin/responses.ts
Normal file
14
packages/core/types/src/http/product-type/admin/responses.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DeleteResponse, PaginatedResponse } from "../../common"
|
||||
import { AdminProductType } from "./entities"
|
||||
|
||||
export interface AdminProductTypeResponse {
|
||||
product_type: AdminProductType
|
||||
}
|
||||
|
||||
export interface AdminProductTypeListResponse
|
||||
extends PaginatedResponse<{
|
||||
product_types: AdminProductType[]
|
||||
}> {}
|
||||
|
||||
export interface AdminProductTypeDeleteResponse
|
||||
extends DeleteResponse<"product_type"> {}
|
||||
8
packages/core/types/src/http/product-type/common.ts
Normal file
8
packages/core/types/src/http/product-type/common.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface BaseProductType {
|
||||
id: string
|
||||
value: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
deleted_at?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
2
packages/core/types/src/http/product-type/index.ts
Normal file
2
packages/core/types/src/http/product-type/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./admin"
|
||||
export * from "./store"
|
||||
@@ -0,0 +1,3 @@
|
||||
import { BaseProductType } from "../common"
|
||||
|
||||
export interface StoreProductType extends BaseProductType {}
|
||||
1
packages/core/types/src/http/product-type/store/index.ts
Normal file
1
packages/core/types/src/http/product-type/store/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./entities"
|
||||
@@ -1,5 +1,7 @@
|
||||
import { AdminCollection } from "../../collection"
|
||||
import { AdminPrice } from "../../pricing"
|
||||
import { AdminProductCategory } from "../../product-category"
|
||||
import { AdminProductType } from "../../product-type"
|
||||
import { AdminSalesChannel } from "../../sales-channel"
|
||||
import {
|
||||
BaseProduct,
|
||||
@@ -7,7 +9,6 @@ import {
|
||||
BaseProductOption,
|
||||
BaseProductOptionValue,
|
||||
BaseProductTag,
|
||||
BaseProductType,
|
||||
BaseProductVariant,
|
||||
ProductStatus,
|
||||
} from "../common"
|
||||
@@ -16,14 +17,15 @@ export interface AdminProductVariant extends BaseProductVariant {
|
||||
prices: AdminPrice[] | null
|
||||
}
|
||||
export interface AdminProductTag extends BaseProductTag {}
|
||||
export interface AdminProductType extends BaseProductType {}
|
||||
export interface AdminProductOption extends BaseProductOption {}
|
||||
export interface AdminProductImage extends BaseProductImage {}
|
||||
export interface AdminProductOptionValue extends BaseProductOptionValue {}
|
||||
export interface AdminProduct
|
||||
extends Omit<BaseProduct, "categories" | "variants"> {
|
||||
collection?: AdminCollection | null
|
||||
categories?: AdminProductCategory[] | null
|
||||
sales_channels?: AdminSalesChannel[] | null
|
||||
variants?: AdminProductVariant[] | null
|
||||
type: AdminProductType | null
|
||||
}
|
||||
export type AdminProductStatus = ProductStatus
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {
|
||||
BaseProductListParams,
|
||||
BaseProductOptionParams,
|
||||
BaseProductParams,
|
||||
BaseProductTagParams,
|
||||
BaseProductTypeParams,
|
||||
BaseProductVariantParams,
|
||||
} from "../common"
|
||||
|
||||
export interface AdminProductTagParams extends BaseProductTagParams {}
|
||||
export interface AdminProductTypeParams extends BaseProductTypeParams {}
|
||||
export interface AdminProductOptionParams extends BaseProductOptionParams {}
|
||||
export interface AdminProductVariantParams extends BaseProductVariantParams {}
|
||||
export interface AdminProductParams extends BaseProductParams {
|
||||
export interface AdminProductListParams extends BaseProductListParams {
|
||||
price_list_id?: string | string[]
|
||||
variants?: AdminProductVariantParams
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BaseFilterable, OperatorMap } from "../../dal"
|
||||
import { BaseCollection } from "../collection/common"
|
||||
import { FindParams } from "../common"
|
||||
import { BaseProductCategory } from "../product-category/common"
|
||||
import { BaseProductType } from "../product-type/common"
|
||||
|
||||
export type ProductStatus = "draft" | "proposed" | "published" | "rejected"
|
||||
export interface BaseProduct {
|
||||
@@ -21,10 +22,10 @@ export interface BaseProduct {
|
||||
hs_code: string | null
|
||||
mid_code: string | null
|
||||
material: string | null
|
||||
collection: BaseCollection | null
|
||||
collection?: BaseCollection | null
|
||||
collection_id: string | null
|
||||
categories?: BaseProductCategory[] | null
|
||||
type: BaseProductType | null
|
||||
type?: BaseProductType | null
|
||||
type_id: string | null
|
||||
tags: BaseProductTag[] | null
|
||||
variants: BaseProductVariant[] | null
|
||||
@@ -72,15 +73,6 @@ export interface BaseProductTag {
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface BaseProductType {
|
||||
id: string
|
||||
value: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
deleted_at?: string | null
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface BaseProductOption {
|
||||
id: string
|
||||
title: string
|
||||
@@ -113,9 +105,9 @@ export interface BaseProductOptionValue {
|
||||
deleted_at?: string | null
|
||||
}
|
||||
|
||||
export interface BaseProductParams
|
||||
export interface BaseProductListParams
|
||||
extends FindParams,
|
||||
BaseFilterable<BaseProductParams> {
|
||||
BaseFilterable<BaseProductListParams> {
|
||||
q?: string
|
||||
status?: ProductStatus | ProductStatus[]
|
||||
sales_channel_id?: string | string[]
|
||||
@@ -143,14 +135,6 @@ export interface BaseProductTagParams
|
||||
value?: string | string[]
|
||||
}
|
||||
|
||||
export interface BaseProductTypeParams
|
||||
extends FindParams,
|
||||
BaseFilterable<BaseProductTypeParams> {
|
||||
q?: string
|
||||
id?: string | string[]
|
||||
value?: string
|
||||
}
|
||||
|
||||
export interface BaseProductOptionParams
|
||||
extends FindParams,
|
||||
BaseFilterable<BaseProductOptionParams> {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { StoreProductCategory } from "../../product-category"
|
||||
import { StoreProductType } from "../../product-type"
|
||||
import {
|
||||
BaseProduct,
|
||||
BaseProductImage,
|
||||
BaseProductOption,
|
||||
BaseProductOptionValue,
|
||||
BaseProductTag,
|
||||
BaseProductType,
|
||||
BaseProductVariant,
|
||||
ProductStatus,
|
||||
} from "../common"
|
||||
|
||||
export interface StoreProduct extends Omit<BaseProduct, "categories"> {
|
||||
categories?: StoreProductCategory[] | null
|
||||
type?: StoreProductType | null
|
||||
}
|
||||
export interface StoreProductVariant extends BaseProductVariant {}
|
||||
export interface StoreProductTag extends BaseProductTag {}
|
||||
export interface StoreProductType extends BaseProductType {}
|
||||
export interface StoreProductOption extends BaseProductOption {}
|
||||
export interface StoreProductImage extends BaseProductImage {}
|
||||
export interface StoreProductOptionValue extends BaseProductOptionValue {}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import {
|
||||
BaseProductListParams,
|
||||
BaseProductOptionParams,
|
||||
BaseProductParams,
|
||||
BaseProductTagParams,
|
||||
BaseProductTypeParams,
|
||||
BaseProductVariantParams,
|
||||
} from "../common"
|
||||
|
||||
export interface StoreProductTagParams extends BaseProductTagParams {}
|
||||
export interface StoreProductTypeParams extends BaseProductTypeParams {}
|
||||
export interface StoreProductOptionParams extends BaseProductOptionParams {}
|
||||
export interface StoreProductVariantParams extends BaseProductVariantParams {}
|
||||
export interface StoreProductParams extends BaseProductParams {
|
||||
export interface StoreProductParams extends BaseProductListParams {
|
||||
// The region ID and currency_code are not params, but are used for the pricing context. Maybe move to separate type definition.
|
||||
region_id?: string
|
||||
currency_code?: string
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
import { remapKeysForProduct, remapProductResponse } from "./helpers"
|
||||
import { refetchEntities, refetchEntity } from "../../utils/refetch-entity"
|
||||
import { remapKeysForProduct, remapProductResponse } from "./helpers"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductParams>,
|
||||
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductListParams>,
|
||||
res: MedusaResponse<HttpTypes.AdminProductListResponse>
|
||||
) => {
|
||||
const selectFields = remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { NextFunction } from "express"
|
||||
import { MedusaRequest } from "../../../../types/routing"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export function maybeApplyPriceListsFilter() {
|
||||
return async (req: MedusaRequest, _, next: NextFunction) => {
|
||||
const filterableFields: HttpTypes.AdminProductParams = req.filterableFields
|
||||
const filterableFields: HttpTypes.AdminProductListParams =
|
||||
req.filterableFields
|
||||
|
||||
if (!filterableFields.price_list_id) {
|
||||
return next()
|
||||
|
||||
Reference in New Issue
Block a user