fix(dashboard,js-sdk): fixes from rma flows testing (#8826)

This commit is contained in:
Riqwan Thamir
2024-08-28 10:59:36 +02:00
committed by GitHub
parent 2ee374fafc
commit c72b4847b9
15 changed files with 335 additions and 30 deletions

View File

@@ -848,6 +848,9 @@
},
"edits": {
"title": "Edit order",
"confirm": "Confirm Edit",
"confirmText": "You are about to confirm an Order Edit. This action cannot be undone.",
"cancel": "Cancel Edit",
"currentItems": "Current items",
"currentItemsDescription": "Adjust item quantity or remove.",
"addItemsDescription": "You can add new items to the order.",
@@ -874,6 +877,8 @@
},
"returns": {
"create": "Create Return",
"confirm": "Confirm Return",
"confirmText": "You are about to confirm a Return. This action cannot be undone.",
"inbound": "Inbound",
"outbound": "Outbound",
"sendNotification": "Send notification",
@@ -903,7 +908,7 @@
},
"receive": {
"action": "Receive items",
"receive": "Return {{label}}",
"receiveItems": "{{ returnType }} {{ id }}",
"restockAll": "Restock all items",
"itemsLabel": "Items received",
"title": "Receive items for #{{returnId}}",
@@ -928,6 +933,9 @@
},
"claims": {
"create": "Create Claim",
"confirm": "Confirm Claim",
"confirmText": "You are about to confirm a Claim. This action cannot be undone.",
"cancel": "Cancel Claim",
"manage": "Manage Claim",
"outbound": "Outbound",
"outboundItemAdded": "{{itemsCount}}x added through claim",
@@ -956,6 +964,9 @@
"exchanges": {
"create": "Create Exchange",
"manage": "Manage Exchange",
"confirm": "Confirm Exchange",
"confirmText": "You are about to confirm an Exchange. This action cannot be undone.",
"cancel": "Cancel Exchange",
"outbound": "Outbound",
"outboundItemAdded": "{{itemsCount}}x added through exchange",
"outboundTotal": "Outbound total",
@@ -1003,7 +1014,7 @@
"shipment": {
"title": "Mark fulfillment shipped",
"trackingNumber": "Tracking number",
"addTracking": "Add additional tracking number",
"addTracking": "Add tracking number",
"sendNotification": "Send notification",
"sendNotificationHint": "Notify the customer about this shipment.",
"toastCreated": "Shipment created successfully."
@@ -1078,6 +1089,10 @@
"deleteButtonText": "Delete comment"
},
"events": {
"common": {
"toReturn": "To return",
"toSend": "To send"
},
"placed": {
"title": "Order placed",
"fromSalesChannel": "from {{salesChannel}}"

View File

@@ -15,6 +15,7 @@ import {
Switch,
Text,
toast,
usePrompt,
} from "@medusajs/ui"
import { useEffect, useMemo, useState } from "react"
import { useFieldArray, useForm } from "react-hook-form"
@@ -305,8 +306,21 @@ export const ClaimCreateForm = ({
const showInboundItemsPlaceholder = !inboundItems.length
const shippingOptionId = form.watch("inbound_option_id")
const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => {
const res = await prompt({
title: t("general.areYouSure"),
description: t("orders.claims.confirmText"),
confirmText: t("actions.continue"),
cancelText: t("actions.cancel"),
variant: "confirmation",
})
if (!res) {
return
}
await confirmClaimRequest(
{ no_notification: !data.send_notification },
{
@@ -518,6 +532,7 @@ export const ClaimCreateForm = ({
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
key="submit-button"
type="submit"
@@ -859,6 +874,8 @@ export const ClaimCreateForm = ({
}}
/>
</div>
<div className="p-8" />
</div>
</RouteFocusModal.Body>
<RouteFocusModal.Footer>
@@ -871,7 +888,7 @@ export const ClaimCreateForm = ({
variant="secondary"
size="small"
>
{t("actions.cancel")}
{t("orders.claims.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
@@ -881,7 +898,7 @@ export const ClaimCreateForm = ({
size="small"
isLoading={isRequestLoading}
>
{t("actions.save")}
{t("orders.claims.confirm")}
</Button>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { AdminOrder, AdminOrderPreview } from "@medusajs/types"
import { Button, Heading, Input, Switch, toast } from "@medusajs/ui"
import { Button, Heading, Input, Switch, toast, usePrompt } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
@@ -10,13 +10,13 @@ import {
} from "../../../../../components/modals"
import { Form } from "../../../../../components/common/form"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { CreateOrderEditSchemaType, OrderEditCreateSchema } from "./schema"
import {
useCancelOrderEdit,
useRequestOrderEdit,
} from "../../../../../hooks/api/order-edits"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { OrderEditItemsSection } from "./order-edit-items-section"
import { CreateOrderEditSchemaType, OrderEditCreateSchema } from "./schema"
type ReturnCreateFormProps = {
order: AdminOrder
@@ -55,8 +55,22 @@ export const OrderEditCreateForm = ({
resolver: zodResolver(OrderEditCreateSchema),
})
const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => {
try {
const res = await prompt({
title: t("general.areYouSure"),
description: t("orders.edits.confirmText"),
confirmText: t("actions.continue"),
cancelText: t("actions.cancel"),
variant: "confirmation",
})
if (!res) {
return
}
await requestOrderEdit()
toast.success(t("orders.edits.createSuccessToast"))
@@ -185,6 +199,8 @@ export const OrderEditCreateForm = ({
}}
/>
</div>
<div className="p-8" />
</div>
</RouteFocusModal.Body>
<RouteFocusModal.Footer>
@@ -192,7 +208,7 @@ export const OrderEditCreateForm = ({
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button type="button" variant="secondary" size="small">
{t("actions.cancel")}
{t("orders.edits.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
@@ -202,7 +218,7 @@ export const OrderEditCreateForm = ({
size="small"
isLoading={isRequestRunning}
>
{t("actions.save")}
{t("orders.edits.confirm")}
</Button>
</div>
</div>

View File

@@ -8,6 +8,7 @@ import {
IconButton,
Switch,
toast,
usePrompt,
} from "@medusajs/ui"
import { useEffect, useMemo, useState } from "react"
import { useForm } from "react-hook-form"
@@ -148,9 +149,22 @@ export const ExchangeCreateForm = ({
})
const shippingOptionId = form.watch("inbound_option_id")
const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => {
try {
const res = await prompt({
title: t("general.areYouSure"),
description: t("orders.exchanges.confirmText"),
confirmText: t("actions.continue"),
cancelText: t("actions.cancel"),
variant: "confirmation",
})
if (!res) {
return
}
await confirmExchangeRequest({ no_notification: !data.send_notification })
handleSuccess()
@@ -390,6 +404,8 @@ export const ExchangeCreateForm = ({
}}
/>
</div>
<div className="p-8" />
</div>
</RouteFocusModal.Body>
<RouteFocusModal.Footer>
@@ -402,9 +418,10 @@ export const ExchangeCreateForm = ({
variant="secondary"
size="small"
>
{t("actions.cancel")}
{t("orders.exchanges.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
key="submit-button"
type="submit"
@@ -412,7 +429,7 @@ export const ExchangeCreateForm = ({
size="small"
isLoading={isRequestLoading}
>
{t("actions.save")}
{t("orders.exchanges.confirm")}
</Button>
</div>
</div>

View File

@@ -10,6 +10,7 @@ import {
Switch,
Text,
toast,
usePrompt,
} from "@medusajs/ui"
import { useEffect, useMemo, useState } from "react"
import { useFieldArray, useForm } from "react-hook-form"
@@ -240,9 +241,22 @@ export const ReturnCreateForm = ({
const showPlaceholder = !items.length
const locationId = form.watch("location_id")
const shippingOptionId = form.watch("option_id")
const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => {
try {
const res = await prompt({
title: t("general.areYouSure"),
description: t("orders.returns.confirmText"),
confirmText: t("actions.continue"),
cancelText: t("actions.cancel"),
variant: "confirmation",
})
if (!res) {
return
}
await confirmReturnRequest({ no_notification: !data.send_notification })
handleSuccess()
@@ -702,6 +716,8 @@ export const ReturnCreateForm = ({
}}
/>
</div>
<div className="p-8" />
</div>
</RouteFocusModal.Body>
<RouteFocusModal.Footer>
@@ -709,7 +725,7 @@ export const ReturnCreateForm = ({
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button type="button" variant="secondary" size="small">
{t("actions.cancel")}
{t("orders.returns.cancel.title")}
</Button>
</RouteFocusModal.Close>
<Button
@@ -719,7 +735,7 @@ export const ReturnCreateForm = ({
size="small"
isLoading={isRequestLoading}
>
{t("actions.save")}
{t("orders.returns.confirm")}
</Button>
</div>
</div>

View File

@@ -65,7 +65,7 @@ export const ActiveOrderClaimSection = ({
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
<Button size="small" variant="secondary" onClick={onCancelClaim}>
{t("actions.cancel")}
{t("orders.claims.cancel")}
</Button>
<Button size="small" variant="secondary" onClick={onContinueClaim}>

View File

@@ -65,7 +65,7 @@ export const ActiveOrderExchangeSection = ({
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
<Button size="small" variant="secondary" onClick={onCancelExchange}>
{t("actions.cancel")}
{t("orders.exchanges.cancel")}
</Button>
<Button

View File

@@ -68,7 +68,7 @@ export const ActiveOrderReturnSection = ({
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
<Button size="small" variant="secondary" onClick={onCancelReturn}>
{t("actions.cancel")}
{t("orders.returns.cancel.title")}
</Button>
<Button size="small" variant="secondary" onClick={onContinueReturn}>

View File

@@ -46,7 +46,7 @@ const CopyPaymentLink = React.forwardRef<any, CopyPaymentLinkProps>(
}
setTimeout(() => {
setText("CopyPaymentLink")
setText("Copy")
}, 500)
}, [done])

View File

@@ -0,0 +1,136 @@
import {
AdminClaim,
AdminExchange,
AdminOrderLineItem,
AdminReturn,
} from "@medusajs/types"
import { Popover, Text } from "@medusajs/ui"
import { useState } from "react"
import { useTranslation } from "react-i18next"
import { Thumbnail } from "../../../../../components/common/thumbnail"
type ActivityItemsProps = {
itemsToSend?:
| AdminClaim["additional_items"]
| AdminExchange["additional_items"]
itemsToReturn?: AdminReturn["items"]
itemsMap?: Map<string, AdminOrderLineItem>
title: string
}
function ActivityItems(props: ActivityItemsProps) {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const itemsToSend = props.itemsToSend
const itemsToReturn = props.itemsToReturn
const itemsMap = props.itemsMap
const title = props.title
const handleMouseEnter = () => {
setOpen(true)
}
const handleMouseLeave = () => {
setOpen(false)
}
if (!itemsToSend?.length && !itemsToReturn?.length) {
return
}
return (
<Popover open={open}>
<Popover.Trigger
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
autoFocus={false}
>
<Text size="small" leading="compact" weight="plus">
{title}
</Text>
</Popover.Trigger>
<Popover.Content
align="center"
side="top"
className="bg-ui-bg-component p-0"
>
<div className="flex flex-col">
{!!itemsToSend?.length && (
<div className="p-3">
<div className="txt-compact-small-plus">
{t("orders.activity.events.common.toSend")}
</div>
<div className="flex flex-col items-center">
{itemsToSend?.map((item) => {
const originalItem = itemsMap?.get(item.item_id)!
return (
<div
className="flex flex-1 items-center gap-x-3"
key={item.id}
>
<Text size="small" className="text-ui-fg-subtle">
{item.quantity}x
</Text>
<Thumbnail
src={originalItem?.variant?.product?.thumbnail}
/>
<div className="">
<Text className="txt-compact-small text-ui-fg-subtle">
{`${originalItem?.variant?.title} · ${originalItem?.variant?.product?.title}`}
</Text>
</div>
</div>
)
})}
<div className="flex flex-1 flex-row items-center gap-2"></div>
</div>
</div>
)}
{!!itemsToReturn?.length && (
<div className="border-t-2 border-dotted p-3">
<div className="txt-compact-small-plus">
{t("orders.activity.events.common.toReturn")}
</div>
<div className="flex flex-col items-center">
{itemsToReturn?.map((item) => {
const originalItem = itemsMap?.get(item.item_id)!
return (
<div
className="flex flex-1 items-center gap-x-3"
key={item.id}
>
<Text size="small" className="text-ui-fg-subtle">
{item.quantity}x
</Text>
<Thumbnail
src={originalItem?.variant?.product?.thumbnail}
/>
<div className="">
<Text className="txt-compact-small text-ui-fg-subtle">
{`${originalItem?.variant?.title} · ${originalItem?.variant?.product?.title}`}
</Text>
</div>
</div>
)
})}
<div className="flex flex-1 flex-row items-center gap-2"></div>
</div>
</div>
)}
</div>
</Popover.Content>
</Popover>
)
}
export default ActivityItems

View File

@@ -1,4 +1,4 @@
import { IconButton, Text, Tooltip, clx, usePrompt, Button } from "@medusajs/ui"
import { Button, IconButton, Text, Tooltip, clx, usePrompt } from "@medusajs/ui"
import * as Collapsible from "@radix-ui/react-collapsible"
import { PropsWithChildren, ReactNode, useMemo, useState } from "react"
@@ -14,12 +14,14 @@ import {
} from "@medusajs/types"
import { useTranslation } from "react-i18next"
import { AdminOrderLineItem } from "@medusajs/types"
import { useClaims } from "../../../../../hooks/api/claims"
import { useExchanges } from "../../../../../hooks/api/exchanges"
import { useCancelReturn, useReturns } from "../../../../../hooks/api/returns"
import { useDate } from "../../../../../hooks/use-date"
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
import { getPaymentsFromOrder } from "../order-payment-section"
import { useDate } from "../../../../../hooks/use-date"
import ActivityItems from "./activity-items"
type OrderTimelineProps = {
order: AdminOrder
@@ -43,6 +45,9 @@ export const OrderTimeline = ({ order }: OrderTimelineProps) => {
title={item.title}
timestamp={item.timestamp}
isFirst={index === items.length - 1}
itemsToSend={item.itemsToSend}
itemsToReturn={item.itemsToReturn}
itemsMap={item.itemsMap}
>
{item.children}
</OrderActivityItem>
@@ -64,6 +69,9 @@ export const OrderTimeline = ({ order }: OrderTimelineProps) => {
key={index}
title={item.title}
timestamp={item.timestamp}
itemsToSend={item.itemsToSend}
itemsToReturn={item.itemsToReturn}
itemsMap={item.itemsMap}
>
{item.children}
</OrderActivityItem>
@@ -74,6 +82,9 @@ export const OrderTimeline = ({ order }: OrderTimelineProps) => {
title={firstItem.title}
timestamp={firstItem.timestamp}
isFirst
itemsToSend={firstItem.itemsToSend}
itemsToReturn={firstItem.itemsToReturn}
itemsMap={firstItem.itemsMap}
>
{firstItem.children}
</OrderActivityItem>
@@ -85,10 +96,20 @@ type Activity = {
title: string
timestamp: string | Date
children?: ReactNode
itemsToSend?: (
| AdminClaim["additional_items"]
| AdminExchange["additional_items"]
)[]
itemsToReturn?: AdminReturn["items"]
itemsMap?: Map<string, AdminOrderLineItem>
}
const useActivityItems = (order: AdminOrder) => {
const useActivityItems = (order: AdminOrder): Activity[] => {
const { t } = useTranslation()
const itemsMap = useMemo(
() => new Map(order?.items?.map((i) => [i.id, i])),
[order.items]
)
const { returns = [] } = useReturns({
order_id: order.id,
@@ -224,6 +245,8 @@ const useActivityItems = (order: AdminOrder) => {
returnId: ret.id.slice(-7),
}),
timestamp: ret.created_at,
itemsToReturn: ret?.items,
itemsMap,
children: <ReturnBody orderReturn={ret} isCreated={!ret.canceled_at} />,
})
@@ -242,6 +265,8 @@ const useActivityItems = (order: AdminOrder) => {
returnId: ret.id.slice(-7),
}),
timestamp: ret.received_at,
itemsToReturn: ret?.items,
itemsMap,
children: <ReturnBody orderReturn={ret} isReceived />,
})
}
@@ -255,6 +280,9 @@ const useActivityItems = (order: AdminOrder) => {
claimId: claim.id.slice(-7),
}),
timestamp: claim.created_at,
itemsToSend: claim.additional_items,
itemsToReturn: claimReturn?.items,
itemsMap,
children: <ClaimBody claim={claim} claimReturn={claimReturn} />,
})
}
@@ -267,6 +295,9 @@ const useActivityItems = (order: AdminOrder) => {
exchangeId: exchange.id.slice(-7),
}),
timestamp: exchange.created_at,
itemsToSend: exchange.additional_items,
itemsToReturn: exchangeReturn?.items,
itemsMap,
children: (
<ExchangeBody exchange={exchange} exchangeReturn={exchangeReturn} />
),
@@ -310,6 +341,11 @@ type OrderActivityItemProps = PropsWithChildren<{
title: string
timestamp: string | Date
isFirst?: boolean
itemsToSend?:
| AdminClaim["additional_items"]
| AdminExchange["additional_items"]
itemsToReturn?: AdminReturn["items"]
itemsMap?: Map<string, AdminOrderLineItem>
}>
const OrderActivityItem = ({
@@ -317,6 +353,9 @@ const OrderActivityItem = ({
timestamp,
isFirst = false,
children,
itemsToSend,
itemsToReturn,
itemsMap,
}: OrderActivityItemProps) => {
const { getFullDate, getRelativeDate } = useDate()
@@ -336,9 +375,19 @@ const OrderActivityItem = ({
})}
>
<div className="flex items-center justify-between">
<Text size="small" leading="compact" weight="plus">
{title}
</Text>
{itemsToSend?.length || itemsToReturn?.length ? (
<ActivityItems
key={title}
title={title}
itemsToSend={itemsToSend}
itemsToReturn={itemsToReturn}
itemsMap={itemsMap}
/>
) : (
<Text size="small" leading="compact" weight="plus">
{title}
</Text>
)}
{timestamp && (
<Tooltip
content={getFullDate({ date: timestamp, includeTime: true })}
@@ -403,6 +452,9 @@ const OrderActivityCollapsible = ({
key={index}
title={item.title}
timestamp={item.timestamp}
itemsToSend={item.itemsToSend}
itemsToReturn={item.itemsToReturn}
itemsMap={item.itemsMap}
>
{item.children}
</OrderActivityItem>

View File

@@ -191,13 +191,29 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
<ButtonMenu
groups={[
{
actions: receivableReturns.map((r) => ({
label: t("orders.returns.receive.receive", {
label: `#${r.id.slice(-7)}`,
}),
icon: <ArrowLongRight />,
to: `/orders/${order.id}/returns/${r.id}/receive`,
})),
actions: receivableReturns.map((r) => {
let id = r.id
let returnType = "Return"
if (r.exchange_id) {
id = r.exchange_id
returnType = "Exchange"
}
if (r.claim_id) {
id = r.claim_id
returnType = "Claim"
}
return {
label: t("orders.returns.receive.receiveItems", {
id: `#${id.slice(-7)}`,
returnType,
}),
icon: <ArrowLongRight />,
to: `/orders/${order.id}/returns/${r.id}/receive`,
}
}),
},
]}
>
@@ -640,6 +656,7 @@ const ClaimBreakdown = ({
>
<div className="flex items-center gap-2">
<ArrowDownRightMini className="text-ui-fg-muted" />
<Text>
{t(`orders.claims.outboundItemAdded`, {
itemsCount: items.reduce(

View File

@@ -34,6 +34,22 @@ export const OrderDetail = () => {
initialData,
}
)
// TODO: Retrieve endpoints don't have an order ability, so a JS sort until this is available
if (order) {
order.items = order.items.sort((itemA, itemB) => {
if (itemA.created_at > itemB.created_at) {
return 1
}
if (itemA.created_at < itemB.created_at) {
return -1
}
return 0
})
}
const { order: orderPreview, isLoading: isPreviewLoading } = useOrderPreview(
id!
)

View File

@@ -31,6 +31,7 @@ export class Order {
return await this.client.fetch<HttpTypes.AdminOrderPreviewResponse>(
`/admin/orders/${id}/preview`,
{
query,
headers,
}
)

View File

@@ -14,6 +14,8 @@ export interface AdminOrder extends BaseOrder {
fulfillments?: BaseOrderFulfillment[]
}
export interface AdminOrderLineItem extends BaseOrderLineItem {}
export interface AdminOrderFulfillment extends BaseOrderFulfillment {}
export interface AdminOrderLineItem extends BaseOrderLineItem {}