feat(core-flows, dashboard, medusa, types): optional shipping profile (#11434)

* feat: create product flow changes

* feat: allow unsetting SP on product update

* feat: update prepare line item helper

* test: add testcase

* wip: fix tests

* fix: update module tests

* fix: cart module test
This commit is contained in:
Frane Polić
2025-02-17 19:08:59 +01:00
committed by GitHub
parent 3b7856e8f5
commit ee848bf0f4
19 changed files with 191 additions and 112 deletions

View File

@@ -57,6 +57,7 @@ interface ComboboxProps<T extends Value = Value>
isFetchingNextPage?: boolean
onCreateOption?: (value: string) => void
noResultsPlaceholder?: ReactNode
allowClear?: boolean
}
const ComboboxImpl = <T extends Value = string>(
@@ -72,6 +73,7 @@ const ComboboxImpl = <T extends Value = string>(
isFetchingNextPage,
onCreateOption,
noResultsPlaceholder,
allowClear,
...inputProps
}: ComboboxProps<T>,
ref: ForwardedRef<HTMLInputElement>
@@ -303,6 +305,18 @@ const ComboboxImpl = <T extends Value = string>(
{...inputProps}
/>
</div>
{allowClear && controlledValue && (
<button
type="button"
onClick={(e) => {
e.preventDefault()
handleValueChange(undefined)
}}
className="bg-ui-bg-base hover:bg-ui-bg-base-hover txt-compact-small-plus text-ui-fg-subtle focus-within:border-ui-fg-interactive transition-fg absolute right-[28px] top-0.5 z-[1] flex h-[28px] items-center rounded-[4px] border px-1.5 py-[2px] outline-none"
>
<XMarkMini className="text-ui-fg-muted" />
</button>
)}
<PrimitiveComboboxDisclosure
render={(props) => {
return (

View File

@@ -181,15 +181,6 @@ export const ProductCreateForm = ({
}
if (currentTab === Tab.ORGANIZE) {
// TODO: this is temp until we add partial validation per tab
if (!form.getValues("shipping_profile_id")) {
form.setError("shipping_profile_id", {
type: "required",
message: t("products.shippingProfile.create.errors.required"),
})
return
}
setTab(Tab.VARIANTS)
}

View File

@@ -173,7 +173,9 @@ export const ProductCreateOrganizationSection = ({
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<Form.Label>{t("products.fields.shipping_profile.label")}</Form.Label>
<Form.Label optional>
{t("products.fields.shipping_profile.label")}
</Form.Label>
<Form.Hint>
<Trans i18nKey={"products.fields.shipping_profile.hint"} />
</Form.Hint>

View File

@@ -1,7 +1,7 @@
import { z } from "zod"
import { i18n } from "../../../components/utilities/i18n/i18n.tsx"
import { optionalFloat, optionalInt } from "../../../lib/validation.ts"
import { decorateVariantsWithDefaultValues } from "./utils.ts"
import { i18n } from "../../../components/utilities/i18n/i18n"
import { optionalFloat, optionalInt } from "../../../lib/validation"
import { decorateVariantsWithDefaultValues } from "./utils"
export const MediaSchema = z.object({
id: z.string().optional(),
@@ -64,7 +64,7 @@ export const ProductCreateSchema = z
discountable: z.boolean(),
type_id: z.string().optional(),
collection_id: z.string().optional(),
shipping_profile_id: z.string(), // TODO: require min(1) when partial validation per tab is added
shipping_profile_id: z.string().optional(),
categories: z.array(z.string()),
tags: z.array(z.string()).optional(),
sales_channels: z

View File

@@ -24,7 +24,7 @@ export const normalizeProductFormValues = (
: undefined,
images,
collection_id: values.collection_id || undefined,
shipping_profile_id: values.shipping_profile_id,
shipping_profile_id: values.shipping_profile_id || undefined,
categories: values.categories.map((id) => ({ id })),
type_id: values.type_id || undefined,
handle: values.handle || undefined,

View File

@@ -7,12 +7,13 @@ 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 { useExtendableForm } from "../../../../../extensions"
import { useUpdateProduct } from "../../../../../hooks/api/products"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { se } from "date-fns/locale"
import { useEffect } from "react"
type ProductShippingProfileFormProps = {
product: HttpTypes.AdminProduct & {
@@ -47,12 +48,15 @@ export const ProductShippingProfileForm = ({
resolver: zodResolver(ProductShippingProfileSchema),
})
const selectedShippingProfile = form.watch("shipping_profile_id")
const { mutateAsync, isPending } = useUpdateProduct(product.id)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
{
shipping_profile_id: data.shipping_profile_id,
shipping_profile_id:
data.shipping_profile_id === "" ? null : data.shipping_profile_id,
},
{
onSuccess: ({ product }) => {
@@ -70,6 +74,12 @@ export const ProductShippingProfileForm = ({
)
})
useEffect(() => {
if (typeof selectedShippingProfile === "undefined") {
form.setValue("shipping_profile_id", "")
}
}, [selectedShippingProfile])
return (
<RouteDrawer.Form form={form}>
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
@@ -87,6 +97,7 @@ export const ProductShippingProfileForm = ({
<Form.Control>
<Combobox
{...field}
allowClear
options={shippingProfiles.options}
searchValue={shippingProfiles.searchValue}
onSearchValueChange={