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:
Frane Polić
2024-12-04 14:56:40 +01:00
committed by GitHub
parent a5263083fa
commit 340769595a
27 changed files with 1202 additions and 63 deletions
@@ -1,5 +1,6 @@
import { Avatar, Text } from "@medusajs/ui"
import { Link } from "react-router-dom"
import { useUser } from "../../../hooks/api/users"
type UserLinkProps = {
id: string
@@ -32,3 +33,13 @@ export const UserLink = ({
</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 }
}
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 = (
id: string,
query?: HttpTypes.AdminOrderFilters,
@@ -4146,6 +4146,65 @@
],
"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": {
"type": "object",
"properties": {
@@ -5364,6 +5423,26 @@
"declined"
],
"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": [
@@ -5377,7 +5456,8 @@
"claim",
"exchange",
"edit",
"transfer"
"transfer",
"update_order"
],
"additionalProperties": false
}
@@ -5426,6 +5506,7 @@
"transfer",
"payment",
"edits",
"edit",
"returns",
"claims",
"exchanges",
@@ -10849,6 +10930,12 @@
},
"removed": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
@@ -10857,7 +10944,9 @@
"available",
"inStock",
"added",
"removed"
"removed",
"from",
"to"
],
"additionalProperties": false
},
@@ -10951,6 +11040,9 @@
"subtitle": {
"type": "string"
},
"by": {
"type": "string"
},
"item": {
"type": "string"
},
@@ -11360,6 +11452,7 @@
"discountable",
"handle",
"subtitle",
"by",
"item",
"qty",
"limit",
@@ -1002,6 +1002,20 @@
"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": {
"create": "Create Return",
"confirm": "Confirm Return",
@@ -1301,6 +1315,11 @@
"requested": "Order transfer #{{transferId}} requested",
"confirmed": "Order transfer #{{transferId}} confirmed",
"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",
"inStock": "In stock",
"added": "Added",
"removed": "Removed"
"removed": "Removed",
"from": "From",
"to": "To"
},
"fields": {
"amount": "Amount",
@@ -2630,6 +2651,7 @@
"discountable": "Discountable",
"handle": "Handle",
"subtitle": "Subtitle",
"by": "By",
"item": "Item",
"qty": "qty.",
"limit": "Limit",
@@ -329,6 +329,20 @@ export const RouteMap: RouteObject[] = [
lazy: () =>
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"),
},
],
},
],
@@ -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
@@ -32,6 +32,9 @@ import { useDate } from "../../../../../hooks/use-date"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { getPaymentsFromOrder } from "../order-payment-section"
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 = {
order: AdminOrder
@@ -42,6 +45,11 @@ type OrderTimelineProps = {
*/
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) => {
const items = useActivityItems(order)
@@ -118,10 +126,19 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
const { t } = useTranslation()
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 { 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 || []) {
// items.push({
// title: t("orders.activity.events.note.comment"),
@@ -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>
)
}
@@ -0,0 +1 @@
export * from "./edit-order-billing-address-form"
@@ -0,0 +1 @@
export { OrderEditBillingAddress as Component } from "./order-edit-billing-address"
@@ -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>
)
}
@@ -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>
)
}
@@ -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>
)
}
@@ -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>
)
}
@@ -0,0 +1 @@
export * from "./edit-order-shipping-address-form"
@@ -0,0 +1 @@
export { OrderEditShippingAddress as Component } from "./order-edit-shipping-address"
@@ -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>
)
}