fix(dashboard): Cleanup unsafe accesses to possibly undefined values in order timeline (#10454)

**What**
- Fixes an unsafe access to a field that may be undefined if an order was created through a custom workflow.
This commit is contained in:
Kasper Fabricius Kristensen
2024-12-05 15:51:35 +00:00
committed by GitHub
parent a7ad809520
commit 2b455b15a6
3 changed files with 78 additions and 69 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard): Cleanup unsafe accesses to possibly undefined values in order timeline
@@ -1,10 +1,8 @@
import { Button, IconButton, Text, Tooltip, clx, usePrompt } from "@medusajs/ui" import { Button, Text, Tooltip, clx, usePrompt } from "@medusajs/ui"
import * as Collapsible from "@radix-ui/react-collapsible" import * as Collapsible from "@radix-ui/react-collapsible"
import { PropsWithChildren, ReactNode, useMemo, useState } from "react" import { PropsWithChildren, ReactNode, useMemo, useState } from "react"
import { Link } from "react-router-dom"
import { XMarkMini } from "@medusajs/icons"
import { import {
AdminClaim, AdminClaim,
AdminExchange, AdminExchange,
@@ -16,6 +14,7 @@ import {
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { AdminOrderLineItem } from "@medusajs/types" import { AdminOrderLineItem } from "@medusajs/types"
import { By } from "../../../../../components/common/user-link"
import { import {
useCancelOrderTransfer, useCancelOrderTransfer,
useCustomer, useCustomer,
@@ -29,12 +28,11 @@ import {
} from "../../../../../hooks/api/exchanges" } from "../../../../../hooks/api/exchanges"
import { useCancelReturn, useReturns } from "../../../../../hooks/api/returns" import { useCancelReturn, useReturns } from "../../../../../hooks/api/returns"
import { useDate } from "../../../../../hooks/use-date" import { useDate } from "../../../../../hooks/use-date"
import { getFormattedAddress } from "../../../../../lib/addresses"
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 ChangeDetailsTooltip from "./change-details-tooltip"
import { getFormattedAddress } from "../../../../../lib/addresses"
type OrderTimelineProps = { type OrderTimelineProps = {
order: AdminOrder order: AdminOrder
@@ -455,7 +453,7 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
), ),
timestamp: update.created_at, timestamp: update.created_at,
children: ( children: (
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle"> <div className="text-ui-fg-subtle mt-2 flex gap-x-2 text-sm">
{t("fields.by")} <By id={update.created_by} /> {t("fields.by")} <By id={update.created_by} />
</div> </div>
), ),
@@ -477,7 +475,7 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
), ),
timestamp: update.created_at, timestamp: update.created_at,
children: ( children: (
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle"> <div className="text-ui-fg-subtle mt-2 flex gap-x-2 text-sm">
{t("fields.by")} <By id={update.created_by} /> {t("fields.by")} <By id={update.created_by} />
</div> </div>
), ),
@@ -495,7 +493,7 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
), ),
timestamp: update.created_at, timestamp: update.created_at,
children: ( children: (
<div className="mt-2 flex text-sm gap-x-2 text-ui-fg-subtle"> <div className="text-ui-fg-subtle mt-2 flex gap-x-2 text-sm">
{t("fields.by")} <By id={update.created_by} /> {t("fields.by")} <By id={update.created_by} />
</div> </div>
), ),
@@ -674,64 +672,67 @@ const OrderActivityCollapsible = ({
) )
} }
const NoteBody = ({ note }: { note: Note }) => { /**
const { t } = useTranslation() * TODO: Add once notes are supported.
const prompt = usePrompt() */
// const NoteBody = ({ note }: { note: Note }) => {
// const { t } = useTranslation()
// const prompt = usePrompt()
const { first_name, last_name, email } = note.author || {} // const { first_name, last_name, email } = note.author || {}
const name = [first_name, last_name].filter(Boolean).join(" ") // const name = [first_name, last_name].filter(Boolean).join(" ")
const byLine = t("orders.activity.events.note.byLine", { // const byLine = t("orders.activity.events.note.byLine", {
author: name || email, // author: name || email,
}) // })
const { mutateAsync } = {} // useAdminDeleteNote(note.id) // const { mutateAsync } = {} // useAdminDeleteNote(note.id)
const handleDelete = async () => { // const handleDelete = async () => {
const res = await prompt({ // const res = await prompt({
title: t("general.areYouSure"), // title: t("general.areYouSure"),
description: "This action cannot be undone", // description: "This action cannot be undone",
confirmText: t("actions.delete"), // confirmText: t("actions.delete"),
cancelText: t("actions.cancel"), // cancelText: t("actions.cancel"),
}) // })
if (!res) { // if (!res) {
return // return
} // }
await mutateAsync() // await mutateAsync()
} // }
return ( // return (
<div className="flex flex-col gap-y-2 pt-2"> // <div className="flex flex-col gap-y-2 pt-2">
<div className="bg-ui-bg-component shadow-borders-base group grid grid-cols-[1fr_20px] items-start gap-x-2 text-pretty rounded-r-2xl rounded-bl-md rounded-tl-xl px-3 py-1.5"> // <div className="bg-ui-bg-component shadow-borders-base group grid grid-cols-[1fr_20px] items-start gap-x-2 text-pretty rounded-r-2xl rounded-bl-md rounded-tl-xl px-3 py-1.5">
<div className="flex h-full min-h-7 items-center"> // <div className="flex h-full min-h-7 items-center">
<Text size="xsmall" className="text-ui-fg-subtle"> // <Text size="xsmall" className="text-ui-fg-subtle">
{note.value} // {note.value}
</Text> // </Text>
</div> // </div>
<IconButton // <IconButton
size="small" // size="small"
variant="transparent" // variant="transparent"
className="transition-fg invisible opacity-0 group-hover:visible group-hover:opacity-100" // className="transition-fg invisible opacity-0 group-hover:visible group-hover:opacity-100"
type="button" // type="button"
onClick={handleDelete} // onClick={handleDelete}
> // >
<span className="sr-only"> // <span className="sr-only">
{t("orders.activity.comment.deleteButtonText")} // {t("orders.activity.comment.deleteButtonText")}
</span> // </span>
<XMarkMini className="text-ui-fg-muted" /> // <XMarkMini className="text-ui-fg-muted" />
</IconButton> // </IconButton>
</div> // </div>
<Link // <Link
to={`/settings/users/${note.author_id}`} // to={`/settings/users/${note.author_id}`}
className="text-ui-fg-subtle hover:text-ui-fg-base transition-fg w-fit" // className="text-ui-fg-subtle hover:text-ui-fg-base transition-fg w-fit"
> // >
<Text size="small">{byLine}</Text> // <Text size="small">{byLine}</Text>
</Link> // </Link>
</div> // </div>
) // )
} // }
const FulfillmentCreatedBody = ({ const FulfillmentCreatedBody = ({
fulfillment, fulfillment,
@@ -1085,11 +1086,15 @@ function getMissingLineItemIds(order: AdminOrder, changes: AdminOrderChange[]) {
changes.forEach((change) => { changes.forEach((change) => {
change.actions.forEach((action) => { change.actions.forEach((action) => {
if (!action.details?.reference_id) {
return
}
if ( if (
(action.details!.reference_id as string).startsWith("ordli_") && (action.details.reference_id as string).startsWith("ordli_") &&
!existingItemsMap.has(action.details!.reference_id as string) !existingItemsMap.has(action.details.reference_id as string)
) { ) {
retIds.add(action.details!.reference_id as string) retIds.add(action.details.reference_id as string)
} }
}) })
}) })
@@ -1,4 +1,3 @@
import { RouteFocusModal } from "../../../components/modals" import { RouteFocusModal } from "../../../components/modals"
import { CreateShippingProfileForm } from "./components/create-shipping-profile-form" import { CreateShippingProfileForm } from "./components/create-shipping-profile-form"