feat(dashboard, js-sdk): shipping option type mngmt dashboard (#13208)
* chore(types, api): support shipping option type api endpoints * core flows * api * typos * compiler errors * integration tests * remove metadata * changeset * modify test * upsert * change remote query * minor to patch * description optional * chore(dashboard, js-sdk): shipping option type management on admin dashboard * description optional * woops my bad * my bad again * create and edit * prettier * build code from label * remove metadata route * remove some translation text that is not used * remove unsued files * changeset * adapt test * fix test * suggestion --------- Co-authored-by: william bouchard <williambouchard@williams-MacBook-Pro.local>
This commit is contained in:
co-authored by
william bouchard
parent
257e71f988
commit
4b3c43fe92
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/fulfillment": patch
|
||||||
|
"@medusajs/admin-shared": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
"@medusajs/js-sdk": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(dashboard, js-sdk): shipping option type mngmt dashboard
|
||||||
+17
@@ -66,6 +66,23 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("returns a list of shipping option types matching free text search param", async () => {
|
it("returns a list of shipping option types matching free text search param", async () => {
|
||||||
|
const res = await api.get("/admin/shipping-option-types?q=st1", adminHeaders)
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200)
|
||||||
|
|
||||||
|
expect(res.data.shipping_option_types).toEqual([
|
||||||
|
{
|
||||||
|
id: expect.stringMatching(/sotype_.{24}/),
|
||||||
|
label: "Test1",
|
||||||
|
code: "test1",
|
||||||
|
description: "Test1 description",
|
||||||
|
created_at: expect.any(String),
|
||||||
|
updated_at: expect.any(String),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns a list of shipping option types matching code search param", async () => {
|
||||||
const res = await api.get("/admin/shipping-option-types?code=test1", adminHeaders)
|
const res = await api.get("/admin/shipping-option-types?code=test1", adminHeaders)
|
||||||
|
|
||||||
expect(res.status).toEqual(200)
|
expect(res.status).toEqual(200)
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ const PRODUCT_CATEGORY_INJECTION_ZONES = [
|
|||||||
"product_category.list.after",
|
"product_category.list.after",
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
const SHIPPING_OPTION_TYPE_INJECTION_ZONES = [
|
||||||
|
"shipping_option_type.details.before",
|
||||||
|
"shipping_option_type.details.after",
|
||||||
|
"shipping_option_type.list.before",
|
||||||
|
"shipping_option_type.list.after",
|
||||||
|
] as const
|
||||||
|
|
||||||
const PRODUCT_TYPE_INJECTION_ZONES = [
|
const PRODUCT_TYPE_INJECTION_ZONES = [
|
||||||
"product_type.details.before",
|
"product_type.details.before",
|
||||||
"product_type.details.after",
|
"product_type.details.after",
|
||||||
@@ -204,6 +211,7 @@ export const INJECTION_ZONES = [
|
|||||||
...PRODUCT_COLLECTION_INJECTION_ZONES,
|
...PRODUCT_COLLECTION_INJECTION_ZONES,
|
||||||
...PRODUCT_CATEGORY_INJECTION_ZONES,
|
...PRODUCT_CATEGORY_INJECTION_ZONES,
|
||||||
...PRODUCT_TYPE_INJECTION_ZONES,
|
...PRODUCT_TYPE_INJECTION_ZONES,
|
||||||
|
...SHIPPING_OPTION_TYPE_INJECTION_ZONES,
|
||||||
...PRODUCT_TAG_INJECTION_ZONES,
|
...PRODUCT_TAG_INJECTION_ZONES,
|
||||||
...PRICE_LIST_INJECTION_ZONES,
|
...PRICE_LIST_INJECTION_ZONES,
|
||||||
...PROMOTION_INJECTION_ZONES,
|
...PROMOTION_INJECTION_ZONES,
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ const useSettingRoutes = (): INavItem[] => {
|
|||||||
label: t("salesChannels.domain"),
|
label: t("salesChannels.domain"),
|
||||||
to: "/settings/sales-channels",
|
to: "/settings/sales-channels",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("shippingOptionTypes.domain"),
|
||||||
|
to: "/settings/shipping-option-types",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t("productTypes.domain"),
|
label: t("productTypes.domain"),
|
||||||
to: "/settings/product-types",
|
to: "/settings/product-types",
|
||||||
|
|||||||
@@ -1369,6 +1369,59 @@ export function getRouteMap({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "shipping-option-types",
|
||||||
|
errorElement: <ErrorBoundary />,
|
||||||
|
element: <Outlet />,
|
||||||
|
handle: {
|
||||||
|
breadcrumb: () => t("shippingOptionTypes.domain"),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/shipping-option-types/shipping-option-type-list"
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "create",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/shipping-option-types/shipping-option-type-create"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ":id",
|
||||||
|
lazy: async () => {
|
||||||
|
const { Component, Breadcrumb, loader } = await import(
|
||||||
|
"../../routes/shipping-option-types/shipping-option-type-detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
Component,
|
||||||
|
loader,
|
||||||
|
handle: {
|
||||||
|
breadcrumb: (
|
||||||
|
match: UIMatch<HttpTypes.AdminShippingOptionTypeResponse>
|
||||||
|
) => <Breadcrumb {...match} />,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "edit",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../routes/shipping-option-types/shipping-option-type-edit"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "product-types",
|
path: "product-types",
|
||||||
errorElement: <ErrorBoundary />,
|
errorElement: <ErrorBoundary />,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export * from "./regions"
|
|||||||
export * from "./reservations"
|
export * from "./reservations"
|
||||||
export * from "./sales-channels"
|
export * from "./sales-channels"
|
||||||
export * from "./shipping-options"
|
export * from "./shipping-options"
|
||||||
|
export * from "./shipping-option-types"
|
||||||
export * from "./shipping-profiles"
|
export * from "./shipping-profiles"
|
||||||
export * from "./stock-locations"
|
export * from "./stock-locations"
|
||||||
export * from "./store"
|
export * from "./store"
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { FetchError } from "@medusajs/js-sdk"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
QueryKey,
|
||||||
|
useMutation,
|
||||||
|
UseMutationOptions,
|
||||||
|
useQuery,
|
||||||
|
UseQueryOptions,
|
||||||
|
} from "@tanstack/react-query"
|
||||||
|
import { sdk } from "../../lib/client"
|
||||||
|
import { queryClient } from "../../lib/query-client"
|
||||||
|
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||||
|
|
||||||
|
const SHIPPING_OPTION_TYPES_QUERY_KEY = "shipping_option_types" as const
|
||||||
|
export const shippingOptionTypesQueryKeys = queryKeysFactory(
|
||||||
|
SHIPPING_OPTION_TYPES_QUERY_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
export const useShippingOptionType = (
|
||||||
|
id: string,
|
||||||
|
query?: HttpTypes.SelectParams,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
HttpTypes.AdminShippingOptionTypeResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminShippingOptionTypeResponse,
|
||||||
|
QueryKey
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { data, ...rest } = useQuery({
|
||||||
|
queryFn: () => sdk.admin.shippingOptionType.retrieve(id, query),
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.detail(id),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { ...data, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useShippingOptionTypes = (
|
||||||
|
query?: HttpTypes.AdminShippingOptionTypeListParams,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
HttpTypes.AdminShippingOptionTypeListResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminShippingOptionTypeListResponse,
|
||||||
|
QueryKey
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
const { data, ...rest } = useQuery({
|
||||||
|
queryFn: () => sdk.admin.shippingOptionType.list(query),
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.list(query),
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { ...data, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateShippingOptionType = (
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminShippingOptionTypeResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminCreateShippingOptionType
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload) => sdk.admin.shippingOptionType.create(payload),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateShippingOptionType = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminShippingOptionTypeResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminUpdateShippingOptionType
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload) => sdk.admin.shippingOptionType.update(id, payload),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.detail(id),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeleteShippingOptionType = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminShippingOptionTypeDeleteResponse,
|
||||||
|
FetchError,
|
||||||
|
void
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => sdk.admin.shippingOptionType.delete(id),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.detail(id),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { DateCell } from "../../../components/table/table-cells/common/date-cell"
|
||||||
|
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<HttpTypes.AdminShippingOptionType>()
|
||||||
|
|
||||||
|
export const useShippingOptionTypeTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor("label", {
|
||||||
|
header: () => t("fields.label"),
|
||||||
|
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("code", {
|
||||||
|
header: () => t("fields.code"),
|
||||||
|
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("description", {
|
||||||
|
header: () => t("fields.description"),
|
||||||
|
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("created_at", {
|
||||||
|
header: () => t("fields.createdAt"),
|
||||||
|
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <DateCell date={getValue()} />
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("updated_at", {
|
||||||
|
header: () => t("fields.updatedAt"),
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <DateCell date={getValue()} />
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import { useDateTableFilters } from "./use-date-table-filters"
|
||||||
|
|
||||||
|
export const useShippingOptionTypeTableFilters = () => {
|
||||||
|
return useDateTableFilters()
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useQueryParams } from "../../use-query-params"
|
||||||
|
|
||||||
|
type UseShippingOptionTypeTableQueryProps = {
|
||||||
|
prefix?: string
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useShippingOptionTypeTableQuery = ({
|
||||||
|
prefix,
|
||||||
|
pageSize = 20,
|
||||||
|
}: UseShippingOptionTypeTableQueryProps) => {
|
||||||
|
const queryObject = useQueryParams(
|
||||||
|
["offset", "q", "order", "created_at", "updated_at"],
|
||||||
|
prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
const { offset, q, order, created_at, updated_at } = queryObject
|
||||||
|
const searchParams: HttpTypes.AdminShippingOptionTypeListParams = {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10411,6 +10411,77 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"shippingOptionTypes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"header": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"hint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"successToast": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["header", "hint", "successToast"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"header": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"successToast": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["header", "successToast"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"confirmation": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"successToast": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["confirmation", "successToast"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"label": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["label", "code", "description"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "subtitle", "create", "edit", "delete", "fields"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"productTypes": {
|
"productTypes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2799,6 +2799,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shippingOptionTypes": {
|
||||||
|
"domain": "Shipping Option Types",
|
||||||
|
"subtitle": "Organize your shipping options into types.",
|
||||||
|
"create": {
|
||||||
|
"header": "Create Shipping Option Type",
|
||||||
|
"hint": "Create a new shipping option type to categorize your shipping options.",
|
||||||
|
"successToast": "Shipping option type {{label}} was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Shipping Option Type",
|
||||||
|
"successToast": "Shipping option type {{label}} was successfully updated."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"confirmation": "You are about to delete the shipping option type \"{{label}}\". This action cannot be undone.",
|
||||||
|
"successToast": "Shipping option type \"{{label}}\" was successfully deleted."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"label": "Label",
|
||||||
|
"code": "Code",
|
||||||
|
"description": "Description"
|
||||||
|
}
|
||||||
|
},
|
||||||
"productTypes": {
|
"productTypes": {
|
||||||
"domain": "Product Types",
|
"domain": "Product Types",
|
||||||
"subtitle": "Organize your products into types.",
|
"subtitle": "Organize your products into types.",
|
||||||
@@ -3051,4 +3073,4 @@
|
|||||||
"seconds_one": "Second",
|
"seconds_one": "Second",
|
||||||
"seconds_other": "Seconds"
|
"seconds_other": "Seconds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { toast, usePrompt } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { useDeleteShippingOptionType } from "../../../../hooks/api/shipping-option-types"
|
||||||
|
|
||||||
|
export const useDeleteShippingOptionTypeAction = (
|
||||||
|
id: string,
|
||||||
|
label: string
|
||||||
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { mutateAsync } = useDeleteShippingOptionType(id)
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const result = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("shippingOptionTypes.delete.confirmation", { label }),
|
||||||
|
confirmText: t("actions.delete"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate("/settings/shipping-option-types", { replace: true })
|
||||||
|
toast.success(t("shippingOptionTypes.delete.successToast", { label }))
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast.error(e.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleDelete
|
||||||
|
}
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
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/modals"
|
||||||
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { useCreateShippingOptionType } from "../../../../../hooks/api"
|
||||||
|
|
||||||
|
const CreateShippingOptionTypeSchema = z.object({
|
||||||
|
label: z.string().min(1),
|
||||||
|
code: z.string().min(1),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CreateShippingOptionTypeForm = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof CreateShippingOptionTypeSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
label: "",
|
||||||
|
code: "",
|
||||||
|
description: undefined,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(CreateShippingOptionTypeSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const generateCodeFromLabel = (label: string) => {
|
||||||
|
return label
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]/g, "_")
|
||||||
|
.replace(/_+/g, "_")
|
||||||
|
.replace(/^_|_$/g, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useCreateShippingOptionType()
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(
|
||||||
|
async (values: z.infer<typeof CreateShippingOptionTypeSchema>) => {
|
||||||
|
await mutateAsync(values, {
|
||||||
|
onSuccess: ({ shipping_option_type }) => {
|
||||||
|
toast.success(
|
||||||
|
t("shippingOptionTypes.create.successToast", {
|
||||||
|
label: shipping_option_type.label.trim(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
handleSuccess(
|
||||||
|
`/settings/shipping-option-types/${shipping_option_type.id}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast.error(e.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal.Form form={form}>
|
||||||
|
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||||
|
<RouteFocusModal.Body className="flex flex-col items-center overflow-auto p-16">
|
||||||
|
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||||
|
<div>
|
||||||
|
<Heading>{t("shippingOptionTypes.create.header")}</Heading>
|
||||||
|
<Text size="small" className="text-ui-fg-subtle">
|
||||||
|
{t("shippingOptionTypes.create.hint")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="label"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (
|
||||||
|
!form.getFieldState("code").isTouched ||
|
||||||
|
!form.getValues("code")
|
||||||
|
) {
|
||||||
|
form.setValue(
|
||||||
|
"code",
|
||||||
|
generateCodeFromLabel(e.target.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
field.onChange(e)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.code")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.description")}
|
||||||
|
<Text
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-muted ml-1 inline"
|
||||||
|
>
|
||||||
|
({t("fields.optional")})
|
||||||
|
</Text>
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
<RouteFocusModal.Footer>
|
||||||
|
<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.Footer>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteFocusModal.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-shipping-option-type-form"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ShippingOptionTypeCreate as Component } from "./shipping-option-type-create"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { RouteFocusModal } from "../../../components/modals"
|
||||||
|
import { CreateShippingOptionTypeForm } from "./components/create-shipping-option-type-form"
|
||||||
|
|
||||||
|
export const ShippingOptionTypeCreate = () => {
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
<CreateShippingOptionTypeForm />
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { UIMatch } from "react-router-dom"
|
||||||
|
|
||||||
|
import { useShippingOptionType } from "../../../hooks/api"
|
||||||
|
|
||||||
|
type ShippingOptionTypeDetailBreadcrumbProps =
|
||||||
|
UIMatch<HttpTypes.AdminShippingOptionTypeResponse>
|
||||||
|
|
||||||
|
export const ShippingOptionTypeDetailBreadcrumb = (
|
||||||
|
props: ShippingOptionTypeDetailBreadcrumbProps
|
||||||
|
) => {
|
||||||
|
const { id } = props.params || {}
|
||||||
|
|
||||||
|
const { shipping_option_type } = useShippingOptionType(id!, undefined, {
|
||||||
|
initialData: props.data,
|
||||||
|
enabled: Boolean(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!shipping_option_type) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{shipping_option_type.label}</span>
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./shipping-option-type-general-section"
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Container, Heading, Text } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { useDeleteShippingOptionTypeAction } from "../../../common/hooks/use-delete-shipping-option-type-action"
|
||||||
|
|
||||||
|
type ShippingOptionTypeGeneralSectionProps = {
|
||||||
|
shippingOptionType: HttpTypes.AdminShippingOptionType
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShippingOptionTypeGeneralSection = ({
|
||||||
|
shippingOptionType,
|
||||||
|
}: ShippingOptionTypeGeneralSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const handleDelete = useDeleteShippingOptionTypeAction(
|
||||||
|
shippingOptionType.id,
|
||||||
|
shippingOptionType.label
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="divide-y p-0">
|
||||||
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
|
<Heading>{shippingOptionType.label}</Heading>
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.edit"),
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
to: "edit",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.delete"),
|
||||||
|
icon: <Trash />,
|
||||||
|
onClick: handleDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-ui-fg-subtle grid grid-cols-2 items-start px-6 py-4">
|
||||||
|
<Text size="small" leading="compact" weight="plus">
|
||||||
|
{t("fields.code")}
|
||||||
|
</Text>
|
||||||
|
<Text size="small" leading="compact">
|
||||||
|
{shippingOptionType.code}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div className="text-ui-fg-subtle grid grid-cols-2 items-start px-6 py-4">
|
||||||
|
<Text size="small" leading="compact" weight="plus">
|
||||||
|
{t("fields.description")}
|
||||||
|
</Text>
|
||||||
|
<Text size="small" leading="compact">
|
||||||
|
{shippingOptionType.description || "-"}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export { ShippingOptionTypeDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
|
||||||
|
export { shippingOptionTypeLoader as loader } from "./loader"
|
||||||
|
export { ShippingOptionTypeDetail as Component } from "./shipping-option-type-detail"
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { LoaderFunctionArgs } from "react-router-dom"
|
||||||
|
|
||||||
|
import { shippingOptionTypesQueryKeys } from "../../../hooks/api/shipping-option-types"
|
||||||
|
import { sdk } from "../../../lib/client"
|
||||||
|
import { queryClient } from "../../../lib/query-client"
|
||||||
|
|
||||||
|
const shippingOptionTypeDetailQuery = (id: string) => ({
|
||||||
|
queryKey: shippingOptionTypesQueryKeys.detail(id),
|
||||||
|
queryFn: async () => sdk.admin.shippingOptionType.retrieve(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const shippingOptionTypeLoader = async ({
|
||||||
|
params,
|
||||||
|
}: LoaderFunctionArgs) => {
|
||||||
|
const id = params.id
|
||||||
|
const query = shippingOptionTypeDetailQuery(id!)
|
||||||
|
|
||||||
|
return queryClient.ensureQueryData(query)
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import { useLoaderData, useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||||
|
import { SingleColumnPage } from "../../../components/layout/pages"
|
||||||
|
import { useShippingOptionType } from "../../../hooks/api"
|
||||||
|
import { useExtension } from "../../../providers/extension-provider"
|
||||||
|
import { ShippingOptionTypeGeneralSection } from "./components/shipping-option-type-general-section"
|
||||||
|
import { shippingOptionTypeLoader } from "./loader"
|
||||||
|
|
||||||
|
export const ShippingOptionTypeDetail = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const initialData = useLoaderData() as Awaited<
|
||||||
|
ReturnType<typeof shippingOptionTypeLoader>
|
||||||
|
>
|
||||||
|
|
||||||
|
const { shipping_option_type, isPending, isError, error } =
|
||||||
|
useShippingOptionType(id!, undefined, {
|
||||||
|
initialData,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { getWidgets } = useExtension()
|
||||||
|
|
||||||
|
if (isPending || !shipping_option_type) {
|
||||||
|
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleColumnPage
|
||||||
|
widgets={{
|
||||||
|
after: getWidgets("shipping_option_type.details.after"),
|
||||||
|
before: getWidgets("shipping_option_type.details.before"),
|
||||||
|
}}
|
||||||
|
showJSON
|
||||||
|
showMetadata
|
||||||
|
data={shipping_option_type}
|
||||||
|
>
|
||||||
|
<ShippingOptionTypeGeneralSection
|
||||||
|
shippingOptionType={shipping_option_type}
|
||||||
|
/>
|
||||||
|
</SingleColumnPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, 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 { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||||
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { useUpdateShippingOptionType } from "../../../../../hooks/api/shipping-option-types"
|
||||||
|
|
||||||
|
const EditShippingOptionTypeSchema = z.object({
|
||||||
|
label: z.string().min(1),
|
||||||
|
code: z.string().min(1),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
type EditShippingOptionTypeFormProps = {
|
||||||
|
shippingOptionType: HttpTypes.AdminShippingOptionType
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditShippingOptionTypeForm = ({
|
||||||
|
shippingOptionType,
|
||||||
|
}: EditShippingOptionTypeFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof EditShippingOptionTypeSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
label: shippingOptionType.label,
|
||||||
|
code: shippingOptionType.code,
|
||||||
|
description: shippingOptionType.description,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditShippingOptionTypeSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateShippingOptionType(
|
||||||
|
shippingOptionType.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
await mutateAsync(
|
||||||
|
{
|
||||||
|
label: data.label,
|
||||||
|
code: data.code,
|
||||||
|
description: data.description,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: ({ shipping_option_type }) => {
|
||||||
|
toast.success(
|
||||||
|
t("shippingOptionTypes.edit.successToast", {
|
||||||
|
value: shipping_option_type.label,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<KeyboundForm
|
||||||
|
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="label"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.label")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.code")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("shippingOptionTypes.fields.description")}
|
||||||
|
<Text
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-muted ml-1 inline"
|
||||||
|
>
|
||||||
|
({t("fields.optional")})
|
||||||
|
</Text>
|
||||||
|
</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>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-shipping-option-type-form"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ShippingOptionTypeEdit as Component } from "./shipping-option-type-edit"
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useShippingOptionType } from "../../../hooks/api"
|
||||||
|
import { EditShippingOptionTypeForm } from "./components/edit-shipping-option-type-form"
|
||||||
|
|
||||||
|
export const ShippingOptionTypeEdit = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { shipping_option_type, isPending, isError, error } =
|
||||||
|
useShippingOptionType(id!)
|
||||||
|
|
||||||
|
const ready = !isPending && !!shipping_option_type
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<Heading>{t("shippingOptionTypes.edit.header")}</Heading>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
{ready && (
|
||||||
|
<EditShippingOptionTypeForm shippingOptionType={shipping_option_type} />
|
||||||
|
)}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./shipping-option-type-list-table"
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { Link } from "react-router-dom"
|
||||||
|
import { _DataTable } from "../../../../../components/table/data-table"
|
||||||
|
import { useShippingOptionTypes } from "../../../../../hooks/api"
|
||||||
|
import { useShippingOptionTypeTableColumns } from "../../../../../hooks/table/columns/use-shipping-option-type-table-columns"
|
||||||
|
import { useShippingOptionTypeTableFilters } from "../../../../../hooks/table/filters/use-shipping-option-type-table-filters"
|
||||||
|
import { useShippingOptionTypeTableQuery } from "../../../../../hooks/table/query/use-shipping-option-type-table-query"
|
||||||
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
|
import { ShippingOptionTypeRowActions } from "./shipping-option-type-table-row-actions"
|
||||||
|
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
|
export const ShippingOptionTypeListTable = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { searchParams, raw } = useShippingOptionTypeTableQuery({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
const { shipping_option_types, count, isLoading, isError, error } =
|
||||||
|
useShippingOptionTypes(searchParams, {
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
|
||||||
|
const filters = useShippingOptionTypeTableFilters()
|
||||||
|
const columns = useColumns()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
columns,
|
||||||
|
data: shipping_option_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">
|
||||||
|
<div>
|
||||||
|
<Heading>{t("shippingOptionTypes.domain")}</Heading>
|
||||||
|
<Text className="text-ui-fg-subtle" size="small">
|
||||||
|
{t("shippingOptionTypes.subtitle")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<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={[
|
||||||
|
{ key: "label", label: t("fields.label") },
|
||||||
|
{ key: "code", label: t("fields.code") },
|
||||||
|
{ key: "description", label: t("fields.description") },
|
||||||
|
{ key: "created_at", label: t("fields.createdAt") },
|
||||||
|
{ key: "updated_at", label: t("fields.updatedAt") },
|
||||||
|
]}
|
||||||
|
navigateTo={({ original }) => original.id}
|
||||||
|
queryObject={raw}
|
||||||
|
pagination
|
||||||
|
search
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<HttpTypes.AdminShippingOptionType>()
|
||||||
|
|
||||||
|
const useColumns = () => {
|
||||||
|
const base = useShippingOptionTypeTableColumns()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
...base,
|
||||||
|
columnHelper.display({
|
||||||
|
id: "actions",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<ShippingOptionTypeRowActions shippingOptionType={row.original} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[base]
|
||||||
|
)
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
|
import { useDeleteShippingOptionTypeAction } from "../../../common/hooks/use-delete-shipping-option-type-action"
|
||||||
|
|
||||||
|
type ShippingOptionTypeRowActionsProps = {
|
||||||
|
shippingOptionType: HttpTypes.AdminShippingOptionType
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShippingOptionTypeRowActions = ({
|
||||||
|
shippingOptionType,
|
||||||
|
}: ShippingOptionTypeRowActionsProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const handleDelete = useDeleteShippingOptionTypeAction(
|
||||||
|
shippingOptionType.id,
|
||||||
|
shippingOptionType.label
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.edit"),
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
to: `/settings/shipping-option-types/${shippingOptionType.id}/edit`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.delete"),
|
||||||
|
icon: <Trash />,
|
||||||
|
onClick: handleDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ShippingOptionTypeList as Component } from "./shipping-option-type-list"
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { SingleColumnPage } from "../../../components/layout/pages"
|
||||||
|
import { useExtension } from "../../../providers/extension-provider"
|
||||||
|
import { ShippingOptionTypeListTable } from "./components/shipping-option-type-list-table"
|
||||||
|
|
||||||
|
export const ShippingOptionTypeList = () => {
|
||||||
|
const { getWidgets } = useExtension()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SingleColumnPage
|
||||||
|
widgets={{
|
||||||
|
after: getWidgets("shipping_option_type.list.after"),
|
||||||
|
before: getWidgets("shipping_option_type.list.before"),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShippingOptionTypeListTable />
|
||||||
|
</SingleColumnPage>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ import { TaxRegion } from "./tax-region"
|
|||||||
import { Upload } from "./upload"
|
import { Upload } from "./upload"
|
||||||
import { User } from "./user"
|
import { User } from "./user"
|
||||||
import { WorkflowExecution } from "./workflow-execution"
|
import { WorkflowExecution } from "./workflow-execution"
|
||||||
|
import { ShippingOptionType } from "./shipping-option-type"
|
||||||
|
|
||||||
export class Admin {
|
export class Admin {
|
||||||
/**
|
/**
|
||||||
@@ -113,6 +114,10 @@ export class Admin {
|
|||||||
* @tags fulfillment
|
* @tags fulfillment
|
||||||
*/
|
*/
|
||||||
public shippingOption: ShippingOption
|
public shippingOption: ShippingOption
|
||||||
|
/**
|
||||||
|
* @tags fulfillment
|
||||||
|
*/
|
||||||
|
public shippingOptionType: ShippingOptionType
|
||||||
/**
|
/**
|
||||||
* @tags fulfillment
|
* @tags fulfillment
|
||||||
*/
|
*/
|
||||||
@@ -240,6 +245,7 @@ export class Admin {
|
|||||||
this.fulfillment = new Fulfillment(client)
|
this.fulfillment = new Fulfillment(client)
|
||||||
this.fulfillmentProvider = new FulfillmentProvider(client)
|
this.fulfillmentProvider = new FulfillmentProvider(client)
|
||||||
this.shippingOption = new ShippingOption(client)
|
this.shippingOption = new ShippingOption(client)
|
||||||
|
this.shippingOptionType = new ShippingOptionType(client)
|
||||||
this.shippingProfile = new ShippingProfile(client)
|
this.shippingProfile = new ShippingProfile(client)
|
||||||
this.inventoryItem = new InventoryItem(client)
|
this.inventoryItem = new InventoryItem(client)
|
||||||
this.notification = new Notification(client)
|
this.notification = new Notification(client)
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Client } from "../client"
|
||||||
|
import { ClientHeaders } from "../types"
|
||||||
|
|
||||||
|
export class ShippingOptionType {
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
private client: Client
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
constructor(client: Client) {
|
||||||
|
this.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a shipping option type. It sends a request to the
|
||||||
|
* [Create Shipping Option Type](TODO HERE)
|
||||||
|
* API route.
|
||||||
|
*
|
||||||
|
* @param body - The shipping option type's details.
|
||||||
|
* @param query - Configure the fields to retrieve in the shipping option type.
|
||||||
|
* @param headers - Headers to pass in the request
|
||||||
|
* @returns The shipping option type's details.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sdk.admin.shippingOptionType.create({
|
||||||
|
* label: "Standard",
|
||||||
|
* code: "standard",
|
||||||
|
* description: "Ship in 2-3 days."
|
||||||
|
* })
|
||||||
|
* .then(({ shipping_option_type }) => {
|
||||||
|
* console.log(shipping_option_type)
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
async create(
|
||||||
|
body: HttpTypes.AdminCreateShippingOptionType,
|
||||||
|
query?: HttpTypes.SelectParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return this.client.fetch<HttpTypes.AdminShippingOptionTypeResponse>(
|
||||||
|
`/admin/shipping-option-types`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method updates a shipping option type. It sends a request to the
|
||||||
|
* [Update Shipping Option Type](TODO HERE)
|
||||||
|
* API route.
|
||||||
|
*
|
||||||
|
* @param id - The shipping option type's ID.
|
||||||
|
* @param body - The data to update in the shipping option type.
|
||||||
|
* @param query - Configure the fields to retrieve in the shipping option type.
|
||||||
|
* @param headers - Headers to pass in the request
|
||||||
|
* @returns The shipping option type's details.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sdk.admin.shippingOptionType.update("sotype_123", {
|
||||||
|
* code: "express"
|
||||||
|
* })
|
||||||
|
* .then(({ shipping_option_type }) => {
|
||||||
|
* console.log(shipping_option_type)
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
body: HttpTypes.AdminUpdateShippingOptionType,
|
||||||
|
query?: HttpTypes.SelectParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return this.client.fetch<HttpTypes.AdminShippingOptionTypeResponse>(
|
||||||
|
`/admin/shipping-option-types/${id}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves a paginated list of shipping option types. It sends a request to the
|
||||||
|
* [List Shipping Option Types](TODO HERE) API route.
|
||||||
|
*
|
||||||
|
* @param query - Filters and pagination configurations.
|
||||||
|
* @param headers - Headers to pass in the request.
|
||||||
|
* @returns The paginated list of shipping option types.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* To retrieve the list of shipping option types:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* sdk.admin.shippingOptionType.list()
|
||||||
|
* .then(({ shipping_option_types, count, limit, offset }) => {
|
||||||
|
* console.log(shipping_option_types)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To configure the pagination, pass the `limit` and `offset` query parameters.
|
||||||
|
*
|
||||||
|
* For example, to retrieve only 10 items and skip 10 items:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* sdk.admin.shippingOptionType.list({
|
||||||
|
* limit: 10,
|
||||||
|
* offset: 10
|
||||||
|
* })
|
||||||
|
* .then(({ shipping_option_types, count, limit, offset }) => {
|
||||||
|
* console.log(shipping_option_types)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
|
||||||
|
* in each shipping option type:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* sdk.admin.shippingOptionType.list({
|
||||||
|
* fields: "id,*shippingOptions"
|
||||||
|
* })
|
||||||
|
* .then(({ shipping_option_types, count, limit, offset }) => {
|
||||||
|
* console.log(shipping_option_types)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||||
|
*/
|
||||||
|
async list(
|
||||||
|
query?: HttpTypes.AdminShippingOptionTypeListParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return this.client.fetch<HttpTypes.AdminShippingOptionTypeListResponse>(
|
||||||
|
`/admin/shipping-option-types`,
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
query: query,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves a shipping option type by its ID. It sends a request to the
|
||||||
|
* [Get Shipping Option Type](TODO HERE)
|
||||||
|
* API route.
|
||||||
|
*
|
||||||
|
* @param id - The shipping option type's ID.
|
||||||
|
* @param query - Configure the fields to retrieve in the shipping option type.
|
||||||
|
* @param headers - Headers to pass in the request
|
||||||
|
* @returns The shipping option type's details.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* To retrieve a shipping option type by its ID:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* sdk.admin.shippingOptionType.retrieve("sotype_123")
|
||||||
|
* .then(({ shipping_option_type }) => {
|
||||||
|
* console.log(shipping_option_type)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To specify the fields and relations to retrieve:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* sdk.admin.shippingOptionType.retrieve("sotype_123", {
|
||||||
|
* fields: "id,*shippingOptions"
|
||||||
|
* })
|
||||||
|
* .then(({ shipping_option_type }) => {
|
||||||
|
* console.log(shipping_option_type)
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
|
||||||
|
*/
|
||||||
|
async retrieve(
|
||||||
|
id: string,
|
||||||
|
query?: HttpTypes.SelectParams,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return this.client.fetch<HttpTypes.AdminShippingOptionTypeResponse>(
|
||||||
|
`/admin/shipping-option-types/${id}`,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method deletes a shipping option type. It sends a request to the
|
||||||
|
* [Delete Shipping Option Type](TODO HERE)
|
||||||
|
* API route.
|
||||||
|
*
|
||||||
|
* @param id - The shipping option type's ID.
|
||||||
|
* @param headers - Headers to pass in the request
|
||||||
|
* @returns The shipping option type's details.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sdk.admin.shippingOptionType.delete("sotype_123")
|
||||||
|
* .then(({ deleted }) => {
|
||||||
|
* console.log(deleted)
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
async delete(id: string, headers?: ClientHeaders) {
|
||||||
|
return this.client.fetch<HttpTypes.AdminShippingOptionTypeDeleteResponse>(
|
||||||
|
`/admin/shipping-option-types/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@ import { ShippingOption } from "./shipping-option"
|
|||||||
|
|
||||||
export const ShippingOptionType = model.define("shipping_option_type", {
|
export const ShippingOptionType = model.define("shipping_option_type", {
|
||||||
id: model.id({ prefix: "sotype" }).primaryKey(),
|
id: model.id({ prefix: "sotype" }).primaryKey(),
|
||||||
label: model.text(),
|
label: model.text().searchable(),
|
||||||
description: model.text().nullable(),
|
description: model.text().searchable().nullable(),
|
||||||
code: model.text(),
|
code: model.text().searchable(),
|
||||||
shipping_option: model.hasOne(() => ShippingOption, {
|
shipping_option: model.hasOne(() => ShippingOption, {
|
||||||
mappedBy: "type",
|
mappedBy: "type",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { ShippingProfile } from "./shipping-profile"
|
|||||||
export const ShippingOption = model
|
export const ShippingOption = model
|
||||||
.define("shipping_option", {
|
.define("shipping_option", {
|
||||||
id: model.id({ prefix: "so" }).primaryKey(),
|
id: model.id({ prefix: "so" }).primaryKey(),
|
||||||
name: model.text(),
|
name: model.text().searchable(),
|
||||||
price_type: model
|
price_type: model
|
||||||
.enum(ShippingOptionPriceType)
|
.enum(ShippingOptionPriceType)
|
||||||
.default(ShippingOptionPriceType.FLAT),
|
.default(ShippingOptionPriceType.FLAT),
|
||||||
|
|||||||
Reference in New Issue
Block a user