feat(dashboard,admin-shared): Add injection zones to regions, shipping profiles, and locations (#7467)
This commit is contained in:
@@ -85,6 +85,29 @@ const GIFT_CARD_INJECTION_ZONES = [
|
||||
"custom_gift_card.after",
|
||||
] as const
|
||||
|
||||
const REGION_INJECTION_ZONES = [
|
||||
"region.details.before",
|
||||
"region.details.after",
|
||||
"region.list.before",
|
||||
"region.list.after",
|
||||
] as const
|
||||
|
||||
const SHIPPING_PROFILE_INJECTION_ZONES = [
|
||||
"shipping_profile.details.before",
|
||||
"shipping_profile.details.after",
|
||||
"shipping_profile.list.before",
|
||||
"shipping_profile.list.after",
|
||||
] as const
|
||||
|
||||
const LOCATION_INJECTION_ZONES = [
|
||||
"location.details.before",
|
||||
"location.details.after",
|
||||
"location.details.side.before",
|
||||
"location.details.side.after",
|
||||
"location.list.before",
|
||||
"location.list.after",
|
||||
] as const
|
||||
|
||||
const LOGIN_INJECTION_ZONES = ["login.before", "login.after"] as const
|
||||
|
||||
/**
|
||||
@@ -103,5 +126,8 @@ export const INJECTION_ZONES = [
|
||||
...DISCOUNT_INJECTION_ZONES,
|
||||
...PROMOTION_INJECTION_ZONES,
|
||||
...GIFT_CARD_INJECTION_ZONES,
|
||||
...REGION_INJECTION_ZONES,
|
||||
...SHIPPING_PROFILE_INJECTION_ZONES,
|
||||
...LOCATION_INJECTION_ZONES,
|
||||
...LOGIN_INJECTION_ZONES,
|
||||
] as const
|
||||
|
||||
+2
-2
@@ -54,8 +54,8 @@ const useSettingRoutes = (): NavItemProps[] => {
|
||||
to: "/settings/shipping-profiles",
|
||||
},
|
||||
{
|
||||
label: t("shipping.domain"),
|
||||
to: "/settings/shipping",
|
||||
label: t("location.domain"),
|
||||
to: "/settings/locations",
|
||||
},
|
||||
],
|
||||
[t]
|
||||
|
||||
@@ -675,8 +675,8 @@
|
||||
"invalidEmail": "Email must be a valid email address."
|
||||
}
|
||||
},
|
||||
"shipping": {
|
||||
"title": "Location & Shipping",
|
||||
"location": {
|
||||
"title": "Locations & Shipping",
|
||||
"domain": "Location & Shipping",
|
||||
"description": "Choose where you ship and how much you charge for shipping at checkout. Define shipping options specific for your locations.",
|
||||
"createLocation": "Create location",
|
||||
|
||||
@@ -682,33 +682,33 @@ export const RouteMap: RouteObject[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "shipping",
|
||||
path: "locations",
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
crumb: () => "Location & Shipping",
|
||||
crumb: () => "Locations & Shipping",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () => import("../../routes/shipping/location-list"),
|
||||
lazy: () => import("../../routes/locations/location-list"),
|
||||
},
|
||||
{
|
||||
path: "create",
|
||||
lazy: () => import("../../routes/shipping/location-create"),
|
||||
lazy: () => import("../../routes/locations/location-create"),
|
||||
},
|
||||
{
|
||||
path: ":location_id",
|
||||
lazy: () => import("../../routes/shipping/location-details"),
|
||||
lazy: () => import("../../routes/locations/location-details"),
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () => import("../../routes/shipping/location-edit"),
|
||||
lazy: () => import("../../routes/locations/location-edit"),
|
||||
},
|
||||
{
|
||||
path: "sales-channels/edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/shipping/location-add-sales-channels"
|
||||
"../../routes/locations/location-add-sales-channels"
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -717,7 +717,7 @@ export const RouteMap: RouteObject[] = [
|
||||
{
|
||||
path: "service-zones/create",
|
||||
lazy: () =>
|
||||
import("../../routes/shipping/service-zone-create"),
|
||||
import("../../routes/locations/service-zone-create"),
|
||||
},
|
||||
{
|
||||
path: "service-zone/:zone_id",
|
||||
@@ -725,13 +725,15 @@ export const RouteMap: RouteObject[] = [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import("../../routes/shipping/service-zone-edit"),
|
||||
import(
|
||||
"../../routes/locations/service-zone-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "edit-areas",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/shipping/service-zone-areas-edit"
|
||||
"../../routes/locations/service-zone-areas-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -741,7 +743,7 @@ export const RouteMap: RouteObject[] = [
|
||||
path: "create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/shipping/shipping-options-create"
|
||||
"../../routes/locations/shipping-options-create"
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -751,14 +753,14 @@ export const RouteMap: RouteObject[] = [
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/shipping/shipping-option-edit"
|
||||
"../../routes/locations/shipping-option-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "edit-pricing",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/shipping/shipping-options-edit-pricing"
|
||||
"../../routes/locations/shipping-options-edit-pricing"
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
+3
-3
@@ -55,7 +55,7 @@ export const CreateLocationForm = () => {
|
||||
address: values.address,
|
||||
})
|
||||
|
||||
handleSuccess("/settings/shipping")
|
||||
handleSuccess("/settings/locations")
|
||||
|
||||
toast.success(t("general.success"), {
|
||||
description: t("locations.toast.create"),
|
||||
@@ -92,10 +92,10 @@ export const CreateLocationForm = () => {
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">
|
||||
<div>
|
||||
<Heading className="capitalize">
|
||||
{t("shipping.createLocation")}
|
||||
{t("location.createLocation")}
|
||||
</Heading>
|
||||
<Text size="small" className="text-ui-fg-subtle">
|
||||
{t("shipping.createLocationDetailsHint")}
|
||||
{t("location.createLocationDetailsHint")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
+44
-44
@@ -1,12 +1,3 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useMemo, useState } from "react"
|
||||
import {
|
||||
FulfillmentSetDTO,
|
||||
ServiceZoneDTO,
|
||||
ShippingOptionDTO,
|
||||
StockLocationDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ChevronDownMini,
|
||||
CurrencyDollar,
|
||||
@@ -15,6 +6,12 @@ import {
|
||||
Plus,
|
||||
Trash,
|
||||
} from "@medusajs/icons"
|
||||
import {
|
||||
FulfillmentSetDTO,
|
||||
ServiceZoneDTO,
|
||||
ShippingOptionDTO,
|
||||
StockLocationDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -25,19 +22,22 @@ import {
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
import { ListSummary } from "../../../../../components/common/list-summary"
|
||||
import { useDeleteShippingOption } from "../../../../../hooks/api/shipping-options"
|
||||
import {
|
||||
useCreateFulfillmentSet,
|
||||
useDeleteFulfillmentSet,
|
||||
useDeleteServiceZone,
|
||||
useDeleteStockLocation,
|
||||
} from "../../../../../hooks/api/stock-locations"
|
||||
import { useDeleteShippingOption } from "../../../../../hooks/api/shipping-options"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
import { ListSummary } from "../../../../../components/common/list-summary"
|
||||
import {
|
||||
isOptionEnabledInStore,
|
||||
isReturnOption,
|
||||
@@ -103,7 +103,7 @@ function ShippingOption({
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("shipping.shippingOptions.deleteWarning", {
|
||||
description: t("location.shippingOptions.deleteWarning", {
|
||||
name: option.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
@@ -118,7 +118,7 @@ function ShippingOption({
|
||||
await deleteOption()
|
||||
|
||||
toast.success(t("general.success"), {
|
||||
description: t("shipping.shippingOptions.toast.delete", {
|
||||
description: t("location.shippingOptions.toast.delete", {
|
||||
name: option.name,
|
||||
}),
|
||||
dismissLabel: t("actions.close"),
|
||||
@@ -141,7 +141,7 @@ function ShippingOption({
|
||||
</div>
|
||||
{isInStore && (
|
||||
<Badge className="mr-4" color="purple">
|
||||
{t("shipping.shippingOptions.inStore")}
|
||||
{t("location.shippingOptions.inStore")}
|
||||
</Badge>
|
||||
)}
|
||||
<ActionMenu
|
||||
@@ -150,13 +150,13 @@ function ShippingOption({
|
||||
actions: [
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("shipping.serviceZone.editOption"),
|
||||
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit`,
|
||||
label: t("location.serviceZone.editOption"),
|
||||
to: `/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit`,
|
||||
},
|
||||
{
|
||||
label: t("shipping.serviceZone.editPrices"),
|
||||
label: t("location.serviceZone.editPrices"),
|
||||
icon: <CurrencyDollar />,
|
||||
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit-pricing`,
|
||||
to: `/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit-pricing`,
|
||||
},
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
@@ -196,18 +196,18 @@ function ServiceZoneOptions({
|
||||
<div className="mt-4 flex flex-col border-t border-dashed px-6 py-4">
|
||||
<div className="item-center flex justify-between">
|
||||
<span className="text-ui-fg-subtle txt-small self-center font-medium">
|
||||
{t("shipping.serviceZone.shippingOptions")}
|
||||
{t("location.serviceZone.shippingOptions")}
|
||||
</span>
|
||||
<Button
|
||||
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent active:bg-transparent"
|
||||
variant="transparent"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/shipping-option/create`
|
||||
`/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/shipping-option/create`
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("shipping.serviceZone.addOption")}
|
||||
{t("location.serviceZone.addOption")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -228,18 +228,18 @@ function ServiceZoneOptions({
|
||||
<div className="-mb-4 flex flex-col border-t border-dashed px-6 py-4">
|
||||
<div className="item-center flex justify-between">
|
||||
<span className="text-ui-fg-subtle txt-small self-center font-medium">
|
||||
{t("shipping.serviceZone.returnOptions")}
|
||||
{t("location.serviceZone.returnOptions")}
|
||||
</span>
|
||||
<Button
|
||||
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent active:bg-transparent"
|
||||
variant="transparent"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/shipping-option/create?is_return`
|
||||
`/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/shipping-option/create?is_return`
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("shipping.serviceZone.addOption")}
|
||||
{t("location.serviceZone.addOption")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -280,7 +280,7 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("shipping.serviceZone.deleteWarning", {
|
||||
description: t("location.serviceZone.deleteWarning", {
|
||||
name: zone.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
@@ -295,7 +295,7 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
||||
await deleteZone()
|
||||
|
||||
toast.success(t("general.success"), {
|
||||
description: t("shipping.serviceZone.toast.delete", {
|
||||
description: t("location.serviceZone.toast.delete", {
|
||||
name: zone.name,
|
||||
}),
|
||||
dismissLabel: t("actions.close"),
|
||||
@@ -350,14 +350,14 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
||||
<span>·</span>
|
||||
<Text className="text-ui-fg-subtle txt-small">
|
||||
{shippingOptionsCount}{" "}
|
||||
{t("shipping.serviceZone.optionsLength", {
|
||||
{t("location.serviceZone.optionsLength", {
|
||||
count: shippingOptionsCount,
|
||||
})}
|
||||
</Text>
|
||||
<span>·</span>
|
||||
<Text className="text-ui-fg-subtle txt-small">
|
||||
{returnOptionsCount}{" "}
|
||||
{t("shipping.serviceZone.returnOptionsLength", {
|
||||
{t("location.serviceZone.returnOptionsLength", {
|
||||
count: returnOptionsCount,
|
||||
})}
|
||||
</Text>
|
||||
@@ -389,12 +389,12 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit`,
|
||||
to: `/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit`,
|
||||
},
|
||||
{
|
||||
label: t("shipping.serviceZone.areas.manage"),
|
||||
label: t("location.serviceZone.areas.manage"),
|
||||
icon: <Map />,
|
||||
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit-areas`,
|
||||
to: `/settings/locations/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit-areas`,
|
||||
},
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
@@ -469,7 +469,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("shipping.fulfillmentSet.disableWarning", {
|
||||
description: t("location.fulfillmentSet.disableWarning", {
|
||||
name: fulfillmentSet?.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
@@ -484,7 +484,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
await deleteFulfillmentSet()
|
||||
|
||||
toast.success(t("general.success"), {
|
||||
description: t("shipping.fulfillmentSet.toast.disable", {
|
||||
description: t("location.fulfillmentSet.toast.disable", {
|
||||
name: fulfillmentSet?.name,
|
||||
}),
|
||||
dismissLabel: t("actions.close"),
|
||||
@@ -502,7 +502,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
<div className="flex flex-col divide-y">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Text size="large" weight="plus" className="flex-1" as="div">
|
||||
{t(`shipping.fulfillmentSet.${type}.offers`)}
|
||||
{t(`location.fulfillmentSet.${type}.offers`)}
|
||||
</Text>
|
||||
<div className="flex items-center gap-4">
|
||||
<StatusBadge color={fulfillmentSetExists ? "green" : "red"}>
|
||||
@@ -517,10 +517,10 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
actions: [
|
||||
{
|
||||
icon: <Plus />,
|
||||
label: t("shipping.fulfillmentSet.addZone"),
|
||||
label: t("location.fulfillmentSet.addZone"),
|
||||
onClick: () =>
|
||||
navigate(
|
||||
`/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSet.id}/service-zones/create`
|
||||
`/settings/locations/${locationId}/fulfillment-set/${fulfillmentSet.id}/service-zones/create`
|
||||
),
|
||||
disabled: !fulfillmentSetExists,
|
||||
},
|
||||
@@ -543,7 +543,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
{fulfillmentSetExists && !hasServiceZones && (
|
||||
<div className="text-ui-fg-muted txt-medium flex flex-col items-center justify-center gap-y-4 py-8">
|
||||
<NoRecords
|
||||
message={t("shipping.fulfillmentSet.placeholder")}
|
||||
message={t("location.fulfillmentSet.placeholder")}
|
||||
className="h-fit"
|
||||
/>
|
||||
|
||||
@@ -551,11 +551,11 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSet.id}/service-zones/create`
|
||||
`/settings/locations/${locationId}/fulfillment-set/${fulfillmentSet.id}/service-zones/create`
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("shipping.fulfillmentSet.addZone")}
|
||||
{t("location.fulfillmentSet.addZone")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -586,7 +586,7 @@ const Actions = ({ location }: { location: StockLocationDTO }) => {
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("shipping.deleteLocationWarning", {
|
||||
description: t("location.deleteLocationWarning", {
|
||||
name: location.name,
|
||||
}),
|
||||
verificationText: location.name,
|
||||
@@ -602,7 +602,7 @@ const Actions = ({ location }: { location: StockLocationDTO }) => {
|
||||
try {
|
||||
await mutateAsync(undefined)
|
||||
toast.success(t("general.success"), {
|
||||
description: t("shipping.toast.delete"),
|
||||
description: t("location.toast.delete"),
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -611,7 +611,7 @@ const Actions = ({ location }: { location: StockLocationDTO }) => {
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
}
|
||||
navigate("/settings/shipping", { replace: true })
|
||||
navigate("/settings/locations", { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import { Channels, PencilSquare } from "@medusajs/icons"
|
||||
import { StockLocationDTO } from "@medusajs/types"
|
||||
import { Heading, Text } from "@medusajs/ui"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import { StockLocationDTO } from "@medusajs/types"
|
||||
import { Channels, PencilSquare } from "@medusajs/icons"
|
||||
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { ListSummary } from "../../../../../components/common/list-summary"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
|
||||
type Props = {
|
||||
location: StockLocationDTO
|
||||
@@ -20,7 +20,7 @@ function LocationsSalesChannelsSection({ location }: Props) {
|
||||
return (
|
||||
<div className="shadow-elevation-card-rest bg-ui-bg-base rounded-md p-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<Heading level="h2">{t("shipping.salesChannels.title")}</Heading>
|
||||
<Heading level="h2">{t("location.salesChannels.title")}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
@@ -43,7 +43,7 @@ function LocationsSalesChannelsSection({ location }: Props) {
|
||||
</div>
|
||||
{noChannels ? (
|
||||
<Text size="small" leading="compact" className="text-ui-fg-subtle">
|
||||
{t("shipping.salesChannels.placeholder")}
|
||||
{t("location.salesChannels.placeholder")}
|
||||
</Text>
|
||||
) : (
|
||||
<ListSummary
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { useStockLocation } from "../../../hooks/api/stock-locations"
|
||||
import { LocationGeneralSection } from "./components/location-general-section"
|
||||
import LocationsSalesChannelsSection from "./components/location-sales-channels-section/locations-sales-channels-section"
|
||||
import { locationLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/location/details/after"
|
||||
import before from "virtual:medusa/widgets/location/details/before"
|
||||
import sideAfter from "virtual:medusa/widgets/location/details/side/after"
|
||||
import sideBefore from "virtual:medusa/widgets/location/details/side/before"
|
||||
|
||||
export const LocationDetails = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof locationLoader>
|
||||
>
|
||||
|
||||
const { location_id } = useParams()
|
||||
const {
|
||||
stock_location: location,
|
||||
isPending: isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useStockLocation(
|
||||
location_id!,
|
||||
{
|
||||
fields:
|
||||
"name,*sales_channels,address.city,address.country_code,fulfillment_sets.type,fulfillment_sets.name,*fulfillment_sets.service_zones.geo_zones,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options,*fulfillment_sets.service_zones.shipping_options.rules,*fulfillment_sets.service_zones.shipping_options.shipping_profile",
|
||||
},
|
||||
{
|
||||
initialData,
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: Move to loading.tsx and set as Suspense fallback for the route
|
||||
if (isLoading || !location) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={location} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<LocationGeneralSection location={location} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={location} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="hidden xl:block">
|
||||
<JsonViewSection data={location} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden w-full max-w-[400px] flex-col gap-y-2 xl:flex">
|
||||
{sideBefore.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={location} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<LocationsSalesChannelsSection location={location} />
|
||||
{sideAfter.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={location} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="xl:hidden">
|
||||
<JsonViewSection data={location} />
|
||||
</div>
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+18
-18
@@ -1,3 +1,9 @@
|
||||
import { Buildings, PencilSquare, Trash } from "@medusajs/icons"
|
||||
import {
|
||||
FulfillmentSetDTO,
|
||||
SalesChannelDTO,
|
||||
StockLocationDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
@@ -6,19 +12,13 @@ import {
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import {
|
||||
FulfillmentSetDTO,
|
||||
SalesChannelDTO,
|
||||
StockLocationDTO,
|
||||
} from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Buildings, PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { countries } from "../../../../../lib/countries"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useDeleteStockLocation } from "../../../../../hooks/api/stock-locations"
|
||||
import { BadgeListSummary } from "../../../../../components/common/badge-list-summary"
|
||||
import { useDeleteStockLocation } from "../../../../../hooks/api/stock-locations"
|
||||
import { countries } from "../../../../../lib/countries"
|
||||
|
||||
type SalesChannelsProps = {
|
||||
salesChannels?: SalesChannelDTO[]
|
||||
@@ -37,7 +37,7 @@ function SalesChannels(props: SalesChannelsProps) {
|
||||
className="text-ui-fg-subtle flex-1"
|
||||
as="div"
|
||||
>
|
||||
{t(`shipping.fulfillmentSet.salesChannels`)}
|
||||
{t(`location.fulfillmentSet.salesChannels`)}
|
||||
</Text>
|
||||
<div className="flex-1 text-left">
|
||||
{salesChannels?.length ? (
|
||||
@@ -80,7 +80,7 @@ function FulfillmentSet(props: FulfillmentSetProps) {
|
||||
className="text-ui-fg-subtle flex-1"
|
||||
as="div"
|
||||
>
|
||||
{t(`shipping.fulfillmentSet.${type}.title`)}
|
||||
{t(`location.fulfillmentSet.${type}.title`)}
|
||||
</Text>
|
||||
<div className="flex-1 text-left">
|
||||
<StatusBadge color={fulfillmentSetExists ? "green" : "red"}>
|
||||
@@ -107,7 +107,7 @@ function Location(props: LocationProps) {
|
||||
const handleDelete = async () => {
|
||||
const result = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("shipping.deleteLocation.confirm", {
|
||||
description: t("location.deleteLocation.confirm", {
|
||||
name: location.name,
|
||||
}),
|
||||
confirmText: t("actions.remove"),
|
||||
@@ -122,7 +122,7 @@ function Location(props: LocationProps) {
|
||||
await deleteLocation()
|
||||
|
||||
toast.success(t("general.success"), {
|
||||
description: t("shipping.deleteLocation.success", {
|
||||
description: t("location.deleteLocation.success", {
|
||||
name: location.name,
|
||||
}),
|
||||
dismissLabel: t("general.close"),
|
||||
@@ -139,14 +139,14 @@ function Location(props: LocationProps) {
|
||||
<Container className="flex flex-col divide-y p-0">
|
||||
<div className="px-6 py-5">
|
||||
<div className="flex flex-row items-center justify-between gap-x-4">
|
||||
{/*ICON*/}
|
||||
{/* ICON*/}
|
||||
<div className="grow-0 rounded-lg border">
|
||||
<div className="bg-ui-bg-field m-1 rounded-md p-2">
|
||||
<Buildings className="text-ui-fg-subtle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*LOCATION INFO*/}
|
||||
{/* LOCATION INFO*/}
|
||||
<div className="grow-1 flex flex-1 flex-col">
|
||||
<Text weight="plus">{location.name}</Text>
|
||||
<Text className="text-ui-fg-subtle txt-small">
|
||||
@@ -160,7 +160,7 @@ function Location(props: LocationProps) {
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{/*ACTION*/}
|
||||
{/* ACTION*/}
|
||||
<div className="flex h-[12px] grow-0 items-center gap-4 divide-x overflow-hidden">
|
||||
<ActionMenu
|
||||
groups={[
|
||||
@@ -169,10 +169,10 @@ function Location(props: LocationProps) {
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: `/settings/shipping/${location.id}/edit`,
|
||||
to: `/settings/locations/${location.id}/edit`,
|
||||
},
|
||||
{
|
||||
label: t("shipping.deleteLocation.label"),
|
||||
label: t("location.deleteLocation.label"),
|
||||
icon: <Trash />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
@@ -182,7 +182,7 @@ function Location(props: LocationProps) {
|
||||
/>
|
||||
<Button
|
||||
className="text-ui-fg-interactive rounded-none pl-5 hover:bg-transparent active:bg-transparent"
|
||||
onClick={() => navigate(`/settings/shipping/${location.id}`)}
|
||||
onClick={() => navigate(`/settings/locations/${location.id}`)}
|
||||
variant="transparent"
|
||||
>
|
||||
{t("actions.viewDetails")}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, Outlet, useLoaderData } from "react-router-dom"
|
||||
|
||||
import { useStockLocations } from "../../../hooks/api/stock-locations"
|
||||
import Location from "./components/location/location"
|
||||
import { locationListFields } from "./const"
|
||||
import { shippingListLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/location/list/after"
|
||||
import before from "virtual:medusa/widgets/location/list/before"
|
||||
|
||||
export function LocationList() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof shippingListLoader>
|
||||
>
|
||||
|
||||
const { stock_locations: stockLocations = [], isPending } = useStockLocations(
|
||||
{
|
||||
fields: locationListFields,
|
||||
},
|
||||
{ initialData }
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Container className="flex h-fit items-center justify-between p-8">
|
||||
<div>
|
||||
<Heading className="mb-2">{t("location.title")}</Heading>
|
||||
<Text className="text-ui-fg-subtle txt-small">
|
||||
{t("location.description")}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size="small" variant="secondary" asChild>
|
||||
<Link to="create">{t("location.createLocation")}</Link>
|
||||
</Button>
|
||||
</Container>
|
||||
<div className="flex flex-col gap-3 lg:col-span-2">
|
||||
{stockLocations.map((location) => (
|
||||
<Location key={location.id} location={location} />
|
||||
))}
|
||||
</div>
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+32
-32
@@ -1,39 +1,39 @@
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import {
|
||||
ColumnDef,
|
||||
createColumnHelper,
|
||||
RowSelectionState,
|
||||
ColumnDef,
|
||||
createColumnHelper,
|
||||
RowSelectionState,
|
||||
} from "@tanstack/react-table"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as zod from "zod"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Heading,
|
||||
IconButton,
|
||||
Text,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { ServiceZoneDTO, HttpTypes } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
|
||||
import { HttpTypes, ServiceZoneDTO } from "@medusajs/types"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { SplitView } from "../../../../../components/layout/split-view"
|
||||
import { useUpdateServiceZone } from "../../../../../hooks/api/stock-locations"
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Heading,
|
||||
IconButton,
|
||||
Text,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||
import { SplitView } from "../../../../../components/layout/split-view"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateServiceZone } from "../../../../../hooks/api/stock-locations"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||
|
||||
const PREFIX = "ac"
|
||||
const PAGE_SIZE = 50
|
||||
@@ -215,7 +215,7 @@ export function EditServiceZoneAreasForm({
|
||||
<SplitView.Content className="mx-auto max-w-[720px]">
|
||||
<div className="container w-fit px-1 py-8">
|
||||
<Heading className="mt-8 text-2xl">
|
||||
{t("shipping.serviceZone.editAreasTitle", {
|
||||
{t("location.serviceZone.editAreasTitle", {
|
||||
zone: zone.name,
|
||||
})}
|
||||
</Heading>
|
||||
@@ -224,10 +224,10 @@ export function EditServiceZoneAreasForm({
|
||||
<div className="container flex items-center justify-between py-8 pr-1">
|
||||
<div>
|
||||
<Text weight="plus">
|
||||
{t("shipping.serviceZone.areas.title")}
|
||||
{t("location.serviceZone.areas.title")}
|
||||
</Text>
|
||||
<Text className="text-ui-fg-subtle mt-2">
|
||||
{t("shipping.serviceZone.areas.description")}
|
||||
{t("location.serviceZone.areas.description")}
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
@@ -235,7 +235,7 @@ export function EditServiceZoneAreasForm({
|
||||
variant="secondary"
|
||||
type="button"
|
||||
>
|
||||
{t("shipping.serviceZone.areas.manage")}
|
||||
{t("location.serviceZone.areas.manage")}
|
||||
</Button>
|
||||
</div>
|
||||
{!!selectedCountries.length && (
|
||||
@@ -268,7 +268,7 @@ export function EditServiceZoneAreasForm({
|
||||
)}
|
||||
{showAreasError && (
|
||||
<Alert dismissible variant="error">
|
||||
{t("shipping.serviceZone.areas.error")}
|
||||
{t("location.serviceZone.areas.error")}
|
||||
</Alert>
|
||||
)}
|
||||
</SplitView.Content>
|
||||
+36
-36
@@ -1,41 +1,41 @@
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import {
|
||||
ColumnDef,
|
||||
createColumnHelper,
|
||||
RowSelectionState,
|
||||
ColumnDef,
|
||||
createColumnHelper,
|
||||
RowSelectionState,
|
||||
} from "@tanstack/react-table"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as zod from "zod"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Heading,
|
||||
IconButton,
|
||||
Input,
|
||||
Text,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { FulfillmentSetDTO, HttpTypes } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
|
||||
import { FulfillmentSetDTO, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Heading,
|
||||
IconButton,
|
||||
Input,
|
||||
Text,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { SplitView } from "../../../../../components/layout/split-view"
|
||||
import { useCreateServiceZone } from "../../../../../hooks/api/stock-locations"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useCreateServiceZone } from "../../../../../hooks/api/stock-locations"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||
|
||||
const PREFIX = "ac"
|
||||
const PAGE_SIZE = 50
|
||||
@@ -211,7 +211,7 @@ export function CreateServiceZoneForm({
|
||||
<SplitView.Content className="mx-auto max-w-[720px]">
|
||||
<div className="container w-fit px-1 py-8">
|
||||
<Heading className="mb-12 mt-8 text-2xl">
|
||||
{t("shipping.fulfillmentSet.create.title", {
|
||||
{t("location.fulfillmentSet.create.title", {
|
||||
fulfillmentSet: fulfillmentSet.name,
|
||||
})}
|
||||
</Heading>
|
||||
@@ -224,7 +224,7 @@ export function CreateServiceZoneForm({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.serviceZone.create.zoneName")}
|
||||
{t("location.serviceZone.create.zoneName")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Input placeholder={t("fields.name")} {...field} />
|
||||
@@ -239,10 +239,10 @@ export function CreateServiceZoneForm({
|
||||
|
||||
<Alert>
|
||||
<Text weight="plus">
|
||||
{t("shipping.serviceZone.create.subtitle")}
|
||||
{t("location.serviceZone.create.subtitle")}
|
||||
</Text>
|
||||
<Text className="text-ui-fg-subtle mt-2">
|
||||
{t("shipping.serviceZone.create.description")}
|
||||
{t("location.serviceZone.create.description")}
|
||||
</Text>
|
||||
</Alert>
|
||||
|
||||
@@ -250,10 +250,10 @@ export function CreateServiceZoneForm({
|
||||
<div className="container flex items-center justify-between py-8 pr-1">
|
||||
<div>
|
||||
<Text weight="plus">
|
||||
{t("shipping.serviceZone.areas.title")}
|
||||
{t("location.serviceZone.areas.title")}
|
||||
</Text>
|
||||
<Text className="text-ui-fg-subtle mt-2">
|
||||
{t("shipping.serviceZone.areas.description")}
|
||||
{t("location.serviceZone.areas.description")}
|
||||
</Text>
|
||||
</div>
|
||||
<Button
|
||||
@@ -261,7 +261,7 @@ export function CreateServiceZoneForm({
|
||||
variant="secondary"
|
||||
type="button"
|
||||
>
|
||||
{t("shipping.serviceZone.areas.manage")}
|
||||
{t("location.serviceZone.areas.manage")}
|
||||
</Button>
|
||||
</div>
|
||||
{!!selectedCountries.length && (
|
||||
@@ -294,7 +294,7 @@ export function CreateServiceZoneForm({
|
||||
)}
|
||||
{showAreasError && (
|
||||
<Alert dismissible variant="error">
|
||||
{t("shipping.serviceZone.areas.error")}
|
||||
{t("location.serviceZone.areas.error")}
|
||||
</Alert>
|
||||
)}
|
||||
</SplitView.Content>
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
import { ServiceZoneDTO } from "@medusajs/types"
|
||||
import { Alert, Button, Input, Text, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { ServiceZoneDTO } from "@medusajs/types"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useUpdateServiceZone } from "../../../../../hooks/api/stock-locations"
|
||||
|
||||
@@ -88,10 +88,10 @@ export const EditServiceZoneForm = ({
|
||||
</div>
|
||||
<Alert>
|
||||
<Text weight="plus">
|
||||
{t("shipping.serviceZone.create.subtitle")}
|
||||
{t("location.serviceZone.create.subtitle")}
|
||||
</Text>
|
||||
<Text className="text-ui-fg-subtle mt-2">
|
||||
{t("shipping.serviceZone.create.description")}
|
||||
{t("location.serviceZone.create.description")}
|
||||
</Text>
|
||||
</Alert>
|
||||
</div>
|
||||
+2
-2
@@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next"
|
||||
import { json, useParams } from "react-router-dom"
|
||||
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditServiceZoneForm } from "./components/edit-region-form"
|
||||
import { useStockLocation } from "../../../hooks/api/stock-locations"
|
||||
import { EditServiceZoneForm } from "./components/edit-region-form"
|
||||
|
||||
export const ServiceZoneEdit = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -36,7 +36,7 @@ export const ServiceZoneEdit = () => {
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("shipping.serviceZone.edit.title")}</Heading>
|
||||
<Heading>{t("location.serviceZone.edit.title")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isPending && zone && (
|
||||
<EditServiceZoneForm
|
||||
+12
-12
@@ -9,12 +9,12 @@ import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useShippingProfiles } from "../../../../../hooks/api/shipping-profiles"
|
||||
import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
||||
import { isOptionEnabledInStore } from "../../../../../lib/shipping-options"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { useShippingProfiles } from "../../../../../hooks/api/shipping-profiles"
|
||||
import { pick } from "../../../../../lib/common"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { isOptionEnabledInStore } from "../../../../../lib/shipping-options"
|
||||
|
||||
enum ShippingAllocation {
|
||||
FlatRate = "flat",
|
||||
@@ -118,26 +118,26 @@ export const EditShippingOptionForm = ({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.allocation")}
|
||||
{t("location.shippingOptions.create.allocation")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup {...field} onValueChange={field.onChange}>
|
||||
<RadioGroup.ChoiceBox
|
||||
className="flex-1"
|
||||
value={ShippingAllocation.FlatRate}
|
||||
label={t("shipping.shippingOptions.create.fixed")}
|
||||
label={t("location.shippingOptions.create.fixed")}
|
||||
description={t(
|
||||
"shipping.shippingOptions.create.fixedDescription"
|
||||
"location.shippingOptions.create.fixedDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.ChoiceBox
|
||||
className="flex-1"
|
||||
value={ShippingAllocation.Calculated}
|
||||
label={t(
|
||||
"shipping.shippingOptions.create.calculated"
|
||||
"location.shippingOptions.create.calculated"
|
||||
)}
|
||||
description={t(
|
||||
"shipping.shippingOptions.create.calculatedDescription"
|
||||
"location.shippingOptions.create.calculatedDescription"
|
||||
)}
|
||||
/>
|
||||
</RadioGroup>
|
||||
@@ -173,7 +173,7 @@ export const EditShippingOptionForm = ({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.profile")}
|
||||
{t("location.shippingOptions.create.profile")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select {...field} onValueChange={onChange}>
|
||||
@@ -203,7 +203,7 @@ export const EditShippingOptionForm = ({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.edit.provider")}
|
||||
{t("location.shippingOptions.edit.provider")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select {...field} onValueChange={onChange}>
|
||||
@@ -236,7 +236,7 @@ export const EditShippingOptionForm = ({
|
||||
<Form.Item>
|
||||
<div className="flex items-center justify-between">
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.enable")}
|
||||
{t("location.shippingOptions.create.enable")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
+2
-2
@@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next"
|
||||
import { json, useParams, useSearchParams } from "react-router-dom"
|
||||
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditShippingOptionForm } from "./components/edit-region-form"
|
||||
import { useShippingOptions } from "../../../hooks/api/shipping-options"
|
||||
import { EditShippingOptionForm } from "./components/edit-region-form"
|
||||
|
||||
export const ShippingOptionEdit = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -31,7 +31,7 @@ export const ShippingOptionEdit = () => {
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("shipping.shippingOptions.edit.title")}</Heading>
|
||||
<Heading>{t("location.shippingOptions.edit.title")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isPending && shippingOption && (
|
||||
<EditShippingOptionForm
|
||||
+22
-22
@@ -1,8 +1,9 @@
|
||||
import { useForm } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import React, { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { ServiceZoneDTO } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
@@ -10,26 +11,25 @@ import {
|
||||
ProgressStatus,
|
||||
ProgressTabs,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Switch,
|
||||
Text,
|
||||
clx,
|
||||
Select,
|
||||
} from "@medusajs/ui"
|
||||
import { ServiceZoneDTO } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { CreateShippingOptionsPricesForm } from "./create-shipping-options-prices-form"
|
||||
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
||||
import { useRegions } from "../../../../../hooks/api/regions"
|
||||
import { useCreateShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { useShippingProfiles } from "../../../../../hooks/api/shipping-profiles"
|
||||
import { getDbAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { useRegions } from "../../../../../hooks/api/regions"
|
||||
import { getDbAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { CreateShippingOptionsPricesForm } from "./create-shipping-options-prices-form"
|
||||
|
||||
enum Tab {
|
||||
DETAILS = "details",
|
||||
@@ -230,7 +230,7 @@ export function CreateShippingOptionsForm({
|
||||
className="w-full max-w-[200px]"
|
||||
>
|
||||
<span className="w-full cursor-auto overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{t("shipping.shippingOptions.create.details")}
|
||||
{t("location.shippingOptions.create.details")}
|
||||
</span>
|
||||
</ProgressTabs.Trigger>
|
||||
{!isCalculatedPriceType && (
|
||||
@@ -241,7 +241,7 @@ export function CreateShippingOptionsForm({
|
||||
disabled={!canMoveToPricing}
|
||||
>
|
||||
<span className="w-full overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{t("shipping.shippingOptions.create.pricing")}
|
||||
{t("location.shippingOptions.create.pricing")}
|
||||
</span>
|
||||
</ProgressTabs.Trigger>
|
||||
)}
|
||||
@@ -286,7 +286,7 @@ export function CreateShippingOptionsForm({
|
||||
<div className="container mx-auto w-[720px] px-1 py-8">
|
||||
<Heading className="mb-12 mt-8 text-2xl">
|
||||
{t(
|
||||
`shipping.${
|
||||
`location.${
|
||||
isReturn ? "returnOptions" : "shippingOptions"
|
||||
}.create.title`,
|
||||
{
|
||||
@@ -298,10 +298,10 @@ export function CreateShippingOptionsForm({
|
||||
{!isReturn && (
|
||||
<div>
|
||||
<Text weight="plus">
|
||||
{t("shipping.shippingOptions.create.subtitle")}
|
||||
{t("location.shippingOptions.create.subtitle")}
|
||||
</Text>
|
||||
<Text className="text-ui-fg-subtle mb-8 mt-2">
|
||||
{t("shipping.shippingOptions.create.description")}
|
||||
{t("location.shippingOptions.create.description")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
@@ -313,7 +313,7 @@ export function CreateShippingOptionsForm({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.allocation")}
|
||||
{t("location.shippingOptions.create.allocation")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
@@ -324,19 +324,19 @@ export function CreateShippingOptionsForm({
|
||||
<RadioGroup.ChoiceBox
|
||||
className="flex-1"
|
||||
value={ShippingAllocation.FlatRate}
|
||||
label={t("shipping.shippingOptions.create.fixed")}
|
||||
label={t("location.shippingOptions.create.fixed")}
|
||||
description={t(
|
||||
"shipping.shippingOptions.create.fixedDescription"
|
||||
"location.shippingOptions.create.fixedDescription"
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.ChoiceBox
|
||||
className="flex-1"
|
||||
value={ShippingAllocation.Calculated}
|
||||
label={t(
|
||||
"shipping.shippingOptions.create.calculated"
|
||||
"location.shippingOptions.create.calculated"
|
||||
)}
|
||||
description={t(
|
||||
"shipping.shippingOptions.create.calculatedDescription"
|
||||
"location.shippingOptions.create.calculatedDescription"
|
||||
)}
|
||||
/>
|
||||
</RadioGroup>
|
||||
@@ -374,7 +374,7 @@ export function CreateShippingOptionsForm({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.profile")}
|
||||
{t("location.shippingOptions.create.profile")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select {...field} onValueChange={onChange}>
|
||||
@@ -405,7 +405,7 @@ export function CreateShippingOptionsForm({
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.edit.provider")}
|
||||
{t("location.shippingOptions.edit.provider")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select {...field} onValueChange={onChange}>
|
||||
@@ -437,7 +437,7 @@ export function CreateShippingOptionsForm({
|
||||
<Form.Item>
|
||||
<div className="flex items-center justify-between">
|
||||
<Form.Label>
|
||||
{t("shipping.shippingOptions.create.enable")}
|
||||
{t("location.shippingOptions.create.enable")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch
|
||||
@@ -449,7 +449,7 @@ export function CreateShippingOptionsForm({
|
||||
</div>
|
||||
<Form.Hint className="!mt-1">
|
||||
{t(
|
||||
"shipping.shippingOptions.create.enableDescription"
|
||||
"location.shippingOptions.create.enableDescription"
|
||||
)}
|
||||
</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
+4
-4
@@ -11,8 +11,8 @@ import {
|
||||
Heading,
|
||||
StatusBadge,
|
||||
Text,
|
||||
toast,
|
||||
Tooltip,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { format } from "date-fns"
|
||||
@@ -21,10 +21,10 @@ import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { Skeleton } from "../../../../../components/common/skeleton"
|
||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||
import { useCancelFulfillment } from "../../../../../hooks/api/fulfillment"
|
||||
import { useStockLocation } from "../../../../../hooks/api/stock-locations"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { useStockLocation } from "../../../../../hooks/api/stock-locations"
|
||||
import { useCancelFulfillment } from "../../../../../hooks/api/fulfillment"
|
||||
|
||||
type OrderFulfillmentSectionProps = {
|
||||
order: OrderDTO & { fulfillments: FulfillmentDTO[] }
|
||||
@@ -285,7 +285,7 @@ const Fulfillment = ({
|
||||
</Text>
|
||||
{stock_location ? (
|
||||
<Link
|
||||
to={`/settings/shipping/${stock_location.id}`}
|
||||
to={`/settings/locations/${stock_location.id}`}
|
||||
className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover transition-fg"
|
||||
>
|
||||
<Text size="small" leading="compact">
|
||||
|
||||
@@ -6,6 +6,9 @@ import { RegionCountrySection } from "./components/region-country-section"
|
||||
import { RegionGeneralSection } from "./components/region-general-section"
|
||||
import { regionLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/region/details/after"
|
||||
import before from "virtual:medusa/widgets/region/details/before"
|
||||
|
||||
export const RegionDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof regionLoader>
|
||||
@@ -36,8 +39,22 @@ export const RegionDetail = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={region} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<RegionGeneralSection region={region} />
|
||||
<RegionCountrySection region={region} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={region} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<JsonViewSection data={region} />
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import { Outlet } from "react-router-dom"
|
||||
|
||||
import { RegionListTable } from "./components/region-list-table"
|
||||
|
||||
import after from "virtual:medusa/widgets/region/list/after"
|
||||
import before from "virtual:medusa/widgets/region/list/before"
|
||||
|
||||
export const RegionList = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<RegionListTable />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useCreateShippingProfile } from "../../../../../hooks/api/shipping-profiles"
|
||||
|
||||
|
||||
+18
@@ -1,4 +1,5 @@
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import {
|
||||
GeneralSectionSkeleton,
|
||||
@@ -7,6 +8,9 @@ import {
|
||||
import { useShippingProfile } from "../../../hooks/api/shipping-profiles"
|
||||
import { ShippingProfileGeneralSection } from "./components/shipping-profile-general-section"
|
||||
|
||||
import after from "virtual:medusa/widgets/shipping_profile/details/after"
|
||||
import before from "virtual:medusa/widgets/shipping_profile/details/before"
|
||||
|
||||
export const ShippingProfileDetail = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
@@ -29,7 +33,21 @@ export const ShippingProfileDetail = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={shipping_profile} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<ShippingProfileGeneralSection profile={shipping_profile} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={shipping_profile} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<JsonViewSection data={shipping_profile} />
|
||||
</div>
|
||||
)
|
||||
|
||||
+18
@@ -1,10 +1,28 @@
|
||||
import { Outlet } from "react-router-dom"
|
||||
|
||||
import { ShippingProfileListTable } from "./components/shipping-profile-list-table"
|
||||
|
||||
import after from "virtual:medusa/widgets/shipping_profile/list/after"
|
||||
import before from "virtual:medusa/widgets/shipping_profile/list/before"
|
||||
|
||||
export const ShippingProfileList = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<ShippingProfileListTable />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { LocationGeneralSection } from "./components/location-general-section"
|
||||
import { useStockLocation } from "../../../hooks/api/stock-locations"
|
||||
import LocationsSalesChannelsSection from "./components/location-sales-channels-section/locations-sales-channels-section"
|
||||
import { locationLoader } from "./loader"
|
||||
|
||||
export const LocationDetails = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof locationLoader>
|
||||
>
|
||||
|
||||
const { location_id } = useParams()
|
||||
const {
|
||||
stock_location: location,
|
||||
isPending: isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useStockLocation(
|
||||
location_id!,
|
||||
{
|
||||
fields:
|
||||
"name,*sales_channels,address.city,address.country_code,fulfillment_sets.type,fulfillment_sets.name,*fulfillment_sets.service_zones.geo_zones,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options,*fulfillment_sets.service_zones.shipping_options.rules,*fulfillment_sets.service_zones.shipping_options.shipping_profile",
|
||||
},
|
||||
{
|
||||
initialData,
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: Move to loading.tsx and set as Suspense fallback for the route
|
||||
if (isLoading || !location) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<LocationGeneralSection location={location} />
|
||||
<div className="flex w-full flex-col gap-y-2 xl:hidden">
|
||||
<LocationsSalesChannelsSection location={location} />
|
||||
</div>
|
||||
<JsonViewSection data={location} />
|
||||
</div>
|
||||
<div className="hidden w-full max-w-[400px] flex-col gap-y-2 xl:flex">
|
||||
<LocationsSalesChannelsSection location={location} />
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, Outlet, useLoaderData } from "react-router-dom"
|
||||
|
||||
import { shippingListLoader } from "./loader"
|
||||
import { useStockLocations } from "../../../hooks/api/stock-locations"
|
||||
import Location from "./components/location/location"
|
||||
import { locationListFields } from "./const"
|
||||
|
||||
export function LocationList() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<typeof shippingListLoader>
|
||||
>
|
||||
|
||||
let { stock_locations: stockLocations = [], isPending } = useStockLocations(
|
||||
{
|
||||
fields: locationListFields,
|
||||
},
|
||||
{ initialData }
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="py-4">
|
||||
<Container className="mb-4 flex h-fit items-center justify-between p-8">
|
||||
<div>
|
||||
<Heading className="mb-2">{t("shipping.title")}</Heading>
|
||||
<Text className="text-ui-fg-subtle txt-small">
|
||||
{t("shipping.description")}
|
||||
</Text>
|
||||
</div>
|
||||
<Button size="small" variant="secondary" asChild>
|
||||
<Link to="create">{t("shipping.createLocation")}</Link>
|
||||
</Button>
|
||||
</Container>
|
||||
<div className="flex flex-col gap-4 lg:col-span-2">
|
||||
{stockLocations.map((location) => (
|
||||
<Location key={location.id} location={location} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Outlet />
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user