feat(admin, js-sdk, types): update order forms (#10418)
* feat: edit shipping, billing and email forms * feat: timeline history, update change model, update tests * fix: address comments * fix: invalidation, translation schema, update label * fix: old/new
This commit is contained in:
1 parent
a5263083fa
commit
340769595a
27 files changed
+1179
-40
No files matched your search
@@ -76,18 +76,41 @@ medusaIntegrationTestRunner({
|
|||||||
version: 1,
|
version: 1,
|
||||||
change_type: "update_order",
|
change_type: "update_order",
|
||||||
status: "confirmed",
|
status: "confirmed",
|
||||||
|
created_by: expect.any(String),
|
||||||
|
confirmed_by: expect.any(String),
|
||||||
confirmed_at: expect.any(String),
|
confirmed_at: expect.any(String),
|
||||||
actions: expect.arrayContaining([
|
actions: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
version: 1,
|
version: 1,
|
||||||
applied: true,
|
applied: true,
|
||||||
reference_id: addressBefore.id,
|
|
||||||
reference: "shipping_address",
|
|
||||||
action: "UPDATE_ORDER_PROPERTIES",
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
details: {
|
details: expect.objectContaining({
|
||||||
city: "New New York",
|
type: "shipping_address",
|
||||||
|
old: expect.objectContaining({
|
||||||
|
address_1: addressBefore.address_1,
|
||||||
|
city: addressBefore.city,
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
company: addressBefore.company,
|
||||||
|
first_name: addressBefore.first_name,
|
||||||
|
last_name: addressBefore.last_name,
|
||||||
|
address_2: addressBefore.address_2,
|
||||||
|
}),
|
||||||
|
new: expect.objectContaining({
|
||||||
address_1: "New Main street 123",
|
address_1: "New Main street 123",
|
||||||
},
|
city: "New New York",
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
company: addressBefore.company,
|
||||||
|
first_name: addressBefore.first_name,
|
||||||
|
last_name: addressBefore.last_name,
|
||||||
|
address_2: addressBefore.address_2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
@@ -162,18 +185,33 @@ medusaIntegrationTestRunner({
|
|||||||
version: 1,
|
version: 1,
|
||||||
change_type: "update_order",
|
change_type: "update_order",
|
||||||
status: "confirmed",
|
status: "confirmed",
|
||||||
|
created_by: expect.any(String),
|
||||||
|
confirmed_by: expect.any(String),
|
||||||
confirmed_at: expect.any(String),
|
confirmed_at: expect.any(String),
|
||||||
actions: expect.arrayContaining([
|
actions: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
version: 1,
|
version: 1,
|
||||||
applied: true,
|
applied: true,
|
||||||
reference_id: addressBefore.id,
|
|
||||||
reference: "billing_address",
|
|
||||||
action: "UPDATE_ORDER_PROPERTIES",
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
details: {
|
details: expect.objectContaining({
|
||||||
city: "New New York",
|
type: "billing_address",
|
||||||
|
old: expect.objectContaining({
|
||||||
|
address_1: addressBefore.address_1,
|
||||||
|
city: addressBefore.city,
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
}),
|
||||||
|
new: expect.objectContaining({
|
||||||
address_1: "New Main street 123",
|
address_1: "New Main street 123",
|
||||||
},
|
city: "New New York",
|
||||||
|
country_code: addressBefore.country_code,
|
||||||
|
province: addressBefore.province,
|
||||||
|
postal_code: addressBefore.postal_code,
|
||||||
|
phone: addressBefore.phone,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
@@ -239,16 +277,23 @@ medusaIntegrationTestRunner({
|
|||||||
change_type: "update_order",
|
change_type: "update_order",
|
||||||
status: "confirmed",
|
status: "confirmed",
|
||||||
confirmed_at: expect.any(String),
|
confirmed_at: expect.any(String),
|
||||||
|
created_by: expect.any(String),
|
||||||
|
confirmed_by: expect.any(String),
|
||||||
actions: expect.arrayContaining([
|
actions: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
version: 1,
|
version: 1,
|
||||||
applied: true,
|
applied: true,
|
||||||
reference_id: order.shipping_address.id,
|
|
||||||
reference: "shipping_address",
|
|
||||||
action: "UPDATE_ORDER_PROPERTIES",
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
details: {
|
details: expect.objectContaining({
|
||||||
|
type: "shipping_address",
|
||||||
|
old: expect.objectContaining({
|
||||||
|
address_1: order.shipping_address.address_1,
|
||||||
|
city: order.shipping_address.city,
|
||||||
|
}),
|
||||||
|
new: expect.objectContaining({
|
||||||
address_1: "New Main street 123",
|
address_1: "New Main street 123",
|
||||||
},
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
@@ -257,16 +302,18 @@ medusaIntegrationTestRunner({
|
|||||||
change_type: "update_order",
|
change_type: "update_order",
|
||||||
status: "confirmed",
|
status: "confirmed",
|
||||||
confirmed_at: expect.any(String),
|
confirmed_at: expect.any(String),
|
||||||
|
created_by: expect.any(String),
|
||||||
|
confirmed_by: expect.any(String),
|
||||||
actions: expect.arrayContaining([
|
actions: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
version: 1,
|
version: 1,
|
||||||
applied: true,
|
applied: true,
|
||||||
reference_id: order.email,
|
|
||||||
reference: "email",
|
|
||||||
action: "UPDATE_ORDER_PROPERTIES",
|
action: "UPDATE_ORDER_PROPERTIES",
|
||||||
details: {
|
details: expect.objectContaining({
|
||||||
email: "new-email@example.com",
|
type: "email",
|
||||||
},
|
old: order.email,
|
||||||
|
new: "new-email@example.com",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Avatar, Text } from "@medusajs/ui"
|
import { Avatar, Text } from "@medusajs/ui"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
import { useUser } from "../../../hooks/api/users"
|
||||||
|
|
||||||
type UserLinkProps = {
|
type UserLinkProps = {
|
||||||
id: string
|
id: string
|
||||||
@@ -32,3 +33,13 @@ export const UserLink = ({
|
|||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const By = ({ id }: { id: string }) => {
|
||||||
|
const { user } = useUser(id) // todo: extend to support customers
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <UserLink {...user} />
|
||||||
|
}
|
||||||
@@ -51,6 +51,37 @@ export const useOrder = (
|
|||||||
return { ...data, ...rest }
|
return { ...data, ...rest }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUpdateOrder = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminOrderResponse,
|
||||||
|
FetchError,
|
||||||
|
HttpTypes.AdminUpdateOrder
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload: HttpTypes.AdminUpdateOrder) =>
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: enable when needed
|
||||||
|
// queryClient.invalidateQueries({
|
||||||
|
// queryKey: ordersQueryKeys.lists(),
|
||||||
|
// })
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const useOrderPreview = (
|
export const useOrderPreview = (
|
||||||
id: string,
|
id: string,
|
||||||
query?: HttpTypes.AdminOrderFilters,
|
query?: HttpTypes.AdminOrderFilters,
|
||||||
|
|||||||
@@ -4146,6 +4146,65 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"edit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requestSuccess": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"requestSuccess"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"shippingAddress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requestSuccess": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"requestSuccess"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"billingAddress": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requestSuccess": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"requestSuccess"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"email",
|
||||||
|
"shippingAddress",
|
||||||
|
"billingAddress"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"returns": {
|
"returns": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5364,6 +5423,26 @@
|
|||||||
"declined"
|
"declined"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"update_order": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"shipping_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"billing_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"shipping_address",
|
||||||
|
"billing_address",
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -5377,7 +5456,8 @@
|
|||||||
"claim",
|
"claim",
|
||||||
"exchange",
|
"exchange",
|
||||||
"edit",
|
"edit",
|
||||||
"transfer"
|
"transfer",
|
||||||
|
"update_order"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -5426,6 +5506,7 @@
|
|||||||
"transfer",
|
"transfer",
|
||||||
"payment",
|
"payment",
|
||||||
"edits",
|
"edits",
|
||||||
|
"edit",
|
||||||
"returns",
|
"returns",
|
||||||
"claims",
|
"claims",
|
||||||
"exchanges",
|
"exchanges",
|
||||||
@@ -10849,6 +10930,12 @@
|
|||||||
},
|
},
|
||||||
"removed": {
|
"removed": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -10857,7 +10944,9 @@
|
|||||||
"available",
|
"available",
|
||||||
"inStock",
|
"inStock",
|
||||||
"added",
|
"added",
|
||||||
"removed"
|
"removed",
|
||||||
|
"from",
|
||||||
|
"to"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
@@ -10951,6 +11040,9 @@
|
|||||||
"subtitle": {
|
"subtitle": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"by": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"item": {
|
"item": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -11360,6 +11452,7 @@
|
|||||||
"discountable",
|
"discountable",
|
||||||
"handle",
|
"handle",
|
||||||
"subtitle",
|
"subtitle",
|
||||||
|
"by",
|
||||||
"item",
|
"item",
|
||||||
"qty",
|
"qty",
|
||||||
"limit",
|
"limit",
|
||||||
|
|||||||
@@ -1002,6 +1002,20 @@
|
|||||||
"quantityLowerThanFulfillment": "Cannot set quantity to be less then or equal to fulfilled quantity"
|
"quantityLowerThanFulfillment": "Cannot set quantity to be less then or equal to fulfilled quantity"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"edit": {
|
||||||
|
"email": {
|
||||||
|
"title": "Edit email",
|
||||||
|
"requestSuccess": "Order email updated to {{email}}."
|
||||||
|
},
|
||||||
|
"shippingAddress": {
|
||||||
|
"title": "Edit shipping address",
|
||||||
|
"requestSuccess": "Order shipping address updated."
|
||||||
|
},
|
||||||
|
"billingAddress": {
|
||||||
|
"title": "Edit billing address",
|
||||||
|
"requestSuccess": "Order billing address updated."
|
||||||
|
}
|
||||||
|
},
|
||||||
"returns": {
|
"returns": {
|
||||||
"create": "Create Return",
|
"create": "Create Return",
|
||||||
"confirm": "Confirm Return",
|
"confirm": "Confirm Return",
|
||||||
@@ -1301,6 +1315,11 @@
|
|||||||
"requested": "Order transfer #{{transferId}} requested",
|
"requested": "Order transfer #{{transferId}} requested",
|
||||||
"confirmed": "Order transfer #{{transferId}} confirmed",
|
"confirmed": "Order transfer #{{transferId}} confirmed",
|
||||||
"declined": "Order transfer #{{transferId}} declined"
|
"declined": "Order transfer #{{transferId}} declined"
|
||||||
|
},
|
||||||
|
"update_order": {
|
||||||
|
"shipping_address": "Shipping address updated",
|
||||||
|
"billing_address": "Billing address updated",
|
||||||
|
"email": "Email updated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2598,7 +2617,9 @@
|
|||||||
"available": "Available",
|
"available": "Available",
|
||||||
"inStock": "In stock",
|
"inStock": "In stock",
|
||||||
"added": "Added",
|
"added": "Added",
|
||||||
"removed": "Removed"
|
"removed": "Removed",
|
||||||
|
"from": "From",
|
||||||
|
"to": "To"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
@@ -2630,6 +2651,7 @@
|
|||||||
"discountable": "Discountable",
|
"discountable": "Discountable",
|
||||||
"handle": "Handle",
|
"handle": "Handle",
|
||||||
"subtitle": "Subtitle",
|
"subtitle": "Subtitle",
|
||||||
|
"by": "By",
|
||||||
"item": "Item",
|
"item": "Item",
|
||||||
"qty": "qty.",
|
"qty": "qty.",
|
||||||
"limit": "Limit",
|
"limit": "Limit",
|
||||||
|
|||||||
@@ -329,6 +329,20 @@ export const RouteMap: RouteObject[] = [
|
|||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../routes/orders/order-request-transfer"),
|
import("../../routes/orders/order-request-transfer"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "email",
|
||||||
|
lazy: () => import("../../routes/orders/order-edit-email"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "shipping-address",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/orders/order-edit-shipping-address"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "billing-address",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/orders/order-edit-billing-address"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
import { Popover, Text } from "@medusajs/ui"
|
||||||
|
import { ReactNode, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
type ChangeDetailsTooltipProps = {
|
||||||
|
previous: ReactNode
|
||||||
|
next: ReactNode
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeDetailsTooltip(props: ChangeDetailsTooltipProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const previous = props.previous
|
||||||
|
const next = props.next
|
||||||
|
const title = props.title
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!previous && !next) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open}>
|
||||||
|
<Popover.Trigger
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
autoFocus={false}
|
||||||
|
className="focus-visible:outline-none"
|
||||||
|
>
|
||||||
|
<Text size="small" leading="compact" weight="plus">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Content
|
||||||
|
align="center"
|
||||||
|
side="top"
|
||||||
|
className="bg-ui-bg-component max-w-[200px] p-0 focus-visible:outline-none"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{!!previous && (
|
||||||
|
<div className="p-3">
|
||||||
|
<div className="txt-compact-small-plus mb-1">
|
||||||
|
{t("labels.from")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="txt-compact-small text-ui-fg-subtle">{previous}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!!next && (
|
||||||
|
<div className="border-t-2 border-dotted p-3">
|
||||||
|
<div className="txt-compact-small-plus mb-1">
|
||||||
|
{t("labels.to")}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="txt-compact-small text-ui-fg-subtle">{next}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeDetailsTooltip
|
||||||
+87
-2
@@ -32,6 +32,9 @@ import { useDate } from "../../../../../hooks/use-date"
|
|||||||
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
||||||
import { getPaymentsFromOrder } from "../order-payment-section"
|
import { getPaymentsFromOrder } from "../order-payment-section"
|
||||||
import ActivityItems from "./activity-items"
|
import ActivityItems from "./activity-items"
|
||||||
|
import { By, UserLink } from "../../../../../components/common/user-link"
|
||||||
|
import ChangeDetailsTooltip from "./change-details-tooltip"
|
||||||
|
import { getFormattedAddress } from "../../../../../lib/addresses"
|
||||||
|
|
||||||
type OrderTimelineProps = {
|
type OrderTimelineProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -42,6 +45,11 @@ type OrderTimelineProps = {
|
|||||||
*/
|
*/
|
||||||
const NOTE_LIMIT = 9999
|
const NOTE_LIMIT = 9999
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Order Changes that are not related to RMA flows
|
||||||
|
*/
|
||||||
|
const NON_RMA_CHANGE_TYPES = ["transfer", "update_order"]
|
||||||
|
|
||||||
export const OrderTimeline = ({ order }: OrderTimelineProps) => {
|
export const OrderTimeline = ({ order }: OrderTimelineProps) => {
|
||||||
const items = useActivityItems(order)
|
const items = useActivityItems(order)
|
||||||
|
|
||||||
@@ -118,10 +126,19 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { order_changes: orderChanges = [] } = useOrderChanges(order.id, {
|
const { order_changes: orderChanges = [] } = useOrderChanges(order.id, {
|
||||||
change_type: ["edit", "claim", "exchange", "return", "transfer"],
|
change_type: [
|
||||||
|
"edit",
|
||||||
|
"claim",
|
||||||
|
"exchange",
|
||||||
|
"return",
|
||||||
|
"transfer",
|
||||||
|
"update_order",
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const rmaChanges = orderChanges.filter((oc) => oc.change_type !== "transfer")
|
const rmaChanges = orderChanges.filter(
|
||||||
|
(oc) => !NON_RMA_CHANGE_TYPES.includes(oc.change_type)
|
||||||
|
)
|
||||||
|
|
||||||
const missingLineItemIds = getMissingLineItemIds(order, rmaChanges)
|
const missingLineItemIds = getMissingLineItemIds(order, rmaChanges)
|
||||||
const { order_items: removedLineItems = [] } = useOrderLineItems(
|
const { order_items: removedLineItems = [] } = useOrderLineItems(
|
||||||
@@ -418,6 +435,74 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const update of orderChanges.filter(
|
||||||
|
(oc) => oc.change_type === "update_order"
|
||||||
|
)) {
|
||||||
|
const updateType = update.actions[0]?.details?.type
|
||||||
|
|
||||||
|
if (updateType === "shipping_address") {
|
||||||
|
items.push({
|
||||||
|
title: (
|
||||||
|
<ChangeDetailsTooltip
|
||||||
|
title={t(`orders.activity.events.update_order.shipping_address`)}
|
||||||
|
previous={getFormattedAddress({
|
||||||
|
address: update.actions[0].details.old,
|
||||||
|
}).join(", ")}
|
||||||
|
next={getFormattedAddress({
|
||||||
|
address: update.actions[0].details.new,
|
||||||
|
}).join(", ")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
timestamp: update.created_at,
|
||||||
|
children: (
|
||||||
|
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle">
|
||||||
|
{t("fields.by")} <By id={update.created_by} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateType === "billing_address") {
|
||||||
|
items.push({
|
||||||
|
title: (
|
||||||
|
<ChangeDetailsTooltip
|
||||||
|
title={t(`orders.activity.events.update_order.billing_address`)}
|
||||||
|
previous={getFormattedAddress({
|
||||||
|
address: update.actions[0].details.old,
|
||||||
|
}).join(", ")}
|
||||||
|
next={getFormattedAddress({
|
||||||
|
address: update.actions[0].details.new,
|
||||||
|
}).join(", ")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
timestamp: update.created_at,
|
||||||
|
children: (
|
||||||
|
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle">
|
||||||
|
{t("fields.by")} <By id={update.created_by} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateType === "email") {
|
||||||
|
items.push({
|
||||||
|
title: (
|
||||||
|
<ChangeDetailsTooltip
|
||||||
|
title={t(`orders.activity.events.update_order.email`)}
|
||||||
|
previous={update.actions[0].details.old}
|
||||||
|
next={update.actions[0].details.new}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
timestamp: update.created_at,
|
||||||
|
children: (
|
||||||
|
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle">
|
||||||
|
{t("fields.by")} <By id={update.created_by} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// for (const note of notes || []) {
|
// for (const note of notes || []) {
|
||||||
// items.push({
|
// items.push({
|
||||||
// title: t("orders.activity.events.note.comment"),
|
// title: t("orders.activity.events.note.comment"),
|
||||||
|
|||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
import * as zod from "zod"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Input, toast } from "@medusajs/ui"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||||
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { useUpdateOrder } from "../../../../../hooks/api/orders"
|
||||||
|
import { CountrySelect } from "../../../../../components/inputs/country-select"
|
||||||
|
|
||||||
|
type EditOrderBillingAddressFormProps = {
|
||||||
|
order: HttpTypes.AdminOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditOrderBillingAddressSchema = zod.object({
|
||||||
|
address_1: zod.string().min(1),
|
||||||
|
address_2: zod.string().optional(),
|
||||||
|
country_code: zod.string().min(2).max(2),
|
||||||
|
city: zod.string().optional(),
|
||||||
|
postal_code: zod.string().optional(),
|
||||||
|
province: zod.string().optional(),
|
||||||
|
company: zod.string().optional(),
|
||||||
|
phone: zod.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function EditOrderBillingAddressForm({
|
||||||
|
order,
|
||||||
|
}: EditOrderBillingAddressFormProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof EditOrderBillingAddressSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
address_1: order.billing_address?.address_1 || "",
|
||||||
|
address_2: order.billing_address?.address_2 || "",
|
||||||
|
city: order.billing_address?.city || "",
|
||||||
|
company: order.billing_address?.company || "",
|
||||||
|
country_code: order.billing_address?.country_code || "",
|
||||||
|
phone: order.billing_address?.phone || "",
|
||||||
|
postal_code: order.billing_address?.postal_code || "",
|
||||||
|
province: order.billing_address?.province || "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditOrderBillingAddressSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateOrder(order.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
billing_address: data,
|
||||||
|
})
|
||||||
|
toast.success(t("orders.edit.billingAddress.requestSuccess"))
|
||||||
|
handleSuccess()
|
||||||
|
} catch (error) {
|
||||||
|
toast.error((error as Error).message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<KeyboundForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex size-full flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex-1 overflow-auto">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="address_1"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.address")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="address_2"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.address2")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="postal_code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.postalCode")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="city"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.city")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="country_code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.country")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<CountrySelect {...field} disabled />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="province"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.state")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="company"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.company")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.phone")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isLoading={isPending}
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
disabled={!!Object.keys(form.formState.errors || {}).length}
|
||||||
|
>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-order-billing-address-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrderEditBillingAddress as Component } from "./order-edit-billing-address"
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useOrder } from "../../../hooks/api"
|
||||||
|
import { DEFAULT_FIELDS } from "../order-detail/constants"
|
||||||
|
import { EditOrderBillingAddressForm } from "./components/edit-order-billing-address-form"
|
||||||
|
|
||||||
|
export const OrderEditBillingAddress = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const { order, isPending, isError, error } = useOrder(params.id!, {
|
||||||
|
fields: DEFAULT_FIELDS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isPending && isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<Heading>{t("orders.edit.billingAddress.title")}</Heading>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
|
||||||
|
{order && <EditOrderBillingAddressForm order={order} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
import * as zod from "zod"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Input, toast } from "@medusajs/ui"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||||
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { useUpdateOrder } from "../../../../../hooks/api/orders"
|
||||||
|
|
||||||
|
type EditOrderEmailFormProps = {
|
||||||
|
order: HttpTypes.AdminOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditOrderEmailSchema = zod.object({
|
||||||
|
email: zod.string().email(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function EditOrderEmailForm({ order }: EditOrderEmailFormProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof EditOrderEmailSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
email: order.email || "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditOrderEmailSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateOrder(order.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
email: data.email,
|
||||||
|
})
|
||||||
|
toast.success(
|
||||||
|
t("orders.edit.email.requestSuccess", { email: data.email })
|
||||||
|
)
|
||||||
|
handleSuccess()
|
||||||
|
} catch (error) {
|
||||||
|
toast.error((error as Error).message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<KeyboundForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex size-full flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex-1 overflow-auto">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.email")}</Form.Label>
|
||||||
|
|
||||||
|
<Form.Control>
|
||||||
|
<Input type="email" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isLoading={isPending}
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-order-email-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrderEditEmail as Component } from "./order-edit-email"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useOrder } from "../../../hooks/api"
|
||||||
|
import { DEFAULT_FIELDS } from "../order-detail/constants"
|
||||||
|
import { EditOrderEmailForm } from "./components/edit-order-email-form"
|
||||||
|
|
||||||
|
export const OrderEditEmail = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const { order, isPending, isError, error } = useOrder(params.id!, {
|
||||||
|
fields: DEFAULT_FIELDS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isPending && isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<Heading>{t("orders.edit.email.title")}</Heading>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
|
||||||
|
{order && <EditOrderEmailForm order={order} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
import * as zod from "zod"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { Button, Input, toast } from "@medusajs/ui"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||||
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { useUpdateOrder } from "../../../../../hooks/api/orders"
|
||||||
|
import { CountrySelect } from "../../../../../components/inputs/country-select"
|
||||||
|
|
||||||
|
type EditOrderShippingAddressFormProps = {
|
||||||
|
order: HttpTypes.AdminOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditOrderShippingAddressSchema = zod.object({
|
||||||
|
address_1: zod.string().min(1),
|
||||||
|
address_2: zod.string().optional(),
|
||||||
|
country_code: zod.string().min(2).max(2),
|
||||||
|
city: zod.string().optional(),
|
||||||
|
postal_code: zod.string().optional(),
|
||||||
|
province: zod.string().optional(),
|
||||||
|
company: zod.string().optional(),
|
||||||
|
phone: zod.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function EditOrderShippingAddressForm({
|
||||||
|
order,
|
||||||
|
}: EditOrderShippingAddressFormProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof EditOrderShippingAddressSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
address_1: order.shipping_address?.address_1 || "",
|
||||||
|
address_2: order.shipping_address?.address_2 || "",
|
||||||
|
city: order.shipping_address?.city || "",
|
||||||
|
company: order.shipping_address?.company || "",
|
||||||
|
country_code: order.shipping_address?.country_code || "",
|
||||||
|
phone: order.shipping_address?.phone || "",
|
||||||
|
postal_code: order.shipping_address?.postal_code || "",
|
||||||
|
province: order.shipping_address?.province || "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditOrderShippingAddressSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending } = useUpdateOrder(order.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
shipping_address: data,
|
||||||
|
})
|
||||||
|
toast.success(t("orders.edit.shippingAddress.requestSuccess"))
|
||||||
|
handleSuccess()
|
||||||
|
} catch (error) {
|
||||||
|
toast.error((error as Error).message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<KeyboundForm
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex size-full flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex-1 overflow-auto">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="address_1"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.address")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="address_2"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.address2")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="postal_code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.postalCode")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="city"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.city")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="country_code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.country")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<CountrySelect {...field} disabled />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="province"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.state")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="company"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.company")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.phone")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input size="small" {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isLoading={isPending}
|
||||||
|
type="submit"
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</KeyboundForm>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-order-shipping-address-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { OrderEditShippingAddress as Component } from "./order-edit-shipping-address"
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteDrawer } from "../../../components/modals"
|
||||||
|
import { useOrder } from "../../../hooks/api"
|
||||||
|
import { DEFAULT_FIELDS } from "../order-detail/constants"
|
||||||
|
import { EditOrderShippingAddressForm } from "./components/edit-order-shipping-address-form"
|
||||||
|
|
||||||
|
export const OrderEditShippingAddress = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const { order, isPending, isError } = useOrder(params.id!, {
|
||||||
|
fields: DEFAULT_FIELDS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isPending && isError) {
|
||||||
|
throw new Error("Order not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<Heading>{t("orders.edit.shippingAddress.title")}</Heading>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
|
||||||
|
{order && <EditOrderShippingAddressForm order={order} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -121,20 +121,28 @@ export const updateOrderWorkflow = createWorkflow(
|
|||||||
return { ...input, ...update }
|
return { ...input, ...update }
|
||||||
})
|
})
|
||||||
|
|
||||||
updateOrdersStep({
|
const updatedOrders = updateOrdersStep({
|
||||||
selector: { id: input.id },
|
selector: { id: input.id },
|
||||||
update: updateInput,
|
update: updateInput,
|
||||||
})
|
})
|
||||||
|
|
||||||
const orderChangeInput = transform({ input, order }, ({ input, order }) => {
|
const orderChangeInput = transform(
|
||||||
|
{ input, updatedOrders, order },
|
||||||
|
({ input, updatedOrders, order }) => {
|
||||||
|
const updatedOrder = updatedOrders[0]
|
||||||
|
|
||||||
const changes: RegisterOrderChangeDTO[] = []
|
const changes: RegisterOrderChangeDTO[] = []
|
||||||
if (input.shipping_address) {
|
if (input.shipping_address) {
|
||||||
changes.push({
|
changes.push({
|
||||||
change_type: "update_order" as const,
|
change_type: "update_order" as const,
|
||||||
order_id: input.id,
|
order_id: input.id,
|
||||||
reference: "shipping_address",
|
created_by: input.user_id,
|
||||||
reference_id: order.shipping_address?.id, // save previous address id as reference
|
confirmed_by: input.user_id,
|
||||||
details: input.shipping_address as Record<string, unknown>, // save what changed on the address
|
details: {
|
||||||
|
type: "shipping_address",
|
||||||
|
old: order.shipping_address,
|
||||||
|
new: updatedOrder.shipping_address,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +150,13 @@ export const updateOrderWorkflow = createWorkflow(
|
|||||||
changes.push({
|
changes.push({
|
||||||
change_type: "update_order" as const,
|
change_type: "update_order" as const,
|
||||||
order_id: input.id,
|
order_id: input.id,
|
||||||
reference: "billing_address",
|
created_by: input.user_id,
|
||||||
reference_id: order.billing_address?.id,
|
confirmed_by: input.user_id,
|
||||||
details: input.billing_address as Record<string, unknown>,
|
details: {
|
||||||
|
type: "billing_address",
|
||||||
|
old: order.billing_address,
|
||||||
|
new: updatedOrder.billing_address,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,14 +164,19 @@ export const updateOrderWorkflow = createWorkflow(
|
|||||||
changes.push({
|
changes.push({
|
||||||
change_type: "update_order" as const,
|
change_type: "update_order" as const,
|
||||||
order_id: input.id,
|
order_id: input.id,
|
||||||
reference: "email",
|
created_by: input.user_id,
|
||||||
reference_id: order.email,
|
confirmed_by: input.user_id,
|
||||||
details: { email: input.email },
|
details: {
|
||||||
|
type: "email",
|
||||||
|
old: order.email,
|
||||||
|
new: input.email,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
registerOrderChangesStep(orderChangeInput)
|
registerOrderChangesStep(orderChangeInput)
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,47 @@ export class Order {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method updates an order. It sends a request to the
|
||||||
|
* [Update Order Email](https://docs.medusajs.com/api/admin#orders_postordersid)
|
||||||
|
* API route.
|
||||||
|
*
|
||||||
|
* @param id - The order's ID.
|
||||||
|
* @param body - The update details.
|
||||||
|
* @param headers - Headers to pass in the request
|
||||||
|
* @returns The order's details.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* sdk.admin.order.update(
|
||||||
|
* "order_123",
|
||||||
|
* {
|
||||||
|
* email: "new_email@example.com",
|
||||||
|
* shipping_address: {
|
||||||
|
* first_name: "John",
|
||||||
|
* last_name: "Doe",
|
||||||
|
* address_1: "123 Main St",
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* .then(({ order }) => {
|
||||||
|
* console.log(order)
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
body: HttpTypes.AdminUpdateOrder,
|
||||||
|
headers?: ClientHeaders
|
||||||
|
) {
|
||||||
|
return await this.client.fetch<HttpTypes.AdminOrderResponse>(
|
||||||
|
`/admin/orders/${id}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method retrieves the preview of an order based on its last associated change. It sends a request to the
|
* This method retrieves the preview of an order based on its last associated change. It sends a request to the
|
||||||
* [Get Order Preview](https://docs.medusajs.com/api/admin#orders_getordersidpreview) API route.
|
* [Get Order Preview](https://docs.medusajs.com/api/admin#orders_getordersidpreview) API route.
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
export interface AdminUpdateOrder {
|
||||||
|
/**
|
||||||
|
* The order's email.
|
||||||
|
*/
|
||||||
|
email?: string
|
||||||
|
/**
|
||||||
|
* The order's shipping address.
|
||||||
|
*/
|
||||||
|
shipping_address?: OrderAddress
|
||||||
|
/**
|
||||||
|
* The order's billing address.
|
||||||
|
*/
|
||||||
|
billing_address?: OrderAddress
|
||||||
|
}
|
||||||
|
|
||||||
export interface AdminCreateOrderFulfillment {
|
export interface AdminCreateOrderFulfillment {
|
||||||
/**
|
/**
|
||||||
* The items to add to the fulfillment.
|
* The items to add to the fulfillment.
|
||||||
@@ -82,3 +97,60 @@ export interface AdminRequestOrderTransfer {
|
|||||||
internal_note?: string
|
internal_note?: string
|
||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OrderAddress {
|
||||||
|
/**
|
||||||
|
* The first name of the address.
|
||||||
|
*/
|
||||||
|
first_name?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last name of the address.
|
||||||
|
*/
|
||||||
|
last_name?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The phone number of the address.
|
||||||
|
*/
|
||||||
|
phone?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The company of the address.
|
||||||
|
*/
|
||||||
|
company?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first address line of the address.
|
||||||
|
*/
|
||||||
|
address_1?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The second address line of the address.
|
||||||
|
*/
|
||||||
|
address_2?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The city of the address.
|
||||||
|
*/
|
||||||
|
city?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The country code of the address.
|
||||||
|
*/
|
||||||
|
country_code?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The province/state of the address.
|
||||||
|
*/
|
||||||
|
province?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The postal code of the address.
|
||||||
|
*/
|
||||||
|
postal_code?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data in key-value pairs.
|
||||||
|
*/
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
@@ -1077,9 +1077,14 @@ export interface RegisterOrderChangeDTO {
|
|||||||
internal_note?: string | null
|
internal_note?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user or customer that requested the order change.
|
* The user that created the order change.
|
||||||
*/
|
*/
|
||||||
requested_by?: string
|
created_by?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user or customer that confirmed the order change.
|
||||||
|
*/
|
||||||
|
confirmed_by?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds custom data in key-value pairs.
|
* Holds custom data in key-value pairs.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { UpsertOrderAddressDTO } from "../../order"
|
|||||||
|
|
||||||
export type UpdateOrderWorkflowInput = {
|
export type UpdateOrderWorkflowInput = {
|
||||||
id: string
|
id: string
|
||||||
|
user_id: string
|
||||||
shipping_address?: UpsertOrderAddressDTO
|
shipping_address?: UpsertOrderAddressDTO
|
||||||
billing_address?: UpsertOrderAddressDTO
|
billing_address?: UpsertOrderAddressDTO
|
||||||
email?: string
|
email?: string
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export const POST = async (
|
|||||||
await updateOrderWorkflow(req.scope).run({
|
await updateOrderWorkflow(req.scope).run({
|
||||||
input: {
|
input: {
|
||||||
...req.validatedBody,
|
...req.validatedBody,
|
||||||
|
user_id: req.auth_context.actor_id,
|
||||||
id: req.params.id,
|
id: req.params.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2268,14 +2268,14 @@ export default class OrderModuleService<
|
|||||||
description: d.description,
|
description: d.description,
|
||||||
metadata: d.metadata,
|
metadata: d.metadata,
|
||||||
confirmed_at: new Date(),
|
confirmed_at: new Date(),
|
||||||
|
created_by: d.created_by,
|
||||||
|
confirmed_by: d.confirmed_by,
|
||||||
status: OrderChangeStatus.CONFIRMED,
|
status: OrderChangeStatus.CONFIRMED,
|
||||||
version: orderVersionsMap.get(d.order_id)!,
|
version: orderVersionsMap.get(d.order_id)!,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
action: ChangeActionType.UPDATE_ORDER_PROPERTIES,
|
action: ChangeActionType.UPDATE_ORDER_PROPERTIES,
|
||||||
details: d.details,
|
details: d.details,
|
||||||
reference: d.reference,
|
|
||||||
reference_id: d.reference_id,
|
|
||||||
version: orderVersionsMap.get(d.order_id)!,
|
version: orderVersionsMap.get(d.order_id)!,
|
||||||
applied: true,
|
applied: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user