Fix(admin-ui, medusa): stock location fixes (#3395)

**What**
- A series of minor fixes for admin-ui relating to managing stock locations: 
  - make "create location" `primary`
  - add delete prompt when cancelling creation if information has been input
  - avoid clipping focus border on country select when creating a stock location
  - allow removals of sales channels from stock locations 

Fixes CORE-1191, CORE-1192, CORE-1190, CORE-1189
This commit is contained in:
Philip Korsholm
2023-03-07 10:54:09 +00:00
committed by GitHub
parent aadc1e19e9
commit 15f47baf56
5 changed files with 66 additions and 26 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/admin-ui": patch
"@medusajs/medusa": patch
---
fix(admin-ui, medusa): Minor ui fixes relating to stock locations
@@ -1,8 +1,9 @@
import React, { useState } from "react" import React, { useState } from "react"
import useNotification from "../../hooks/use-notification"
import { getErrorMessage } from "../../utils/error-messages"
import Button from "../fundamentals/button" import Button from "../fundamentals/button"
import Modal from "../molecules/modal" import Modal from "../molecules/modal"
import { getErrorMessage } from "../../utils/error-messages"
import useNotification from "../../hooks/use-notification"
type DeletePromptProps = { type DeletePromptProps = {
heading?: string heading?: string
@@ -31,7 +32,11 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
setIsLoading(true) setIsLoading(true)
onDelete() onDelete()
.then(() => notification("Success", successText, "success")) .then(() => {
if (successText) {
notification("Success", successText, "success")
}
})
.catch((err) => notification("Error", getErrorMessage(err), "error")) .catch((err) => notification("Error", getErrorMessage(err), "error"))
.finally(() => { .finally(() => {
setIsLoading(false) setIsLoading(false)
@@ -45,14 +50,14 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
<Modal.Content> <Modal.Content>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="inter-large-semibold">{heading}</span> <span className="inter-large-semibold">{heading}</span>
<span className="inter-base-regular mt-1 text-grey-50">{text}</span> <span className="inter-base-regular text-grey-50 mt-1">{text}</span>
</div> </div>
</Modal.Content> </Modal.Content>
<Modal.Footer> <Modal.Footer>
<div className="flex w-full h-8 justify-end"> <div className="flex h-8 w-full justify-end">
<Button <Button
variant="ghost" variant="ghost"
className="mr-2 w-24 text-small justify-center" className="text-small min-w-24 mr-2 justify-center"
size="small" size="small"
onClick={handleClose} onClick={handleClose}
> >
@@ -61,7 +66,7 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
<Button <Button
loading={isLoading} loading={isLoading}
size="small" size="small"
className="w-24 text-small justify-center" className="text-small w-24 justify-center"
variant="nuclear" variant="nuclear"
onClick={handleSubmit} onClick={handleSubmit}
disabled={isLoading} disabled={isLoading}
@@ -1,11 +1,12 @@
import { StockLocationExpandedDTO } from "@medusajs/medusa" import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
import { import {
useAdminAddLocationToSalesChannel, useAdminAddLocationToSalesChannel,
useAdminRemoveLocationFromSalesChannel, useAdminRemoveLocationFromSalesChannel,
} from "medusa-react" } from "medusa-react"
import Button from "../../../../../components/fundamentals/button" import Button from "../../../../../components/fundamentals/button"
import useToggleState from "../../../../../hooks/use-toggle-state"
import SalesChannelsModal from "../../../../products/components/sales-channels-modal" import SalesChannelsModal from "../../../../products/components/sales-channels-modal"
import useToggleState from "../../../../../hooks/use-toggle-state"
const EditSalesChannels = ({ const EditSalesChannels = ({
location, location,
@@ -23,7 +24,7 @@ const EditSalesChannels = ({
const { mutateAsync: removeLocationFromSalesChannel } = const { mutateAsync: removeLocationFromSalesChannel } =
useAdminRemoveLocationFromSalesChannel() useAdminRemoveLocationFromSalesChannel()
const onSave = async (channels) => { const onSave = async (channels: SalesChannel[]) => {
const existingChannels = location.sales_channels const existingChannels = location.sales_channels
const channelsToRemove = const channelsToRemove =
existingChannels?.filter( existingChannels?.filter(
@@ -4,22 +4,25 @@ import {
StockLocationAddressDTO, StockLocationAddressDTO,
StockLocationAddressInput, StockLocationAddressInput,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import GeneralForm, { GeneralFormType } from "../components/general-form"
import { import {
useAdminAddLocationToSalesChannel, useAdminAddLocationToSalesChannel,
useAdminCreateStockLocation, useAdminCreateStockLocation,
} from "medusa-react" } from "medusa-react"
import { useForm } from "react-hook-form"
import Accordion from "../../../../components/organisms/accordion"
import AddressForm from "../components/address-form"
import Button from "../../../../components/fundamentals/button" import Button from "../../../../components/fundamentals/button"
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon" import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
import DeletePrompt from "../../../../components/organisms/delete-prompt"
import FocusModal from "../../../../components/molecules/modal/focus-modal" import FocusModal from "../../../../components/molecules/modal/focus-modal"
import Accordion from "../../../../components/organisms/accordion" import SalesChannelsForm from "../components/sales-channels-form"
import useNotification from "../../../../hooks/use-notification"
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
import { getErrorMessage } from "../../../../utils/error-messages" import { getErrorMessage } from "../../../../utils/error-messages"
import { nestedForm } from "../../../../utils/nested-form" import { nestedForm } from "../../../../utils/nested-form"
import AddressForm from "../components/address-form" import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
import GeneralForm, { GeneralFormType } from "../components/general-form" import { useForm } from "react-hook-form"
import SalesChannelsForm from "../components/sales-channels-form" import useNotification from "../../../../hooks/use-notification"
import useToggleState from "../../../../hooks/use-toggle-state"
type NewLocationForm = { type NewLocationForm = {
general: GeneralFormType general: GeneralFormType
@@ -43,7 +46,16 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
reValidateMode: "onBlur", reValidateMode: "onBlur",
mode: "onBlur", mode: "onBlur",
}) })
const { handleSubmit, formState } = form const {
handleSubmit,
formState: { isDirty, isValid },
} = form
const {
state: isShowingClosePrompt,
open: showClosePrompt,
close: closeClosePrompt,
} = useToggleState()
const notification = useNotification() const notification = useNotification()
const { isFeatureEnabled } = useFeatureFlag() const { isFeatureEnabled } = useFeatureFlag()
@@ -58,6 +70,14 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
location_id: locationId, location_id: locationId,
}) })
const handleClose = () => {
if (!isDirty) {
onClose()
} else {
showClosePrompt()
}
}
const onSubmit = () => const onSubmit = () =>
handleSubmit(async (data) => { handleSubmit(async (data) => {
const { locationPayload, salesChannelsPayload } = createPayload(data) const { locationPayload, salesChannelsPayload } = createPayload(data)
@@ -89,8 +109,6 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
} }
}) })
const { isDirty, isValid } = formState
return ( return (
<form className="w-full"> <form className="w-full">
<FocusModal> <FocusModal>
@@ -100,14 +118,24 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
size="small" size="small"
variant="ghost" variant="ghost"
type="button" type="button"
onClick={onClose} onClick={handleClose}
> >
<CrossIcon size={20} /> <CrossIcon size={20} />
</Button> </Button>
{isShowingClosePrompt && (
<DeletePrompt
heading="Are you sure you want to cancel with unsaved changes"
confirmText="Yes, cancel"
cancelText="No, continue creating"
successText={undefined}
handleClose={closeClosePrompt}
onDelete={async () => onClose()}
/>
)}
<div className="gap-x-small flex"> <div className="gap-x-small flex">
<Button <Button
size="small" size="small"
variant="secondary" variant="primary"
type="button" type="button"
disabled={!isDirty || !isValid} disabled={!isDirty || !isValid}
onClick={onSubmit()} onClick={onSubmit()}
@@ -131,7 +159,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
<p className="inter-base-regular text-grey-50"> <p className="inter-base-regular text-grey-50">
Specify the details about this location Specify the details about this location
</p> </p>
<div className="mt-xlarge gap-y-xlarge flex flex-col"> <div className="mt-xlarge gap-y-xlarge flex flex-col pb-0.5">
<GeneralForm form={nestedForm(form, "general")} /> <GeneralForm form={nestedForm(form, "general")} />
<AddressForm form={nestedForm(form, "address")} /> <AddressForm form={nestedForm(form, "address")} />
</div> </div>
@@ -1,7 +1,7 @@
import { IsString } from "class-validator"
import { Request, Response } from "express" import { Request, Response } from "express"
import { EntityManager } from "typeorm"
import { EntityManager } from "typeorm"
import { IsString } from "class-validator"
import { SalesChannelLocationService } from "../../../../services" import { SalesChannelLocationService } from "../../../../services"
/** /**
@@ -80,7 +80,7 @@ export default async (req: Request, res: Response) => {
await manager.transaction(async (transactionManager) => { await manager.transaction(async (transactionManager) => {
await channelLocationService await channelLocationService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.removeLocation(id, validatedBody.location_id) .removeLocation(validatedBody.location_id, id)
}) })
res.json({ res.json({