feat(core-flows,dashboard,js-sdk,medusa,types): support Fulfillment Options (#10622)
**What** - add a list point for fetching fulfillment options for a provider - add FO support on SO create & update on dashboard - pass `cart` and `stockLocation` to `validateFufillmentData` context --- CLOSES CMRC-789 CLOSES CMRC-790
This commit is contained in:
@@ -9,6 +9,12 @@ export const fulfillmentProvidersQueryKeys = queryKeysFactory(
|
||||
FULFILLMENT_PROVIDERS_QUERY_KEY
|
||||
)
|
||||
|
||||
const FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY =
|
||||
"fulfillment_provider_options" as const
|
||||
export const fulfillmentProviderOptionsQueryKeys = queryKeysFactory(
|
||||
FULFILLMENT_PROVIDER_OPTIONS_QUERY_KEY
|
||||
)
|
||||
|
||||
export const useFulfillmentProviders = (
|
||||
query?: HttpTypes.AdminFulfillmentProviderListParams,
|
||||
options?: Omit<
|
||||
@@ -29,3 +35,25 @@ export const useFulfillmentProviders = (
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useFulfillmentProviderOptions = (
|
||||
providerId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminFulfillmentProviderOptionsListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminFulfillmentProviderOptionsListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () =>
|
||||
sdk.admin.fulfillmentProvider.listFulfillmentOptions(providerId),
|
||||
queryKey: fulfillmentProviderOptionsQueryKeys.list(providerId),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type ComboboxQueryParams = {
|
||||
|
||||
export const useComboboxData = <
|
||||
TResponse extends ComboboxExternalData,
|
||||
TParams extends ComboboxQueryParams
|
||||
TParams extends ComboboxQueryParams,
|
||||
>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
@@ -50,7 +50,6 @@ export const useComboboxData = <
|
||||
enabled: !!defaultValue,
|
||||
})
|
||||
|
||||
|
||||
const { data, ...rest } = useInfiniteQuery({
|
||||
queryKey: [...queryKey, query],
|
||||
queryFn: async ({ pageParam = 0 }) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1523,7 +1523,8 @@
|
||||
"hint": "Whether customers can use this option during checkout."
|
||||
},
|
||||
"provider": "Fulfillment provider",
|
||||
"profile": "Shipping profile"
|
||||
"profile": "Shipping profile",
|
||||
"fulfillmentOption": "Fulfillment option"
|
||||
}
|
||||
},
|
||||
"serviceZones": {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Heading, Input, RadioGroup, Text } from "@medusajs/ui"
|
||||
import { Heading, Input, RadioGroup, Select, Text } from "@medusajs/ui"
|
||||
import { UseFormReturn } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
import { Divider } from "../../../../../components/common/divider"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { SwitchBox } from "../../../../../components/common/switch-box"
|
||||
@@ -18,6 +19,8 @@ type CreateShippingOptionDetailsFormProps = {
|
||||
isReturn?: boolean
|
||||
zone: HttpTypes.AdminServiceZone
|
||||
locationId: string
|
||||
fulfillmentProviderOptions: HttpTypes.AdminFulfillmentProviderOption[]
|
||||
selectedProviderId?: string
|
||||
}
|
||||
|
||||
export const CreateShippingOptionDetailsForm = ({
|
||||
@@ -25,6 +28,8 @@ export const CreateShippingOptionDetailsForm = ({
|
||||
isReturn = false,
|
||||
zone,
|
||||
locationId,
|
||||
fulfillmentProviderOptions,
|
||||
selectedProviderId,
|
||||
}: CreateShippingOptionDetailsFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -134,9 +139,6 @@ export const CreateShippingOptionDetailsForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="shipping_profile_id"
|
||||
@@ -160,7 +162,9 @@ export const CreateShippingOptionDetailsForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="provider_id"
|
||||
@@ -177,6 +181,10 @@ export const CreateShippingOptionDetailsForm = ({
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e)
|
||||
form.setValue("fulfillment_option_id", "")
|
||||
}}
|
||||
options={fulfillmentProviders.options}
|
||||
searchValue={fulfillmentProviders.searchValue}
|
||||
onSearchValueChange={
|
||||
@@ -190,6 +198,45 @@ export const CreateShippingOptionDetailsForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="fulfillment_option_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t(
|
||||
"stockLocations.shippingOptions.fields.fulfillmentOption"
|
||||
)}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={!selectedProviderId}
|
||||
key={selectedProviderId}
|
||||
>
|
||||
<Select.Trigger ref={field.ref}>
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Content>
|
||||
{fulfillmentProviderOptions
|
||||
?.filter((fo) => !!fo.is_return === isReturn)
|
||||
.map((option) => (
|
||||
<Select.Item value={option.id} key={option.id}>
|
||||
{option.name || option.id}
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, ProgressStatus, ProgressTabs, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useState } from "react"
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
CreateShippingOptionDetailsSchema,
|
||||
CreateShippingOptionSchema,
|
||||
} from "./schema"
|
||||
import { useFulfillmentProviderOptions } from "../../../../../hooks/api"
|
||||
|
||||
enum Tab {
|
||||
DETAILS = "details",
|
||||
@@ -50,6 +51,7 @@ export function CreateShippingOptionsForm({
|
||||
enabled_in_store: true,
|
||||
shipping_profile_id: "",
|
||||
provider_id: "",
|
||||
fulfillment_option_id: "",
|
||||
region_prices: {},
|
||||
currency_prices: {},
|
||||
conditional_region_prices: {},
|
||||
@@ -58,6 +60,16 @@ export function CreateShippingOptionsForm({
|
||||
resolver: zodResolver(CreateShippingOptionSchema),
|
||||
})
|
||||
|
||||
const selectedProviderId = useWatch({
|
||||
control: form.control,
|
||||
name: "provider_id",
|
||||
})
|
||||
|
||||
const { fulfillment_options: fulfillmentProviderOptions } =
|
||||
useFulfillmentProviderOptions(selectedProviderId, {
|
||||
enabled: !!selectedProviderId,
|
||||
})
|
||||
|
||||
const isCalculatedPriceType =
|
||||
form.watch("price_type") === ShippingOptionPriceType.Calculated
|
||||
|
||||
@@ -123,6 +135,10 @@ export function CreateShippingOptionsForm({
|
||||
...conditionalRegionPrices,
|
||||
]
|
||||
|
||||
const fulfillmentOptionData = fulfillmentProviderOptions?.find(
|
||||
(fo) => fo.id === data.fulfillment_option_id
|
||||
)!
|
||||
|
||||
await mutateAsync(
|
||||
{
|
||||
name: data.name,
|
||||
@@ -131,6 +147,7 @@ export function CreateShippingOptionsForm({
|
||||
shipping_profile_id: data.shipping_profile_id,
|
||||
provider_id: data.provider_id,
|
||||
prices: allPrices,
|
||||
data: fulfillmentOptionData as unknown as Record<string, unknown>,
|
||||
rules: [
|
||||
{
|
||||
// eslint-disable-next-line
|
||||
@@ -293,6 +310,8 @@ export function CreateShippingOptionsForm({
|
||||
zone={zone}
|
||||
isReturn={isReturn}
|
||||
locationId={locationId}
|
||||
fulfillmentProviderOptions={fulfillmentProviderOptions || []}
|
||||
selectedProviderId={selectedProviderId}
|
||||
/>
|
||||
</ProgressTabs.Content>
|
||||
<ProgressTabs.Content value={Tab.PRICING} className="size-full">
|
||||
|
||||
@@ -12,6 +12,7 @@ export const CreateShippingOptionDetailsSchema = z.object({
|
||||
enabled_in_store: z.boolean(),
|
||||
shipping_profile_id: z.string().min(1),
|
||||
provider_id: z.string().min(1),
|
||||
fulfillment_option_id: z.string().min(1),
|
||||
})
|
||||
|
||||
export const ShippingOptionConditionalPriceSchema = z.object({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, Input, RadioGroup, toast } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Divider } from "../../../../../components/common/divider"
|
||||
@@ -14,7 +15,6 @@ import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-opti
|
||||
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { pick } from "../../../../../lib/common"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { isOptionEnabledInStore } from "../../../../../lib/shipping-options"
|
||||
import { ShippingOptionPriceType } from "../../../common/constants"
|
||||
|
||||
@@ -28,7 +28,6 @@ const EditShippingOptionSchema = zod.object({
|
||||
price_type: zod.nativeEnum(ShippingOptionPriceType),
|
||||
enabled_in_store: zod.boolean().optional(),
|
||||
shipping_profile_id: zod.string(),
|
||||
provider_id: zod.string(),
|
||||
})
|
||||
|
||||
export const EditShippingOptionForm = ({
|
||||
@@ -49,29 +48,14 @@ export const EditShippingOptionForm = ({
|
||||
defaultValue: shippingOption.shipping_profile_id,
|
||||
})
|
||||
|
||||
const fulfillmentProviders = useComboboxData({
|
||||
queryFn: (params) =>
|
||||
sdk.admin.fulfillmentProvider.list({
|
||||
...params,
|
||||
stock_location_id: locationId,
|
||||
}),
|
||||
queryKey: ["fulfillment_providers"],
|
||||
getOptions: (data) =>
|
||||
data.fulfillment_providers.map((provider) => ({
|
||||
label: formatProvider(provider.id),
|
||||
value: provider.id,
|
||||
})),
|
||||
defaultValue: shippingOption.provider_id,
|
||||
})
|
||||
|
||||
const form = useForm<zod.infer<typeof EditShippingOptionSchema>>({
|
||||
defaultValues: {
|
||||
name: shippingOption.name,
|
||||
price_type: shippingOption.price_type as ShippingOptionPriceType,
|
||||
enabled_in_store: isOptionEnabledInStore(shippingOption),
|
||||
shipping_profile_id: shippingOption.shipping_profile_id,
|
||||
provider_id: shippingOption.provider_id,
|
||||
},
|
||||
resolver: zodResolver(EditShippingOptionSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions(
|
||||
@@ -101,7 +85,6 @@ export const EditShippingOptionForm = ({
|
||||
name: values.name,
|
||||
price_type: values.price_type,
|
||||
shipping_profile_id: values.shipping_profile_id,
|
||||
provider_id: values.provider_id,
|
||||
rules,
|
||||
},
|
||||
{
|
||||
@@ -209,35 +192,6 @@ export const EditShippingOptionForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="provider_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label
|
||||
tooltip={t(
|
||||
"stockLocations.fulfillmentProviders.shippingOptionsTooltip"
|
||||
)}
|
||||
>
|
||||
{t("stockLocations.shippingOptions.fields.provider")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
{...field}
|
||||
options={fulfillmentProviders.options}
|
||||
searchValue={fulfillmentProviders.searchValue}
|
||||
onSearchValueChange={
|
||||
fulfillmentProviders.onSearchValueChange
|
||||
}
|
||||
disabled={fulfillmentProviders.disabled}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
Reference in New Issue
Block a user