fix(dashboard): Allow admins to update default Sales Channel and Stock Location (#11196)

**Note** The huge diff is because the i18n schema wasn't formatted properly again. Not sure how that happened with the update to the script but perhaps someone didn't update their branch to include the change to format the schema on creation.
This commit is contained in:
Kasper Fabricius Kristensen
2025-01-29 17:14:47 +01:00
committed by GitHub
parent 88202761a5
commit 51d2960a57
6 changed files with 571 additions and 1686 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard): Allow admins to update default Sales Channel and Stock Location

View File

@@ -1,3 +1,9 @@
import { FetchError } from "@medusajs/js-sdk"
import {
AdminSalesChannelListResponse,
AdminSalesChannelResponse,
HttpTypes,
} from "@medusajs/types"
import {
QueryKey,
UseMutationOptions,
@@ -5,16 +11,10 @@ import {
useMutation,
useQuery,
} from "@tanstack/react-query"
import {
AdminSalesChannelListResponse,
AdminSalesChannelResponse,
HttpTypes,
} from "@medusajs/types"
import { sdk } from "../../lib/client"
import { queryClient } from "../../lib/query-client"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { productsQueryKeys } from "./products"
import { FetchError } from "@medusajs/js-sdk"
const SALES_CHANNELS_QUERY_KEY = "sales-channels" as const
export const salesChannelsQueryKeys = queryKeysFactory(SALES_CHANNELS_QUERY_KEY)

File diff suppressed because it is too large Load Diff

View File

@@ -2321,6 +2321,8 @@
"editStore": "Edit store",
"defaultCurrency": "Default currency",
"defaultRegion": "Default region",
"defaultSalesChannel": "Default sales channel",
"defaultLocation": "Default location",
"swapLinkTemplate": "Swap link template",
"paymentLinkTemplate": "Payment link template",
"inviteLinkTemplate": "Invite link template",

View File

@@ -3,7 +3,9 @@ import { AdminStore } from "@medusajs/types"
import { Badge, Container, Heading, Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { useSalesChannel, useStockLocation } from "../../../../../hooks/api"
import { useRegion } from "../../../../../hooks/api/regions"
type StoreGeneralSectionProps = {
@@ -19,6 +21,20 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
const defaultCurrency = store.supported_currencies?.find((c) => c.is_default)
const { sales_channel } = useSalesChannel(store.default_sales_channel_id!, {
enabled: !!store.default_sales_channel_id,
})
const { stock_location } = useStockLocation(
store.default_location_id!,
{
fields: "id,name",
},
{
enabled: !!store.default_location_id,
}
)
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
@@ -74,9 +90,51 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
{t("store.defaultRegion")}
</Text>
<div className="flex items-center gap-x-2">
<Text size="small" leading="compact">
{region?.name || "-"}
</Text>
{region ? (
<Badge size="2xsmall" asChild>
<Link to={`/settings/regions/${region.id}`}>{region.name}</Link>
</Badge>
) : (
<Text size="small" leading="compact">
-
</Text>
)}
</div>
</div>
<div className="text-ui-fg-subtle grid grid-cols-2 px-6 py-4">
<Text size="small" leading="compact" weight="plus">
{t("store.defaultSalesChannel")}
</Text>
<div className="flex items-center gap-x-2">
{sales_channel ? (
<Badge size="2xsmall" asChild>
<Link to={`/settings/sales-channels/${sales_channel.id}`}>
{sales_channel.name}
</Link>
</Badge>
) : (
<Text size="small" leading="compact">
-
</Text>
)}
</div>
</div>
<div className="text-ui-fg-subtle grid grid-cols-2 px-6 py-4">
<Text size="small" leading="compact" weight="plus">
{t("store.defaultLocation")}
</Text>
<div className="flex items-center gap-x-2">
{stock_location ? (
<Badge size="2xsmall" asChild>
<Link to={`/settings/locations/${stock_location.id}`}>
{stock_location.name}
</Link>
</Badge>
) : (
<Text size="small" leading="compact">
-
</Text>
)}
</div>
</div>
</Container>

View File

@@ -6,10 +6,12 @@ import { useTranslation } from "react-i18next"
import { z } from "zod"
import { Form } from "../../../../../components/common/form"
import { Combobox } from "../../../../../components/inputs/combobox"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { useRegions } from "../../../../../hooks/api/regions"
import { useUpdateStore } from "../../../../../hooks/api/store"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
type EditStoreFormProps = {
store: HttpTypes.AdminStore
@@ -19,7 +21,8 @@ const EditStoreSchema = z.object({
name: z.string().min(1),
default_currency_code: z.string().optional(),
default_region_id: z.string().optional(),
// default_location_id: z.string().optional(),
default_sales_channel_id: z.string().optional(),
default_location_id: z.string().optional(),
})
export const EditStoreForm = ({ store }: EditStoreFormProps) => {
@@ -33,21 +36,49 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
default_currency_code:
store.supported_currencies?.find((c) => c.is_default)?.currency_code ||
undefined,
default_sales_channel_id: store.default_sales_channel_id || undefined,
default_location_id: store.default_location_id || undefined,
},
resolver: zodResolver(EditStoreSchema),
})
const { mutateAsync, isPending } = useUpdateStore(store.id)
const { regions, isPending: isRegionsLoading } = useRegions({ limit: 999 })
const regionsCombobox = useComboboxData({
queryKey: ["regions", "default_region_id"],
queryFn: (params) =>
sdk.admin.region.list({ ...params, fields: "id,name" }),
defaultValue: store.default_region_id || undefined,
getOptions: (data) =>
data.regions.map((r) => ({ label: r.name, value: r.id })),
})
const salesChannelsCombobox = useComboboxData({
queryFn: (params) =>
sdk.admin.salesChannel.list({ ...params, fields: "id,name" }),
getOptions: (data) =>
data.sales_channels.map((sc) => ({ label: sc.name, value: sc.id })),
queryKey: ["sales_channels", "default_sales_channel_id"],
defaultValue: store.default_sales_channel_id || undefined,
})
const locationsCombobox = useComboboxData({
queryFn: (params) =>
sdk.admin.stockLocation.list({ ...params, fields: "id,name" }),
getOptions: (data) =>
data.stock_locations.map((l) => ({ label: l.name, value: l.id })),
queryKey: ["stock_locations", "default_location_id"],
defaultValue: store.default_location_id || undefined,
})
const handleSubmit = form.handleSubmit(async (values) => {
const normalizedMutation = {
...values,
default_currency_code: undefined,
const { default_currency_code, ...rest } = values
const normalizedMutation: HttpTypes.AdminUpdateStore = {
...rest,
supported_currencies: store.supported_currencies?.map((c) => ({
...c,
is_default: c.currency_code === values.default_currency_code,
is_default: c.currency_code === default_currency_code,
})),
}
await mutateAsync(normalizedMutation, {
@@ -79,7 +110,6 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
</Form.Item>
)}
/>
{/* TODO: Add comboboxes for default sales channel and location */}
<Form.Field
control={form.control}
name="default_currency_code"
@@ -111,27 +141,64 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
<Form.Field
control={form.control}
name="default_region_id"
render={({ field: { onChange, ...field } }) => {
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultRegion")}</Form.Label>
<Form.Control>
<Select
<Combobox
{...field}
onValueChange={onChange}
disabled={isRegionsLoading}
>
<Select.Trigger ref={field.ref}>
<Select.Value />
</Select.Trigger>
<Select.Content>
{(regions || []).map((region) => (
<Select.Item key={region.id} value={region.id}>
{region.name}
</Select.Item>
))}
</Select.Content>
</Select>
options={regionsCombobox.options}
searchValue={regionsCombobox.searchValue}
onSearchValueChange={
regionsCombobox.onSearchValueChange
}
disabled={regionsCombobox.disabled}
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="default_sales_channel_id"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultSalesChannel")}</Form.Label>
<Form.Control>
<Combobox
{...field}
options={salesChannelsCombobox.options}
searchValue={salesChannelsCombobox.searchValue}
onSearchValueChange={
salesChannelsCombobox.onSearchValueChange
}
disabled={salesChannelsCombobox.disabled}
/>
</Form.Control>
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="default_location_id"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultLocation")}</Form.Label>
<Form.Control>
<Combobox
{...field}
options={locationsCombobox.options}
searchValue={locationsCombobox.searchValue}
onSearchValueChange={
locationsCombobox.onSearchValueChange
}
disabled={locationsCombobox.disabled}
/>
</Form.Control>
</Form.Item>
)