(
+ render: (props: PropsWithoutRef, ref: Ref) => ReactNode
+): (props: P & RefAttributes) => ReactNode {
+ return forwardRef(render) as any
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/customers.tsx b/packages/plugins/draft-order/src/admin/hooks/api/customers.tsx
new file mode 100644
index 0000000000..ecbd7c5415
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/customers.tsx
@@ -0,0 +1,139 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
+
+import { sdk } from "../../lib/queries/sdk"
+
+const CUSTOMER_QUERY_KEY = "customers"
+
+export const customersQueryKeys = {
+ list: (query?: Record) => [
+ CUSTOMER_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ CUSTOMER_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+ addresses: (id: string, query?: Record) => [
+ CUSTOMER_QUERY_KEY,
+ id,
+ "addresses",
+ query ? query : undefined,
+ ],
+ address: (id: string, addressId: string, query?: Record) => [
+ CUSTOMER_QUERY_KEY,
+ id,
+ "addresses",
+ addressId,
+ query ? query : undefined,
+ ],
+}
+
+export const useCustomer = (
+ id: string,
+ query?: HttpTypes.SelectParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminCustomerResponse,
+ FetchError,
+ HttpTypes.AdminCustomerResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: customersQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.customer.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useCustomers = (
+ query?: HttpTypes.AdminCustomerFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminCustomerListResponse,
+ FetchError,
+ HttpTypes.AdminCustomerListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: customersQueryKeys.list(query),
+ queryFn: async () => sdk.admin.customer.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useCustomerAddresses = (
+ id: string,
+ query?: HttpTypes.FindParams & HttpTypes.AdminCustomerAddressFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminCustomerAddressListResponse,
+ FetchError,
+ HttpTypes.AdminCustomerAddressListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: customersQueryKeys.addresses(id, query),
+ queryFn: async () => {
+ const response = await sdk.client.fetch(
+ "/admin/customers/" + id + "/addresses",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "include",
+ }
+ )
+
+ return response as HttpTypes.AdminCustomerAddressListResponse
+ },
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useCustomerAddress = (
+ id: string,
+ addressId: string,
+ query?: HttpTypes.FindParams & HttpTypes.AdminCustomerAddressFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminCustomerAddressResponse,
+ FetchError,
+ HttpTypes.AdminCustomerAddressResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: customersQueryKeys.address(id, addressId, query),
+ queryFn: async () => {
+ const response = await sdk.client.fetch(
+ "/admin/customers/" + id + "/addresses/" + addressId
+ )
+
+ return response as HttpTypes.AdminCustomerAddressResponse
+ },
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/draft-orders.tsx b/packages/plugins/draft-order/src/admin/hooks/api/draft-orders.tsx
new file mode 100644
index 0000000000..406bc02ae3
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/draft-orders.tsx
@@ -0,0 +1,581 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import {
+ QueryKey,
+ useMutation,
+ UseMutationOptions,
+ useQuery,
+ useQueryClient,
+ UseQueryOptions,
+} from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+import { ordersQueryKeys } from "./orders"
+import { shippingOptionsQueryKeys } from "./shipping-options"
+const DRAFT_ORDERS_QUERY_KEY = "draft-orders"
+
+export const draftOrdersQueryKeys = {
+ detail: (id: string, query?: Record) => [
+ DRAFT_ORDERS_QUERY_KEY,
+ "details",
+ id,
+ query ? { query } : undefined,
+ ],
+ details: () => [DRAFT_ORDERS_QUERY_KEY, "details"],
+ list: (query?: Record) => [
+ DRAFT_ORDERS_QUERY_KEY,
+ "lists",
+ query ? { query } : undefined,
+ ],
+ lists: () => [DRAFT_ORDERS_QUERY_KEY, "lists"],
+}
+
+export const useDraftOrders = (
+ query?: HttpTypes.AdminOrderFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminDraftOrderListResponse,
+ FetchError,
+ HttpTypes.AdminDraftOrderListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: async () => {
+ return await sdk.admin.draftOrder.list(query)
+ },
+ queryKey: draftOrdersQueryKeys.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useDraftOrder = (
+ id: string,
+ query?: HttpTypes.AdminDraftOrderParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminDraftOrderResponse,
+ FetchError,
+ HttpTypes.AdminDraftOrderResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: async () => {
+ return await sdk.admin.draftOrder.retrieve(id, query)
+ },
+ queryKey: draftOrdersQueryKeys.detail(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useCreateDraftOrder = (
+ options?: Omit<
+ UseMutationOptions<
+ HttpTypes.AdminDraftOrderResponse,
+ FetchError,
+ HttpTypes.AdminCreateDraftOrder
+ >,
+ "mutationFn" | "mutationKey"
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async (payload) => {
+ return await sdk.admin.draftOrder.create(payload)
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.lists(),
+ })
+
+ // NOTE: Invalidate shipping options since we have a lot of places where we enable SO fetching
+ // depending on a condition but RQ will return stale data from cache which will render wrong UI.
+ queryClient.invalidateQueries({
+ queryKey: shippingOptionsQueryKeys.list(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDeleteDraftOrder = (
+ options?: Omit<
+ UseMutationOptions<
+ HttpTypes.DeleteResponse<"draft-order">,
+ FetchError,
+ string
+ >,
+ "mutationFn" | "mutationKey"
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async (id: string) => {
+ return await sdk.admin.draftOrder.delete(id)
+ },
+ onSuccess: (data, undefined, context) => {
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.lists(),
+ })
+
+ options?.onSuccess?.(data, undefined, context)
+ },
+ ...options,
+ })
+}
+
+export const useUpdateDraftOrder = (
+ id: string,
+ options?: Omit<
+ UseMutationOptions<
+ HttpTypes.AdminDraftOrderResponse,
+ FetchError,
+ HttpTypes.AdminUpdateDraftOrder
+ >,
+ "mutationFn" | "mutationKey"
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async (payload) => {
+ return await sdk.admin.draftOrder.update(id, payload)
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.details(),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.lists(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useConvertDraftOrder = (
+ id: string,
+ options?: UseMutationOptions
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.draftOrder.convertToOrder(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.detail(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.lists(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.detail(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.lists(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderAddItems = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminAddDraftOrderItems
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) => sdk.admin.draftOrder.addItems(id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderUpdateItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateDraftOrderItem & { item_id: string }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ item_id, ...payload }) =>
+ sdk.admin.draftOrder.updateItem(id, item_id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderRemoveActionItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ string
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (action_id: string) =>
+ sdk.admin.draftOrder.removeActionItem(id, action_id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderUpdateActionItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateDraftOrderItem & { action_id: string }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ action_id, ...payload }) =>
+ sdk.admin.draftOrder.updateActionItem(id, action_id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderAddPromotions = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminAddDraftOrderPromotions
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) => sdk.admin.draftOrder.addPromotions(id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderRemovePromotions = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminRemoveDraftOrderPromotions
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) => sdk.admin.draftOrder.removePromotions(id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderAddShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminAddDraftOrderShippingMethod
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) =>
+ sdk.admin.draftOrder.addShippingMethod(id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderUpdateActionShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateDraftOrderActionShippingMethod & { action_id: string }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ action_id, ...payload }) =>
+ sdk.admin.draftOrder.updateActionShippingMethod(id, action_id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderRemoveActionShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ string
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (action_id: string) =>
+ sdk.admin.draftOrder.removeActionShippingMethod(id, action_id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderRemoveShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ string
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (shipping_method_id: string) =>
+ sdk.admin.draftOrder.removeShippingMethod(id, shipping_method_id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderUpdateShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateDraftOrderShippingMethod & { method_id: string }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ method_id, ...payload }) =>
+ sdk.admin.draftOrder.updateShippingMethod(id, method_id, payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderBeginEdit = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.draftOrder.beginEdit(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderCancelEdit = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.DeleteResponse<"draft-order-edit">,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.draftOrder.cancelEdit(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.details(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderRequestEdit = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.draftOrder.requestEdit(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useDraftOrderConfirmEdit = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminDraftOrderPreviewResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.draftOrder.confirmEdit(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.detail(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.lists(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/orders.tsx b/packages/plugins/draft-order/src/admin/hooks/api/orders.tsx
new file mode 100644
index 0000000000..dbcd1b2083
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/orders.tsx
@@ -0,0 +1,457 @@
+import {
+ QueryKey,
+ useMutation,
+ useQuery,
+ useQueryClient,
+ UseQueryOptions,
+} from "@tanstack/react-query"
+
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { UseMutationOptions } from "@tanstack/react-query"
+
+import {
+ AdminOrderEditAddShippingMethod,
+ AdminOrderEditUpdateShippingMethod,
+} from "../../../types/http/orders/requests"
+import { sdk } from "../../lib/queries/sdk"
+import { draftOrdersQueryKeys } from "./draft-orders"
+
+const ORDERS_QUERY_KEY = "orders"
+
+export const ordersQueryKeys = {
+ detail: (id: string, query?: Record) => [
+ ORDERS_QUERY_KEY,
+ "details",
+ id,
+ query ? { query } : undefined,
+ ],
+ details: () => [ORDERS_QUERY_KEY, "details"],
+ list: (query?: Record) => [
+ ORDERS_QUERY_KEY,
+ "lists",
+ query ? { query } : undefined,
+ ],
+ lists: () => [ORDERS_QUERY_KEY, "lists"],
+ preview: (id: string) => [ORDERS_QUERY_KEY, "preview", id],
+ changes: (id: string) => [ORDERS_QUERY_KEY, "changes", id],
+}
+
+export const useOrder = (
+ id: string,
+ query?: HttpTypes.AdminOrderFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminOrderResponse,
+ FetchError,
+ HttpTypes.AdminOrderResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: ordersQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.order.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useOrderChanges = (
+ id: string,
+ query?: HttpTypes.AdminOrderChangesFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.PaginatedResponse,
+ FetchError,
+ HttpTypes.PaginatedResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: async () => sdk.admin.order.listChanges(id, query),
+ queryKey: ordersQueryKeys.changes(id),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useUpdateOrder = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderResponse,
+ FetchError,
+ HttpTypes.AdminUpdateOrder
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) => sdk.admin.order.update(id, payload),
+ onSuccess: (data: any, variables: any, context: any) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.detail(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.lists(),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderPreview = (
+ id: string,
+ query?: HttpTypes.AdminOrderFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminOrderPreviewResponse,
+ FetchError,
+ HttpTypes.AdminOrderPreviewResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryFn: async () => sdk.admin.order.retrievePreview(id, query),
+ queryKey: ordersQueryKeys.preview(id),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useOrderEditCreate = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditResponse,
+ FetchError,
+ HttpTypes.AdminInitiateOrderEditRequest
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async (payload) => sdk.admin.orderEdit.initiateRequest(payload),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.detail(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditCancel = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditDeleteResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: async () => await sdk.admin.orderEdit.cancelRequest(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditRequest = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.orderEdit.request(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditConfirm = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ void
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => sdk.admin.orderEdit.confirm(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: draftOrdersQueryKeys.details(),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditAddItems = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ HttpTypes.AdminAddOrderEditItems
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload: HttpTypes.AdminAddOrderEditItems) =>
+ sdk.admin.orderEdit.addItems(id, payload),
+ onSuccess: (data: any, variables: any, context: any) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.changes(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditAddShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ AdminOrderEditAddShippingMethod
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (payload) => {
+ return sdk.client.fetch(`/admin/order-edits/${id}/shipping-method`, {
+ method: "POST",
+ body: payload,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "include",
+ })
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditDeleteShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ string
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (action_id: string) => {
+ return sdk.client.fetch(
+ `/admin/order-edits/${id}/shipping-method/${action_id}`,
+ {
+ method: "DELETE",
+ credentials: "include",
+ }
+ ) as Promise
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditUpdateShippingMethod = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ AdminOrderEditUpdateShippingMethod & { action_id: string }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ action_id, ...payload }) => {
+ return sdk.client.fetch(
+ `/admin/order-edits/${id}/shipping-method/${action_id}`,
+ {
+ method: "POST",
+ body: payload,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditUpdateActionItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateOrderEditItem & {
+ action_id: string
+ unit_price?: number | null
+ compare_at_unit_price?: number | null
+ }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ action_id, ...payload }) => {
+ return sdk.admin.orderEdit.updateAddedItem(id, action_id, payload)
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditUpdateOriginalItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ HttpTypes.AdminUpdateOrderEditItem & {
+ item_id: string
+ unit_price?: number | null
+ compare_at_unit_price?: number | null
+ }
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ item_id, ...payload }) => {
+ return sdk.admin.orderEdit.updateOriginalItem(id, item_id, payload)
+ },
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
+
+export const useOrderEditRemoveActionItem = (
+ id: string,
+ options?: UseMutationOptions<
+ HttpTypes.AdminOrderEditPreviewResponse,
+ FetchError,
+ string
+ >
+) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (action_id: string) =>
+ sdk.admin.orderEdit.removeAddedItem(id, action_id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({
+ queryKey: ordersQueryKeys.preview(id),
+ })
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/product-variants.tsx b/packages/plugins/draft-order/src/admin/hooks/api/product-variants.tsx
new file mode 100644
index 0000000000..dafdd5e3a7
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/product-variants.tsx
@@ -0,0 +1,35 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
+
+import { sdk } from "../../lib/queries/sdk"
+
+const PRODUCT_VARIANTS_QUERY_KEY = "product-variants"
+
+export const productVariantsQueryKeys = {
+ list: (query?: Record) => [
+ PRODUCT_VARIANTS_QUERY_KEY,
+ query ? query : undefined,
+ ],
+}
+
+export const useProductVariants = (
+ query?: HttpTypes.AdminProductVariantParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminProductVariantListResponse,
+ FetchError,
+ HttpTypes.AdminProductVariantListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: productVariantsQueryKeys.list(query),
+ queryFn: async () => await sdk.admin.productVariant.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/products.tsx b/packages/plugins/draft-order/src/admin/hooks/api/products.tsx
new file mode 100644
index 0000000000..56d9831900
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/products.tsx
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+
+export const useProducts = (query: Record) => {
+ const { data, ...rest } = useQuery({
+ queryKey: ["products", query],
+ queryFn: () => {
+ return sdk.admin.product.list(query)
+ },
+ })
+
+ return {
+ ...data,
+ ...rest,
+ }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/promotions.tsx b/packages/plugins/draft-order/src/admin/hooks/api/promotions.tsx
new file mode 100644
index 0000000000..3ba2f42034
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/promotions.tsx
@@ -0,0 +1,39 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, useQuery, UseQueryOptions } from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+
+const PROMOTION_QUERY_KEY = "promotions"
+
+export const promotionsQueryKeys = {
+ list: (query?: Record) => [
+ PROMOTION_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ PROMOTION_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+}
+
+export const usePromotions = (
+ query?: HttpTypes.AdminGetPromotionsParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminPromotionListResponse,
+ FetchError,
+ HttpTypes.AdminPromotionListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: promotionsQueryKeys.list(query),
+ queryFn: async () => sdk.admin.promotion.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/regions.tsx b/packages/plugins/draft-order/src/admin/hooks/api/regions.tsx
new file mode 100644
index 0000000000..ef76262167
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/regions.tsx
@@ -0,0 +1,65 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, useQuery, UseQueryOptions } from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+
+const REGION_QUERY_KEY = "regions"
+
+export const regionsQueryKeys = {
+ list: (query?: Record) => [
+ REGION_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ REGION_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+}
+
+export const useRegion = (
+ id: string,
+ query?: Record,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminRegionResponse,
+ FetchError,
+ HttpTypes.AdminRegionResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: regionsQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.region.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useRegions = (
+ query?: HttpTypes.FindParams & HttpTypes.AdminRegionFilters,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.PaginatedResponse<{
+ regions: HttpTypes.AdminRegion[]
+ }>,
+ FetchError,
+ HttpTypes.PaginatedResponse<{
+ regions: HttpTypes.AdminRegion[]
+ }>,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: regionsQueryKeys.list(query),
+ queryFn: async () => sdk.admin.region.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/sales-channels.tsx b/packages/plugins/draft-order/src/admin/hooks/api/sales-channels.tsx
new file mode 100644
index 0000000000..fb07f10c23
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/sales-channels.tsx
@@ -0,0 +1,61 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, useQuery, UseQueryOptions } from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+
+const SALES_CHANNEL_QUERY_KEY = "sales-channels"
+
+export const salesChannelsQueryKeys = {
+ list: (query?: Record) => [
+ SALES_CHANNEL_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ SALES_CHANNEL_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+}
+
+export const useSalesChannel = (
+ id: string,
+ query?: HttpTypes.SelectParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminSalesChannelResponse,
+ FetchError,
+ HttpTypes.AdminSalesChannelResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: salesChannelsQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.salesChannel.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useSalesChannels = (
+ query?: HttpTypes.AdminSalesChannelListParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminSalesChannelListResponse,
+ FetchError,
+ HttpTypes.AdminSalesChannelListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: salesChannelsQueryKeys.list(query),
+ queryFn: async () => sdk.admin.salesChannel.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/shipping-options.tsx b/packages/plugins/draft-order/src/admin/hooks/api/shipping-options.tsx
new file mode 100644
index 0000000000..23e5c68693
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/shipping-options.tsx
@@ -0,0 +1,61 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
+import { sdk } from "../../lib/queries/sdk"
+
+const SHIPPING_OPTION_QUERY_KEY = "shipping-options"
+
+export const shippingOptionsQueryKeys = {
+ list: (query?: Record) => [
+ SHIPPING_OPTION_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ SHIPPING_OPTION_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+}
+
+export const useShippingOption = (
+ id: string,
+ query?: HttpTypes.SelectParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminShippingOptionResponse,
+ FetchError,
+ HttpTypes.AdminShippingOptionResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: shippingOptionsQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.shippingOption.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
+
+export const useShippingOptions = (
+ query?: HttpTypes.AdminShippingOptionListParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminShippingOptionListResponse,
+ FetchError,
+ HttpTypes.AdminShippingOptionListResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: shippingOptionsQueryKeys.list(query),
+ queryFn: async () => sdk.admin.shippingOption.list(query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/api/users.tsx b/packages/plugins/draft-order/src/admin/hooks/api/users.tsx
new file mode 100644
index 0000000000..56ceae989b
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/api/users.tsx
@@ -0,0 +1,41 @@
+import { FetchError } from "@medusajs/js-sdk"
+import { HttpTypes } from "@medusajs/types"
+import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
+
+import { sdk } from "../../lib/queries/sdk"
+
+const USER_QUERY_KEY = "users"
+
+export const usersQueryKeys = {
+ list: (query?: Record) => [
+ USER_QUERY_KEY,
+ query ? query : undefined,
+ ],
+ detail: (id: string, query?: Record) => [
+ USER_QUERY_KEY,
+ id,
+ query ? query : undefined,
+ ],
+}
+
+export const useUser = (
+ id: string,
+ query?: HttpTypes.SelectParams,
+ options?: Omit<
+ UseQueryOptions<
+ HttpTypes.AdminUserResponse,
+ FetchError,
+ HttpTypes.AdminUserResponse,
+ QueryKey
+ >,
+ "queryFn" | "queryKey"
+ >
+) => {
+ const { data, ...rest } = useQuery({
+ queryKey: usersQueryKeys.detail(id, query),
+ queryFn: async () => sdk.admin.user.retrieve(id, query),
+ ...options,
+ })
+
+ return { ...data, ...rest }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/common/use-combobox-data.tsx b/packages/plugins/draft-order/src/admin/hooks/common/use-combobox-data.tsx
new file mode 100644
index 0000000000..4e8d8e9c77
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/common/use-combobox-data.tsx
@@ -0,0 +1,98 @@
+import {
+ QueryKey,
+ keepPreviousData,
+ useInfiniteQuery,
+ useQuery,
+} from "@tanstack/react-query"
+import { useDebouncedSearch } from "./use-debounced-search"
+
+type ComboboxExternalData = {
+ offset: number
+ limit: number
+ count: number
+}
+
+type ComboboxQueryParams = {
+ q?: string
+ offset?: number
+ limit?: number
+}
+
+export const useComboboxData = <
+ TResponse extends ComboboxExternalData,
+ TParams extends ComboboxQueryParams
+>({
+ queryKey,
+ queryFn,
+ getOptions,
+ defaultValue,
+ defaultValueKey,
+ pageSize = 10,
+ enabled = true,
+}: {
+ queryKey: QueryKey
+ queryFn: (params: TParams) => Promise
+ getOptions: (data: TResponse) => { label: string; value: string }[]
+ defaultValueKey?: keyof TParams
+ defaultValue?: string | string[]
+ pageSize?: number
+ enabled?: boolean
+}) => {
+ const { searchValue, onSearchValueChange, query } = useDebouncedSearch()
+
+ const queryInitialDataBy = defaultValueKey || "id"
+ const { data: initialData } = useQuery({
+ queryKey: queryKey,
+ queryFn: async () => {
+ return queryFn({
+ [queryInitialDataBy]: defaultValue,
+ limit: Array.isArray(defaultValue) ? defaultValue.length : 1,
+ } as TParams)
+ },
+ enabled: !!defaultValue,
+ })
+
+ const { data, ...rest } = useInfiniteQuery({
+ queryKey: [...queryKey, query],
+ queryFn: async ({ pageParam = 0 }) => {
+ return await queryFn({
+ q: query,
+ limit: pageSize,
+ offset: pageParam,
+ } as TParams)
+ },
+ initialPageParam: 0,
+ getNextPageParam: (lastPage) => {
+ const moreItemsExist = lastPage.count > lastPage.offset + lastPage.limit
+ return moreItemsExist ? lastPage.offset + lastPage.limit : undefined
+ },
+ placeholderData: keepPreviousData,
+ enabled,
+ })
+
+ const options = data?.pages.flatMap((page) => getOptions(page)) ?? []
+ const defaultOptions = initialData ? getOptions(initialData) : []
+
+ /**
+ * If there are no options and the query is empty, then the combobox should be disabled,
+ * as there is no data to search for.
+ */
+ const disabled = !rest.isPending && !options.length && !searchValue
+
+ // make sure that the default value is included in the options
+ if (defaultValue && defaultOptions.length && !searchValue) {
+ defaultOptions.forEach((option) => {
+ if (!options.find((o) => o.value === option.value)) {
+ options.unshift(option)
+ }
+ })
+ }
+
+ return {
+ options,
+ searchValue,
+ onSearchValueChange,
+ disabled,
+ ...rest,
+ }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/common/use-data-table-date-filters.tsx b/packages/plugins/draft-order/src/admin/hooks/common/use-data-table-date-filters.tsx
new file mode 100644
index 0000000000..b8e96f7d45
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/common/use-data-table-date-filters.tsx
@@ -0,0 +1,90 @@
+import { createDataTableFilterHelper } from "@medusajs/ui"
+import { subDays, subMonths } from "date-fns"
+import { useMemo } from "react"
+
+import { getFullDate } from "../../lib/utils/date-utils"
+
+const filterHelper = createDataTableFilterHelper()
+
+const useDateFilterOptions = () => {
+ const today = useMemo(() => {
+ const date = new Date()
+ date.setHours(0, 0, 0, 0)
+ return date
+ }, [])
+
+ return useMemo(() => {
+ return [
+ {
+ label: "Today",
+ value: {
+ $gte: today.toISOString(),
+ },
+ },
+ {
+ label: "Last 7 days",
+ value: {
+ $gte: subDays(today, 7).toISOString(), // 7 days ago
+ },
+ },
+ {
+ label: "Last 30 days",
+ value: {
+ $gte: subDays(today, 30).toISOString(), // 30 days ago
+ },
+ },
+ {
+ label: "Last 90 days",
+ value: {
+ $gte: subDays(today, 90).toISOString(), // 90 days ago
+ },
+ },
+ {
+ label: "Last 12 months",
+ value: {
+ $gte: subMonths(today, 12).toISOString(), // 12 months ago
+ },
+ },
+ ]
+ }, [today])
+}
+
+export const useDataTableDateFilters = (disableRangeOption?: boolean) => {
+ const dateFilterOptions = useDateFilterOptions()
+
+ const rangeOptions = useMemo(() => {
+ if (disableRangeOption) {
+ return {
+ disableRangeOption: true,
+ }
+ }
+
+ return {
+ rangeOptionStartLabel: "Starting",
+ rangeOptionEndLabel: "Ending",
+ rangeOptionLabel: "Custom",
+ options: dateFilterOptions,
+ }
+ }, [disableRangeOption, dateFilterOptions])
+
+ return useMemo(() => {
+ return [
+ filterHelper.accessor("created_at", {
+ type: "date",
+ label: "Created at",
+ format: "date",
+ formatDateValue: (date) => getFullDate({ date }),
+ options: dateFilterOptions,
+ ...rangeOptions,
+ }),
+ filterHelper.accessor("updated_at", {
+ type: "date",
+ label: "Updated at",
+ format: "date",
+ formatDateValue: (date) => getFullDate({ date }),
+ options: dateFilterOptions,
+ ...rangeOptions,
+ }),
+ ]
+ }, [dateFilterOptions, getFullDate, rangeOptions])
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/common/use-debounced-search.tsx b/packages/plugins/draft-order/src/admin/hooks/common/use-debounced-search.tsx
new file mode 100644
index 0000000000..3f59c33833
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/common/use-debounced-search.tsx
@@ -0,0 +1,29 @@
+import debounce from "lodash/debounce"
+import { useCallback, useEffect, useState } from "react"
+
+/**
+ * Hook for debouncing search input
+ * @returns searchValue, onSearchValueChange, query
+ */
+export const useDebouncedSearch = () => {
+ const [searchValue, onSearchValueChange] = useState("")
+ const [debouncedQuery, setDebouncedQuery] = useState("")
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const debouncedUpdate = useCallback(
+ debounce((query: string) => setDebouncedQuery(query), 300),
+ []
+ )
+
+ useEffect(() => {
+ debouncedUpdate(searchValue)
+
+ return () => debouncedUpdate.cancel()
+ }, [searchValue, debouncedUpdate])
+
+ return {
+ searchValue,
+ onSearchValueChange,
+ query: debouncedQuery,
+ }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/common/use-query-params.tsx b/packages/plugins/draft-order/src/admin/hooks/common/use-query-params.tsx
new file mode 100644
index 0000000000..623975e5fb
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/common/use-query-params.tsx
@@ -0,0 +1,24 @@
+import { useSearchParams } from "react-router-dom"
+
+type QueryParams = {
+ [key in T]: string | undefined
+}
+
+export function useQueryParams(
+ keys: T[],
+ prefix?: string
+): QueryParams {
+ const [params] = useSearchParams()
+
+ // Use a type assertion to initialize the result
+ const result = {} as QueryParams
+
+ keys.forEach((key) => {
+ const prefixedKey = prefix ? `${prefix}_${key}` : key
+ const value = params.get(prefixedKey) || undefined
+
+ result[key] = value
+ })
+
+ return result
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/order-edits/use-cancel-order-edit.tsx b/packages/plugins/draft-order/src/admin/hooks/order-edits/use-cancel-order-edit.tsx
new file mode 100644
index 0000000000..59274648f2
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/order-edits/use-cancel-order-edit.tsx
@@ -0,0 +1,33 @@
+import { HttpTypes } from "@medusajs/types"
+import { toast } from "@medusajs/ui"
+import { useCallback } from "react"
+import { useDraftOrderCancelEdit } from "../api/draft-orders"
+
+interface UseCancelOrderEditProps {
+ preview?: HttpTypes.AdminOrderPreview
+}
+
+export const useCancelOrderEdit = ({ preview }: UseCancelOrderEditProps) => {
+ const { mutateAsync: cancelOrderEdit } = useDraftOrderCancelEdit(preview?.id!)
+
+ const onCancel = useCallback(async () => {
+ if (!preview) {
+ return true
+ }
+
+ let res = false
+
+ await cancelOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ onSuccess: () => {
+ res = true
+ },
+ })
+
+ return res
+ }, [preview, cancelOrderEdit])
+
+ return { onCancel }
+}
diff --git a/packages/plugins/draft-order/src/admin/hooks/order-edits/use-initiate-order-edit.tsx b/packages/plugins/draft-order/src/admin/hooks/order-edits/use-initiate-order-edit.tsx
new file mode 100644
index 0000000000..8eed79e65d
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/hooks/order-edits/use-initiate-order-edit.tsx
@@ -0,0 +1,47 @@
+import { HttpTypes } from "@medusajs/types"
+import { toast } from "@medusajs/ui"
+import { useEffect } from "react"
+import { useNavigate } from "react-router-dom"
+import { useDraftOrderBeginEdit } from "../api/draft-orders"
+
+let IS_REQUEST_RUNNING = false
+
+interface UseInitiateOrderEditProps {
+ preview?: HttpTypes.AdminOrderPreview
+}
+
+export const useInitiateOrderEdit = ({
+ preview,
+}: UseInitiateOrderEditProps) => {
+ const navigate = useNavigate()
+
+ const { mutateAsync } = useDraftOrderBeginEdit(preview?.id!)
+
+ useEffect(() => {
+ async function run() {
+ if (IS_REQUEST_RUNNING || !preview) {
+ return
+ }
+
+ // If an order edit is already in progress, don't try to create a new one.
+ if (preview.order_change) {
+ return
+ }
+
+ IS_REQUEST_RUNNING = true
+
+ await mutateAsync(undefined, {
+ onError: (e) => {
+ toast.error(e.message)
+ navigate(`/draft-orders/${preview.id}`, { replace: true })
+
+ return
+ },
+ })
+
+ IS_REQUEST_RUNNING = false
+ }
+
+ run()
+ }, [preview, navigate, mutateAsync])
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/data/countries.ts b/packages/plugins/draft-order/src/admin/lib/data/countries.ts
new file mode 100644
index 0000000000..8add8d496a
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/data/countries.ts
@@ -0,0 +1,1767 @@
+import { HttpTypes } from "@medusajs/types"
+
+export interface StaticCountry
+ extends Required> {}
+
+export function getCountryByIso2(
+ iso2: string | null
+): StaticCountry | undefined {
+ if (!iso2) {
+ return
+ }
+
+ return countries.find((c) => c.iso_2.toLowerCase() === iso2.toLowerCase())
+}
+
+export const countries: StaticCountry[] = [
+ {
+ iso_2: "af",
+ iso_3: "afg",
+ num_code: "4",
+ name: "AFGHANISTAN",
+ display_name: "Afghanistan",
+ },
+ {
+ iso_2: "al",
+ iso_3: "alb",
+ num_code: "8",
+ name: "ALBANIA",
+ display_name: "Albania",
+ },
+ {
+ iso_2: "dz",
+ iso_3: "dza",
+ num_code: "12",
+ name: "ALGERIA",
+ display_name: "Algeria",
+ },
+ {
+ iso_2: "as",
+ iso_3: "asm",
+ num_code: "16",
+ name: "AMERICAN SAMOA",
+ display_name: "American Samoa",
+ },
+ {
+ iso_2: "ad",
+ iso_3: "and",
+ num_code: "20",
+ name: "ANDORRA",
+ display_name: "Andorra",
+ },
+ {
+ iso_2: "ao",
+ iso_3: "ago",
+ num_code: "24",
+ name: "ANGOLA",
+ display_name: "Angola",
+ },
+ {
+ iso_2: "ai",
+ iso_3: "aia",
+ num_code: "660",
+ name: "ANGUILLA",
+ display_name: "Anguilla",
+ },
+ {
+ iso_2: "aq",
+ iso_3: "ata",
+ num_code: "10",
+ name: "ANTARCTICA",
+ display_name: "Antarctica",
+ },
+ {
+ iso_2: "ag",
+ iso_3: "atg",
+ num_code: "28",
+ name: "ANTIGUA AND BARBUDA",
+ display_name: "Antigua and Barbuda",
+ },
+ {
+ iso_2: "ar",
+ iso_3: "arg",
+ num_code: "32",
+ name: "ARGENTINA",
+ display_name: "Argentina",
+ },
+ {
+ iso_2: "am",
+ iso_3: "arm",
+ num_code: "51",
+ name: "ARMENIA",
+ display_name: "Armenia",
+ },
+ {
+ iso_2: "aw",
+ iso_3: "abw",
+ num_code: "533",
+ name: "ARUBA",
+ display_name: "Aruba",
+ },
+ {
+ iso_2: "au",
+ iso_3: "aus",
+ num_code: "36",
+ name: "AUSTRALIA",
+ display_name: "Australia",
+ },
+ {
+ iso_2: "at",
+ iso_3: "aut",
+ num_code: "40",
+ name: "AUSTRIA",
+ display_name: "Austria",
+ },
+ {
+ iso_2: "az",
+ iso_3: "aze",
+ num_code: "31",
+ name: "AZERBAIJAN",
+ display_name: "Azerbaijan",
+ },
+ {
+ iso_2: "bs",
+ iso_3: "bhs",
+ num_code: "44",
+ name: "BAHAMAS",
+ display_name: "Bahamas",
+ },
+ {
+ iso_2: "bh",
+ iso_3: "bhr",
+ num_code: "48",
+ name: "BAHRAIN",
+ display_name: "Bahrain",
+ },
+ {
+ iso_2: "bd",
+ iso_3: "bgd",
+ num_code: "50",
+ name: "BANGLADESH",
+ display_name: "Bangladesh",
+ },
+ {
+ iso_2: "bb",
+ iso_3: "brb",
+ num_code: "52",
+ name: "BARBADOS",
+ display_name: "Barbados",
+ },
+ {
+ iso_2: "by",
+ iso_3: "blr",
+ num_code: "112",
+ name: "BELARUS",
+ display_name: "Belarus",
+ },
+ {
+ iso_2: "be",
+ iso_3: "bel",
+ num_code: "56",
+ name: "BELGIUM",
+ display_name: "Belgium",
+ },
+ {
+ iso_2: "bz",
+ iso_3: "blz",
+ num_code: "84",
+ name: "BELIZE",
+ display_name: "Belize",
+ },
+ {
+ iso_2: "bj",
+ iso_3: "ben",
+ num_code: "204",
+ name: "BENIN",
+ display_name: "Benin",
+ },
+ {
+ iso_2: "bm",
+ iso_3: "bmu",
+ num_code: "60",
+ name: "BERMUDA",
+ display_name: "Bermuda",
+ },
+ {
+ iso_2: "bt",
+ iso_3: "btn",
+ num_code: "64",
+ name: "BHUTAN",
+ display_name: "Bhutan",
+ },
+ {
+ iso_2: "bo",
+ iso_3: "bol",
+ num_code: "68",
+ name: "BOLIVIA",
+ display_name: "Bolivia",
+ },
+ {
+ iso_2: "bq",
+ iso_3: "bes",
+ num_code: "535",
+ name: "BONAIRE, SINT EUSTATIUS AND SABA",
+ display_name: "Bonaire, Sint Eustatius and Saba",
+ },
+ {
+ iso_2: "ba",
+ iso_3: "bih",
+ num_code: "70",
+ name: "BOSNIA AND HERZEGOVINA",
+ display_name: "Bosnia and Herzegovina",
+ },
+ {
+ iso_2: "bw",
+ iso_3: "bwa",
+ num_code: "72",
+ name: "BOTSWANA",
+ display_name: "Botswana",
+ },
+ {
+ iso_2: "bv",
+ iso_3: "bvd",
+ num_code: "74",
+ name: "BOUVET ISLAND",
+ display_name: "Bouvet Island",
+ },
+ {
+ iso_2: "br",
+ iso_3: "bra",
+ num_code: "76",
+ name: "BRAZIL",
+ display_name: "Brazil",
+ },
+ {
+ iso_2: "io",
+ iso_3: "iot",
+ num_code: "86",
+ name: "BRITISH INDIAN OCEAN TERRITORY",
+ display_name: "British Indian Ocean Territory",
+ },
+ {
+ iso_2: "bn",
+ iso_3: "brn",
+ num_code: "96",
+ name: "BRUNEI DARUSSALAM",
+ display_name: "Brunei Darussalam",
+ },
+ {
+ iso_2: "bg",
+ iso_3: "bgr",
+ num_code: "100",
+ name: "BULGARIA",
+ display_name: "Bulgaria",
+ },
+ {
+ iso_2: "bf",
+ iso_3: "bfa",
+ num_code: "854",
+ name: "BURKINA FASO",
+ display_name: "Burkina Faso",
+ },
+ {
+ iso_2: "bi",
+ iso_3: "bdi",
+ num_code: "108",
+ name: "BURUNDI",
+ display_name: "Burundi",
+ },
+ {
+ iso_2: "kh",
+ iso_3: "khm",
+ num_code: "116",
+ name: "CAMBODIA",
+ display_name: "Cambodia",
+ },
+ {
+ iso_2: "cm",
+ iso_3: "cmr",
+ num_code: "120",
+ name: "CAMEROON",
+ display_name: "Cameroon",
+ },
+ {
+ iso_2: "ca",
+ iso_3: "can",
+ num_code: "124",
+ name: "CANADA",
+ display_name: "Canada",
+ },
+ {
+ iso_2: "cv",
+ iso_3: "cpv",
+ num_code: "132",
+ name: "CAPE VERDE",
+ display_name: "Cape Verde",
+ },
+ {
+ iso_2: "ky",
+ iso_3: "cym",
+ num_code: "136",
+ name: "CAYMAN ISLANDS",
+ display_name: "Cayman Islands",
+ },
+ {
+ iso_2: "cf",
+ iso_3: "caf",
+ num_code: "140",
+ name: "CENTRAL AFRICAN REPUBLIC",
+ display_name: "Central African Republic",
+ },
+ {
+ iso_2: "td",
+ iso_3: "tcd",
+ num_code: "148",
+ name: "CHAD",
+ display_name: "Chad",
+ },
+ {
+ iso_2: "cl",
+ iso_3: "chl",
+ num_code: "152",
+ name: "CHILE",
+ display_name: "Chile",
+ },
+ {
+ iso_2: "cn",
+ iso_3: "chn",
+ num_code: "156",
+ name: "CHINA",
+ display_name: "China",
+ },
+ {
+ iso_2: "cx",
+ iso_3: "cxr",
+ num_code: "162",
+ name: "CHRISTMAS ISLAND",
+ display_name: "Christmas Island",
+ },
+ {
+ iso_2: "cc",
+ iso_3: "cck",
+ num_code: "166",
+ name: "COCOS (KEELING) ISLANDS",
+ display_name: "Cocos (Keeling) Islands",
+ },
+ {
+ iso_2: "co",
+ iso_3: "col",
+ num_code: "170",
+ name: "COLOMBIA",
+ display_name: "Colombia",
+ },
+ {
+ iso_2: "km",
+ iso_3: "com",
+ num_code: "174",
+ name: "COMOROS",
+ display_name: "Comoros",
+ },
+ {
+ iso_2: "cg",
+ iso_3: "cog",
+ num_code: "178",
+ name: "CONGO",
+ display_name: "Congo",
+ },
+ {
+ iso_2: "cd",
+ iso_3: "cod",
+ num_code: "180",
+ name: "CONGO, THE DEMOCRATIC REPUBLIC OF THE",
+ display_name: "Congo, the Democratic Republic of the",
+ },
+ {
+ iso_2: "ck",
+ iso_3: "cok",
+ num_code: "184",
+ name: "COOK ISLANDS",
+ display_name: "Cook Islands",
+ },
+ {
+ iso_2: "cr",
+ iso_3: "cri",
+ num_code: "188",
+ name: "COSTA RICA",
+ display_name: "Costa Rica",
+ },
+ {
+ iso_2: "ci",
+ iso_3: "civ",
+ num_code: "384",
+ name: "COTE D'IVOIRE",
+ display_name: "Cote D'Ivoire",
+ },
+ {
+ iso_2: "hr",
+ iso_3: "hrv",
+ num_code: "191",
+ name: "CROATIA",
+ display_name: "Croatia",
+ },
+ {
+ iso_2: "cu",
+ iso_3: "cub",
+ num_code: "192",
+ name: "CUBA",
+ display_name: "Cuba",
+ },
+ {
+ iso_2: "cw",
+ iso_3: "cuw",
+ num_code: "531",
+ name: "CURAÇAO",
+ display_name: "Curaçao",
+ },
+ {
+ iso_2: "cy",
+ iso_3: "cyp",
+ num_code: "196",
+ name: "CYPRUS",
+ display_name: "Cyprus",
+ },
+ {
+ iso_2: "cz",
+ iso_3: "cze",
+ num_code: "203",
+ name: "CZECH REPUBLIC",
+ display_name: "Czech Republic",
+ },
+ {
+ iso_2: "dk",
+ iso_3: "dnk",
+ num_code: "208",
+ name: "DENMARK",
+ display_name: "Denmark",
+ },
+ {
+ iso_2: "dj",
+ iso_3: "dji",
+ num_code: "262",
+ name: "DJIBOUTI",
+ display_name: "Djibouti",
+ },
+ {
+ iso_2: "dm",
+ iso_3: "dma",
+ num_code: "212",
+ name: "DOMINICA",
+ display_name: "Dominica",
+ },
+ {
+ iso_2: "do",
+ iso_3: "dom",
+ num_code: "214",
+ name: "DOMINICAN REPUBLIC",
+ display_name: "Dominican Republic",
+ },
+ {
+ iso_2: "ec",
+ iso_3: "ecu",
+ num_code: "218",
+ name: "ECUADOR",
+ display_name: "Ecuador",
+ },
+ {
+ iso_2: "eg",
+ iso_3: "egy",
+ num_code: "818",
+ name: "EGYPT",
+ display_name: "Egypt",
+ },
+ {
+ iso_2: "sv",
+ iso_3: "slv",
+ num_code: "222",
+ name: "EL SALVADOR",
+ display_name: "El Salvador",
+ },
+ {
+ iso_2: "gq",
+ iso_3: "gnq",
+ num_code: "226",
+ name: "EQUATORIAL GUINEA",
+ display_name: "Equatorial Guinea",
+ },
+ {
+ iso_2: "er",
+ iso_3: "eri",
+ num_code: "232",
+ name: "ERITREA",
+ display_name: "Eritrea",
+ },
+ {
+ iso_2: "ee",
+ iso_3: "est",
+ num_code: "233",
+ name: "ESTONIA",
+ display_name: "Estonia",
+ },
+ {
+ iso_2: "et",
+ iso_3: "eth",
+ num_code: "231",
+ name: "ETHIOPIA",
+ display_name: "Ethiopia",
+ },
+ {
+ iso_2: "fk",
+ iso_3: "flk",
+ num_code: "238",
+ name: "FALKLAND ISLANDS (MALVINAS)",
+ display_name: "Falkland Islands (Malvinas)",
+ },
+ {
+ iso_2: "fo",
+ iso_3: "fro",
+ num_code: "234",
+ name: "FAROE ISLANDS",
+ display_name: "Faroe Islands",
+ },
+ {
+ iso_2: "fj",
+ iso_3: "fji",
+ num_code: "242",
+ name: "FIJI",
+ display_name: "Fiji",
+ },
+ {
+ iso_2: "fi",
+ iso_3: "fin",
+ num_code: "246",
+ name: "FINLAND",
+ display_name: "Finland",
+ },
+ {
+ iso_2: "fr",
+ iso_3: "fra",
+ num_code: "250",
+ name: "FRANCE",
+ display_name: "France",
+ },
+ {
+ iso_2: "gf",
+ iso_3: "guf",
+ num_code: "254",
+ name: "FRENCH GUIANA",
+ display_name: "French Guiana",
+ },
+ {
+ iso_2: "pf",
+ iso_3: "pyf",
+ num_code: "258",
+ name: "FRENCH POLYNESIA",
+ display_name: "French Polynesia",
+ },
+ {
+ iso_2: "tf",
+ iso_3: "atf",
+ num_code: "260",
+ name: "FRENCH SOUTHERN TERRITORIES",
+ display_name: "French Southern Territories",
+ },
+ {
+ iso_2: "ga",
+ iso_3: "gab",
+ num_code: "266",
+ name: "GABON",
+ display_name: "Gabon",
+ },
+ {
+ iso_2: "gm",
+ iso_3: "gmb",
+ num_code: "270",
+ name: "GAMBIA",
+ display_name: "Gambia",
+ },
+ {
+ iso_2: "ge",
+ iso_3: "geo",
+ num_code: "268",
+ name: "GEORGIA",
+ display_name: "Georgia",
+ },
+ {
+ iso_2: "de",
+ iso_3: "deu",
+ num_code: "276",
+ name: "GERMANY",
+ display_name: "Germany",
+ },
+ {
+ iso_2: "gh",
+ iso_3: "gha",
+ num_code: "288",
+ name: "GHANA",
+ display_name: "Ghana",
+ },
+ {
+ iso_2: "gi",
+ iso_3: "gib",
+ num_code: "292",
+ name: "GIBRALTAR",
+ display_name: "Gibraltar",
+ },
+ {
+ iso_2: "gr",
+ iso_3: "grc",
+ num_code: "300",
+ name: "GREECE",
+ display_name: "Greece",
+ },
+ {
+ iso_2: "gl",
+ iso_3: "grl",
+ num_code: "304",
+ name: "GREENLAND",
+ display_name: "Greenland",
+ },
+ {
+ iso_2: "gd",
+ iso_3: "grd",
+ num_code: "308",
+ name: "GRENADA",
+ display_name: "Grenada",
+ },
+ {
+ iso_2: "gp",
+ iso_3: "glp",
+ num_code: "312",
+ name: "GUADELOUPE",
+ display_name: "Guadeloupe",
+ },
+ {
+ iso_2: "gu",
+ iso_3: "gum",
+ num_code: "316",
+ name: "GUAM",
+ display_name: "Guam",
+ },
+ {
+ iso_2: "gt",
+ iso_3: "gtm",
+ num_code: "320",
+ name: "GUATEMALA",
+ display_name: "Guatemala",
+ },
+ {
+ iso_2: "gg",
+ iso_3: "ggy",
+ num_code: "831",
+ name: "GUERNSEY",
+ display_name: "Guernsey",
+ },
+ {
+ iso_2: "gn",
+ iso_3: "gin",
+ num_code: "324",
+ name: "GUINEA",
+ display_name: "Guinea",
+ },
+ {
+ iso_2: "gw",
+ iso_3: "gnb",
+ num_code: "624",
+ name: "GUINEA-BISSAU",
+ display_name: "Guinea-Bissau",
+ },
+ {
+ iso_2: "gy",
+ iso_3: "guy",
+ num_code: "328",
+ name: "GUYANA",
+ display_name: "Guyana",
+ },
+ {
+ iso_2: "ht",
+ iso_3: "hti",
+ num_code: "332",
+ name: "HAITI",
+ display_name: "Haiti",
+ },
+ {
+ iso_2: "hm",
+ iso_3: "hmd",
+ num_code: "334",
+ name: "HEARD ISLAND AND MCDONALD ISLANDS",
+ display_name: "Heard Island And Mcdonald Islands",
+ },
+ {
+ iso_2: "va",
+ iso_3: "vat",
+ num_code: "336",
+ name: "HOLY SEE (VATICAN CITY STATE)",
+ display_name: "Holy See (Vatican City State)",
+ },
+ {
+ iso_2: "hn",
+ iso_3: "hnd",
+ num_code: "340",
+ name: "HONDURAS",
+ display_name: "Honduras",
+ },
+ {
+ iso_2: "hk",
+ iso_3: "hkg",
+ num_code: "344",
+ name: "HONG KONG",
+ display_name: "Hong Kong",
+ },
+ {
+ iso_2: "hu",
+ iso_3: "hun",
+ num_code: "348",
+ name: "HUNGARY",
+ display_name: "Hungary",
+ },
+ {
+ iso_2: "is",
+ iso_3: "isl",
+ num_code: "352",
+ name: "ICELAND",
+ display_name: "Iceland",
+ },
+ {
+ iso_2: "in",
+ iso_3: "ind",
+ num_code: "356",
+ name: "INDIA",
+ display_name: "India",
+ },
+ {
+ iso_2: "id",
+ iso_3: "idn",
+ num_code: "360",
+ name: "INDONESIA",
+ display_name: "Indonesia",
+ },
+ {
+ iso_2: "ir",
+ iso_3: "irn",
+ num_code: "364",
+ name: "IRAN, ISLAMIC REPUBLIC OF",
+ display_name: "Iran, Islamic Republic of",
+ },
+ {
+ iso_2: "iq",
+ iso_3: "irq",
+ num_code: "368",
+ name: "IRAQ",
+ display_name: "Iraq",
+ },
+ {
+ iso_2: "ie",
+ iso_3: "irl",
+ num_code: "372",
+ name: "IRELAND",
+ display_name: "Ireland",
+ },
+ {
+ iso_2: "im",
+ iso_3: "imn",
+ num_code: "833",
+ name: "ISLE OF MAN",
+ display_name: "Isle Of Man",
+ },
+ {
+ iso_2: "il",
+ iso_3: "isr",
+ num_code: "376",
+ name: "ISRAEL",
+ display_name: "Israel",
+ },
+ {
+ iso_2: "it",
+ iso_3: "ita",
+ num_code: "380",
+ name: "ITALY",
+ display_name: "Italy",
+ },
+ {
+ iso_2: "jm",
+ iso_3: "jam",
+ num_code: "388",
+ name: "JAMAICA",
+ display_name: "Jamaica",
+ },
+ {
+ iso_2: "jp",
+ iso_3: "jpn",
+ num_code: "392",
+ name: "JAPAN",
+ display_name: "Japan",
+ },
+ {
+ iso_2: "je",
+ iso_3: "jey",
+ num_code: "832",
+ name: "JERSEY",
+ display_name: "Jersey",
+ },
+ {
+ iso_2: "jo",
+ iso_3: "jor",
+ num_code: "400",
+ name: "JORDAN",
+ display_name: "Jordan",
+ },
+ {
+ iso_2: "kz",
+ iso_3: "kaz",
+ num_code: "398",
+ name: "KAZAKHSTAN",
+ display_name: "Kazakhstan",
+ },
+ {
+ iso_2: "ke",
+ iso_3: "ken",
+ num_code: "404",
+ name: "KENYA",
+ display_name: "Kenya",
+ },
+ {
+ iso_2: "ki",
+ iso_3: "kir",
+ num_code: "296",
+ name: "KIRIBATI",
+ display_name: "Kiribati",
+ },
+ {
+ iso_2: "kp",
+ iso_3: "prk",
+ num_code: "408",
+ name: "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF",
+ display_name: "Korea, Democratic People's Republic of",
+ },
+ {
+ iso_2: "kr",
+ iso_3: "kor",
+ num_code: "410",
+ name: "KOREA, REPUBLIC OF",
+ display_name: "Korea, Republic of",
+ },
+ {
+ iso_2: "xk",
+ iso_3: "xkx",
+ num_code: "900",
+ name: "KOSOVO",
+ display_name: "Kosovo",
+ },
+ {
+ iso_2: "kw",
+ iso_3: "kwt",
+ num_code: "414",
+ name: "KUWAIT",
+ display_name: "Kuwait",
+ },
+ {
+ iso_2: "kg",
+ iso_3: "kgz",
+ num_code: "417",
+ name: "KYRGYZSTAN",
+ display_name: "Kyrgyzstan",
+ },
+ {
+ iso_2: "la",
+ iso_3: "lao",
+ num_code: "418",
+ name: "LAO PEOPLE'S DEMOCRATIC REPUBLIC",
+ display_name: "Lao People's Democratic Republic",
+ },
+ {
+ iso_2: "lv",
+ iso_3: "lva",
+ num_code: "428",
+ name: "LATVIA",
+ display_name: "Latvia",
+ },
+ {
+ iso_2: "lb",
+ iso_3: "lbn",
+ num_code: "422",
+ name: "LEBANON",
+ display_name: "Lebanon",
+ },
+ {
+ iso_2: "ls",
+ iso_3: "lso",
+ num_code: "426",
+ name: "LESOTHO",
+ display_name: "Lesotho",
+ },
+ {
+ iso_2: "lr",
+ iso_3: "lbr",
+ num_code: "430",
+ name: "LIBERIA",
+ display_name: "Liberia",
+ },
+ {
+ iso_2: "ly",
+ iso_3: "lby",
+ num_code: "434",
+ name: "LIBYA",
+ display_name: "Libya",
+ },
+ {
+ iso_2: "li",
+ iso_3: "lie",
+ num_code: "438",
+ name: "LIECHTENSTEIN",
+ display_name: "Liechtenstein",
+ },
+ {
+ iso_2: "lt",
+ iso_3: "ltu",
+ num_code: "440",
+ name: "LITHUANIA",
+ display_name: "Lithuania",
+ },
+ {
+ iso_2: "lu",
+ iso_3: "lux",
+ num_code: "442",
+ name: "LUXEMBOURG",
+ display_name: "Luxembourg",
+ },
+ {
+ iso_2: "mo",
+ iso_3: "mac",
+ num_code: "446",
+ name: "MACAO",
+ display_name: "Macao",
+ },
+ {
+ iso_2: "mk",
+ iso_3: "mkd",
+ num_code: "807",
+ name: "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF",
+ display_name: "Macedonia, the Former Yugoslav Republic of",
+ },
+ {
+ iso_2: "mg",
+ iso_3: "mdg",
+ num_code: "450",
+ name: "MADAGASCAR",
+ display_name: "Madagascar",
+ },
+ {
+ iso_2: "mw",
+ iso_3: "mwi",
+ num_code: "454",
+ name: "MALAWI",
+ display_name: "Malawi",
+ },
+ {
+ iso_2: "my",
+ iso_3: "mys",
+ num_code: "458",
+ name: "MALAYSIA",
+ display_name: "Malaysia",
+ },
+ {
+ iso_2: "mv",
+ iso_3: "mdv",
+ num_code: "462",
+ name: "MALDIVES",
+ display_name: "Maldives",
+ },
+ {
+ iso_2: "ml",
+ iso_3: "mli",
+ num_code: "466",
+ name: "MALI",
+ display_name: "Mali",
+ },
+ {
+ iso_2: "mt",
+ iso_3: "mlt",
+ num_code: "470",
+ name: "MALTA",
+ display_name: "Malta",
+ },
+ {
+ iso_2: "mh",
+ iso_3: "mhl",
+ num_code: "584",
+ name: "MARSHALL ISLANDS",
+ display_name: "Marshall Islands",
+ },
+ {
+ iso_2: "mq",
+ iso_3: "mtq",
+ num_code: "474",
+ name: "MARTINIQUE",
+ display_name: "Martinique",
+ },
+ {
+ iso_2: "mr",
+ iso_3: "mrt",
+ num_code: "478",
+ name: "MAURITANIA",
+ display_name: "Mauritania",
+ },
+ {
+ iso_2: "mu",
+ iso_3: "mus",
+ num_code: "480",
+ name: "MAURITIUS",
+ display_name: "Mauritius",
+ },
+ {
+ iso_2: "yt",
+ iso_3: "myt",
+ num_code: "175",
+ name: "MAYOTTE",
+ display_name: "Mayotte",
+ },
+ {
+ iso_2: "mx",
+ iso_3: "mex",
+ num_code: "484",
+ name: "MEXICO",
+ display_name: "Mexico",
+ },
+ {
+ iso_2: "fm",
+ iso_3: "fsm",
+ num_code: "583",
+ name: "MICRONESIA, FEDERATED STATES OF",
+ display_name: "Micronesia, Federated States of",
+ },
+ {
+ iso_2: "md",
+ iso_3: "mda",
+ num_code: "498",
+ name: "MOLDOVA, REPUBLIC OF",
+ display_name: "Moldova, Republic of",
+ },
+ {
+ iso_2: "mc",
+ iso_3: "mco",
+ num_code: "492",
+ name: "MONACO",
+ display_name: "Monaco",
+ },
+ {
+ iso_2: "mn",
+ iso_3: "mng",
+ num_code: "496",
+ name: "MONGOLIA",
+ display_name: "Mongolia",
+ },
+ {
+ iso_2: "me",
+ iso_3: "mne",
+ num_code: "499",
+ name: "MONTENEGRO",
+ display_name: "Montenegro",
+ },
+ {
+ iso_2: "ms",
+ iso_3: "msr",
+ num_code: "500",
+ name: "MONTSERRAT",
+ display_name: "Montserrat",
+ },
+ {
+ iso_2: "ma",
+ iso_3: "mar",
+ num_code: "504",
+ name: "MOROCCO",
+ display_name: "Morocco",
+ },
+ {
+ iso_2: "mz",
+ iso_3: "moz",
+ num_code: "508",
+ name: "MOZAMBIQUE",
+ display_name: "Mozambique",
+ },
+ {
+ iso_2: "mm",
+ iso_3: "mmr",
+ num_code: "104",
+ name: "MYANMAR",
+ display_name: "Myanmar",
+ },
+ {
+ iso_2: "na",
+ iso_3: "nam",
+ num_code: "516",
+ name: "NAMIBIA",
+ display_name: "Namibia",
+ },
+ {
+ iso_2: "nr",
+ iso_3: "nru",
+ num_code: "520",
+ name: "NAURU",
+ display_name: "Nauru",
+ },
+ {
+ iso_2: "np",
+ iso_3: "npl",
+ num_code: "524",
+ name: "NEPAL",
+ display_name: "Nepal",
+ },
+ {
+ iso_2: "nl",
+ iso_3: "nld",
+ num_code: "528",
+ name: "NETHERLANDS",
+ display_name: "Netherlands",
+ },
+ {
+ iso_2: "nc",
+ iso_3: "ncl",
+ num_code: "540",
+ name: "NEW CALEDONIA",
+ display_name: "New Caledonia",
+ },
+ {
+ iso_2: "nz",
+ iso_3: "nzl",
+ num_code: "554",
+ name: "NEW ZEALAND",
+ display_name: "New Zealand",
+ },
+ {
+ iso_2: "ni",
+ iso_3: "nic",
+ num_code: "558",
+ name: "NICARAGUA",
+ display_name: "Nicaragua",
+ },
+ {
+ iso_2: "ne",
+ iso_3: "ner",
+ num_code: "562",
+ name: "NIGER",
+ display_name: "Niger",
+ },
+ {
+ iso_2: "ng",
+ iso_3: "nga",
+ num_code: "566",
+ name: "NIGERIA",
+ display_name: "Nigeria",
+ },
+ {
+ iso_2: "nu",
+ iso_3: "niu",
+ num_code: "570",
+ name: "NIUE",
+ display_name: "Niue",
+ },
+ {
+ iso_2: "nf",
+ iso_3: "nfk",
+ num_code: "574",
+ name: "NORFOLK ISLAND",
+ display_name: "Norfolk Island",
+ },
+ {
+ iso_2: "mp",
+ iso_3: "mnp",
+ num_code: "580",
+ name: "NORTHERN MARIANA ISLANDS",
+ display_name: "Northern Mariana Islands",
+ },
+ {
+ iso_2: "no",
+ iso_3: "nor",
+ num_code: "578",
+ name: "NORWAY",
+ display_name: "Norway",
+ },
+ {
+ iso_2: "om",
+ iso_3: "omn",
+ num_code: "512",
+ name: "OMAN",
+ display_name: "Oman",
+ },
+ {
+ iso_2: "pk",
+ iso_3: "pak",
+ num_code: "586",
+ name: "PAKISTAN",
+ display_name: "Pakistan",
+ },
+ {
+ iso_2: "pw",
+ iso_3: "plw",
+ num_code: "585",
+ name: "PALAU",
+ display_name: "Palau",
+ },
+ {
+ iso_2: "ps",
+ iso_3: "pse",
+ num_code: "275",
+ name: "PALESTINIAN TERRITORY, OCCUPIED",
+ display_name: "Palestinian Territory, Occupied",
+ },
+ {
+ iso_2: "pa",
+ iso_3: "pan",
+ num_code: "591",
+ name: "PANAMA",
+ display_name: "Panama",
+ },
+ {
+ iso_2: "pg",
+ iso_3: "png",
+ num_code: "598",
+ name: "PAPUA NEW GUINEA",
+ display_name: "Papua New Guinea",
+ },
+ {
+ iso_2: "py",
+ iso_3: "pry",
+ num_code: "600",
+ name: "PARAGUAY",
+ display_name: "Paraguay",
+ },
+ {
+ iso_2: "pe",
+ iso_3: "per",
+ num_code: "604",
+ name: "PERU",
+ display_name: "Peru",
+ },
+ {
+ iso_2: "ph",
+ iso_3: "phl",
+ num_code: "608",
+ name: "PHILIPPINES",
+ display_name: "Philippines",
+ },
+ {
+ iso_2: "pn",
+ iso_3: "pcn",
+ num_code: "612",
+ name: "PITCAIRN",
+ display_name: "Pitcairn",
+ },
+ {
+ iso_2: "pl",
+ iso_3: "pol",
+ num_code: "616",
+ name: "POLAND",
+ display_name: "Poland",
+ },
+ {
+ iso_2: "pt",
+ iso_3: "prt",
+ num_code: "620",
+ name: "PORTUGAL",
+ display_name: "Portugal",
+ },
+ {
+ iso_2: "pr",
+ iso_3: "pri",
+ num_code: "630",
+ name: "PUERTO RICO",
+ display_name: "Puerto Rico",
+ },
+ {
+ iso_2: "qa",
+ iso_3: "qat",
+ num_code: "634",
+ name: "QATAR",
+ display_name: "Qatar",
+ },
+ {
+ iso_2: "re",
+ iso_3: "reu",
+ num_code: "638",
+ name: "REUNION",
+ display_name: "Reunion",
+ },
+ {
+ iso_2: "ro",
+ iso_3: "rom",
+ num_code: "642",
+ name: "ROMANIA",
+ display_name: "Romania",
+ },
+ {
+ iso_2: "ru",
+ iso_3: "rus",
+ num_code: "643",
+ name: "RUSSIAN FEDERATION",
+ display_name: "Russian Federation",
+ },
+ {
+ iso_2: "rw",
+ iso_3: "rwa",
+ num_code: "646",
+ name: "RWANDA",
+ display_name: "Rwanda",
+ },
+ {
+ iso_2: "bl",
+ iso_3: "blm",
+ num_code: "652",
+ name: "SAINT BARTHÉLEMY",
+ display_name: "Saint Barthélemy",
+ },
+ {
+ iso_2: "sh",
+ iso_3: "shn",
+ num_code: "654",
+ name: "SAINT HELENA",
+ display_name: "Saint Helena",
+ },
+ {
+ iso_2: "kn",
+ iso_3: "kna",
+ num_code: "659",
+ name: "SAINT KITTS AND NEVIS",
+ display_name: "Saint Kitts and Nevis",
+ },
+ {
+ iso_2: "lc",
+ iso_3: "lca",
+ num_code: "662",
+ name: "SAINT LUCIA",
+ display_name: "Saint Lucia",
+ },
+ {
+ iso_2: "mf",
+ iso_3: "maf",
+ num_code: "663",
+ name: "SAINT MARTIN (FRENCH PART)",
+ display_name: "Saint Martin (French part)",
+ },
+ {
+ iso_2: "pm",
+ iso_3: "spm",
+ num_code: "666",
+ name: "SAINT PIERRE AND MIQUELON",
+ display_name: "Saint Pierre and Miquelon",
+ },
+ {
+ iso_2: "vc",
+ iso_3: "vct",
+ num_code: "670",
+ name: "SAINT VINCENT AND THE GRENADINES",
+ display_name: "Saint Vincent and the Grenadines",
+ },
+ {
+ iso_2: "ws",
+ iso_3: "wsm",
+ num_code: "882",
+ name: "SAMOA",
+ display_name: "Samoa",
+ },
+ {
+ iso_2: "sm",
+ iso_3: "smr",
+ num_code: "674",
+ name: "SAN MARINO",
+ display_name: "San Marino",
+ },
+ {
+ iso_2: "st",
+ iso_3: "stp",
+ num_code: "678",
+ name: "SAO TOME AND PRINCIPE",
+ display_name: "Sao Tome and Principe",
+ },
+ {
+ iso_2: "sa",
+ iso_3: "sau",
+ num_code: "682",
+ name: "SAUDI ARABIA",
+ display_name: "Saudi Arabia",
+ },
+ {
+ iso_2: "sn",
+ iso_3: "sen",
+ num_code: "686",
+ name: "SENEGAL",
+ display_name: "Senegal",
+ },
+ {
+ iso_2: "rs",
+ iso_3: "srb",
+ num_code: "688",
+ name: "SERBIA",
+ display_name: "Serbia",
+ },
+ {
+ iso_2: "sc",
+ iso_3: "syc",
+ num_code: "690",
+ name: "SEYCHELLES",
+ display_name: "Seychelles",
+ },
+ {
+ iso_2: "sl",
+ iso_3: "sle",
+ num_code: "694",
+ name: "SIERRA LEONE",
+ display_name: "Sierra Leone",
+ },
+ {
+ iso_2: "sg",
+ iso_3: "sgp",
+ num_code: "702",
+ name: "SINGAPORE",
+ display_name: "Singapore",
+ },
+ {
+ iso_2: "sx",
+ iso_3: "sxm",
+ num_code: "534",
+ name: "SINT MAARTEN",
+ display_name: "Sint Maarten",
+ },
+ {
+ iso_2: "sk",
+ iso_3: "svk",
+ num_code: "703",
+ name: "SLOVAKIA",
+ display_name: "Slovakia",
+ },
+ {
+ iso_2: "si",
+ iso_3: "svn",
+ num_code: "705",
+ name: "SLOVENIA",
+ display_name: "Slovenia",
+ },
+ {
+ iso_2: "sb",
+ iso_3: "slb",
+ num_code: "90",
+ name: "SOLOMON ISLANDS",
+ display_name: "Solomon Islands",
+ },
+ {
+ iso_2: "so",
+ iso_3: "som",
+ num_code: "706",
+ name: "SOMALIA",
+ display_name: "Somalia",
+ },
+ {
+ iso_2: "za",
+ iso_3: "zaf",
+ num_code: "710",
+ name: "SOUTH AFRICA",
+ display_name: "South Africa",
+ },
+ {
+ iso_2: "gs",
+ iso_3: "sgs",
+ num_code: "239",
+ name: "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS",
+ display_name: "South Georgia and the South Sandwich Islands",
+ },
+ {
+ iso_2: "ss",
+ iso_3: "ssd",
+ num_code: "728",
+ name: "SOUTH SUDAN",
+ display_name: "South Sudan",
+ },
+ {
+ iso_2: "es",
+ iso_3: "esp",
+ num_code: "724",
+ name: "SPAIN",
+ display_name: "Spain",
+ },
+ {
+ iso_2: "lk",
+ iso_3: "lka",
+ num_code: "144",
+ name: "SRI LANKA",
+ display_name: "Sri Lanka",
+ },
+ {
+ iso_2: "sd",
+ iso_3: "sdn",
+ num_code: "729",
+ name: "SUDAN",
+ display_name: "Sudan",
+ },
+ {
+ iso_2: "sr",
+ iso_3: "sur",
+ num_code: "740",
+ name: "SURINAME",
+ display_name: "Suriname",
+ },
+ {
+ iso_2: "sj",
+ iso_3: "sjm",
+ num_code: "744",
+ name: "SVALBARD AND JAN MAYEN",
+ display_name: "Svalbard and Jan Mayen",
+ },
+ {
+ iso_2: "sz",
+ iso_3: "swz",
+ num_code: "748",
+ name: "SWAZILAND",
+ display_name: "Swaziland",
+ },
+ {
+ iso_2: "se",
+ iso_3: "swe",
+ num_code: "752",
+ name: "SWEDEN",
+ display_name: "Sweden",
+ },
+ {
+ iso_2: "ch",
+ iso_3: "che",
+ num_code: "756",
+ name: "SWITZERLAND",
+ display_name: "Switzerland",
+ },
+ {
+ iso_2: "sy",
+ iso_3: "syr",
+ num_code: "760",
+ name: "SYRIAN ARAB REPUBLIC",
+ display_name: "Syrian Arab Republic",
+ },
+ {
+ iso_2: "tw",
+ iso_3: "twn",
+ num_code: "158",
+ name: "TAIWAN, PROVINCE OF CHINA",
+ display_name: "Taiwan, Province of China",
+ },
+ {
+ iso_2: "tj",
+ iso_3: "tjk",
+ num_code: "762",
+ name: "TAJIKISTAN",
+ display_name: "Tajikistan",
+ },
+ {
+ iso_2: "tz",
+ iso_3: "tza",
+ num_code: "834",
+ name: "TANZANIA, UNITED REPUBLIC OF",
+ display_name: "Tanzania, United Republic of",
+ },
+ {
+ iso_2: "th",
+ iso_3: "tha",
+ num_code: "764",
+ name: "THAILAND",
+ display_name: "Thailand",
+ },
+ {
+ iso_2: "tl",
+ iso_3: "tls",
+ num_code: "626",
+ name: "TIMOR LESTE",
+ display_name: "Timor Leste",
+ },
+ {
+ iso_2: "tg",
+ iso_3: "tgo",
+ num_code: "768",
+ name: "TOGO",
+ display_name: "Togo",
+ },
+ {
+ iso_2: "tk",
+ iso_3: "tkl",
+ num_code: "772",
+ name: "TOKELAU",
+ display_name: "Tokelau",
+ },
+ {
+ iso_2: "to",
+ iso_3: "ton",
+ num_code: "776",
+ name: "TONGA",
+ display_name: "Tonga",
+ },
+ {
+ iso_2: "tt",
+ iso_3: "tto",
+ num_code: "780",
+ name: "TRINIDAD AND TOBAGO",
+ display_name: "Trinidad and Tobago",
+ },
+ {
+ iso_2: "tn",
+ iso_3: "tun",
+ num_code: "788",
+ name: "TUNISIA",
+ display_name: "Tunisia",
+ },
+ {
+ iso_2: "tr",
+ iso_3: "tur",
+ num_code: "792",
+ name: "TURKEY",
+ display_name: "Turkey",
+ },
+ {
+ iso_2: "tm",
+ iso_3: "tkm",
+ num_code: "795",
+ name: "TURKMENISTAN",
+ display_name: "Turkmenistan",
+ },
+ {
+ iso_2: "tc",
+ iso_3: "tca",
+ num_code: "796",
+ name: "TURKS AND CAICOS ISLANDS",
+ display_name: "Turks and Caicos Islands",
+ },
+ {
+ iso_2: "tv",
+ iso_3: "tuv",
+ num_code: "798",
+ name: "TUVALU",
+ display_name: "Tuvalu",
+ },
+ {
+ iso_2: "ug",
+ iso_3: "uga",
+ num_code: "800",
+ name: "UGANDA",
+ display_name: "Uganda",
+ },
+ {
+ iso_2: "ua",
+ iso_3: "ukr",
+ num_code: "804",
+ name: "UKRAINE",
+ display_name: "Ukraine",
+ },
+ {
+ iso_2: "ae",
+ iso_3: "are",
+ num_code: "784",
+ name: "UNITED ARAB EMIRATES",
+ display_name: "United Arab Emirates",
+ },
+ {
+ iso_2: "gb",
+ iso_3: "gbr",
+ num_code: "826",
+ name: "UNITED KINGDOM",
+ display_name: "United Kingdom",
+ },
+ {
+ iso_2: "us",
+ iso_3: "usa",
+ num_code: "840",
+ name: "UNITED STATES",
+ display_name: "United States",
+ },
+ {
+ iso_2: "um",
+ iso_3: "umi",
+ num_code: "581",
+ name: "UNITED STATES MINOR OUTLYING ISLANDS",
+ display_name: "United States Minor Outlying Islands",
+ },
+ {
+ iso_2: "uy",
+ iso_3: "ury",
+ num_code: "858",
+ name: "URUGUAY",
+ display_name: "Uruguay",
+ },
+ {
+ iso_2: "uz",
+ iso_3: "uzb",
+ num_code: "860",
+ name: "UZBEKISTAN",
+ display_name: "Uzbekistan",
+ },
+ {
+ iso_2: "vu",
+ iso_3: "vut",
+ num_code: "548",
+ name: "VANUATU",
+ display_name: "Vanuatu",
+ },
+ {
+ iso_2: "ve",
+ iso_3: "ven",
+ num_code: "862",
+ name: "VENEZUELA",
+ display_name: "Venezuela",
+ },
+ {
+ iso_2: "vn",
+ iso_3: "vnm",
+ num_code: "704",
+ name: "VIET NAM",
+ display_name: "Viet Nam",
+ },
+ {
+ iso_2: "vg",
+ iso_3: "vgb",
+ num_code: "92",
+ name: "VIRGIN ISLANDS, BRITISH",
+ display_name: "Virgin Islands, British",
+ },
+ {
+ iso_2: "vi",
+ iso_3: "vir",
+ num_code: "850",
+ name: "VIRGIN ISLANDS, U.S.",
+ display_name: "Virgin Islands, U.S.",
+ },
+ {
+ iso_2: "wf",
+ iso_3: "wlf",
+ num_code: "876",
+ name: "WALLIS AND FUTUNA",
+ display_name: "Wallis and Futuna",
+ },
+ {
+ iso_2: "eh",
+ iso_3: "esh",
+ num_code: "732",
+ name: "WESTERN SAHARA",
+ display_name: "Western Sahara",
+ },
+ {
+ iso_2: "ye",
+ iso_3: "yem",
+ num_code: "887",
+ name: "YEMEN",
+ display_name: "Yemen",
+ },
+ {
+ iso_2: "zm",
+ iso_3: "zmb",
+ num_code: "894",
+ name: "ZAMBIA",
+ display_name: "Zambia",
+ },
+ {
+ iso_2: "zw",
+ iso_3: "zwe",
+ num_code: "716",
+ name: "ZIMBABWE",
+ display_name: "Zimbabwe",
+ },
+ {
+ iso_2: "ax",
+ iso_3: "ala",
+ num_code: "248",
+ name: "Ã…LAND ISLANDS",
+ display_name: "Ã…land Islands",
+ },
+]
diff --git a/packages/plugins/draft-order/src/admin/lib/data/currencies.ts b/packages/plugins/draft-order/src/admin/lib/data/currencies.ts
new file mode 100644
index 0000000000..1f90688a30
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/data/currencies.ts
@@ -0,0 +1,40 @@
+export const getLocaleAmount = (amount: number, currencyCode: string) => {
+ const formatter = new Intl.NumberFormat([], {
+ style: "currency",
+ currencyDisplay: "narrowSymbol",
+ currency: currencyCode,
+ })
+
+ return formatter.format(amount)
+}
+
+export const getNativeSymbol = (currencyCode: string) => {
+ const formatted = new Intl.NumberFormat([], {
+ style: "currency",
+ currency: currencyCode,
+ currencyDisplay: "narrowSymbol",
+ }).format(0)
+
+ return formatted.replace(/\d/g, "").replace(/[.,]/g, "").trim()
+}
+
+export const getDecimalDigits = (currencyCode: string) => {
+ const formatter = new Intl.NumberFormat(undefined, {
+ style: "currency",
+ currency: currencyCode,
+ })
+
+ return formatter.resolvedOptions().maximumFractionDigits
+}
+
+export const getStylizedAmount = (amount: number, currencyCode: string) => {
+ const symbol = getNativeSymbol(currencyCode)
+ const decimalDigits = getDecimalDigits(currencyCode)
+
+ const total = amount.toLocaleString(undefined, {
+ minimumFractionDigits: decimalDigits,
+ maximumFractionDigits: decimalDigits,
+ })
+
+ return `${symbol} ${total} ${currencyCode.toUpperCase()}`
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/queries/draft-order-details.ts b/packages/plugins/draft-order/src/admin/lib/queries/draft-order-details.ts
new file mode 100644
index 0000000000..f1a65324fb
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/queries/draft-order-details.ts
@@ -0,0 +1,7 @@
+const orderDetailQuery = (id: string) => ({
+ queryKey: ordersQueryKeys.detail(id),
+ queryFn: async () =>
+ sdk.admin.order.retrieve(id, {
+ fields: DEFAULT_FIELDS,
+ }),
+})
diff --git a/packages/plugins/draft-order/src/admin/lib/queries/sdk.ts b/packages/plugins/draft-order/src/admin/lib/queries/sdk.ts
new file mode 100644
index 0000000000..03cc1dd247
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/queries/sdk.ts
@@ -0,0 +1,8 @@
+import Medusa from "@medusajs/js-sdk"
+
+export const sdk = new Medusa({
+ baseUrl: "/",
+ auth: {
+ type: "session",
+ },
+})
diff --git a/packages/plugins/draft-order/src/admin/lib/schemas/address.ts b/packages/plugins/draft-order/src/admin/lib/schemas/address.ts
new file mode 100644
index 0000000000..dab5117b6c
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/schemas/address.ts
@@ -0,0 +1,14 @@
+import { z } from "zod"
+
+export const addressSchema = z.object({
+ country_code: z.string().min(1),
+ first_name: z.string().min(1),
+ last_name: z.string().min(1),
+ address_1: z.string().min(1),
+ address_2: z.string().optional(),
+ company: z.string().optional(),
+ city: z.string().min(1),
+ province: z.string().optional(),
+ postal_code: z.string().min(1),
+ phone: z.string().optional(),
+})
diff --git a/packages/plugins/draft-order/src/admin/lib/utils/address-utils.ts b/packages/plugins/draft-order/src/admin/lib/utils/address-utils.ts
new file mode 100644
index 0000000000..58b3810fc1
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/utils/address-utils.ts
@@ -0,0 +1,88 @@
+import { HttpTypes } from "@medusajs/types"
+import { getCountryByIso2 } from "../data/countries"
+
+export function isSameAddress(
+ a?: HttpTypes.AdminOrderAddress | null,
+ b?: HttpTypes.AdminOrderAddress | null
+) {
+ if (!a || !b) {
+ return false
+ }
+
+ return (
+ a.first_name === b.first_name &&
+ a.last_name === b.last_name &&
+ a.address_1 === b.address_1 &&
+ a.address_2 === b.address_2 &&
+ a.city === b.city &&
+ a.postal_code === b.postal_code &&
+ a.province === b.province &&
+ a.country_code === b.country_code &&
+ a.phone === b.phone &&
+ a.company === b.company
+ )
+}
+
+export function getFormattedAddress(
+ address?: HttpTypes.AdminOrderAddress | HttpTypes.AdminCustomerAddress | null
+) {
+ if (!address) {
+ return []
+ }
+
+ const {
+ first_name,
+ last_name,
+ company,
+ address_1,
+ address_2,
+ city,
+ postal_code,
+ province,
+ country_code,
+ } = address
+
+ const country = "country" in address ? address.country : null
+
+ const name = [first_name, last_name].filter(Boolean).join(" ")
+
+ const formattedAddress: string[] = []
+
+ if (name) {
+ formattedAddress.push(name)
+ }
+
+ if (company) {
+ formattedAddress.push(company)
+ }
+
+ if (address_1) {
+ formattedAddress.push(address_1)
+ }
+
+ if (address_2) {
+ formattedAddress.push(address_2)
+ }
+
+ const cityProvincePostal = [city, province, postal_code]
+ .filter(Boolean)
+ .join(" ")
+
+ if (cityProvincePostal) {
+ formattedAddress.push(cityProvincePostal)
+ }
+
+ if (country) {
+ formattedAddress.push(country.display_name!)
+ } else if (country_code) {
+ const country = getCountryByIso2(country_code)
+
+ if (country) {
+ formattedAddress.push(country.display_name)
+ } else {
+ formattedAddress.push(country_code.toUpperCase())
+ }
+ }
+
+ return formattedAddress
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/utils/date-utils.ts b/packages/plugins/draft-order/src/admin/lib/utils/date-utils.ts
new file mode 100644
index 0000000000..db1bc7052a
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/utils/date-utils.ts
@@ -0,0 +1,33 @@
+import { format, formatDistance, sub } from "date-fns"
+import { enUS } from "date-fns/locale"
+
+const LOCALE = enUS
+
+export function getRelativeDate(date: string | Date): string {
+ const now = new Date()
+
+ return formatDistance(sub(new Date(date), { minutes: 0 }), now, {
+ addSuffix: true,
+ locale: LOCALE,
+ })
+}
+
+export const getFullDate = ({
+ date,
+ includeTime = false,
+}: {
+ date: string | Date
+ includeTime?: boolean
+}) => {
+ const ensuredDate = new Date(date)
+
+ if (isNaN(ensuredDate.getTime())) {
+ return ""
+ }
+
+ const timeFormat = includeTime ? "p" : ""
+
+ return format(ensuredDate, `PP ${timeFormat}`, {
+ locale: LOCALE,
+ })
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/utils/number-utils.ts b/packages/plugins/draft-order/src/admin/lib/utils/number-utils.ts
new file mode 100644
index 0000000000..d6748ca1c5
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/utils/number-utils.ts
@@ -0,0 +1,3 @@
+export function convertNumber(value?: string | number) {
+ return typeof value === "string" ? Number(value.replace(",", ".")) : value
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/utils/order-utils.ts b/packages/plugins/draft-order/src/admin/lib/utils/order-utils.ts
new file mode 100644
index 0000000000..5d334334f3
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/utils/order-utils.ts
@@ -0,0 +1,40 @@
+import { HttpTypes } from "@medusajs/types"
+
+export function getUniqueShippingProfiles(
+ items: HttpTypes.AdminOrderLineItem[]
+): HttpTypes.AdminShippingProfile[] {
+ const profiles = new Map()
+ items.forEach((item) => {
+ const profile = item.variant?.product?.shipping_profile
+ if (profile) {
+ profiles.set(profile.id, profile)
+ }
+ })
+ return Array.from(profiles.values())
+}
+
+export function getItemsWithShippingProfile(
+ shipping_profile_id: string,
+ items: HttpTypes.AdminOrderLineItem[]
+) {
+ return items.filter(
+ (item) =>
+ item.variant?.product?.shipping_profile?.id === shipping_profile_id
+ )
+}
+
+export function getOrderCustomer(obj: HttpTypes.AdminOrder) {
+ const { first_name: sFirstName, last_name: sLastName } =
+ obj.shipping_address || {}
+ const { first_name: bFirstName, last_name: bLastName } =
+ obj.billing_address || {}
+ const { first_name: cFirstName, last_name: cLastName } = obj.customer || {}
+
+ const customerName = [cFirstName, cLastName].filter(Boolean).join(" ")
+ const shippingName = [sFirstName, sLastName].filter(Boolean).join(" ")
+ const billingName = [bFirstName, bLastName].filter(Boolean).join(" ")
+
+ const name = customerName || shippingName || billingName
+
+ return name
+}
diff --git a/packages/plugins/draft-order/src/admin/lib/utils/string-utils.ts b/packages/plugins/draft-order/src/admin/lib/utils/string-utils.ts
new file mode 100644
index 0000000000..7a3d437569
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/lib/utils/string-utils.ts
@@ -0,0 +1,3 @@
+export function pluralize(count: number, plural: string, singular: string) {
+ return count === 1 ? singular : plural
+}
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/@create/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/@create/page.tsx
new file mode 100644
index 0000000000..498ed251fe
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/@create/page.tsx
@@ -0,0 +1,780 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import {
+ Button,
+ Divider,
+ Heading,
+ Hint,
+ Input,
+ Label,
+ Switch,
+ toast,
+} from "@medusajs/ui"
+import { Fragment, useCallback } from "react"
+import { Control, useForm, UseFormSetValue, useWatch } from "react-hook-form"
+import { z } from "zod"
+import { AddressCard } from "../../../components/common/address-card"
+import { ConditionalTooltip } from "../../../components/common/conditional-tooltip"
+import { CustomerCard } from "../../../components/common/customer-card"
+import { Form } from "../../../components/common/form"
+import { KeyboundForm } from "../../../components/common/keybound-form"
+import { Combobox } from "../../../components/inputs/combobox"
+import { CountrySelect } from "../../../components/inputs/country-select"
+import { RouteFocusModal, useRouteModal } from "../../../components/modals"
+import { useCreateDraftOrder } from "../../../hooks/api/draft-orders"
+import { useComboboxData } from "../../../hooks/common/use-combobox-data"
+import { sdk } from "../../../lib/queries/sdk"
+import { addressSchema } from "../../../lib/schemas/address"
+import { getFormattedAddress } from "../../../lib/utils/address-utils"
+import { useCustomer } from "../../../hooks/api/customers"
+
+const Create = () => {
+ return (
+
+
+
+ )
+}
+
+const CreateForm = () => {
+ const { handleSuccess } = useRouteModal()
+
+ const form = useForm>({
+ defaultValues: {
+ region_id: "",
+ sales_channel_id: "",
+ customer_id: "",
+ email: "",
+ shipping_address_id: "",
+ shipping_address: initialAddress,
+ billing_address_id: "",
+ billing_address: null,
+ same_as_shipping: true,
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const regions = useComboboxData({
+ queryFn: async (params) => {
+ return await sdk.admin.region.list(params)
+ },
+ queryKey: ["regions"],
+ getOptions: (data) => {
+ return data.regions.map((region) => ({
+ label: region.name,
+ value: region.id,
+ }))
+ },
+ })
+
+ const salesChannels = useComboboxData({
+ queryFn: async (params) => {
+ return await sdk.admin.salesChannel.list(params)
+ },
+ queryKey: ["sales-channels"],
+ getOptions: (data) => {
+ return data.sales_channels.map((salesChannel) => ({
+ label: salesChannel.name,
+ value: salesChannel.id,
+ }))
+ },
+ })
+
+ const { mutateAsync } = useCreateDraftOrder()
+
+ const onSubmit = form.handleSubmit(
+ async (data) => {
+ const billingAddress = data.same_as_shipping
+ ? data.shipping_address
+ : data.billing_address
+
+ await mutateAsync(
+ {
+ region_id: data.region_id,
+ sales_channel_id: data.sales_channel_id,
+ customer_id: data.customer_id || undefined,
+ email: !data.customer_id ? data.email : undefined,
+ shipping_address: data.shipping_address,
+ billing_address: billingAddress!,
+ },
+ {
+ onSuccess: (response) => {
+ handleSuccess(`/draft-orders/${response.draft_order.id}`)
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ },
+ (error) => {
+ toast.error(JSON.stringify(error, null, 2))
+ }
+ )
+
+ if (regions.isError) {
+ throw regions.error
+ }
+
+ if (salesChannels.isError) {
+ throw salesChannels.error
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Create Draft Order
+
+
+ Create a new draft order
+
+
+
+
+
{
+ return (
+
+
+
+
Region
+ Choose region
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+
+
+
+
{
+ return (
+
+
+
+
Sales Channel
+ Choose sales channel
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+ )
+}
+
+interface EmailFieldProps {
+ control: Control>
+}
+
+const EmailField = ({ control }: EmailFieldProps) => {
+ const customerId = useWatch({ control, name: "customer_id" })
+
+ return (
+ {
+ return (
+
+
+
+
Email
+ Input a email to associate with the order
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+ )
+}
+interface CustomerFieldProps {
+ control: Control>
+ setValue: UseFormSetValue>
+}
+
+const CustomerField = ({ control, setValue }: CustomerFieldProps) => {
+ const email = useWatch({ control, name: "email" })
+ const customerId = useWatch({ control, name: "customer_id" })
+
+ const customers = useComboboxData({
+ queryFn: async (params) => {
+ return await sdk.admin.customer.list(params)
+ },
+ queryKey: ["customers"],
+ getOptions: (data) => {
+ return data.customers.map((customer) => {
+ const name = [customer.first_name, customer.last_name]
+ .filter(Boolean)
+ .join(" ")
+
+ return {
+ label: name ? `${name} (${customer.email})` : customer.email,
+ value: customer.id,
+ }
+ })
+ },
+ })
+
+ const onPropagateEmail = useCallback(
+ (value?: string) => {
+ const label = customers.options.find(
+ (option) => option.value === value
+ )?.label
+
+ const customerEmail = label?.match(/\((.*@.*)\)$/)?.[1] || label
+
+ if (!email && customerEmail) {
+ setValue("email", customerEmail, {
+ shouldDirty: true,
+ shouldTouch: true,
+ })
+ }
+ },
+ [email, setValue, customers.options]
+ )
+
+ if (customers.isError) {
+ throw customers.error
+ }
+
+ return (
+ {
+ const onRemove = () => {
+ onChange("")
+ // If the customer is removed, we need to clear the shipping address id
+ setValue("shipping_address_id", "")
+ }
+
+ return (
+
+
+
+
Customer
+ Choose an existing customer
+
+
+ {customerId ? (
+
+ ) : (
+ {
+ onPropagateEmail(value)
+ onChange(value)
+ }}
+ {...field}
+ />
+ )}
+
+
+
+ )
+ }}
+ />
+ )
+}
+
+interface AddressFieldProps {
+ type: "shipping_address" | "billing_address"
+ control: Control>
+ setValue: UseFormSetValue>
+}
+
+const AddressField = ({ type, control, setValue }: AddressFieldProps) => {
+ const customerId = useWatch({ control, name: "customer_id" })
+ const addressId = useWatch({ control, name: `${type}_id` })
+ const sameAsShipping = useWatch({ control, name: "same_as_shipping" })
+
+ const { customer } = useCustomer(
+ customerId!,
+ {},
+ {
+ enabled: !!customerId,
+ }
+ )
+
+ const addresses = useComboboxData({
+ queryFn: async (params) => {
+ const response = await sdk.client.fetch(
+ "/admin/customers/" + customerId + "/addresses",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ query: params,
+ credentials: "include",
+ }
+ )
+
+ return response as HttpTypes.AdminCustomerAddressListResponse
+ },
+ queryKey: [type, customerId],
+ getOptions: (data) => {
+ return data.addresses.map((address) => {
+ const formattedAddress = getFormattedAddress(address).join(",\n")
+
+ return {
+ label: formattedAddress,
+ value: address.id,
+ }
+ })
+ },
+ enabled: !!customerId,
+ })
+
+ const onSelectAddress = async (addressId?: string) => {
+ if (!addressId || !customerId) {
+ return
+ }
+
+ const response = (await sdk.client.fetch(
+ "/admin/customers/" + customerId + "/addresses/" + addressId,
+ {
+ method: "GET",
+ credentials: "include",
+ }
+ )) as HttpTypes.AdminCustomerAddressResponse
+
+ const address = response.address
+
+ setValue(type, {
+ ...address,
+ first_name: address.first_name || customer?.first_name,
+ last_name: address.last_name || customer?.last_name,
+ } as z.infer)
+ }
+
+ const showFields = type === "billing_address" ? !sameAsShipping : true
+
+ return (
+
+
+
+ {type === "shipping_address" ? "Shipping address" : "Billing address"}
+
+
+ Address used for{" "}
+ {type === "shipping_address" ? "shipping" : "billing"}
+
+
+
+ {type === "billing_address" && (
+
{
+ const onCheckedChange = (checked: boolean) => {
+ if (!checked) {
+ setValue("billing_address", initialAddress)
+ } else {
+ setValue("billing_address_id", "")
+ setValue("billing_address", null)
+ }
+
+ onChange(checked)
+ }
+
+ return (
+
+
+
+
+
+
+
Same as shipping address
+
+ Use the same address for billing and shipping
+
+
+
+
+ )
+ }}
+ />
+ )}
+ {showFields && (
+
+ {customerId && (
+
+
{
+ const onRemove = () => {
+ onChange("")
+ }
+
+ return (
+
+ {addressId ? (
+
+ ) : (
+
+
+ Saved addresses
+
+
+ Choose one of the customers saved addresses.
+
+
+ {
+ onSelectAddress(value)
+ onChange(value)
+ }}
+ {...field}
+ />
+
+
+
+ )}
+
+ )
+ }}
+ />
+
+
+ )}
+ {!addressId && (
+
+ )}
+
+ )}
+
+
+ )
+}
+
+const initialAddress = {
+ country_code: "",
+ first_name: "",
+ last_name: "",
+ address_1: "",
+ address_2: "",
+ city: "",
+ province: "",
+ postal_code: "",
+ phone: "",
+ company: "",
+}
+
+const schema = z
+ .object({
+ region_id: z.string().min(1),
+ sales_channel_id: z.string().min(1),
+ customer_id: z.string().optional(),
+ email: z.string().email().optional(),
+ shipping_address_id: z.string().optional(),
+ shipping_address: addressSchema,
+ billing_address_id: z.string().optional(),
+ billing_address: addressSchema.nullable(),
+ same_as_shipping: z.boolean().default(true),
+ })
+ .superRefine((data, ctx) => {
+ if (!data.customer_id && !data.email) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Either a customer or email must be provided",
+ path: ["customer_id", "email"],
+ })
+ }
+
+ if (!data.shipping_address && !data.shipping_address_id) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Shipping address is required",
+ path: ["shipping_address"],
+ })
+ }
+
+ if (data.same_as_shipping === false) {
+ if (!data.billing_address && !data.billing_address_id) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Billing address is required",
+ path: ["billing_address"],
+ })
+ }
+ }
+ })
+
+export default Create
\ No newline at end of file
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@billing-address/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@billing-address/page.tsx
new file mode 100644
index 0000000000..bbe9ba919e
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@billing-address/page.tsx
@@ -0,0 +1,246 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, Heading, Input, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { CountrySelect } from "../../../../components/inputs/country-select"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import { useUpdateDraftOrder } from "../../../../hooks/api/draft-orders"
+import { useOrder } from "../../../../hooks/api/orders"
+import { addressSchema } from "../../../../lib/schemas/address"
+
+const BillingAddress = () => {
+ const { id } = useParams()
+
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields: "+billing_address",
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ const isReady = !isPending && !!order
+
+ return (
+
+
+
+ Edit Billing Address
+
+
+
+ Edit the billing address for the draft order
+
+
+
+ {isReady && }
+
+ )
+}
+
+interface BillingAddressFormProps {
+ order: HttpTypes.AdminOrder
+}
+
+const BillingAddressForm = ({ order }: BillingAddressFormProps) => {
+ const form = useForm>({
+ defaultValues: {
+ first_name: order.billing_address?.first_name ?? "",
+ last_name: order.billing_address?.last_name ?? "",
+ company: order.billing_address?.company ?? "",
+ address_1: order.billing_address?.address_1 ?? "",
+ address_2: order.billing_address?.address_2 ?? "",
+ city: order.billing_address?.city ?? "",
+ province: order.billing_address?.province ?? "",
+ country_code: order.billing_address?.country_code ?? "",
+ postal_code: order.billing_address?.postal_code ?? "",
+ phone: order.billing_address?.phone ?? "",
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(order.id)
+ const { handleSuccess } = useRouteModal()
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await mutateAsync(
+ { billing_address: data },
+ {
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+const schema = addressSchema
+
+export default BillingAddress
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@custom-items/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@custom-items/page.tsx
new file mode 100644
index 0000000000..91acc8d878
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@custom-items/page.tsx
@@ -0,0 +1,51 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Button, Heading } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { RouteDrawer } from "../../../../components/modals"
+
+const CustomItems = () => {
+ return (
+
+
+
+ Edit Custom Items
+
+
+
+
+ )
+}
+
+const CustomItemsForm = () => {
+ const form = useForm>({
+ resolver: zodResolver(schema),
+ })
+
+ return (
+
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+const schema = z.object({
+ email: z.string().email(),
+})
+
+export default CustomItems
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@email/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@email/page.tsx
new file mode 100644
index 0000000000..cc5c37d89c
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@email/page.tsx
@@ -0,0 +1,111 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, Heading, Input, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import { useUpdateDraftOrder } from "../../../../hooks/api/draft-orders"
+import { useOrder } from "../../../../hooks/api/orders"
+
+const Email = () => {
+ const { id } = useParams()
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields: "+email",
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ const isReady = !isPending && !!order
+
+ return (
+
+
+
+ Edit Email
+
+
+ Edit the email for the draft order
+
+
+ {isReady && }
+
+ )
+}
+
+interface EmailFormProps {
+ order: HttpTypes.AdminOrder
+}
+
+const EmailForm = ({ order }: EmailFormProps) => {
+ const form = useForm>({
+ defaultValues: {
+ email: order.email ?? "",
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(order.id)
+ const { handleSuccess } = useRouteModal()
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await mutateAsync(
+ { email: data.email },
+ {
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+ (
+
+ Email
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+const schema = z.object({
+ email: z.string().email(),
+})
+
+export default Email
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@items/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@items/page.tsx
new file mode 100644
index 0000000000..30bc3710e3
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@items/page.tsx
@@ -0,0 +1,1162 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { Check, PencilSquare, Plus } from "@medusajs/icons"
+import { HttpTypes } from "@medusajs/types"
+import {
+ Button,
+ createDataTableColumnHelper,
+ CurrencyInput,
+ DataTableRowSelectionState,
+ Divider,
+ DropdownMenu,
+ Heading,
+ IconButton,
+ Input,
+ Text,
+ toast,
+ Tooltip,
+} from "@medusajs/ui"
+import { keepPreviousData } from "@tanstack/react-query"
+import { matchSorter } from "match-sorter"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import { useParams } from "react-router-dom"
+
+import type { AdminOrderPreviewLineItem } from "../../../../../types/http/orders/entity"
+import { DataTable } from "../../../../components/common/data-table"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { Thumbnail } from "../../../../components/common/thumbnail"
+import { NumberInput } from "../../../../components/inputs/number-input"
+import {
+ RouteFocusModal,
+ StackedFocusModal,
+ useRouteModal,
+ useStackedModal,
+} from "../../../../components/modals"
+import {
+ useDraftOrder,
+ useDraftOrderAddItems,
+ useDraftOrderConfirmEdit,
+ useDraftOrderRemoveActionItem,
+ useDraftOrderRequestEdit,
+ useDraftOrderUpdateActionItem,
+ useDraftOrderUpdateItem,
+} from "../../../../hooks/api/draft-orders"
+import { useOrderPreview } from "../../../../hooks/api/orders"
+import { useProductVariants } from "../../../../hooks/api/product-variants"
+import { useDebouncedSearch } from "../../../../hooks/common/use-debounced-search"
+import { useQueryParams } from "../../../../hooks/common/use-query-params"
+import { useCancelOrderEdit } from "../../../../hooks/order-edits/use-cancel-order-edit"
+import { useInitiateOrderEdit } from "../../../../hooks/order-edits/use-initiate-order-edit"
+import {
+ getLocaleAmount,
+ getNativeSymbol,
+ getStylizedAmount,
+} from "../../../../lib/data/currencies"
+import { getFullDate } from "../../../../lib/utils/date-utils"
+import { convertNumber } from "../../../../lib/utils/number-utils"
+
+const STACKED_MODAL_ID = "items_stacked_modal"
+
+const Items = () => {
+ const { id } = useParams()
+
+ const {
+ order: preview,
+ isPending: isPreviewPending,
+ isError: isPreviewError,
+ error: previewError,
+ } = useOrderPreview(id!, undefined, {
+ placeholderData: keepPreviousData,
+ })
+
+ useInitiateOrderEdit({ preview })
+
+ const { draft_order, isPending, isError, error } = useDraftOrder(
+ id!,
+ {
+ fields: "currency_code",
+ },
+ {
+ enabled: !!id,
+ }
+ )
+
+ const { onCancel } = useCancelOrderEdit({ preview })
+
+ if (isError) {
+ throw error
+ }
+
+ if (isPreviewError) {
+ throw previewError
+ }
+
+ const ready = !!preview && !isPreviewPending && !!draft_order && !isPending
+
+ return (
+
+ {ready ? (
+
+ ) : (
+
+
+ Edit Items
+
+
+
+ Loading data for the draft order, please wait...
+
+
+
+ )}
+
+ )
+}
+
+interface ItemsFormProps {
+ preview: HttpTypes.AdminOrderPreview
+ currencyCode: string
+}
+
+const ItemsForm = ({ preview, currencyCode }: ItemsFormProps) => {
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [modalContent, setModalContent] = useState(
+ null
+ )
+
+ const { handleSuccess } = useRouteModal()
+ const { searchValue, onSearchValueChange, query } = useDebouncedSearch()
+
+ const { mutateAsync: confirmOrderEdit } = useDraftOrderConfirmEdit(preview.id)
+ const { mutateAsync: requestOrderEdit } = useDraftOrderRequestEdit(preview.id)
+
+ const itemCount =
+ preview.items?.reduce((acc, item) => acc + item.quantity, 0) || 0
+
+ const matches = useMemo(() => {
+ return matchSorter(preview.items, query, {
+ keys: ["product_title", "variant_title", "variant_sku", "title"],
+ })
+ }, [preview.items, query])
+
+ const onSubmit = async () => {
+ setIsSubmitting(true)
+
+ let requestSucceeded = false
+
+ await requestOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(`Failed to request order edit: ${e.message}`)
+ },
+ onSuccess: () => {
+ requestSucceeded = true
+ },
+ })
+
+ if (!requestSucceeded) {
+ setIsSubmitting(false)
+ return
+ }
+
+ await confirmOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(`Failed to confirm order edit: ${e.message}`)
+ },
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onSettled: () => {
+ setIsSubmitting(false)
+ },
+ })
+ }
+
+ const onKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ if (modalContent || isSubmitting) {
+ // Don't do anything if the StackedFocusModal is open or the form is submitting
+ return
+ }
+
+ onSubmit()
+ }
+ },
+ [modalContent, isSubmitting, onSubmit]
+ )
+
+ useEffect(() => {
+ document.addEventListener("keydown", onKeyDown)
+
+ return () => {
+ document.removeEventListener("keydown", onKeyDown)
+ }
+ }, [onKeyDown])
+
+ return (
+
+
+
+ {
+ if (!open) {
+ setModalContent(null)
+ }
+ }}
+ >
+
+
+
+
+ Edit Items
+
+
+
+ Edit the items in the draft order.
+
+
+
+
+
+
+
+
+ Items
+
+
+ Choose items from the product catalog.
+
+
+
+
+ onSearchValueChange(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Item
+
+
+
+
+ Quantity
+
+
+
+
+ Price
+
+
+
+
+
+
+ {itemCount <= 0 ? (
+
+
+ There are no items in this order
+
+
+ Add items to the order to get started.
+
+
+ ) : matches.length > 0 ? (
+ matches?.map((item) => (
+
+ ))
+ ) : (
+
+
+ No items found
+
+
+ No items found for "{query}".
+
+
+ )}
+
+
+
+
+
+
+
+ Subtotal
+
+
+
+
+ {itemCount} {itemCount === 1 ? "item" : "items"}
+
+
+
+
+ {getStylizedAmount(preview.item_subtotal, currencyCode)}
+
+
+
+
+
+ {modalContent &&
+ (modalContent === StackedModalContent.ADD_ITEMS ? (
+
+ ) : modalContent === StackedModalContent.ADD_CUSTOM_ITEM ? (
+
+ ) : null)}
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+ )
+}
+
+interface ItemProps {
+ item: AdminOrderPreviewLineItem
+ preview: HttpTypes.AdminOrderPreview
+ currencyCode: string
+}
+
+const Item = ({ item, preview, currencyCode }: ItemProps) => {
+ if (item.variant_id) {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+}
+
+const VariantItem = ({ item, preview, currencyCode }: ItemProps) => {
+ const [editing, setEditing] = useState(false)
+
+ const form = useForm>({
+ defaultValues: {
+ quantity: item.quantity,
+ unit_price: item.unit_price,
+ },
+ resolver: zodResolver(variantItemSchema),
+ })
+
+ const actionId = useMemo(() => {
+ return item.actions?.find((a) => a.action === "ITEM_ADD")?.id
+ }, [item])
+
+ const { mutateAsync: updateActionItem, isPending: isUpdatingActionItem } =
+ useDraftOrderUpdateActionItem(preview.id)
+ const { mutateAsync: updateOriginalItem, isPending: isUpdatingOriginalItem } =
+ useDraftOrderUpdateItem(preview.id)
+
+ const isPending = isUpdatingActionItem || isUpdatingOriginalItem
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ /**
+ * If none of the values have changed, we don't need to update anything
+ */
+ if (
+ convertNumber(data.unit_price) === item.unit_price &&
+ data.quantity === item.quantity
+ ) {
+ setEditing(false)
+
+ return
+ }
+
+ if (!actionId) {
+ await updateOriginalItem(
+ {
+ item_id: item.id,
+ quantity: data.quantity,
+ unit_price: convertNumber(data.unit_price),
+ },
+ {
+ onSuccess: () => {
+ setEditing(false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+
+ return
+ }
+
+ await updateActionItem(
+ {
+ action_id: actionId,
+ quantity: data.quantity,
+ unit_price: convertNumber(data.unit_price),
+ },
+ {
+ onSuccess: () => {
+ setEditing(false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+ )
+}
+
+const variantItemSchema = z.object({
+ quantity: z.number(),
+ unit_price: z.union([z.number(), z.string()]),
+})
+
+const CustomItem = ({ item, preview, currencyCode }: ItemProps) => {
+ const [editing, setEditing] = useState(false)
+ const { quantity, unit_price, title } = item
+
+ const form = useForm>({
+ defaultValues: {
+ title,
+ quantity,
+ unit_price,
+ },
+ resolver: zodResolver(customItemSchema),
+ })
+
+ useEffect(() => {
+ form.reset({
+ title,
+ quantity,
+ unit_price,
+ })
+ }, [form, title, quantity, unit_price])
+
+ const actionId = useMemo(() => {
+ return item.actions?.find((a) => a.action === "ITEM_ADD")?.id
+ }, [item])
+
+ const { mutateAsync: updateActionItem, isPending: isUpdatingActionItem } =
+ useDraftOrderUpdateActionItem(preview.id)
+
+ const { mutateAsync: removeActionItem, isPending: isRemovingActionItem } =
+ useDraftOrderRemoveActionItem(preview.id)
+
+ const { mutateAsync: updateOriginalItem, isPending: isUpdatingOriginalItem } =
+ useDraftOrderUpdateItem(preview.id)
+
+ const isPending = isUpdatingActionItem || isUpdatingOriginalItem
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ /**
+ * If none of the values have changed, we don't need to update anything
+ */
+ if (
+ convertNumber(data.unit_price) === item.unit_price &&
+ data.quantity === item.quantity &&
+ data.title === item.title
+ ) {
+ setEditing(false)
+
+ return
+ }
+
+ if (!actionId) {
+ await updateOriginalItem(
+ {
+ item_id: item.id,
+ quantity: data.quantity,
+ unit_price: convertNumber(data.unit_price),
+ },
+ {
+ onSuccess: () => {
+ setEditing(false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+
+ return
+ }
+
+ if (data.quantity === 0) {
+ await removeActionItem(actionId, {
+ onSuccess: () => {
+ setEditing(false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ })
+
+ return
+ }
+
+ await updateActionItem(
+ {
+ action_id: actionId,
+ quantity: data.quantity,
+ unit_price: convertNumber(data.unit_price),
+ },
+ {
+ onSuccess: () => {
+ setEditing(false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+ )
+}
+
+enum StackedModalContent {
+ ADD_ITEMS = "add-items",
+ ADD_CUSTOM_ITEM = "add-custom-item",
+}
+
+interface StackedModalTriggerProps {
+ type: StackedModalContent
+ setModalContent: (content: StackedModalContent) => void
+}
+
+const StackedModalTrigger = ({
+ type,
+ setModalContent,
+}: StackedModalTriggerProps) => {
+ const { setIsOpen } = useStackedModal()
+
+ const onClick = useCallback(() => {
+ setModalContent(type)
+ setIsOpen(STACKED_MODAL_ID, true)
+ }, [setModalContent, setIsOpen, type])
+
+ return (
+
+
+ {type === StackedModalContent.ADD_ITEMS
+ ? "Add items"
+ : "Add custom item"}
+
+
+ )
+}
+
+const VARIANT_PREFIX = "items"
+const LIMIT = 50
+
+interface ExistingItemsFormProps {
+ orderId: string
+ items: AdminOrderPreviewLineItem[]
+}
+
+const ExistingItemsForm = ({ orderId, items }: ExistingItemsFormProps) => {
+ const { setIsOpen } = useStackedModal()
+ const [rowSelection, setRowSelection] = useState(
+ items.reduce((acc, item) => {
+ acc[item.variant_id!] = true
+
+ return acc
+ }, {} as DataTableRowSelectionState)
+ )
+
+ useEffect(() => {
+ setRowSelection(
+ items.reduce((acc, item) => {
+ if (item.variant_id) {
+ acc[item.variant_id!] = true
+ }
+
+ return acc
+ }, {} as DataTableRowSelectionState)
+ )
+ }, [items])
+
+ const { q, order, offset } = useQueryParams(
+ ["q", "order", "offset"],
+ VARIANT_PREFIX
+ )
+ const { variants, count, isPending, isError, error } = useProductVariants(
+ {
+ q,
+ order,
+ offset: offset ? parseInt(offset) : undefined,
+ limit: LIMIT,
+ },
+ {
+ placeholderData: keepPreviousData,
+ }
+ )
+
+ const columns = useColumns()
+
+ const { mutateAsync } = useDraftOrderAddItems(orderId)
+
+ const onSubmit = async () => {
+ const ids = Object.keys(rowSelection).filter(
+ (id) => !items.find((i) => i.variant_id === id)
+ )
+
+ await mutateAsync(
+ {
+ items: ids.map((id) => ({
+ variant_id: id,
+ quantity: 1,
+ })),
+ },
+ {
+ onSuccess: () => {
+ setRowSelection({})
+ setIsOpen(STACKED_MODAL_ID, false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+ }
+
+ if (isError) {
+ throw error
+ }
+
+ return (
+ {
+ e.preventDefault()
+
+ const searchInput = document.querySelector(
+ "[data-modal-id='modal-search-input']"
+ )
+
+ if (searchInput) {
+ ;(searchInput as HTMLInputElement).focus()
+ }
+ }}
+ >
+
+
+ Product Variants
+
+
+
+ Choose product variants to add to the order.
+
+
+
+
+ row.id}
+ rowCount={count}
+ prefix={VARIANT_PREFIX}
+ layout="fill"
+ rowSelection={{
+ state: rowSelection,
+ onRowSelectionChange: setRowSelection,
+ enableRowSelection: (row) => {
+ return !items.find((i) => i.variant_id === row.original.id)
+ },
+ }}
+ autoFocusSearch={true}
+ />
+
+
+
+
+
+ Cancel
+
+
+
+ Update items
+
+
+
+
+ )
+}
+
+const columnHelper =
+ createDataTableColumnHelper()
+
+const useColumns = () => {
+ return useMemo(() => {
+ return [
+ columnHelper.select(),
+ columnHelper.accessor("product.title", {
+ header: "Product",
+ cell: ({ row }) => {
+ return (
+
+
+ {row.original.product?.title}
+
+ )
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("title", {
+ header: "Variant",
+ enableSorting: true,
+ }),
+ columnHelper.accessor("sku", {
+ header: "SKU",
+ cell: ({ getValue }) => {
+ return getValue() ?? "-"
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("updated_at", {
+ header: "Updated",
+ cell: ({ getValue }) => {
+ return (
+
+ {getFullDate({ date: getValue() })}
+
+ )
+ },
+ enableSorting: true,
+ sortAscLabel: "Oldest first",
+ sortDescLabel: "Newest first",
+ }),
+ columnHelper.accessor("created_at", {
+ header: "Created",
+ cell: ({ getValue }) => {
+ return (
+
+ {getFullDate({ date: getValue() })}
+
+ )
+ },
+ enableSorting: true,
+ sortAscLabel: "Oldest first",
+ sortDescLabel: "Newest first",
+ }),
+ ]
+ }, [])
+}
+
+interface CustomItemFormProps {
+ orderId: string
+ currencyCode: string
+}
+
+const CustomItemForm = ({ orderId, currencyCode }: CustomItemFormProps) => {
+ const { setIsOpen } = useStackedModal()
+ const { mutateAsync: addItems } = useDraftOrderAddItems(orderId)
+
+ const form = useForm>({
+ defaultValues: {
+ title: "",
+ quantity: 1,
+ unit_price: "",
+ },
+ resolver: zodResolver(customItemSchema),
+ })
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await addItems(
+ {
+ items: [
+ {
+ title: data.title,
+ quantity: data.quantity,
+ unit_price: convertNumber(data.unit_price),
+ },
+ ],
+ },
+ {
+ onSuccess: () => {
+ setIsOpen(STACKED_MODAL_ID, false)
+ },
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+ )
+}
+
+const customItemSchema = z.object({
+ title: z.string().min(1),
+ quantity: z.number(),
+ unit_price: z.union([z.number(), z.string()]),
+})
+
+export default Items
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@metadata/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@metadata/page.tsx
new file mode 100644
index 0000000000..696b255d84
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@metadata/page.tsx
@@ -0,0 +1,420 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import {
+ ArrowDownMini,
+ ArrowUpMini,
+ EllipsisVertical,
+ Trash,
+} from "@medusajs/icons"
+import {
+ Button,
+ DropdownMenu,
+ Heading,
+ IconButton,
+ Skeleton,
+ clx,
+ toast,
+} from "@medusajs/ui"
+import { ComponentPropsWithoutRef, forwardRef } from "react"
+import { useFieldArray, useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { useParams } from "react-router-dom"
+import { ConditionalTooltip } from "../../../../components/common/conditional-tooltip"
+import { Form } from "../../../../components/common/form"
+import { InlineTip } from "../../../../components/common/inline-tip"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import { useUpdateDraftOrder } from "../../../../hooks/api/draft-orders"
+import { useOrder } from "../../../../hooks/api/orders"
+
+const MetadataFieldSchema = z.object({
+ key: z.string(),
+ disabled: z.boolean().optional(),
+ value: z.any(),
+})
+
+const MetadataSchema = z.object({
+ metadata: z.array(MetadataFieldSchema),
+})
+
+const Metadata = () => {
+ const { id } = useParams()
+
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields: "metadata",
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ const isReady = !isPending && !!order
+
+ return (
+
+
+
+ Metadata
+
+
+ Add metadata to the draft order.
+
+
+ {!isReady ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
+const METADATA_KEY_LABEL_ID = "metadata-form-key-label"
+const METADATA_VALUE_LABEL_ID = "metadata-form-value-label"
+
+interface MetadataFormProps {
+ orderId: string
+ metadata?: Record | null
+}
+
+const MetadataForm = ({ orderId, metadata }: MetadataFormProps) => {
+ const { handleSuccess } = useRouteModal()
+
+ const hasUneditableRows = getHasUneditableRows(metadata)
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(orderId)
+
+ const form = useForm>({
+ defaultValues: {
+ metadata: getDefaultValues(metadata),
+ },
+ resolver: zodResolver(MetadataSchema),
+ })
+
+ const handleSubmit = form.handleSubmit(async (data) => {
+ const parsedData = parseValues(data)
+
+ await mutateAsync(
+ {
+ metadata: parsedData,
+ },
+ {
+ onSuccess: () => {
+ toast.success("Metadata updated")
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ const { fields, insert, remove } = useFieldArray({
+ control: form.control,
+ name: "metadata",
+ })
+
+ function deleteRow(index: number) {
+ remove(index)
+
+ // If the last row is deleted, add a new blank row
+ if (fields.length === 1) {
+ insert(0, {
+ key: "",
+ value: "",
+ disabled: false,
+ })
+ }
+ }
+
+ function insertRow(index: number, position: "above" | "below") {
+ insert(index + (position === "above" ? 0 : 1), {
+ key: "",
+ value: "",
+ disabled: false,
+ })
+ }
+
+ return (
+
+
+
+
+
+ {fields.map((field, index) => {
+ const isDisabled = field.disabled || false
+ let placeholder = "-"
+
+ if (typeof field.value === "object") {
+ placeholder = "{ ... }"
+ }
+
+ if (Array.isArray(field.value)) {
+ placeholder = "[ ... ]"
+ }
+
+ return (
+
+
+
+
{
+ return (
+
+
+
+
+
+ )
+ }}
+ />
+ {
+ return (
+
+
+
+
+
+ )
+ }}
+ />
+
+
+
+
+
+
+
+
+ insertRow(index, "above")}
+ >
+
+ Insert row above
+
+ insertRow(index, "below")}
+ >
+
+ Insert row below
+
+
+ deleteRow(index)}
+ >
+
+ Delete row
+
+
+
+
+
+ )
+ })}
+
+ {hasUneditableRows && (
+
+ This object contains non-primitive metadata, such as arrays or
+ objects, that can't be edited here. To edit the disabled rows, use
+ the API directly.
+
+ )}
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+const GridInput = forwardRef<
+ HTMLInputElement,
+ ComponentPropsWithoutRef<"input">
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+GridInput.displayName = "MetadataForm.GridInput"
+
+const PlaceholderInner = () => {
+ return (
+
+ )
+}
+
+const EDITABLE_TYPES = ["string", "number", "boolean"]
+
+function getDefaultValues(
+ metadata?: Record | null
+): z.infer[] {
+ if (!metadata || !Object.keys(metadata).length) {
+ return [
+ {
+ key: "",
+ value: "",
+ disabled: false,
+ },
+ ]
+ }
+
+ return Object.entries(metadata).map(([key, value]) => {
+ if (!EDITABLE_TYPES.includes(typeof value)) {
+ return {
+ key,
+ value: value,
+ disabled: true,
+ }
+ }
+
+ let stringValue = value
+
+ if (typeof value !== "string") {
+ stringValue = JSON.stringify(value)
+ }
+
+ return {
+ key,
+ value: stringValue,
+ original_key: key,
+ }
+ })
+}
+
+function parseValues(
+ values: z.infer
+): Record | null {
+ const metadata = values.metadata
+
+ const isEmpty =
+ !metadata.length ||
+ (metadata.length === 1 && !metadata[0].key && !metadata[0].value)
+
+ if (isEmpty) {
+ return null
+ }
+
+ const update: Record = {}
+
+ metadata.forEach((field) => {
+ let key = field.key
+ let value = field.value
+ const disabled = field.disabled
+
+ if (!key || !value) {
+ return
+ }
+
+ if (disabled) {
+ update[key] = value
+ return
+ }
+
+ key = key.trim()
+ value = value.trim()
+
+ // We try to cast the value to a boolean or number if possible
+ if (value === "true") {
+ update[key] = true
+ } else if (value === "false") {
+ update[key] = false
+ } else {
+ const parsedNumber = parseFloat(value)
+ if (!isNaN(parsedNumber)) {
+ update[key] = parsedNumber
+ } else {
+ update[key] = value
+ }
+ }
+ })
+
+ return update
+}
+
+function getHasUneditableRows(metadata?: Record | null) {
+ if (!metadata) {
+ return false
+ }
+
+ return Object.values(metadata).some(
+ (value) => !EDITABLE_TYPES.includes(typeof value)
+ )
+}
+
+export default Metadata
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@promotions/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@promotions/page.tsx
new file mode 100644
index 0000000000..9c20005d99
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@promotions/page.tsx
@@ -0,0 +1,369 @@
+import { XMark } from "@medusajs/icons"
+import { HttpTypes } from "@medusajs/types"
+import {
+ Button,
+ clx,
+ Divider,
+ Heading,
+ Hint,
+ IconButton,
+ Label,
+ Text,
+ toast,
+} from "@medusajs/ui"
+import { useState } from "react"
+import { useParams } from "react-router-dom"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { Combobox } from "../../../../components/inputs/combobox"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import {
+ useDraftOrderAddPromotions,
+ useDraftOrderConfirmEdit,
+ useDraftOrderRemovePromotions,
+} from "../../../../hooks/api/draft-orders"
+import {
+ useOrderEditRequest,
+ useOrderPreview,
+} from "../../../../hooks/api/orders"
+import { usePromotions } from "../../../../hooks/api/promotions"
+import { useComboboxData } from "../../../../hooks/common/use-combobox-data"
+import { useCancelOrderEdit } from "../../../../hooks/order-edits/use-cancel-order-edit"
+import { useInitiateOrderEdit } from "../../../../hooks/order-edits/use-initiate-order-edit"
+import { getLocaleAmount } from "../../../../lib/data/currencies"
+import { sdk } from "../../../../lib/queries/sdk"
+
+const Promotions = () => {
+ const { id } = useParams()
+
+ const {
+ order: preview,
+ isError: isPreviewError,
+ error: previewError,
+ } = useOrderPreview(id!, undefined)
+
+ useInitiateOrderEdit({ preview })
+
+ const { onCancel } = useCancelOrderEdit({ preview })
+
+ if (isPreviewError) {
+ throw previewError
+ }
+
+ const isReady = !!preview
+
+ return (
+
+
+
+ Edit Promotions
+
+
+ {isReady && }
+
+ )
+}
+
+interface PromotionFormProps {
+ preview: HttpTypes.AdminOrderPreview
+}
+
+const PromotionForm = ({ preview }: PromotionFormProps) => {
+ const { items, shipping_methods } = preview
+
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [comboboxValue, setComboboxValue] = useState("")
+
+ const { handleSuccess } = useRouteModal()
+
+ const { mutateAsync: addPromotions, isPending: isAddingPromotions } =
+ useDraftOrderAddPromotions(preview.id)
+
+ const promoCodes = getPromotionCodes(items, shipping_methods)
+
+ const { promotions, isPending, isError, error } = usePromotions(
+ {
+ code: promoCodes,
+ },
+ {
+ enabled: !!promoCodes.length,
+ }
+ )
+
+ const comboboxData = useComboboxData({
+ queryKey: ["promotions", "combobox", promoCodes],
+ queryFn: async (params) => {
+ return await sdk.admin.promotion.list({
+ ...params,
+ code: {
+ $nin: promoCodes,
+ },
+ })
+ },
+ getOptions: (data) => {
+ return data.promotions.map((promotion) => ({
+ label: promotion.code!,
+ value: promotion.code!,
+ }))
+ },
+ })
+
+ const add = async (value?: string) => {
+ if (!value) {
+ return
+ }
+
+ addPromotions(
+ {
+ promo_codes: [value],
+ },
+ {
+ onError: (e) => {
+ toast.error(e.message)
+ comboboxData.onSearchValueChange("")
+ setComboboxValue("")
+ },
+ onSuccess: () => {
+ comboboxData.onSearchValueChange("")
+ setComboboxValue("")
+ },
+ }
+ )
+ }
+
+ const { mutateAsync: confirmOrderEdit } = useDraftOrderConfirmEdit(preview.id)
+ const { mutateAsync: requestOrderEdit } = useOrderEditRequest(preview.id)
+
+ const onSubmit = async () => {
+ setIsSubmitting(true)
+
+ let requestSucceeded = false
+
+ await requestOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ onSuccess: () => {
+ requestSucceeded = true
+ },
+ })
+
+ if (!requestSucceeded) {
+ setIsSubmitting(false)
+ return
+ }
+
+ await confirmOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onSettled: () => {
+ setIsSubmitting(false)
+ },
+ })
+ }
+
+ if (isError) {
+ throw error
+ }
+
+ return (
+
+
+
+
+
+
+ Apply promotions
+
+
+ Manage promotions that should be applied to the order.
+
+
+
+
+
+
+ {promotions?.map((promotion) => (
+
+ ))}
+
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+ )
+}
+
+interface PromotionItemProps {
+ promotion: HttpTypes.AdminPromotion
+ orderId: string
+ isLoading: boolean
+}
+
+const PromotionItem = ({
+ promotion,
+ orderId,
+ isLoading,
+}: PromotionItemProps) => {
+ const { mutateAsync: removePromotions, isPending } =
+ useDraftOrderRemovePromotions(orderId)
+
+ const onRemove = async () => {
+ removePromotions(
+ {
+ promo_codes: [promotion.code!],
+ },
+ {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ }
+ )
+ }
+
+ const displayValue = getDisplayValue(promotion)
+
+ return (
+
+
+
+ {promotion.code}
+
+
+ {displayValue && (
+
+
+ {displayValue}
+
+
+ ·
+
+
+ )}
+
+ {promotion.application_method?.allocation!}
+
+
+
+
+
+
+
+ )
+}
+
+function getDisplayValue(promotion: HttpTypes.AdminPromotion) {
+ const value = promotion.application_method?.value
+
+ if (!value) {
+ return null
+ }
+
+ if (promotion.application_method?.type === "fixed") {
+ const currency = promotion.application_method?.currency_code
+
+ if (!currency) {
+ return null
+ }
+
+ return getLocaleAmount(value, currency)
+ } else if (promotion.application_method?.type === "percentage") {
+ return formatPercentage(value)
+ }
+
+ return null
+}
+
+const formatter = new Intl.NumberFormat([], {
+ style: "percent",
+ minimumFractionDigits: 2,
+})
+
+const formatPercentage = (value?: number | null, isPercentageValue = false) => {
+ let val = value || 0
+
+ if (!isPercentageValue) {
+ val = val / 100
+ }
+
+ return formatter.format(val)
+}
+
+function getPromotionCodes(
+ items: HttpTypes.AdminOrderPreview["items"],
+ shippingMethods: HttpTypes.AdminOrderPreview["shipping_methods"]
+) {
+ const codes = new Set()
+
+ for (const item of items) {
+ if (item.adjustments) {
+ for (const adjustment of item.adjustments) {
+ if (adjustment.code) {
+ codes.add(adjustment.code)
+ }
+ }
+ }
+ }
+
+ for (const shippingMethod of shippingMethods) {
+ if (shippingMethod.adjustments) {
+ for (const adjustment of shippingMethod.adjustments) {
+ if (adjustment.code) {
+ codes.add(adjustment.code)
+ }
+ }
+ }
+ }
+
+ return Array.from(codes)
+}
+
+export default Promotions
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@sales-channel/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@sales-channel/page.tsx
new file mode 100644
index 0000000000..641e3670ca
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@sales-channel/page.tsx
@@ -0,0 +1,164 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, Heading, toast } from "@medusajs/ui"
+import { Control, useForm } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { Combobox } from "../../../../components/inputs/combobox"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import {
+ useDraftOrder,
+ useUpdateDraftOrder,
+} from "../../../../hooks/api/draft-orders"
+import { useComboboxData } from "../../../../hooks/common/use-combobox-data"
+import { sdk } from "../../../../lib/queries/sdk"
+
+const SalesChannel = () => {
+ const { id } = useParams()
+
+ const { draft_order, isPending, isError, error } = useDraftOrder(
+ id!,
+ {
+ fields: "+sales_channel_id",
+ },
+ {
+ enabled: !!id,
+ }
+ )
+
+ if (isError) {
+ throw error
+ }
+
+ const ISrEADY = !!draft_order && !isPending
+
+ return (
+
+
+
+ Edit Sales Channel
+
+
+
+ Update which sales channel the draft order is associated with
+
+
+
+ {ISrEADY && }
+
+ )
+}
+
+interface SalesChannelFormProps {
+ order: HttpTypes.AdminOrder
+}
+
+const SalesChannelForm = ({ order }: SalesChannelFormProps) => {
+ const form = useForm>({
+ defaultValues: {
+ sales_channel_id: order.sales_channel_id || "",
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(order.id)
+ const { handleSuccess } = useRouteModal()
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await mutateAsync(
+ {
+ sales_channel_id: data.sales_channel_id,
+ },
+ {
+ onSuccess: () => {
+ toast.success("Sales channel updated")
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+interface SalesChannelFieldProps {
+ order: HttpTypes.AdminOrder
+ control: Control>
+}
+
+const SalesChannelField = ({ control, order }: SalesChannelFieldProps) => {
+ const salesChannels = useComboboxData({
+ queryFn: async (params) => {
+ return await sdk.admin.salesChannel.list(params)
+ },
+ queryKey: ["sales-channels"],
+ getOptions: (data) => {
+ return data.sales_channels.map((salesChannel) => ({
+ label: salesChannel.name,
+ value: salesChannel.id,
+ }))
+ },
+ defaultValue: order.sales_channel_id || undefined,
+ })
+
+ return (
+ {
+ return (
+
+ Sales Channel
+
+
+
+
+
+ )
+ }}
+ />
+ )
+}
+
+const schema = z.object({
+ sales_channel_id: z.string().min(1),
+})
+
+export default SalesChannel
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping-address/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping-address/page.tsx
new file mode 100644
index 0000000000..7d4572e103
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping-address/page.tsx
@@ -0,0 +1,259 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, Heading, Input, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { CountrySelect } from "../../../../components/inputs/country-select"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import { useUpdateDraftOrder } from "../../../../hooks/api/draft-orders"
+import { useOrder } from "../../../../hooks/api/orders"
+import { addressSchema } from "../../../../lib/schemas/address"
+
+const ShippingAddress = () => {
+ const { id } = useParams()
+
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields: "+shipping_address",
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ const isReady = !isPending && !!order
+
+ return (
+
+
+
+ Edit Shipping Address
+
+
+
+ Edit the shipping address for the draft order
+
+
+
+ {isReady && }
+
+ )
+}
+
+interface ShippingAddressFormProps {
+ order: HttpTypes.AdminOrder
+}
+
+const ShippingAddressForm = ({ order }: ShippingAddressFormProps) => {
+ const form = useForm>({
+ defaultValues: {
+ first_name: order.shipping_address?.first_name ?? "",
+ last_name: order.shipping_address?.last_name ?? "",
+ company: order.shipping_address?.company ?? "",
+ address_1: order.shipping_address?.address_1 ?? "",
+ address_2: order.shipping_address?.address_2 ?? "",
+ city: order.shipping_address?.city ?? "",
+ province: order.shipping_address?.province ?? "",
+ country_code: order.shipping_address?.country_code ?? "",
+ postal_code: order.shipping_address?.postal_code ?? "",
+ phone: order.shipping_address?.phone ?? "",
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(order.id)
+ const { handleSuccess } = useRouteModal()
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await mutateAsync(
+ {
+ shipping_address: {
+ first_name: data.first_name,
+ last_name: data.last_name,
+ company: data.company,
+ address_1: data.address_1,
+ address_2: data.address_2,
+ city: data.city,
+ province: data.province,
+ country_code: data.country_code,
+ postal_code: data.postal_code,
+ phone: data.phone,
+ },
+ },
+ {
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+const schema = addressSchema
+
+export default ShippingAddress
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping/page.tsx
new file mode 100644
index 0000000000..5f05e8bd0e
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@shipping/page.tsx
@@ -0,0 +1,1044 @@
+import { HttpTypes } from "@medusajs/types"
+import {
+ Badge,
+ Button,
+ CurrencyInput,
+ Divider,
+ Heading,
+ IconButton,
+ Text,
+ toast,
+ Tooltip,
+} from "@medusajs/ui"
+import { useCallback, useEffect, useMemo, useState } from "react"
+import { Control, useForm, UseFormSetValue, useWatch } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+
+import { zodResolver } from "@hookform/resolvers/zod"
+import {
+ Buildings,
+ Channels,
+ Shopping,
+ Trash,
+ TriangleRightMini,
+ TruckFast,
+} from "@medusajs/icons"
+import { isEqual } from "lodash"
+import { Accordion } from "radix-ui"
+import { ConditionalTooltip } from "../../../../components/common/conditional-tooltip"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { Thumbnail } from "../../../../components/common/thumbnail"
+import { Combobox } from "../../../../components/inputs/combobox"
+import {
+ RouteFocusModal,
+ StackedFocusModal,
+ useRouteModal,
+ useStackedModal,
+} from "../../../../components/modals"
+import {
+ useDraftOrderAddShippingMethod,
+ useDraftOrderConfirmEdit,
+ useDraftOrderRemoveActionShippingMethod,
+ useDraftOrderRemoveShippingMethod,
+ useDraftOrderRequestEdit,
+ useDraftOrderUpdateShippingMethod,
+} from "../../../../hooks/api/draft-orders"
+import { useOrder, useOrderPreview } from "../../../../hooks/api/orders"
+import { useShippingOptions } from "../../../../hooks/api/shipping-options"
+import { useComboboxData } from "../../../../hooks/common/use-combobox-data"
+import { useCancelOrderEdit } from "../../../../hooks/order-edits/use-cancel-order-edit"
+import { useInitiateOrderEdit } from "../../../../hooks/order-edits/use-initiate-order-edit"
+import { getNativeSymbol } from "../../../../lib/data/currencies"
+import { sdk } from "../../../../lib/queries/sdk"
+import { convertNumber } from "../../../../lib/utils/number-utils"
+import {
+ getItemsWithShippingProfile,
+ getUniqueShippingProfiles,
+} from "../../../../lib/utils/order-utils"
+import { pluralize } from "../../../../lib/utils/string-utils"
+import { ActionMenu } from "../../../../components/common/action-menu"
+
+const STACKED_FOCUS_MODAL_ID = "shipping-form"
+
+const Shipping = () => {
+ const { id } = useParams()
+
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields:
+ "+items.*,+items.variant.*,+items.variant.product.*,+items.variant.product.shipping_profile.*,+currency_code",
+ })
+
+ const {
+ order: preview,
+ isPending: isPreviewPending,
+ isError: isPreviewError,
+ error: previewError,
+ } = useOrderPreview(id!)
+
+ useInitiateOrderEdit({ preview })
+ const { onCancel } = useCancelOrderEdit({ preview })
+
+ if (isError) {
+ throw error
+ }
+
+ if (isPreviewError) {
+ throw previewError
+ }
+
+ const orderHasItems = (order?.items?.length || 0) > 0
+ const isReady = preview && !isPreviewPending && order && !isPending
+
+ return (
+
+ {!orderHasItems ? (
+
+
+
+
+
+
+ Shipping
+
+
+
+ This draft order currently has no items. Add items to the
+ order before adding shipping.
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ ) : isReady ? (
+
+ ) : (
+
+
+ Edit Shipping
+
+
+
+ Loading data for the draft order, please wait...
+
+
+
+ )}
+
+ )
+}
+
+interface ShippingFormProps {
+ preview: HttpTypes.AdminOrderPreview
+ order: HttpTypes.AdminOrder
+}
+
+interface ShippingFormData {
+ shippingProfileId: string
+ shippingOption?: HttpTypes.AdminShippingOption
+ shippingMethod?: HttpTypes.AdminOrderShippingMethod
+}
+
+const ShippingForm = ({ preview, order }: ShippingFormProps) => {
+ const { setIsOpen } = useStackedModal()
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [data, setData] = useState(null)
+
+ const appliedShippingOptionIds = preview.shipping_methods
+ ?.map((method) => method.shipping_option_id)
+ .filter(Boolean) as string[]
+
+ const { shipping_options } = useShippingOptions(
+ {
+ id: appliedShippingOptionIds,
+ fields:
+ "+service_zone.*,+service_zone.fulfillment_set.*,+service_zone.fulfillment_set.location.*",
+ },
+ {
+ enabled: appliedShippingOptionIds.length > 0,
+ }
+ )
+
+ const uniqueShippingProfiles = useMemo(() => {
+ const profiles = new Map()
+
+ getUniqueShippingProfiles(order.items).forEach((profile) => {
+ profiles.set(profile.id, profile)
+ })
+
+ shipping_options?.forEach((option) => {
+ profiles.set(option.shipping_profile_id, option.shipping_profile)
+ })
+
+ return Array.from(profiles.values())
+ }, [order.items, shipping_options])
+
+ const { handleSuccess } = useRouteModal()
+
+ const { mutateAsync: confirmOrderEdit } = useDraftOrderConfirmEdit(preview.id)
+ const { mutateAsync: requestOrderEdit } = useDraftOrderRequestEdit(preview.id)
+
+ const { mutateAsync: removeShippingMethod } =
+ useDraftOrderRemoveShippingMethod(preview.id)
+
+ const { mutateAsync: removeActionShippingMethod } =
+ useDraftOrderRemoveActionShippingMethod(preview.id)
+
+ const onSubmit = async () => {
+ setIsSubmitting(true)
+
+ let requestSucceeded = false
+
+ await requestOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(`Failed to request order edit: ${e.message}`)
+ },
+ onSuccess: () => {
+ requestSucceeded = true
+ },
+ })
+
+ if (!requestSucceeded) {
+ setIsSubmitting(false)
+ return
+ }
+
+ await confirmOrderEdit(undefined, {
+ onError: (e) => {
+ toast.error(`Failed to confirm order edit: ${e.message}`)
+ },
+ onSuccess: () => {
+ handleSuccess()
+ },
+ onSettled: () => {
+ setIsSubmitting(false)
+ },
+ })
+ }
+
+ const onKeydown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ if (data || isSubmitting) {
+ // Don't do anything if the StackedFocusModal is open or the form is submitting
+ return
+ }
+
+ onSubmit()
+ }
+ },
+ [data, isSubmitting, onSubmit]
+ )
+
+ useEffect(() => {
+ document.addEventListener("keydown", onKeydown)
+
+ return () => {
+ document.removeEventListener("keydown", onKeydown)
+ }
+ }, [onKeydown])
+
+ return (
+
+
+
+
+
+
+
+ Shipping
+
+
+
+ Choose which shipping method(s) to use for the items in the
+ order.
+
+
+
+
+
+
+
+
+ Shipping profile
+
+
+ Action
+
+
+
+ {uniqueShippingProfiles.map((profile) => {
+ const items = getItemsWithShippingProfile(
+ profile.id,
+ order.items
+ )
+
+ const hasItems = items.length > 0
+
+ const shippingOption = shipping_options?.find(
+ (option) => option.shipping_profile_id === profile.id
+ )
+
+ const shippingMethod = preview.shipping_methods.find(
+ (method) =>
+ method.shipping_option_id === shippingOption?.id
+ )
+
+ const addShippingMethodAction =
+ shippingMethod?.actions?.find(
+ (action) => action.action === "SHIPPING_ADD"
+ )
+
+ return (
+
+
+
+
+
+
+
+
+ {!shippingOption ? (
+
+
+
+
+ {profile.name}
+
+
+ {items.length}{" "}
+ {pluralize(items.length, "items", "item")}
+
+
+
+ ) : (
+
+
+ {items.map((item) => (
+ {`${item.quantity}x ${item.variant?.product?.title} (${item.variant?.title})`}
+ ))}
+
+ }
+ >
+
+
+
+ {items.reduce(
+ (acc, item) => acc + item.quantity,
+ 0
+ )}
+ x{" "}
+ {pluralize(items.length, "items", "item")}
+
+
+
+
+
+
+
+ {
+ shippingOption.service_zone
+ ?.fulfillment_set?.location?.name
+ }
+
+
+
+
+
+
+
+ {shippingOption.name}
+
+
+
+
+ )}
+
+
+ {shippingOption ? (
+
,
+ onClick: () => {
+ setIsOpen(
+ STACKED_FOCUS_MODAL_ID,
+ true
+ )
+ setData({
+ shippingProfileId: profile.id,
+ shippingOption,
+ shippingMethod,
+ })
+ },
+ }
+ : undefined,
+ {
+ label: "Remove shipping option",
+ icon:
,
+ onClick: () => {
+ if (shippingMethod) {
+ if (addShippingMethodAction) {
+ removeActionShippingMethod(
+ addShippingMethodAction.id
+ )
+ } else {
+ removeShippingMethod(
+ shippingMethod.id
+ )
+ }
+ }
+ },
+ },
+ ].filter(Boolean),
+ },
+ ]}
+ />
+ ) : (
+
+ Add shipping option
+
+ )}
+
+
+
+ {items.map((item, idx) => {
+ return (
+
+
+
+
+
+
+ {item.quantity}x
+
+
+
+
+
+ {item.variant?.product?.title} (
+ {item.variant?.title})
+
+
+ {item.variant?.options
+ ?.map((option) => option.value)
+ .join(" · ")}
+
+
+
+
+ {idx !== items.length - 1 && (
+
+ )}
+
+ )
+ })}
+
+
+ )
+ })}
+
+
+
+
+
+ {
+ if (!open) {
+ setData(null)
+ }
+
+ return open
+ }}
+ >
+ {data && (
+
+ )}
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+ )
+}
+
+interface StackedModalTriggerProps {
+ shippingProfileId: string
+ shippingOption?: HttpTypes.AdminShippingOption
+ shippingMethod?: HttpTypes.AdminOrderShippingMethod
+ setData: (data: ShippingFormData | null) => void
+ children: React.ReactNode
+}
+
+const StackedModalTrigger = ({
+ shippingProfileId,
+ shippingOption,
+ shippingMethod,
+ setData,
+ children,
+}: StackedModalTriggerProps) => {
+ const { setIsOpen, getIsOpen } = useStackedModal()
+
+ const isOpen = getIsOpen(STACKED_FOCUS_MODAL_ID)
+
+ const onToggle = () => {
+ if (isOpen) {
+ setIsOpen(STACKED_FOCUS_MODAL_ID, false)
+ setData(null)
+ } else {
+ setIsOpen(STACKED_FOCUS_MODAL_ID, true)
+ setData({
+ shippingProfileId,
+ shippingOption,
+ shippingMethod,
+ })
+ }
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+interface ShippingProfileFormProps {
+ data: ShippingFormData
+ order: HttpTypes.AdminOrder
+ preview: HttpTypes.AdminOrderPreview
+}
+
+const ShippingProfileForm = ({
+ data,
+ order,
+ preview,
+}: ShippingProfileFormProps) => {
+ const { setIsOpen } = useStackedModal()
+
+ const form = useForm>({
+ resolver: zodResolver(shippingMethodSchema),
+ defaultValues: {
+ location_id:
+ data.shippingOption?.service_zone?.fulfillment_set?.location?.id,
+ shipping_option_id: data.shippingOption?.id,
+ custom_amount: data.shippingMethod?.amount,
+ },
+ })
+
+ const { mutateAsync: addShippingMethod, isPending } =
+ useDraftOrderAddShippingMethod(order.id)
+ const {
+ mutateAsync: updateShippingMethod,
+ isPending: isUpdatingShippingMethod,
+ } = useDraftOrderUpdateShippingMethod(order.id)
+
+ const onSubmit = form.handleSubmit(async (values) => {
+ if (isEqual(values, form.formState.defaultValues)) {
+ setIsOpen(STACKED_FOCUS_MODAL_ID, false)
+ return
+ }
+
+ if (data.shippingMethod) {
+ await updateShippingMethod(
+ {
+ method_id: data.shippingMethod.id,
+ shipping_option_id: values.shipping_option_id,
+ custom_amount: values.custom_amount
+ ? convertNumber(values.custom_amount)
+ : undefined,
+ },
+ {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ onSuccess: () => {
+ setIsOpen(STACKED_FOCUS_MODAL_ID, false)
+ },
+ }
+ )
+
+ return
+ }
+
+ await addShippingMethod(
+ {
+ shipping_option_id: values.shipping_option_id,
+ custom_amount: values.custom_amount
+ ? convertNumber(values.custom_amount)
+ : undefined,
+ },
+ {
+ onError: (e) => {
+ toast.error(e.message)
+ },
+ onSuccess: () => {
+ setIsOpen(STACKED_FOCUS_MODAL_ID, false)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+ )
+}
+
+const shippingMethodSchema = z.object({
+ location_id: z.string(),
+ shipping_option_id: z.string(),
+ custom_amount: z.union([z.number(), z.string()]).optional(),
+})
+
+interface ItemsPreviewProps {
+ order: HttpTypes.AdminOrder
+ shippingProfileId: string
+}
+
+const ItemsPreview = ({ order, shippingProfileId }: ItemsPreviewProps) => {
+ const matches = order.items.filter(
+ (item) => item.variant?.product?.shipping_profile?.id === shippingProfileId
+ )
+
+ return (
+
+
+
+
+ Items to ship
+
+
+ Items with the selected shipping profile.
+
+
+
+
+
+
+
+ Item
+
+
+
+
+ Quantity
+
+
+
+
+ {matches.length > 0 ? (
+ matches?.map((item) => (
+
+
+
+
+
+
+ {item.product_title}
+
+
+ ({item.variant_title})
+
+
+
+ {item.variant_sku}
+
+
+
+
+ {item.quantity}x
+
+
+ ))
+ ) : (
+
+
+ No items found
+
+
+ No items found for "{query}".
+
+
+ )}
+
+
+
+ )
+}
+
+type LocationFieldProps = {
+ control: Control>
+ setValue: UseFormSetValue>
+}
+
+const LocationField = ({ control, setValue }: LocationFieldProps) => {
+ const locations = useComboboxData({
+ queryKey: ["locations"],
+ queryFn: async (params) => {
+ return await sdk.admin.stockLocation.list(params)
+ },
+ getOptions: (data) => {
+ return data.stock_locations.map((location) => ({
+ label: location.name,
+ value: location.id,
+ }))
+ },
+ })
+
+ return (
+ {
+ return (
+
+
+
+
Location
+
+ Choose where you want to ship the items from.
+
+
+
+ {
+ setValue("shipping_option_id", "", {
+ shouldDirty: true,
+ shouldTouch: true,
+ })
+ onChange(value)
+ }}
+ {...field}
+ />
+
+
+
+ )
+ }}
+ />
+ )
+}
+
+type ShippingOptionFieldProps = {
+ shippingProfileId: string
+ preview: HttpTypes.AdminOrderPreview
+ control: Control>
+}
+
+const ShippingOptionField = ({
+ shippingProfileId,
+ preview,
+ control,
+}: ShippingOptionFieldProps) => {
+ const locationId = useWatch({ control, name: "location_id" })
+
+ const shippingOptions = useComboboxData({
+ queryKey: ["shipping_options", locationId, shippingProfileId],
+ queryFn: async (params) => {
+ return await sdk.admin.shippingOption.list({
+ ...params,
+ stock_location_id: locationId,
+ shipping_profile_id: shippingProfileId,
+ })
+ },
+ getOptions: (data) => {
+ return data.shipping_options
+ .map((option) => {
+ // The API does not support filtering out return shipping options,
+ // so we need to do it client side for now.
+ if (
+ option.rules?.find(
+ (r) => r.attribute === "is_return" && r.value === "true"
+ )
+ ) {
+ return undefined
+ }
+
+ return {
+ label: option.name,
+ value: option.id,
+ }
+ })
+ .filter(Boolean) as { label: string; value: string }[]
+ },
+ enabled: !!locationId && !!shippingProfileId,
+ defaultValue: preview.shipping_methods[0]?.shipping_option_id || undefined,
+ })
+
+ const tooltipContent =
+ !locationId && !shippingProfileId
+ ? "Choose a location and shipping profile first."
+ : !locationId
+ ? "Choose a location first."
+ : "Choose a shipping profile first."
+
+ return (
+ {
+ return (
+
+
+
+
Shipping option
+ Choose the shipping option to use.
+
+
+
+
+
+
+
+
+
+
+ )
+ }}
+ />
+ )
+}
+
+interface CustomAmountFieldProps {
+ control: Control>
+ currencyCode: string
+}
+
+const CustomAmountField = ({
+ control,
+ currencyCode,
+}: CustomAmountFieldProps) => {
+ return (
+ {
+ return (
+
+
+
Custom amount
+
+ Set a custom amount for the shipping option.
+
+
+
+ onChange(value)}
+ symbol={getNativeSymbol(currencyCode)}
+ code={currencyCode}
+ />
+
+
+ )
+ }}
+ />
+ )
+}
+
+export default Shipping
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@transfer-ownership/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@transfer-ownership/page.tsx
new file mode 100644
index 0000000000..f6d37e0a93
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/@transfer-ownership/page.tsx
@@ -0,0 +1,480 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { HttpTypes } from "@medusajs/types"
+import { Button, Heading, Hint, Label, Select, toast } from "@medusajs/ui"
+import { Control, useForm } from "react-hook-form"
+import { useParams } from "react-router-dom"
+import { z } from "zod"
+import { Form } from "../../../../components/common/form"
+import { KeyboundForm } from "../../../../components/common/keybound-form"
+import { Combobox } from "../../../../components/inputs/combobox"
+import { RouteDrawer, useRouteModal } from "../../../../components/modals"
+import {
+ useDraftOrder,
+ useUpdateDraftOrder,
+} from "../../../../hooks/api/draft-orders"
+import { useComboboxData } from "../../../../hooks/common/use-combobox-data"
+import { sdk } from "../../../../lib/queries/sdk"
+
+const TransferOwnership = () => {
+ const { id } = useParams()
+
+ const { draft_order, isPending, isError, error } = useDraftOrder(id!, {
+ fields: "id,customer_id,customer.*",
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ const isReady = !isPending && !!draft_order
+
+ return (
+
+
+
+ Transfer Ownership
+
+
+
+ Transfer the ownership of this draft order to a new customer
+
+
+
+ {isReady && }
+
+ )
+}
+
+interface TransferOwnershipFormProps {
+ order: HttpTypes.AdminDraftOrder
+}
+
+const TransferOwnershipForm = ({ order }: TransferOwnershipFormProps) => {
+ const form = useForm>({
+ defaultValues: {
+ customer_id: order.customer_id || "",
+ },
+ resolver: zodResolver(schema),
+ })
+
+ const { mutateAsync, isPending } = useUpdateDraftOrder(order.id)
+ const { handleSuccess } = useRouteModal()
+
+ const name = [order.customer?.first_name, order.customer?.last_name]
+ .filter(Boolean)
+ .join(" ")
+
+ const currentCustomer = order.customer
+ ? {
+ label: name
+ ? `${name} (${order.customer.email})`
+ : order.customer.email,
+ value: order.customer.id,
+ }
+ : null
+
+ const onSubmit = form.handleSubmit(async (data) => {
+ await mutateAsync(
+ { customer_id: data.customer_id },
+ {
+ onSuccess: () => {
+ toast.success("Customer updated")
+ handleSuccess()
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ }
+ )
+ })
+
+ return (
+
+
+
+
+
+
+ {currentCustomer && (
+
+
+
+ Current owner
+
+
+ The customer that is currently associated with this draft
+ order.
+
+
+
+
+
+
+
+
+ {currentCustomer.label}
+
+
+
+
+ )}
+
+
+
+
+
+
+ Cancel
+
+
+
+ Save
+
+
+
+
+
+ )
+}
+
+interface CustomerFieldProps {
+ currentCustomerId: string | null
+ control: Control>
+}
+
+const CustomerField = ({ control, currentCustomerId }: CustomerFieldProps) => {
+ const customers = useComboboxData({
+ queryFn: async (params) => {
+ return await sdk.admin.customer.list({
+ ...params,
+ id: currentCustomerId ? { $nin: [currentCustomerId] } : undefined,
+ })
+ },
+ queryKey: ["customers"],
+ getOptions: (data) => {
+ return data.customers.map((customer) => {
+ const name = [customer.first_name, customer.last_name]
+ .filter(Boolean)
+ .join(" ")
+
+ return {
+ label: name ? `${name} (${customer.email})` : customer.email,
+ value: customer.id,
+ }
+ })
+ },
+ })
+
+ return (
+ (
+
+
+
New customer
+ The customer to transfer this draft order to.
+
+
+
+
+
+
+ )}
+ />
+ )
+}
+
+const Illustration = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const schema = z.object({
+ customer_id: z.string().min(1),
+})
+
+export default TransferOwnership
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/page.tsx
new file mode 100644
index 0000000000..74c26e739c
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/[id]/page.tsx
@@ -0,0 +1,109 @@
+import { Fragment } from "react"
+import {
+ LoaderFunctionArgs,
+ Outlet,
+ UIMatch,
+ useParams,
+} from "react-router-dom"
+
+import { HttpTypes } from "@medusajs/types"
+import { PageSkeleton } from "../../../components/common/page-skeleton"
+import { ActiveOrderChange } from "../../../components/draft-orders/active-order-changes"
+import { ActivitySection } from "../../../components/draft-orders/activity-section"
+import { CustomerSection } from "../../../components/draft-orders/customer-section"
+import { GeneralSection } from "../../../components/draft-orders/general-section"
+import { JsonViewSection } from "../../../components/draft-orders/json-view-section"
+import { MetadataSection } from "../../../components/draft-orders/metadata-section"
+import { ShippingSection } from "../../../components/draft-orders/shipping-section"
+import { SummarySection } from "../../../components/draft-orders/summary-section"
+import { useOrder, useOrderChanges } from "../../../hooks/api/orders"
+import { sdk } from "../../../lib/queries/sdk"
+
+type AdminDraftOrderSummary = HttpTypes.AdminOrder & {
+ promotions: HttpTypes.AdminPromotion[]
+}
+
+export async function loader({ params }: LoaderFunctionArgs) {
+ const { id } = params
+
+ const data = await sdk.admin.order.retrieve(id!, {
+ fields: "id,display_id",
+ })
+
+ return data
+}
+
+export const handle = {
+ breadcrumb: (match: UIMatch) =>
+ `#${match.data.order.display_id}`,
+}
+
+const ID = () => {
+ const { id } = useParams()
+
+ const { order, isPending, isError, error } = useOrder(id!, {
+ fields:
+ "+customer.*,+sales_channel.*,+region.*,+email,+items.*,+items.variant.*,+items.variant.product.*,+items.variant.product.shipping_profile.*,+items.variant.options.*,+currency_code,+promotions.*",
+ })
+
+ const {
+ order_changes,
+ isPending: isOrderChangesPending,
+ isError: isOrderChangesError,
+ error: orderChangesError,
+ } = useOrderChanges(id!, {
+ change_type: ["edit", "transfer", "update_order"],
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ if (isOrderChangesError) {
+ throw orderChangesError
+ }
+
+ const isReady =
+ !isPending && !isOrderChangesPending && !!order && !!order_changes
+
+ if (!isReady) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+export default ID
\ No newline at end of file
diff --git a/packages/plugins/draft-order/src/admin/routes/draft-orders/page.tsx b/packages/plugins/draft-order/src/admin/routes/draft-orders/page.tsx
new file mode 100644
index 0000000000..d7284a7bf1
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/routes/draft-orders/page.tsx
@@ -0,0 +1,245 @@
+import { defineRouteConfig } from "@medusajs/admin-sdk"
+import type { HttpTypes } from "@medusajs/types"
+import {
+ Container,
+ createDataTableColumnHelper,
+ DataTableFilter,
+ Tooltip,
+} from "@medusajs/ui"
+import { keepPreviousData } from "@tanstack/react-query"
+import { Fragment, useMemo } from "react"
+import { Outlet } from "react-router-dom"
+
+import { DataTable } from "../../components/common/data-table"
+import { useCustomers } from "../../hooks/api/customers"
+import { useDraftOrders } from "../../hooks/api/draft-orders"
+import { useRegions } from "../../hooks/api/regions"
+import { useSalesChannels } from "../../hooks/api/sales-channels"
+import { useDataTableDateFilters } from "../../hooks/common/use-data-table-date-filters"
+import { useQueryParams } from "../../hooks/common/use-query-params"
+import { getFullDate } from "../../lib/utils/date-utils"
+
+const PAGE_SIZE = 20
+
+export const handle = {
+ breadcrumb: () => "Draft Orders",
+}
+
+const List = () => {
+ const queryParams = useDraftOrderTableQuery({
+ pageSize: PAGE_SIZE,
+ })
+
+ const { draft_orders, count, isPending, isError, error } = useDraftOrders(
+ {
+ ...queryParams,
+ order: queryParams.order ?? "-created_at",
+ fields:
+ "+customer.*,+sales_channel.*,+email,+display_id,+total,+currency_code,+shipping_total,+tax_total,+discount_total,+items.*,+items.variant.*,+items.variant.product.*,+items.variant.product.shipping_profile.*,+items.variant.options.*,+region.*",
+ },
+ {
+ placeholderData: keepPreviousData,
+ }
+ )
+
+ const columns = useColumns()
+ const filters = useFilters()
+
+ if (isError) {
+ throw error
+ }
+
+ return (
+
+
+ row.id}
+ columns={columns}
+ filters={filters}
+ isLoading={isPending}
+ pageSize={PAGE_SIZE}
+ rowCount={count}
+ heading="Draft Orders"
+ action={{
+ label: "Create",
+ to: "create",
+ }}
+ rowHref={(row) => `${row.id}`}
+ emptyState={{
+ empty: {
+ heading: "No draft orders found",
+ description: "Create a new draft order to get started.",
+ },
+ filtered: {
+ heading: "No results found",
+ description: "No draft orders match your filter criteria.",
+ },
+ }}
+ />
+
+
+
+ )
+}
+
+export const config = defineRouteConfig({
+ label: "Drafts",
+ nested: "/orders",
+})
+
+const columnHelper = createDataTableColumnHelper()
+
+const useColumns = () => {
+ return useMemo(
+ () => [
+ columnHelper.accessor("display_id", {
+ header: "Display ID",
+ cell: ({ getValue }) => {
+ return `#${getValue()}`
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("created_at", {
+ header: "Date",
+ cell: ({ getValue }) => {
+ return (
+
+ {getFullDate({ date: getValue() })}
+
+ )
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("customer_id", {
+ header: "Customer",
+ cell: ({ row }) => {
+ return row.original.customer?.email || "-"
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("sales_channel_id", {
+ header: "Sales Channel",
+ cell: ({ row }) => {
+ return row.original.sales_channel?.name || "-"
+ },
+ enableSorting: true,
+ }),
+ columnHelper.accessor("region_id", {
+ header: "Region",
+ cell: ({ row }) => {
+ return row.original.region?.name || "-"
+ },
+ enableSorting: true,
+ }),
+ ],
+ []
+ )
+}
+
+const useFilters = (): DataTableFilter[] => {
+ const dateFilterOptions = useDataTableDateFilters()
+
+ const { customers } = useCustomers(
+ {
+ limit: 1000,
+ fields: "id,email",
+ },
+ {
+ throwOnError: true,
+ }
+ )
+
+ const { sales_channels } = useSalesChannels(
+ {
+ limit: 1000,
+ fields: "id,name",
+ },
+ {
+ throwOnError: true,
+ }
+ )
+
+ const { regions } = useRegions(
+ {
+ limit: 1000,
+ fields: "id,name",
+ },
+ { throwOnError: true }
+ )
+
+ return useMemo(() => {
+ return [
+ {
+ id: "customer_id",
+ label: "Customer",
+ options:
+ customers?.map((customer) => ({
+ label: customer.email,
+ value: customer.id,
+ })) ?? [],
+ type: "select",
+ },
+ {
+ id: "sales_channel_id",
+ label: "Sales Channel",
+ options:
+ sales_channels?.map((sales_channel) => ({
+ label: sales_channel.name,
+ value: sales_channel.id,
+ })) ?? [],
+ type: "select",
+ },
+ {
+ id: "region_id",
+ label: "Region",
+ options:
+ regions?.map((region) => ({
+ label: region.name,
+ value: region.id,
+ })) ?? [],
+ type: "select",
+ },
+ ...dateFilterOptions,
+ ] satisfies DataTableFilter[]
+ }, [customers, sales_channels, regions, dateFilterOptions])
+}
+
+type UseDraftOrderTableQueryProps = {
+ prefix?: string
+ pageSize?: number
+}
+
+export const useDraftOrderTableQuery = ({
+ prefix,
+ pageSize = 20,
+}: UseDraftOrderTableQueryProps) => {
+ const queryObject = useQueryParams(
+ [
+ "offset",
+ "q",
+ "order",
+ "customer_id",
+ "region_id",
+ "created_at",
+ "updated_at",
+ ],
+ prefix
+ )
+
+ const { offset, created_at, updated_at, ...rest } = queryObject
+
+ const searchParams: HttpTypes.AdminDraftOrderListParams = {
+ limit: pageSize,
+ offset: offset ? Number(offset) : 0,
+ created_at: created_at ? JSON.parse(created_at) : undefined,
+ updated_at: updated_at ? JSON.parse(updated_at) : undefined,
+ ...rest,
+ }
+
+ return searchParams
+}
+
+export default List
diff --git a/packages/plugins/draft-order/src/admin/tsconfig.json b/packages/plugins/draft-order/src/admin/tsconfig.json
new file mode 100644
index 0000000000..9c44204a59
--- /dev/null
+++ b/packages/plugins/draft-order/src/admin/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["."]
+}
diff --git a/packages/plugins/draft-order/src/types/http/draft-orders/payloads.ts b/packages/plugins/draft-order/src/types/http/draft-orders/payloads.ts
new file mode 100644
index 0000000000..8a3120be1a
--- /dev/null
+++ b/packages/plugins/draft-order/src/types/http/draft-orders/payloads.ts
@@ -0,0 +1,26 @@
+interface AdminAddressPayload {
+ first_name: string
+ last_name: string
+ address_1: string
+ address_2?: string | null
+ city: string
+ country_code: string
+ province?: string | null
+ postal_code: string
+ phone?: string | null
+ company?: string | null
+}
+
+export interface AdminCreateDraftOrder {
+ sales_channel_id?: string | null
+ email?: string | null
+ customer_id?: string | null
+ region_id: string
+ promo_codes?: string[] | null
+ currency_code?: string | null
+ billing_address?: AdminAddressPayload | null
+ shipping_address?: AdminAddressPayload | null
+ no_notification_order?: boolean | null
+ shipping_methods?: string[] | null
+ metadata?: Record | null
+}
diff --git a/packages/plugins/draft-order/src/types/http/draft-orders/responses.ts b/packages/plugins/draft-order/src/types/http/draft-orders/responses.ts
new file mode 100644
index 0000000000..32b7ad18a5
--- /dev/null
+++ b/packages/plugins/draft-order/src/types/http/draft-orders/responses.ts
@@ -0,0 +1,5 @@
+import { HttpTypes } from "@medusajs/types"
+
+export interface AdminOrderResponse {
+ draft_order: HttpTypes.AdminOrder
+}
diff --git a/packages/plugins/draft-order/src/types/http/orders/entity.ts b/packages/plugins/draft-order/src/types/http/orders/entity.ts
new file mode 100644
index 0000000000..70f698e84e
--- /dev/null
+++ b/packages/plugins/draft-order/src/types/http/orders/entity.ts
@@ -0,0 +1,5 @@
+import { HttpTypes } from "@medusajs/types"
+
+export type AdminOrderPreviewLineItem = HttpTypes.AdminOrderLineItem & {
+ actions?: HttpTypes.AdminOrderChangeAction[]
+}
diff --git a/packages/plugins/draft-order/src/types/http/orders/requests.ts b/packages/plugins/draft-order/src/types/http/orders/requests.ts
new file mode 100644
index 0000000000..cf9205c09a
--- /dev/null
+++ b/packages/plugins/draft-order/src/types/http/orders/requests.ts
@@ -0,0 +1,13 @@
+export interface AdminOrderEditAddShippingMethod {
+ shipping_option_id: string
+ custom_amount?: number | undefined
+ description?: string | undefined
+ internal_note?: string | undefined
+ metadata?: Record | undefined
+}
+
+export interface AdminOrderEditUpdateShippingMethod {
+ custom_amount?: number | null | undefined
+ internal_note?: string | null | undefined
+ metadata?: Record | null | undefined
+}
diff --git a/packages/plugins/draft-order/tailwind.config.ts b/packages/plugins/draft-order/tailwind.config.ts
new file mode 100644
index 0000000000..5de4ca4be0
--- /dev/null
+++ b/packages/plugins/draft-order/tailwind.config.ts
@@ -0,0 +1,9 @@
+import preset from "@medusajs/ui-preset";
+import { Config } from "tailwindcss";
+
+const config: Config = {
+ content: ["./src/admin/**/*.{ts,tsx}"],
+ presets: [preset],
+};
+
+export default config;
diff --git a/packages/plugins/draft-order/tsconfig.json b/packages/plugins/draft-order/tsconfig.json
new file mode 100644
index 0000000000..b9e8b2535e
--- /dev/null
+++ b/packages/plugins/draft-order/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "target": "ES2021",
+ "esModuleInterop": true,
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "skipLibCheck": true,
+ "skipDefaultLibCheck": true,
+ "declaration": false,
+ "sourceMap": false,
+ "inlineSourceMap": true,
+ "outDir": "./.medusa/server",
+ "rootDir": "./",
+ "baseUrl": ".",
+ "jsx": "react-jsx",
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "checkJs": false,
+ "strictNullChecks": true
+ },
+ "ts-node": {
+ "swc": true
+ },
+ "include": [
+ "**/*",
+ ".medusa/types/*"
+ ],
+ "exclude": [
+ "node_modules",
+ ".medusa/server",
+ ".medusa/admin",
+ "src/admin",
+ ".cache"
+ ]
+}
diff --git a/yarn.lock b/yarn.lock
index c917d749bf..808ebea36d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3851,6 +3851,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/aix-ppc64@npm:0.25.8"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-arm64@npm:0.18.20"
@@ -3886,6 +3893,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/android-arm64@npm:0.25.8"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-arm@npm:0.18.20"
@@ -3921,6 +3935,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/android-arm@npm:0.25.8"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/android-x64@npm:0.18.20"
@@ -3956,6 +3977,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/android-x64@npm:0.25.8"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/darwin-arm64@npm:0.18.20"
@@ -3991,6 +4019,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/darwin-arm64@npm:0.25.8"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/darwin-x64@npm:0.18.20"
@@ -4026,6 +4061,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/darwin-x64@npm:0.25.8"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/freebsd-arm64@npm:0.18.20"
@@ -4061,6 +4103,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/freebsd-arm64@npm:0.25.8"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/freebsd-x64@npm:0.18.20"
@@ -4096,6 +4145,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/freebsd-x64@npm:0.25.8"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-arm64@npm:0.18.20"
@@ -4131,6 +4187,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-arm64@npm:0.25.8"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-arm@npm:0.18.20"
@@ -4166,6 +4229,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-arm@npm:0.25.8"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-ia32@npm:0.18.20"
@@ -4201,6 +4271,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-ia32@npm:0.25.8"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-loong64@npm:0.18.20"
@@ -4236,6 +4313,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-loong64@npm:0.25.8"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-mips64el@npm:0.18.20"
@@ -4271,6 +4355,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-mips64el@npm:0.25.8"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-ppc64@npm:0.18.20"
@@ -4306,6 +4397,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-ppc64@npm:0.25.8"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-riscv64@npm:0.18.20"
@@ -4341,6 +4439,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-riscv64@npm:0.25.8"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-s390x@npm:0.18.20"
@@ -4376,6 +4481,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-s390x@npm:0.25.8"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/linux-x64@npm:0.18.20"
@@ -4411,6 +4523,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/linux-x64@npm:0.25.8"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-arm64@npm:0.24.2":
version: 0.24.2
resolution: "@esbuild/netbsd-arm64@npm:0.24.2"
@@ -4418,6 +4537,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/netbsd-arm64@npm:0.25.8"
+ conditions: os=netbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/netbsd-x64@npm:0.18.20"
@@ -4453,6 +4579,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/netbsd-x64@npm:0.25.8"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-arm64@npm:0.23.1":
version: 0.23.1
resolution: "@esbuild/openbsd-arm64@npm:0.23.1"
@@ -4467,6 +4600,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/openbsd-arm64@npm:0.25.8"
+ conditions: os=openbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/openbsd-x64@npm:0.18.20"
@@ -4502,6 +4642,20 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/openbsd-x64@npm:0.25.8"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
+"@esbuild/openharmony-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/openharmony-arm64@npm:0.25.8"
+ conditions: os=openharmony & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/sunos-x64@npm:0.18.20"
@@ -4537,6 +4691,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/sunos-x64@npm:0.25.8"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-arm64@npm:0.18.20"
@@ -4572,6 +4733,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/win32-arm64@npm:0.25.8"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-ia32@npm:0.18.20"
@@ -4607,6 +4775,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/win32-ia32@npm:0.25.8"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.18.20":
version: 0.18.20
resolution: "@esbuild/win32-x64@npm:0.18.20"
@@ -4642,6 +4817,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.25.8":
+ version: 0.25.8
+ resolution: "@esbuild/win32-x64@npm:0.25.8"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
version: 4.4.0
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
@@ -5775,7 +5957,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@medusajs/admin-sdk@workspace:packages/admin/admin-sdk":
+"@medusajs/admin-sdk@2.9.0, @medusajs/admin-sdk@workspace:packages/admin/admin-sdk":
version: 0.0.0-use.local
resolution: "@medusajs/admin-sdk@workspace:packages/admin/admin-sdk"
dependencies:
@@ -6209,6 +6391,69 @@ __metadata:
languageName: unknown
linkType: soft
+"@medusajs/draft-order@workspace:packages/plugins/draft-order":
+ version: 0.0.0-use.local
+ resolution: "@medusajs/draft-order@workspace:packages/plugins/draft-order"
+ dependencies:
+ "@ariakit/react": ^0.4.15
+ "@hookform/resolvers": 3.4.2
+ "@medusajs/admin-sdk": 2.9.0
+ "@medusajs/cli": 2.9.0
+ "@medusajs/framework": 2.9.0
+ "@medusajs/icons": 2.9.0
+ "@medusajs/js-sdk": 2.9.0
+ "@medusajs/medusa": 2.9.0
+ "@medusajs/test-utils": 2.9.0
+ "@medusajs/types": 2.9.0
+ "@medusajs/ui": 4.0.19
+ "@medusajs/ui-preset": 2.9.0
+ "@mikro-orm/cli": 6.4.3
+ "@mikro-orm/core": 6.4.3
+ "@mikro-orm/knex": 6.4.3
+ "@mikro-orm/migrations": 6.4.3
+ "@mikro-orm/postgresql": 6.4.3
+ "@swc/core": 1.5.7
+ "@tanstack/react-query": 5.64.2
+ "@types/lodash": ^4.17.15
+ "@types/node": ^20.0.0
+ "@types/react": ^18.3.2
+ "@types/react-dom": ^18.2.25
+ "@uiw/react-json-view": ^2.0.0-alpha.17
+ awilix: ^8.0.1
+ date-fns: ^3.6.0
+ lodash: ^4.17.21
+ match-sorter: ^6.3.4
+ pg: ^8.13.0
+ radix-ui: 1.1.2
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ react-hook-form: 7.49.1
+ react-router-dom: 6.20.1
+ ts-node: ^10.9.2
+ tsup: ^8.4.0
+ typescript: ^5.6.2
+ vite: ^5.2.11
+ yalc: ^1.0.0-pre.53
+ peerDependencies:
+ "@medusajs/admin-sdk": 2.9.0
+ "@medusajs/cli": 2.9.0
+ "@medusajs/framework": 2.9.0
+ "@medusajs/icons": 2.9.0
+ "@medusajs/medusa": 2.9.0
+ "@medusajs/test-utils": 2.9.0
+ "@medusajs/ui": 4.0.19
+ "@mikro-orm/cli": 6.4.3
+ "@mikro-orm/core": 6.4.3
+ "@mikro-orm/knex": 6.4.3
+ "@mikro-orm/migrations": 6.4.3
+ "@mikro-orm/postgresql": 6.4.3
+ awilix: ^8.0.1
+ lodash: ^4.17.21
+ pg: ^8.13.0
+ react-router-dom: 6.20.1
+ languageName: unknown
+ linkType: soft
+
"@medusajs/event-bus-local@2.9.0, @medusajs/event-bus-local@workspace:*, @medusajs/event-bus-local@workspace:packages/modules/event-bus-local":
version: 0.0.0-use.local
resolution: "@medusajs/event-bus-local@workspace:packages/modules/event-bus-local"
@@ -12035,6 +12280,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-android-arm-eabi@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-android-arm64@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-android-arm64@npm:4.17.2"
@@ -12056,6 +12308,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-android-arm64@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-android-arm64@npm:4.46.2"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-darwin-arm64@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-darwin-arm64@npm:4.17.2"
@@ -12077,6 +12336,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-darwin-arm64@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-darwin-x64@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-darwin-x64@npm:4.17.2"
@@ -12098,6 +12364,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-darwin-x64@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-darwin-x64@npm:4.46.2"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-freebsd-arm64@npm:4.34.6":
version: 4.34.6
resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.6"
@@ -12105,6 +12378,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-freebsd-arm64@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-freebsd-x64@npm:4.34.6":
version: 4.34.6
resolution: "@rollup/rollup-freebsd-x64@npm:4.34.6"
@@ -12112,6 +12392,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-freebsd-x64@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2"
@@ -12133,6 +12420,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2"
+ conditions: os=linux & cpu=arm & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm-musleabihf@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.17.2"
@@ -12154,6 +12448,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2"
+ conditions: os=linux & cpu=arm & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm64-gnu@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.17.2"
@@ -12175,6 +12476,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm64-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=arm64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-arm64-musl@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-arm64-musl@npm:4.17.2"
@@ -12196,6 +12504,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-arm64-musl@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2"
+ conditions: os=linux & cpu=arm64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-loongarch64-gnu@npm:4.34.6":
version: 4.34.6
resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.6"
@@ -12203,6 +12518,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=loong64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2"
@@ -12224,6 +12546,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=ppc64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-riscv64-gnu@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.17.2"
@@ -12245,6 +12574,20 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=riscv64 & libc=glibc
+ languageName: node
+ linkType: hard
+
+"@rollup/rollup-linux-riscv64-musl@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2"
+ conditions: os=linux & cpu=riscv64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-s390x-gnu@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.17.2"
@@ -12266,6 +12609,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-s390x-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=s390x & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-x64-gnu@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-x64-gnu@npm:4.17.2"
@@ -12287,6 +12637,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-x64-gnu@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2"
+ conditions: os=linux & cpu=x64 & libc=glibc
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-linux-x64-musl@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-linux-x64-musl@npm:4.17.2"
@@ -12308,6 +12665,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-linux-x64-musl@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2"
+ conditions: os=linux & cpu=x64 & libc=musl
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-arm64-msvc@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.17.2"
@@ -12329,6 +12693,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-win32-arm64-msvc@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-ia32-msvc@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.17.2"
@@ -12350,6 +12721,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-win32-ia32-msvc@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@rollup/rollup-win32-x64-msvc@npm:4.17.2":
version: 4.17.2
resolution: "@rollup/rollup-win32-x64-msvc@npm:4.17.2"
@@ -12371,6 +12749,13 @@ __metadata:
languageName: node
linkType: hard
+"@rollup/rollup-win32-x64-msvc@npm:4.46.2":
+ version: 4.46.2
+ resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@rushstack/node-core-library@npm:4.2.0":
version: 4.2.0
resolution: "@rushstack/node-core-library@npm:4.2.0"
@@ -15152,6 +15537,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/estree@npm:1.0.8":
+ version: 1.0.8
+ resolution: "@types/estree@npm:1.0.8"
+ checksum: 39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5
+ languageName: node
+ linkType: hard
+
"@types/estree@npm:^0.0.51":
version: 0.0.51
resolution: "@types/estree@npm:0.0.51"
@@ -15346,6 +15738,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/lodash@npm:^4.17.15":
+ version: 4.17.20
+ resolution: "@types/lodash@npm:4.17.20"
+ checksum: 98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f
+ languageName: node
+ linkType: hard
+
"@types/mdx@npm:^2.0.0":
version: 2.0.13
resolution: "@types/mdx@npm:2.0.13"
@@ -15450,6 +15849,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/node@npm:^20.0.0":
+ version: 20.19.10
+ resolution: "@types/node@npm:20.19.10"
+ dependencies:
+ undici-types: ~6.21.0
+ checksum: dac81561f6d7cbd37c876c4023846095a71d8b62df52c8b6b32de67f6ca6aaf2ffd0c1e02ede6d2c8af41e934ce2005f67b8b6348b79dd355c05f3192aa7b59a
+ languageName: node
+ linkType: hard
+
"@types/node@npm:^20.12.11":
version: 20.12.12
resolution: "@types/node@npm:20.12.12"
@@ -16432,6 +16840,15 @@ __metadata:
languageName: node
linkType: hard
+"acorn@npm:^8.14.0":
+ version: 8.15.0
+ resolution: "acorn@npm:8.15.0"
+ bin:
+ acorn: bin/acorn
+ checksum: dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec
+ languageName: node
+ linkType: hard
+
"adjust-sourcemap-loader@npm:^4.0.0":
version: 4.0.0
resolution: "adjust-sourcemap-loader@npm:4.0.0"
@@ -17690,6 +18107,17 @@ __metadata:
languageName: node
linkType: hard
+"bundle-require@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "bundle-require@npm:5.1.0"
+ dependencies:
+ load-tsconfig: ^0.2.3
+ peerDependencies:
+ esbuild: ">=0.18"
+ checksum: 8bff9df68eb686f05af952003c78e70ffed2817968f92aebb2af620cc0b7428c8154df761d28f1b38508532204278950624ef86ce63644013dc57660a9d1810f
+ languageName: node
+ linkType: hard
+
"bunyan@npm:1.8.15":
version: 1.8.15
resolution: "bunyan@npm:1.8.15"
@@ -18098,6 +18526,15 @@ __metadata:
languageName: node
linkType: hard
+"chokidar@npm:^4.0.3":
+ version: 4.0.3
+ resolution: "chokidar@npm:4.0.3"
+ dependencies:
+ readdirp: ^4.0.1
+ checksum: a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad
+ languageName: node
+ linkType: hard
+
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
@@ -18653,6 +19090,13 @@ __metadata:
languageName: node
linkType: hard
+"confbox@npm:^0.1.8":
+ version: 0.1.8
+ resolution: "confbox@npm:0.1.8"
+ checksum: fc2c68d97cb54d885b10b63e45bd8da83a8a71459d3ecf1825143dd4c7f9f1b696b3283e07d9d12a144c1301c2ebc7842380bdf0014e55acc4ae1c9550102418
+ languageName: node
+ linkType: hard
+
"configstore@npm:5.0.1, configstore@npm:^5.0.1":
version: 5.0.1
resolution: "configstore@npm:5.0.1"
@@ -18699,6 +19143,13 @@ __metadata:
languageName: node
linkType: hard
+"consola@npm:^3.4.0":
+ version: 3.4.2
+ resolution: "consola@npm:3.4.2"
+ checksum: 7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a
+ languageName: node
+ linkType: hard
+
"constant-case@npm:^3.0.4":
version: 3.0.4
resolution: "constant-case@npm:3.0.4"
@@ -20982,6 +21433,95 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:^0.25.0":
+ version: 0.25.8
+ resolution: "esbuild@npm:0.25.8"
+ dependencies:
+ "@esbuild/aix-ppc64": 0.25.8
+ "@esbuild/android-arm": 0.25.8
+ "@esbuild/android-arm64": 0.25.8
+ "@esbuild/android-x64": 0.25.8
+ "@esbuild/darwin-arm64": 0.25.8
+ "@esbuild/darwin-x64": 0.25.8
+ "@esbuild/freebsd-arm64": 0.25.8
+ "@esbuild/freebsd-x64": 0.25.8
+ "@esbuild/linux-arm": 0.25.8
+ "@esbuild/linux-arm64": 0.25.8
+ "@esbuild/linux-ia32": 0.25.8
+ "@esbuild/linux-loong64": 0.25.8
+ "@esbuild/linux-mips64el": 0.25.8
+ "@esbuild/linux-ppc64": 0.25.8
+ "@esbuild/linux-riscv64": 0.25.8
+ "@esbuild/linux-s390x": 0.25.8
+ "@esbuild/linux-x64": 0.25.8
+ "@esbuild/netbsd-arm64": 0.25.8
+ "@esbuild/netbsd-x64": 0.25.8
+ "@esbuild/openbsd-arm64": 0.25.8
+ "@esbuild/openbsd-x64": 0.25.8
+ "@esbuild/openharmony-arm64": 0.25.8
+ "@esbuild/sunos-x64": 0.25.8
+ "@esbuild/win32-arm64": 0.25.8
+ "@esbuild/win32-ia32": 0.25.8
+ "@esbuild/win32-x64": 0.25.8
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-arm64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-arm64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/openharmony-arm64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 43747a25e120d5dd9ce75c82f57306580d715647c8db4f4a0a84e73b04cf16c27572d3937d3cfb95d5ac3266a4d1bbd3913e3d76ae719693516289fc86f8a5fd
+ languageName: node
+ linkType: hard
+
"escalade@npm:^3.1.1, escalade@npm:^3.1.2":
version: 3.1.2
resolution: "escalade@npm:3.1.2"
@@ -21976,6 +22516,18 @@ __metadata:
languageName: node
linkType: hard
+"fdir@npm:^6.4.4":
+ version: 6.4.6
+ resolution: "fdir@npm:6.4.6"
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ checksum: 45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9
+ languageName: node
+ linkType: hard
+
"fecha@npm:^4.2.0":
version: 4.2.3
resolution: "fecha@npm:4.2.3"
@@ -22176,6 +22728,17 @@ __metadata:
languageName: node
linkType: hard
+"fix-dts-default-cjs-exports@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "fix-dts-default-cjs-exports@npm:1.0.1"
+ dependencies:
+ magic-string: ^0.30.17
+ mlly: ^1.7.4
+ rollup: ^4.34.8
+ checksum: 61a3cbe32b6c29df495ef3aded78199fe9dbb52e2801c899fe76d9ca413d3c8c51f79986bac83f8b4b2094ebde883ddcfe47b68ce469806ba13ca6ed4e7cd362
+ languageName: node
+ linkType: hard
+
"flat-cache@npm:^3.0.4":
version: 3.2.0
resolution: "flat-cache@npm:3.2.0"
@@ -25684,6 +26247,13 @@ __metadata:
languageName: node
linkType: hard
+"lilconfig@npm:^3.1.1":
+ version: 3.1.3
+ resolution: "lilconfig@npm:3.1.3"
+ checksum: f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc
+ languageName: node
+ linkType: hard
+
"line-counter@npm:^1.0.3":
version: 1.1.0
resolution: "line-counter@npm:1.1.0"
@@ -26859,6 +27429,18 @@ __metadata:
languageName: node
linkType: hard
+"mlly@npm:^1.7.4":
+ version: 1.7.4
+ resolution: "mlly@npm:1.7.4"
+ dependencies:
+ acorn: ^8.14.0
+ pathe: ^2.0.1
+ pkg-types: ^1.3.0
+ ufo: ^1.5.4
+ checksum: 69e738218a13d6365caf930e0ab4e2b848b84eec261597df9788cefb9930f3e40667be9cb58a4718834ba5f97a6efeef31d3b5a95f4388143fd4e0d0deff72ff
+ languageName: node
+ linkType: hard
+
"mnemonist@npm:0.38.3":
version: 0.38.3
resolution: "mnemonist@npm:0.38.3"
@@ -28330,6 +28912,13 @@ __metadata:
languageName: node
linkType: hard
+"pathe@npm:^2.0.1":
+ version: 2.0.3
+ resolution: "pathe@npm:2.0.3"
+ checksum: c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1
+ languageName: node
+ linkType: hard
+
"pathe@npm:^2.0.2":
version: 2.0.2
resolution: "pathe@npm:2.0.2"
@@ -28528,6 +29117,13 @@ __metadata:
languageName: node
linkType: hard
+"picomatch@npm:^4.0.2":
+ version: 4.0.3
+ resolution: "picomatch@npm:4.0.3"
+ checksum: 9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2
+ languageName: node
+ linkType: hard
+
"pify@npm:^2.3.0":
version: 2.3.0
resolution: "pify@npm:2.3.0"
@@ -28574,6 +29170,17 @@ __metadata:
languageName: node
linkType: hard
+"pkg-types@npm:^1.3.0":
+ version: 1.3.1
+ resolution: "pkg-types@npm:1.3.1"
+ dependencies:
+ confbox: ^0.1.8
+ mlly: ^1.7.4
+ pathe: ^2.0.1
+ checksum: 19e6cb8b66dcc66c89f2344aecfa47f2431c988cfa3366bdfdcfb1dd6695f87dcce37fbd90fe9d1605e2f4440b77f391e83c23255347c35cf84e7fd774d7fcea
+ languageName: node
+ linkType: hard
+
"pkginfo@npm:0.4.1":
version: 0.4.1
resolution: "pkginfo@npm:0.4.1"
@@ -28770,6 +29377,29 @@ __metadata:
languageName: node
linkType: hard
+"postcss-load-config@npm:^6.0.1":
+ version: 6.0.1
+ resolution: "postcss-load-config@npm:6.0.1"
+ dependencies:
+ lilconfig: ^3.1.1
+ peerDependencies:
+ jiti: ">=1.21.0"
+ postcss: ">=8.0.9"
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ checksum: 74173a58816dac84e44853f7afbd283f4ef13ca0b6baeba27701214beec33f9e309b128f8102e2b173e8d45ecba45d279a9be94b46bf48d219626aa9b5730848
+ languageName: node
+ linkType: hard
+
"postcss-loader@npm:^7.2.4":
version: 7.3.4
resolution: "postcss-loader@npm:7.3.4"
@@ -30329,6 +30959,13 @@ __metadata:
languageName: node
linkType: hard
+"readdirp@npm:^4.0.1":
+ version: 4.1.2
+ resolution: "readdirp@npm:4.1.2"
+ checksum: 60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62
+ languageName: node
+ linkType: hard
+
"readdirp@npm:~3.6.0":
version: 3.6.0
resolution: "readdirp@npm:3.6.0"
@@ -31333,6 +31970,81 @@ __metadata:
languageName: node
linkType: hard
+"rollup@npm:^4.34.8":
+ version: 4.46.2
+ resolution: "rollup@npm:4.46.2"
+ dependencies:
+ "@rollup/rollup-android-arm-eabi": 4.46.2
+ "@rollup/rollup-android-arm64": 4.46.2
+ "@rollup/rollup-darwin-arm64": 4.46.2
+ "@rollup/rollup-darwin-x64": 4.46.2
+ "@rollup/rollup-freebsd-arm64": 4.46.2
+ "@rollup/rollup-freebsd-x64": 4.46.2
+ "@rollup/rollup-linux-arm-gnueabihf": 4.46.2
+ "@rollup/rollup-linux-arm-musleabihf": 4.46.2
+ "@rollup/rollup-linux-arm64-gnu": 4.46.2
+ "@rollup/rollup-linux-arm64-musl": 4.46.2
+ "@rollup/rollup-linux-loongarch64-gnu": 4.46.2
+ "@rollup/rollup-linux-ppc64-gnu": 4.46.2
+ "@rollup/rollup-linux-riscv64-gnu": 4.46.2
+ "@rollup/rollup-linux-riscv64-musl": 4.46.2
+ "@rollup/rollup-linux-s390x-gnu": 4.46.2
+ "@rollup/rollup-linux-x64-gnu": 4.46.2
+ "@rollup/rollup-linux-x64-musl": 4.46.2
+ "@rollup/rollup-win32-arm64-msvc": 4.46.2
+ "@rollup/rollup-win32-ia32-msvc": 4.46.2
+ "@rollup/rollup-win32-x64-msvc": 4.46.2
+ "@types/estree": 1.0.8
+ fsevents: ~2.3.2
+ dependenciesMeta:
+ "@rollup/rollup-android-arm-eabi":
+ optional: true
+ "@rollup/rollup-android-arm64":
+ optional: true
+ "@rollup/rollup-darwin-arm64":
+ optional: true
+ "@rollup/rollup-darwin-x64":
+ optional: true
+ "@rollup/rollup-freebsd-arm64":
+ optional: true
+ "@rollup/rollup-freebsd-x64":
+ optional: true
+ "@rollup/rollup-linux-arm-gnueabihf":
+ optional: true
+ "@rollup/rollup-linux-arm-musleabihf":
+ optional: true
+ "@rollup/rollup-linux-arm64-gnu":
+ optional: true
+ "@rollup/rollup-linux-arm64-musl":
+ optional: true
+ "@rollup/rollup-linux-loongarch64-gnu":
+ optional: true
+ "@rollup/rollup-linux-ppc64-gnu":
+ optional: true
+ "@rollup/rollup-linux-riscv64-gnu":
+ optional: true
+ "@rollup/rollup-linux-riscv64-musl":
+ optional: true
+ "@rollup/rollup-linux-s390x-gnu":
+ optional: true
+ "@rollup/rollup-linux-x64-gnu":
+ optional: true
+ "@rollup/rollup-linux-x64-musl":
+ optional: true
+ "@rollup/rollup-win32-arm64-msvc":
+ optional: true
+ "@rollup/rollup-win32-ia32-msvc":
+ optional: true
+ "@rollup/rollup-win32-x64-msvc":
+ optional: true
+ fsevents:
+ optional: true
+ bin:
+ rollup: dist/bin/rollup
+ checksum: f428497fe119fe7c4e34f1020d45ba13e99b94c9aa36958d88823d932b155c9df3d84f53166f3ee913ff68ea6c7599a9ab34861d88562ad9d8420f64ca5dad4c
+ languageName: node
+ linkType: hard
+
"root@workspace:.":
version: 0.0.0-use.local
resolution: "root@workspace:."
@@ -32793,7 +33505,7 @@ __metadata:
languageName: node
linkType: hard
-"sucrase@npm:^3.20.3, sucrase@npm:^3.32.0":
+"sucrase@npm:^3.20.3, sucrase@npm:^3.32.0, sucrase@npm:^3.35.0":
version: 3.35.0
resolution: "sucrase@npm:3.35.0"
dependencies:
@@ -33195,6 +33907,16 @@ __metadata:
languageName: node
linkType: hard
+"tinyglobby@npm:^0.2.11":
+ version: 0.2.14
+ resolution: "tinyglobby@npm:0.2.14"
+ dependencies:
+ fdir: ^6.4.4
+ picomatch: ^4.0.2
+ checksum: f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6
+ languageName: node
+ linkType: hard
+
"tinypool@npm:^1.0.2":
version: 1.0.2
resolution: "tinypool@npm:1.0.2"
@@ -33505,7 +34227,7 @@ __metadata:
languageName: node
linkType: hard
-"ts-node@npm:^10.9.1":
+"ts-node@npm:^10.9.1, ts-node@npm:^10.9.2":
version: 10.9.2
resolution: "ts-node@npm:10.9.2"
dependencies:
@@ -33762,6 +34484,48 @@ __metadata:
languageName: node
linkType: hard
+"tsup@npm:^8.4.0":
+ version: 8.5.0
+ resolution: "tsup@npm:8.5.0"
+ dependencies:
+ bundle-require: ^5.1.0
+ cac: ^6.7.14
+ chokidar: ^4.0.3
+ consola: ^3.4.0
+ debug: ^4.4.0
+ esbuild: ^0.25.0
+ fix-dts-default-cjs-exports: ^1.0.0
+ joycon: ^3.1.1
+ picocolors: ^1.1.1
+ postcss-load-config: ^6.0.1
+ resolve-from: ^5.0.0
+ rollup: ^4.34.8
+ source-map: 0.8.0-beta.0
+ sucrase: ^3.35.0
+ tinyexec: ^0.3.2
+ tinyglobby: ^0.2.11
+ tree-kill: ^1.2.2
+ peerDependencies:
+ "@microsoft/api-extractor": ^7.36.0
+ "@swc/core": ^1
+ postcss: ^8.4.12
+ typescript: ">=4.5.0"
+ peerDependenciesMeta:
+ "@microsoft/api-extractor":
+ optional: true
+ "@swc/core":
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+ bin:
+ tsup: dist/cli-default.js
+ tsup-node: dist/cli-node.js
+ checksum: 2eddc1138ad992a2e67d826e92e0b0c4f650367355866c77df8368ade9489e0a8bf2b52b352e97fec83dc690af05881c29c489af27acb86ac2cef38b0d029087
+ languageName: node
+ linkType: hard
+
"tsutils@npm:^3.21.0":
version: 3.21.0
resolution: "tsutils@npm:3.21.0"
@@ -34170,6 +34934,13 @@ __metadata:
languageName: node
linkType: hard
+"ufo@npm:^1.5.4":
+ version: 1.6.1
+ resolution: "ufo@npm:1.6.1"
+ checksum: 5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b
+ languageName: node
+ linkType: hard
+
"uglify-js@npm:^3.1.4":
version: 3.17.4
resolution: "uglify-js@npm:3.17.4"
@@ -34250,6 +35021,13 @@ __metadata:
languageName: node
linkType: hard
+"undici-types@npm:~6.21.0":
+ version: 6.21.0
+ resolution: "undici-types@npm:6.21.0"
+ checksum: c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04
+ languageName: node
+ linkType: hard
+
"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
version: 2.0.0
resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0"
@@ -34860,6 +35638,49 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^5.2.11":
+ version: 5.4.19
+ resolution: "vite@npm:5.4.19"
+ dependencies:
+ esbuild: ^0.21.3
+ fsevents: ~2.3.3
+ postcss: ^8.4.43
+ rollup: ^4.20.0
+ peerDependencies:
+ "@types/node": ^18.0.0 || >=20.0.0
+ less: "*"
+ lightningcss: ^1.21.0
+ sass: "*"
+ sass-embedded: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.4.0
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: c97601234dba482cea5290f2a2ea0fcd65e1fab3df06718ea48adc8ceb14bc3129508216c4989329c618f6a0470b42f439677a207aef62b0c76f445091c2d89e
+ languageName: node
+ linkType: hard
+
"vite@npm:^5.4.14":
version: 5.4.14
resolution: "vite@npm:5.4.14"
@@ -35485,7 +36306,7 @@ __metadata:
languageName: node
linkType: hard
-"yalc@npm:1.0.0-pre.53":
+"yalc@npm:1.0.0-pre.53, yalc@npm:^1.0.0-pre.53":
version: 1.0.0-pre.53
resolution: "yalc@npm:1.0.0-pre.53"
dependencies: