feat(dashboard): allow custom shopping prices for claims/exchanges (#8912)

**What**
- add editing custom shipping price for claims and exchanges

---

CLOSES CC-369
This commit is contained in:
Frane Polić
2024-09-03 07:17:24 +00:00
committed by GitHub
parent 1a63209290
commit 6652db45ef
4 changed files with 261 additions and 47 deletions
@@ -47,6 +47,7 @@ import {
useRemoveClaimInboundItem, useRemoveClaimInboundItem,
useUpdateClaimInboundItem, useUpdateClaimInboundItem,
useUpdateClaimInboundShipping, useUpdateClaimInboundShipping,
useUpdateClaimOutboundShipping,
} from "../../../../../hooks/api/claims" } from "../../../../../hooks/api/claims"
import { useUpdateReturn } from "../../../../../hooks/api/returns.tsx" import { useUpdateReturn } from "../../../../../hooks/api/returns.tsx"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
@@ -78,10 +79,17 @@ export const ClaimCreateForm = ({
* STATE * STATE
*/ */
const { setIsOpen } = useStackedModal() const { setIsOpen } = useStackedModal()
const [isShippingPriceEdit, setIsShippingPriceEdit] = useState(false) const [isShippingInboundPriceEdit, setIsShippingInboundPriceEdit] =
const [customShippingAmount, setCustomShippingAmount] = useState< useState(false)
number | string const [isShippingOutboundPriceEdit, setIsShippingOutboundPriceEdit] =
>(0) useState(false)
const [customInboundShippingAmount, setCustomInboundShippingAmount] =
useState<number | string>(0)
const [customOutboundShippingAmount, setCustomOutboundShippingAmount] =
useState<number | string>(0)
const [inventoryMap, setInventoryMap] = useState< const [inventoryMap, setInventoryMap] = useState<
Record<string, InventoryLevelDTO[]> Record<string, InventoryLevelDTO[]>
>({}) >({})
@@ -113,6 +121,11 @@ export const ClaimCreateForm = ({
isPending: isUpdatingInboundShipping, isPending: isUpdatingInboundShipping,
} = useUpdateClaimInboundShipping(claim.id, order.id) } = useUpdateClaimInboundShipping(claim.id, order.id)
const {
mutateAsync: updateOutboundShipping,
isPending: isUpdatingOutboundShipping,
} = useUpdateClaimOutboundShipping(claim.id, order.id)
const { const {
mutateAsync: deleteInboundShipping, mutateAsync: deleteInboundShipping,
isPending: isDeletingInboundShipping, isPending: isDeletingInboundShipping,
@@ -133,6 +146,7 @@ export const ClaimCreateForm = ({
isAddingInboundShipping || isAddingInboundShipping ||
isUpdatingInboundShipping || isUpdatingInboundShipping ||
isDeletingInboundShipping || isDeletingInboundShipping ||
isUpdatingOutboundShipping ||
isAddingInboundItem || isAddingInboundItem ||
isRemovingInboundItem || isRemovingInboundItem ||
isUpdatingInboundItem || isUpdatingInboundItem ||
@@ -235,10 +249,28 @@ export const ClaimCreateForm = ({
) )
) )
const inboundShipping = preview.shipping_methods.find((s) => {
return !!s.actions?.find(
(a) => a.action === "SHIPPING_ADD" && !!a.return_id
)
})
const outboundShipping = preview.shipping_methods.find((s) => { const outboundShipping = preview.shipping_methods.find((s) => {
return !!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id) return !!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id)
}) })
useEffect(() => {
if (inboundShipping) {
setCustomInboundShippingAmount(inboundShipping.total)
}
}, [inboundShipping])
useEffect(() => {
if (outboundShipping) {
setCustomOutboundShippingAmount(outboundShipping.total)
}
}, [outboundShipping])
const { const {
fields: inboundItems, fields: inboundItems,
append, append,
@@ -291,7 +323,7 @@ export const ClaimCreateForm = ({
}, [previewItems]) }, [previewItems])
useEffect(() => { useEffect(() => {
const method = preview.shipping_methods.find( let method = preview.shipping_methods.find(
(s) => (s) =>
!!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !!a.return_id) !!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !!a.return_id)
) )
@@ -299,14 +331,27 @@ export const ClaimCreateForm = ({
if (method) { if (method) {
form.setValue("inbound_option_id", method.shipping_option_id) form.setValue("inbound_option_id", method.shipping_option_id)
} }
method = preview.shipping_methods.find(
(s) =>
!!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id)
)
if (method) {
form.setValue("outbound_option_id", method.shipping_option_id)
}
}, [preview.shipping_methods]) }, [preview.shipping_methods])
useEffect(() => { useEffect(() => {
form.setValue("location_id", orderReturn?.location_id) form.setValue("location_id", orderReturn?.location_id)
}, [orderReturn]) }, [orderReturn])
const showInboundItemsPlaceholder = !inboundItems.length const showInboundItemsPlaceholder = !inboundPreviewItems.length
const shippingOptionId = form.watch("inbound_option_id") const showOutboundItemsPlaceholder = !outboundPreviewItems.length
const inboundShippingOptionId = form.watch("inbound_option_id")
const outboundShippingOptionId = form.watch("outbound_option_id")
const prompt = usePrompt() const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
@@ -393,10 +438,16 @@ export const ClaimCreateForm = ({
} }
useEffect(() => { useEffect(() => {
if (isShippingPriceEdit) { if (isShippingInboundPriceEdit) {
document.getElementById("js-shipping-input")?.focus() document.getElementById("js-shipping-inbound-input")?.focus()
} }
}, [isShippingPriceEdit]) }, [isShippingInboundPriceEdit])
useEffect(() => {
if (isShippingOutboundPriceEdit) {
document.getElementById("js-shipping-outbound-input")?.focus()
}
}, [isShippingOutboundPriceEdit])
const showLevelsWarning = useMemo(() => { const showLevelsWarning = useMemo(() => {
if (!locationId) { if (!locationId) {
@@ -753,22 +804,22 @@ export const ClaimCreateForm = ({
</span> </span>
<span className="txt-small text-ui-fg-subtle flex items-center"> <span className="txt-small text-ui-fg-subtle flex items-center">
{!isShippingPriceEdit && ( {!isShippingInboundPriceEdit && (
<IconButton <IconButton
onClick={() => setIsShippingPriceEdit(true)} onClick={() => setIsShippingInboundPriceEdit(true)}
variant="transparent" variant="transparent"
className="text-ui-fg-muted" className="text-ui-fg-muted"
disabled={ disabled={
showInboundItemsPlaceholder || !shippingOptionId showInboundItemsPlaceholder || !inboundShippingOptionId
} }
> >
<PencilSquare /> <PencilSquare />
</IconButton> </IconButton>
)} )}
{isShippingPriceEdit ? ( {isShippingInboundPriceEdit ? (
<CurrencyInput <CurrencyInput
id="js-shipping-input" id="js-shipping-inbound-input"
onBlur={() => { onBlur={() => {
let actionId let actionId
@@ -786,9 +837,9 @@ export const ClaimCreateForm = ({
}) })
const customPrice = const customPrice =
customShippingAmount === "" customInboundShippingAmount === ""
? null ? null
: parseFloat(customShippingAmount) : parseFloat(customInboundShippingAmount)
if (actionId) { if (actionId) {
updateInboundShipping( updateInboundShipping(
@@ -803,15 +854,15 @@ export const ClaimCreateForm = ({
} }
) )
} }
setIsShippingPriceEdit(false) setIsShippingInboundPriceEdit(false)
}} }}
symbol={ symbol={
currencies[order.currency_code.toUpperCase()] currencies[order.currency_code.toUpperCase()]
.symbol_native .symbol_native
} }
code={order.currency_code} code={order.currency_code}
onValueChange={setCustomShippingAmount} onValueChange={setCustomInboundShippingAmount}
value={customShippingAmount} value={customInboundShippingAmount}
disabled={showInboundItemsPlaceholder} disabled={showInboundItemsPlaceholder}
/> />
) : ( ) : (
@@ -826,9 +877,70 @@ export const ClaimCreateForm = ({
</span> </span>
<span className="txt-small text-ui-fg-subtle flex items-center"> <span className="txt-small text-ui-fg-subtle flex items-center">
{getStylizedAmount( {!isShippingOutboundPriceEdit && (
outboundShipping?.amount ?? 0, <IconButton
order.currency_code onClick={() => setIsShippingOutboundPriceEdit(true)}
variant="transparent"
className="text-ui-fg-muted"
disabled={
showOutboundItemsPlaceholder ||
!outboundShippingOptionId
}
>
<PencilSquare />
</IconButton>
)}
{isShippingOutboundPriceEdit ? (
<CurrencyInput
id="js-shipping-outbound-input"
onBlur={() => {
let actionId
preview.shipping_methods.forEach((s) => {
if (s.actions) {
for (const a of s.actions) {
if (a.action === "SHIPPING_ADD" && !a.return_id) {
actionId = a.id
}
}
}
})
const customPrice =
customOutboundShippingAmount === ""
? null
: parseFloat(customOutboundShippingAmount)
if (actionId) {
updateOutboundShipping(
{
actionId,
custom_price: customPrice,
},
{
onError: (error) => {
toast.error(error.message)
},
}
)
}
setIsShippingOutboundPriceEdit(false)
}}
symbol={
currencies[order.currency_code.toUpperCase()]
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomOutboundShippingAmount}
value={customOutboundShippingAmount}
disabled={showOutboundItemsPlaceholder}
/>
) : (
getStylizedAmount(
outboundShipping?.amount ?? 0,
order.currency_code
)
)} )}
</span> </span>
</div> </div>
@@ -17,6 +17,7 @@ export const ClaimCreateSchema = z.object({
), ),
location_id: z.string().optional(), location_id: z.string().optional(),
inbound_option_id: z.string().nullish(), inbound_option_id: z.string().nullish(),
outbound_option_id: z.string().nullish(),
send_notification: z.boolean().optional(), send_notification: z.boolean().optional(),
}) })
@@ -28,6 +28,7 @@ import {
useCancelExchangeRequest, useCancelExchangeRequest,
useExchangeConfirmRequest, useExchangeConfirmRequest,
useUpdateExchangeInboundShipping, useUpdateExchangeInboundShipping,
useUpdateExchangeOutboundShipping,
} from "../../../../../hooks/api/exchanges" } from "../../../../../hooks/api/exchanges"
import { currencies } from "../../../../../lib/data/currencies" import { currencies } from "../../../../../lib/data/currencies"
import { ExchangeInboundSection } from "./exchange-inbound-section.tsx" import { ExchangeInboundSection } from "./exchange-inbound-section.tsx"
@@ -54,10 +55,14 @@ export const ExchangeCreateForm = ({
/** /**
* STATE * STATE
*/ */
const [isShippingPriceEdit, setIsShippingPriceEdit] = useState(false) const [isInboundShippingPriceEdit, setIsInboundShippingPriceEdit] =
const [customShippingAmount, setCustomShippingAmount] = useState< useState(false)
number | string const [isOutboundShippingPriceEdit, setIsOutboundShippingPriceEdit] =
>(0) useState(false)
const [customInboundShippingAmount, setCustomInboundShippingAmount] =
useState<number | string>(0)
const [customOutboundShippingAmount, setCustomOutboundShippingAmount] =
useState<number | string>(0)
/** /**
* MUTATIONS * MUTATIONS
@@ -70,11 +75,19 @@ export const ExchangeCreateForm = ({
const { const {
mutateAsync: updateInboundShipping, mutateAsync: updateInboundShipping,
isPending: isUpdatingInboundShipping, isPending: isUpdatingOutboundShipping,
} = useUpdateExchangeInboundShipping(exchange.id, order.id) } = useUpdateExchangeInboundShipping(exchange.id, order.id)
const {
mutateAsync: updateOutboundShipping,
isPending: isUpdatingInboundShipping,
} = useUpdateExchangeOutboundShipping(exchange.id, order.id)
const isRequestLoading = const isRequestLoading =
isConfirming || isCanceling || isUpdatingInboundShipping isConfirming ||
isCanceling ||
isUpdatingInboundShipping ||
isUpdatingOutboundShipping
/** /**
* Only consider items that belong to this exchange. * Only consider items that belong to this exchange.
@@ -144,11 +157,31 @@ export const ExchangeCreateForm = ({
resolver: zodResolver(ExchangeCreateSchema), resolver: zodResolver(ExchangeCreateSchema),
}) })
const inboundShipping = preview.shipping_methods.find((s) => {
return !!s.actions?.find(
(a) => a.action === "SHIPPING_ADD" && !!a.return_id
)
})
const outboundShipping = preview.shipping_methods.find((s) => { const outboundShipping = preview.shipping_methods.find((s) => {
return !!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id) return !!s.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id)
}) })
const shippingOptionId = form.watch("inbound_option_id") useEffect(() => {
if (inboundShipping) {
setCustomInboundShippingAmount(inboundShipping.total)
}
}, [inboundShipping])
useEffect(() => {
if (outboundShipping) {
setCustomOutboundShippingAmount(outboundShipping.total)
}
}, [outboundShipping])
const inboundShippingOptionId = form.watch("inbound_option_id")
const outboundShippingOptionId = form.watch("outbound_option_id")
const prompt = usePrompt() const prompt = usePrompt()
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
@@ -176,10 +209,16 @@ export const ExchangeCreateForm = ({
}) })
useEffect(() => { useEffect(() => {
if (isShippingPriceEdit) { if (isInboundShippingPriceEdit) {
document.getElementById("js-shipping-input")?.focus() document.getElementById("js-inbound-shipping-input")?.focus()
} }
}, [isShippingPriceEdit]) }, [isInboundShippingPriceEdit])
useEffect(() => {
if (isOutboundShippingPriceEdit) {
document.getElementById("js-outbound-shipping-input")?.focus()
}
}, [isOutboundShippingPriceEdit])
useEffect(() => { useEffect(() => {
/** /**
@@ -284,22 +323,22 @@ export const ExchangeCreateForm = ({
</span> </span>
<span className="txt-small text-ui-fg-subtle flex items-center"> <span className="txt-small text-ui-fg-subtle flex items-center">
{!isShippingPriceEdit && ( {!isInboundShippingPriceEdit && (
<IconButton <IconButton
onClick={() => setIsShippingPriceEdit(true)} onClick={() => setIsInboundShippingPriceEdit(true)}
variant="transparent" variant="transparent"
className="text-ui-fg-muted" className="text-ui-fg-muted"
disabled={ disabled={
!inboundPreviewItems?.length || !shippingOptionId !inboundPreviewItems?.length || !inboundShippingOptionId
} }
> >
<PencilSquare /> <PencilSquare />
</IconButton> </IconButton>
)} )}
{isShippingPriceEdit ? ( {isInboundShippingPriceEdit ? (
<CurrencyInput <CurrencyInput
id="js-shipping-input" id="js-inbound-shipping-input"
onBlur={() => { onBlur={() => {
let actionId let actionId
@@ -317,9 +356,9 @@ export const ExchangeCreateForm = ({
}) })
const customPrice = const customPrice =
customShippingAmount === "" customInboundShippingAmount === ""
? null ? null
: parseFloat(customShippingAmount) : parseFloat(customInboundShippingAmount)
if (actionId) { if (actionId) {
updateInboundShipping( updateInboundShipping(
@@ -334,15 +373,15 @@ export const ExchangeCreateForm = ({
} }
) )
} }
setIsShippingPriceEdit(false) setIsInboundShippingPriceEdit(false)
}} }}
symbol={ symbol={
currencies[order.currency_code.toUpperCase()] currencies[order.currency_code.toUpperCase()]
.symbol_native .symbol_native
} }
code={order.currency_code} code={order.currency_code}
onValueChange={setCustomShippingAmount} onValueChange={setCustomInboundShippingAmount}
value={customShippingAmount} value={customInboundShippingAmount}
disabled={!inboundPreviewItems?.length} disabled={!inboundPreviewItems?.length}
/> />
) : ( ) : (
@@ -357,9 +396,70 @@ export const ExchangeCreateForm = ({
</span> </span>
<span className="txt-small text-ui-fg-subtle flex items-center"> <span className="txt-small text-ui-fg-subtle flex items-center">
{getStylizedAmount( {!isOutboundShippingPriceEdit && (
outboundShipping?.amount ?? 0, <IconButton
order.currency_code onClick={() => setIsOutboundShippingPriceEdit(true)}
variant="transparent"
className="text-ui-fg-muted"
disabled={
!outboundPreviewItems?.length ||
!outboundShippingOptionId
}
>
<PencilSquare />
</IconButton>
)}
{isOutboundShippingPriceEdit ? (
<CurrencyInput
id="js-outbound-shipping-input"
onBlur={() => {
let actionId
preview.shipping_methods.forEach((s) => {
if (s.actions) {
for (const a of s.actions) {
if (a.action === "SHIPPING_ADD" && !a.return_id) {
actionId = a.id
}
}
}
})
const customPrice =
customOutboundShippingAmount === ""
? null
: parseFloat(customOutboundShippingAmount)
if (actionId) {
updateOutboundShipping(
{
actionId,
custom_price: customPrice,
},
{
onError: (error) => {
toast.error(error.message)
},
}
)
}
setIsOutboundShippingPriceEdit(false)
}}
symbol={
currencies[order.currency_code.toUpperCase()]
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomOutboundShippingAmount}
value={customOutboundShippingAmount}
disabled={!outboundPreviewItems?.length}
/>
) : (
getStylizedAmount(
outboundShipping?.amount ?? 0,
order.currency_code
)
)} )}
</span> </span>
</div> </div>
@@ -11,12 +11,13 @@ export const ExchangeCreateSchema = z.object({
), ),
outbound_items: z.array( outbound_items: z.array(
z.object({ z.object({
item_id: z.string(), // TODO: variant id? item_id: z.string(),
quantity: z.number(), quantity: z.number(),
}) })
), ),
location_id: z.string().optional(), location_id: z.string().optional(),
inbound_option_id: z.string().nullish(), inbound_option_id: z.string().nullish(),
outbound_option_id: z.string().nullish(),
send_notification: z.boolean().optional(), send_notification: z.boolean().optional(),
}) })