fix(dashboard): Clean Edit Variant form payload of empty strings (#7512)
* fix(dashboard): Clean Edit Variant form paylod of empty strings * fix(dashboard,medusa): Allow passing null to update variant to unset fields * fix product edit form * cleanup * cleanup * pass prop
This commit is contained in:
@@ -244,7 +244,8 @@
|
|||||||
"tooltip": "The handle is used to reference the product in your storefront. If not specified, the handle will be generated from the product title."
|
"tooltip": "The handle is used to reference the product in your storefront. If not specified, the handle will be generated from the product title."
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"label": "Description"
|
"label": "Description",
|
||||||
|
"hint": "Give your product a short and clear description.<0/>120-160 characters is the recommended length for search engines."
|
||||||
},
|
},
|
||||||
"discountable": {
|
"discountable": {
|
||||||
"label": "Discountable",
|
"label": "Discountable",
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { castNumber } from "./cast-number"
|
||||||
|
|
||||||
|
export function parseOptionalFormValue<T>(
|
||||||
|
value: T,
|
||||||
|
nullify = true
|
||||||
|
): T | undefined | null {
|
||||||
|
if (typeof value === "string" && value.trim() === "") {
|
||||||
|
return nullify ? null : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
|
return nullify ? null : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nullable<T> = { [K in keyof T]: T[K] | null }
|
||||||
|
|
||||||
|
export function parseOptionalFormData<T extends Record<string, unknown>>(
|
||||||
|
data: T,
|
||||||
|
nullify = true
|
||||||
|
): Nullable<T> {
|
||||||
|
return Object.entries(data).reduce((acc, [key, value]) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[key]: parseOptionalFormValue(value, nullify),
|
||||||
|
}
|
||||||
|
}, {} as Nullable<T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseOptionalFormNumber(
|
||||||
|
value?: string | number,
|
||||||
|
nullify = true
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
typeof value === "undefined" ||
|
||||||
|
(typeof value === "string" && value.trim() === "")
|
||||||
|
) {
|
||||||
|
return nullify ? null : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return castNumber(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
+143
-206
@@ -5,7 +5,6 @@ import { useForm } from "react-hook-form"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
import { Fragment } from "react"
|
|
||||||
import { Divider } from "../../../../../components/common/divider"
|
import { Divider } from "../../../../../components/common/divider"
|
||||||
import { Form } from "../../../../../components/common/form"
|
import { Form } from "../../../../../components/common/form"
|
||||||
import { Combobox } from "../../../../../components/inputs/combobox"
|
import { Combobox } from "../../../../../components/inputs/combobox"
|
||||||
@@ -15,13 +14,15 @@ import {
|
|||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { useUpdateProductVariant } from "../../../../../hooks/api/products"
|
import { useUpdateProductVariant } from "../../../../../hooks/api/products"
|
||||||
import { castNumber } from "../../../../../lib/cast-number"
|
import {
|
||||||
|
parseOptionalFormData,
|
||||||
|
parseOptionalFormNumber,
|
||||||
|
} from "../../../../../lib/form-helpers"
|
||||||
import { optionalInt } from "../../../../../lib/validation"
|
import { optionalInt } from "../../../../../lib/validation"
|
||||||
|
|
||||||
type ProductEditVariantFormProps = {
|
type ProductEditVariantFormProps = {
|
||||||
product: Product
|
product: Product
|
||||||
variant: ProductVariant
|
variant: ProductVariant
|
||||||
isStockAndInventoryEnabled?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductEditVariantSchema = z.object({
|
const ProductEditVariantSchema = z.object({
|
||||||
@@ -31,7 +32,6 @@ const ProductEditVariantSchema = z.object({
|
|||||||
ean: z.string().optional(),
|
ean: z.string().optional(),
|
||||||
upc: z.string().optional(),
|
upc: z.string().optional(),
|
||||||
barcode: z.string().optional(),
|
barcode: z.string().optional(),
|
||||||
inventory_quantity: optionalInt,
|
|
||||||
manage_inventory: z.boolean(),
|
manage_inventory: z.boolean(),
|
||||||
allow_backorder: z.boolean(),
|
allow_backorder: z.boolean(),
|
||||||
weight: optionalInt,
|
weight: optionalInt,
|
||||||
@@ -48,7 +48,6 @@ const ProductEditVariantSchema = z.object({
|
|||||||
export const ProductEditVariantForm = ({
|
export const ProductEditVariantForm = ({
|
||||||
product,
|
product,
|
||||||
variant,
|
variant,
|
||||||
isStockAndInventoryEnabled = false,
|
|
||||||
}: ProductEditVariantFormProps) => {
|
}: ProductEditVariantFormProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
@@ -81,65 +80,38 @@ export const ProductEditVariantForm = ({
|
|||||||
resolver: zodResolver(ProductEditVariantSchema),
|
resolver: zodResolver(ProductEditVariantSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useUpdateProductVariant(
|
const { mutateAsync, isPending } = useUpdateProductVariant(
|
||||||
product.id,
|
product.id,
|
||||||
variant.id
|
variant.id
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
const parseNumber = (value?: string | number) => {
|
|
||||||
if (typeof value === "undefined" || value === "") {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return castNumber(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
title,
|
||||||
weight,
|
weight,
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
length,
|
length,
|
||||||
inventory_quantity,
|
|
||||||
allow_backorder,
|
allow_backorder,
|
||||||
manage_inventory,
|
manage_inventory,
|
||||||
sku,
|
options,
|
||||||
ean,
|
...optional
|
||||||
upc,
|
|
||||||
barcode,
|
|
||||||
...rest
|
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
/**
|
const nullableData = parseOptionalFormData(optional)
|
||||||
* If stock and inventory is not enabled, we need to send the inventory and
|
|
||||||
* stock related fields to the API. If it is enabled, it should be handled
|
|
||||||
* in the separate stock and inventory form.
|
|
||||||
*/
|
|
||||||
const conditionalPayload = !isStockAndInventoryEnabled
|
|
||||||
? {
|
|
||||||
sku,
|
|
||||||
ean,
|
|
||||||
upc,
|
|
||||||
barcode,
|
|
||||||
inventory_quantity: parseNumber(inventory_quantity),
|
|
||||||
allow_backorder,
|
|
||||||
manage_inventory,
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
|
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
id: variant.id,
|
id: variant.id,
|
||||||
weight: parseNumber(weight),
|
weight: parseOptionalFormNumber(weight),
|
||||||
height: parseNumber(height),
|
height: parseOptionalFormNumber(height),
|
||||||
width: parseNumber(width),
|
width: parseOptionalFormNumber(width),
|
||||||
length: parseNumber(length),
|
length: parseOptionalFormNumber(length),
|
||||||
...conditionalPayload,
|
title,
|
||||||
...rest,
|
allow_backorder,
|
||||||
|
manage_inventory,
|
||||||
|
options,
|
||||||
|
...nullableData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -218,165 +190,130 @@ export const ProductEditVariantForm = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
{!isStockAndInventoryEnabled && (
|
<div className="flex flex-col gap-y-8">
|
||||||
<Fragment>
|
<div className="flex flex-col gap-y-4">
|
||||||
<div className="flex flex-col gap-y-8">
|
<Heading level="h2">
|
||||||
<div className="flex flex-col gap-y-4">
|
{t("products.variant.inventory.header")}
|
||||||
<Heading level="h2">
|
</Heading>
|
||||||
{t("products.variant.inventory.header")}
|
<Form.Field
|
||||||
</Heading>
|
control={form.control}
|
||||||
<Form.Field
|
name="sku"
|
||||||
control={form.control}
|
render={({ field }) => {
|
||||||
name="sku"
|
return (
|
||||||
render={({ field }) => {
|
<Form.Item>
|
||||||
return (
|
<Form.Label optional>{t("fields.sku")}</Form.Label>
|
||||||
<Form.Item>
|
<Form.Control>
|
||||||
<Form.Label optional>{t("fields.sku")}</Form.Label>
|
<Input {...field} />
|
||||||
<Form.Control>
|
</Form.Control>
|
||||||
<Input {...field} />
|
<Form.ErrorMessage />
|
||||||
</Form.Control>
|
</Form.Item>
|
||||||
<Form.ErrorMessage />
|
)
|
||||||
</Form.Item>
|
}}
|
||||||
)
|
/>
|
||||||
}}
|
<Form.Field
|
||||||
/>
|
control={form.control}
|
||||||
<Form.Field
|
name="ean"
|
||||||
control={form.control}
|
render={({ field }) => {
|
||||||
name="ean"
|
return (
|
||||||
render={({ field }) => {
|
<Form.Item>
|
||||||
return (
|
<Form.Label optional>{t("fields.ean")}</Form.Label>
|
||||||
<Form.Item>
|
<Form.Control>
|
||||||
<Form.Label optional>{t("fields.ean")}</Form.Label>
|
<Input {...field} />
|
||||||
<Form.Control>
|
</Form.Control>
|
||||||
<Input {...field} />
|
<Form.ErrorMessage />
|
||||||
</Form.Control>
|
</Form.Item>
|
||||||
<Form.ErrorMessage />
|
)
|
||||||
</Form.Item>
|
}}
|
||||||
)
|
/>
|
||||||
}}
|
<Form.Field
|
||||||
/>
|
control={form.control}
|
||||||
<Form.Field
|
name="upc"
|
||||||
control={form.control}
|
render={({ field }) => {
|
||||||
name="upc"
|
return (
|
||||||
render={({ field }) => {
|
<Form.Item>
|
||||||
return (
|
<Form.Label optional>{t("fields.upc")}</Form.Label>
|
||||||
<Form.Item>
|
<Form.Control>
|
||||||
<Form.Label optional>{t("fields.upc")}</Form.Label>
|
<Input {...field} />
|
||||||
<Form.Control>
|
</Form.Control>
|
||||||
<Input {...field} />
|
<Form.ErrorMessage />
|
||||||
</Form.Control>
|
</Form.Item>
|
||||||
<Form.ErrorMessage />
|
)
|
||||||
</Form.Item>
|
}}
|
||||||
)
|
/>
|
||||||
}}
|
<Form.Field
|
||||||
/>
|
control={form.control}
|
||||||
<Form.Field
|
name="barcode"
|
||||||
control={form.control}
|
render={({ field }) => {
|
||||||
name="barcode"
|
return (
|
||||||
render={({ field }) => {
|
<Form.Item>
|
||||||
return (
|
<Form.Label optional>{t("fields.barcode")}</Form.Label>
|
||||||
<Form.Item>
|
<Form.Control>
|
||||||
<Form.Label optional>
|
<Input {...field} />
|
||||||
{t("fields.barcode")}
|
</Form.Control>
|
||||||
</Form.Label>
|
<Form.ErrorMessage />
|
||||||
<Form.Control>
|
</Form.Item>
|
||||||
<Input {...field} />
|
)
|
||||||
</Form.Control>
|
}}
|
||||||
<Form.ErrorMessage />
|
/>
|
||||||
</Form.Item>
|
</div>
|
||||||
)
|
<Form.Field
|
||||||
}}
|
control={form.control}
|
||||||
/>
|
name="manage_inventory"
|
||||||
<Form.Field
|
render={({ field: { value, onChange, ...field } }) => {
|
||||||
control={form.control}
|
return (
|
||||||
name="inventory_quantity"
|
<Form.Item>
|
||||||
render={({ field }) => {
|
<div className="flex flex-col gap-y-1">
|
||||||
return (
|
<div className="flex items-center justify-between">
|
||||||
<Form.Item>
|
<Form.Label>
|
||||||
<Form.Label>
|
{t("products.variant.inventory.manageInventoryLabel")}
|
||||||
{t("fields.inventoryQuantity")}
|
</Form.Label>
|
||||||
</Form.Label>
|
<Form.Control>
|
||||||
<Form.Control>
|
<Switch
|
||||||
<Input type="number" {...field} />
|
checked={value}
|
||||||
</Form.Control>
|
onCheckedChange={(checked) => onChange(!!checked)}
|
||||||
<Form.ErrorMessage />
|
{...field}
|
||||||
</Form.Item>
|
/>
|
||||||
)
|
</Form.Control>
|
||||||
}}
|
</div>
|
||||||
/>
|
<Form.Hint>
|
||||||
</div>
|
{t("products.variant.inventory.manageInventoryHint")}
|
||||||
<Form.Field
|
</Form.Hint>
|
||||||
control={form.control}
|
</div>
|
||||||
name="manage_inventory"
|
<Form.ErrorMessage />
|
||||||
render={({ field: { value, onChange, ...field } }) => {
|
</Form.Item>
|
||||||
return (
|
)
|
||||||
<Form.Item>
|
}}
|
||||||
<div className="flex flex-col gap-y-1">
|
/>
|
||||||
<div className="flex items-center justify-between">
|
<Form.Field
|
||||||
<Form.Label>
|
control={form.control}
|
||||||
{t(
|
name="allow_backorder"
|
||||||
"products.variant.inventory.manageInventoryLabel"
|
render={({ field: { value, onChange, ...field } }) => {
|
||||||
)}
|
return (
|
||||||
</Form.Label>
|
<Form.Item>
|
||||||
<Form.Control>
|
<div className="flex flex-col gap-y-1">
|
||||||
<Switch
|
<div className="flex items-center justify-between">
|
||||||
checked={value}
|
<Form.Label>
|
||||||
onCheckedChange={(checked) =>
|
{t("products.variant.inventory.allowBackordersLabel")}
|
||||||
onChange(!!checked)
|
</Form.Label>
|
||||||
}
|
<Form.Control>
|
||||||
{...field}
|
<Switch
|
||||||
/>
|
checked={value}
|
||||||
</Form.Control>
|
onCheckedChange={(checked) => onChange(!!checked)}
|
||||||
</div>
|
{...field}
|
||||||
<Form.Hint>
|
/>
|
||||||
{t(
|
</Form.Control>
|
||||||
"products.variant.inventory.manageInventoryHint"
|
</div>
|
||||||
)}
|
<Form.Hint>
|
||||||
</Form.Hint>
|
{t("products.variant.inventory.allowBackordersHint")}
|
||||||
</div>
|
</Form.Hint>
|
||||||
<Form.ErrorMessage />
|
</div>
|
||||||
</Form.Item>
|
<Form.ErrorMessage />
|
||||||
)
|
</Form.Item>
|
||||||
}}
|
)
|
||||||
/>
|
}}
|
||||||
<Form.Field
|
/>
|
||||||
control={form.control}
|
</div>
|
||||||
name="allow_backorder"
|
<Divider />
|
||||||
render={({ field: { value, onChange, ...field } }) => {
|
|
||||||
return (
|
|
||||||
<Form.Item>
|
|
||||||
<div className="flex flex-col gap-y-1">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Form.Label>
|
|
||||||
{t(
|
|
||||||
"products.variant.inventory.allowBackordersLabel"
|
|
||||||
)}
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Control>
|
|
||||||
<Switch
|
|
||||||
checked={value}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
onChange(!!checked)
|
|
||||||
}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
|
||||||
</div>
|
|
||||||
<Form.Hint>
|
|
||||||
{t(
|
|
||||||
"products.variant.inventory.allowBackordersHint"
|
|
||||||
)}
|
|
||||||
</Form.Hint>
|
|
||||||
</div>
|
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Divider />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-4">
|
||||||
<Heading level="h2">{t("products.attributes")}</Heading>
|
<Heading level="h2">{t("products.attributes")}</Heading>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
@@ -495,7 +432,7 @@ export const ProductEditVariantForm = ({
|
|||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteDrawer.Close>
|
</RouteDrawer.Close>
|
||||||
<Button type="submit" size="small" isLoading={isLoading}>
|
<Button type="submit" size="small" isLoading={isPending}>
|
||||||
{t("actions.save")}
|
{t("actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ const queryKey = (id: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryFn = async (id: string) => {
|
const queryFn = async (id: string) => {
|
||||||
const productRes = await client.products.retrieve(id)
|
return await client.products.retrieve(id)
|
||||||
return {
|
|
||||||
initialData: productRes,
|
|
||||||
isStockAndInventoryEnabled: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const editProductVariantQuery = (id: string) => ({
|
const editProductVariantQuery = (id: string) => ({
|
||||||
|
|||||||
+3
-4
@@ -3,12 +3,12 @@ import { Heading } from "@medusajs/ui"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { json, useLoaderData, useParams } from "react-router-dom"
|
import { json, useLoaderData, useParams } from "react-router-dom"
|
||||||
import { RouteDrawer } from "../../../components/route-modal"
|
import { RouteDrawer } from "../../../components/route-modal"
|
||||||
|
import { useProduct } from "../../../hooks/api/products"
|
||||||
import { ProductEditVariantForm } from "./components/product-edit-variant-form"
|
import { ProductEditVariantForm } from "./components/product-edit-variant-form"
|
||||||
import { editProductVariantLoader } from "./loader"
|
import { editProductVariantLoader } from "./loader"
|
||||||
import { useProduct } from "../../../hooks/api/products"
|
|
||||||
|
|
||||||
export const ProductEditVariant = () => {
|
export const ProductEditVariant = () => {
|
||||||
const loaderData = useLoaderData() as Awaited<
|
const initialData = useLoaderData() as Awaited<
|
||||||
ReturnType<typeof editProductVariantLoader>
|
ReturnType<typeof editProductVariantLoader>
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export const ProductEditVariant = () => {
|
|||||||
const { id, variant_id } = useParams()
|
const { id, variant_id } = useParams()
|
||||||
|
|
||||||
const { product, isLoading, isError, error } = useProduct(id!, undefined, {
|
const { product, isLoading, isError, error } = useProduct(id!, undefined, {
|
||||||
initialData: loaderData?.initialData,
|
initialData,
|
||||||
})
|
})
|
||||||
|
|
||||||
const variant = product?.variants.find(
|
const variant = product?.variants.find(
|
||||||
@@ -45,7 +45,6 @@ export const ProductEditVariant = () => {
|
|||||||
<ProductEditVariantForm
|
<ProductEditVariantForm
|
||||||
product={product}
|
product={product}
|
||||||
variant={variant as unknown as ProductVariant}
|
variant={variant as unknown as ProductVariant}
|
||||||
isStockAndInventoryEnabled={loaderData?.isStockAndInventoryEnabled}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</RouteDrawer>
|
</RouteDrawer>
|
||||||
|
|||||||
+15
-7
@@ -12,6 +12,7 @@ import {
|
|||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||||
|
import { parseOptionalFormData } from "../../../../../lib/form-helpers"
|
||||||
|
|
||||||
type EditProductFormProps = {
|
type EditProductFormProps = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -20,10 +21,10 @@ type EditProductFormProps = {
|
|||||||
const EditProductSchema = zod.object({
|
const EditProductSchema = zod.object({
|
||||||
status: zod.enum(["draft", "published", "proposed", "rejected"]),
|
status: zod.enum(["draft", "published", "proposed", "rejected"]),
|
||||||
title: zod.string().min(1),
|
title: zod.string().min(1),
|
||||||
subtitle: zod.string(),
|
subtitle: zod.string().optional(),
|
||||||
handle: zod.string().min(1),
|
handle: zod.string().min(1),
|
||||||
material: zod.string(),
|
material: zod.string().optional(),
|
||||||
description: zod.string(),
|
description: zod.string().optional(),
|
||||||
discountable: zod.boolean(),
|
discountable: zod.boolean(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -44,13 +45,20 @@ export const EditProductForm = ({ product }: EditProductFormProps) => {
|
|||||||
resolver: zodResolver(EditProductSchema),
|
resolver: zodResolver(EditProductSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useUpdateProduct(product.id)
|
const { mutateAsync, isPending } = useUpdateProduct(product.id)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
const { title, discountable, handle, status, ...optional } = data
|
||||||
|
|
||||||
|
const nullableData = parseOptionalFormData(optional)
|
||||||
|
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
...data,
|
title,
|
||||||
status: data.status as ProductStatus,
|
discountable,
|
||||||
|
handle,
|
||||||
|
status: status as ProductStatus,
|
||||||
|
...nullableData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -249,7 +257,7 @@ export const EditProductForm = ({ product }: EditProductFormProps) => {
|
|||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteDrawer.Close>
|
</RouteDrawer.Close>
|
||||||
<Button size="small" type="submit" isLoading={isLoading}>
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
{t("actions.save")}
|
{t("actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1183,19 +1183,19 @@ export interface CreateProductVariantDTO {
|
|||||||
/**
|
/**
|
||||||
* The SKU of the product variant.
|
* The SKU of the product variant.
|
||||||
*/
|
*/
|
||||||
sku?: string
|
sku?: string | null
|
||||||
/**
|
/**
|
||||||
* The barcode of the product variant.
|
* The barcode of the product variant.
|
||||||
*/
|
*/
|
||||||
barcode?: string
|
barcode?: string | null
|
||||||
/**
|
/**
|
||||||
* The EAN of the product variant.
|
* The EAN of the product variant.
|
||||||
*/
|
*/
|
||||||
ean?: string
|
ean?: string | null
|
||||||
/**
|
/**
|
||||||
* The UPC of the product variant.
|
* The UPC of the product variant.
|
||||||
*/
|
*/
|
||||||
upc?: string
|
upc?: string | null
|
||||||
/**
|
/**
|
||||||
* Whether the product variant can be ordered when it's out of stock.
|
* Whether the product variant can be ordered when it's out of stock.
|
||||||
*/
|
*/
|
||||||
@@ -1207,35 +1207,35 @@ export interface CreateProductVariantDTO {
|
|||||||
/**
|
/**
|
||||||
* The HS Code of the product variant.
|
* The HS Code of the product variant.
|
||||||
*/
|
*/
|
||||||
hs_code?: string
|
hs_code?: string | null
|
||||||
/**
|
/**
|
||||||
* The origin country of the product variant.
|
* The origin country of the product variant.
|
||||||
*/
|
*/
|
||||||
origin_country?: string
|
origin_country?: string | null
|
||||||
/**
|
/**
|
||||||
* The MID Code of the product variant.
|
* The MID Code of the product variant.
|
||||||
*/
|
*/
|
||||||
mid_code?: string
|
mid_code?: string | null
|
||||||
/**
|
/**
|
||||||
* The material of the product variant.
|
* The material of the product variant.
|
||||||
*/
|
*/
|
||||||
material?: string
|
material?: string | null
|
||||||
/**
|
/**
|
||||||
* The weight of the product variant.
|
* The weight of the product variant.
|
||||||
*/
|
*/
|
||||||
weight?: number
|
weight?: number | null
|
||||||
/**
|
/**
|
||||||
* The length of the product variant.
|
* The length of the product variant.
|
||||||
*/
|
*/
|
||||||
length?: number
|
length?: number | null
|
||||||
/**
|
/**
|
||||||
* The height of the product variant.
|
* The height of the product variant.
|
||||||
*/
|
*/
|
||||||
height?: number
|
height?: number | null
|
||||||
/**
|
/**
|
||||||
* The width of the product variant.
|
* The width of the product variant.
|
||||||
*/
|
*/
|
||||||
width?: number
|
width?: number | null
|
||||||
/**
|
/**
|
||||||
* The options of the variant. Each key is an option's title, and value
|
* The options of the variant. Each key is an option's title, and value
|
||||||
* is an option's value. If an option with the specified title doesn't exist,
|
* is an option's value. If an option with the specified title doesn't exist,
|
||||||
@@ -1280,19 +1280,19 @@ export interface UpdateProductVariantDTO {
|
|||||||
/**
|
/**
|
||||||
* The SKU of the product variant.
|
* The SKU of the product variant.
|
||||||
*/
|
*/
|
||||||
sku?: string
|
sku?: string | null
|
||||||
/**
|
/**
|
||||||
* The barcode of the product variant.
|
* The barcode of the product variant.
|
||||||
*/
|
*/
|
||||||
barcode?: string
|
barcode?: string | null
|
||||||
/**
|
/**
|
||||||
* The EAN of the product variant.
|
* The EAN of the product variant.
|
||||||
*/
|
*/
|
||||||
ean?: string
|
ean?: string | null
|
||||||
/**
|
/**
|
||||||
* The UPC of the product variant.
|
* The UPC of the product variant.
|
||||||
*/
|
*/
|
||||||
upc?: string
|
upc?: string | null
|
||||||
/**
|
/**
|
||||||
* Whether the product variant can be ordered when it's out of stock.
|
* Whether the product variant can be ordered when it's out of stock.
|
||||||
*/
|
*/
|
||||||
@@ -1304,35 +1304,35 @@ export interface UpdateProductVariantDTO {
|
|||||||
/**
|
/**
|
||||||
* The HS Code of the product variant.
|
* The HS Code of the product variant.
|
||||||
*/
|
*/
|
||||||
hs_code?: string
|
hs_code?: string | null
|
||||||
/**
|
/**
|
||||||
* The origin country of the product variant.
|
* The origin country of the product variant.
|
||||||
*/
|
*/
|
||||||
origin_country?: string
|
origin_country?: string | null
|
||||||
/**
|
/**
|
||||||
* The MID Code of the product variant.
|
* The MID Code of the product variant.
|
||||||
*/
|
*/
|
||||||
mid_code?: string
|
mid_code?: string | null
|
||||||
/**
|
/**
|
||||||
* The material of the product variant.
|
* The material of the product variant.
|
||||||
*/
|
*/
|
||||||
material?: string
|
material?: string | null
|
||||||
/**
|
/**
|
||||||
* The weight of the product variant.
|
* The weight of the product variant.
|
||||||
*/
|
*/
|
||||||
weight?: number
|
weight?: number | null
|
||||||
/**
|
/**
|
||||||
* The length of the product variant.
|
* The length of the product variant.
|
||||||
*/
|
*/
|
||||||
length?: number
|
length?: number | null
|
||||||
/**
|
/**
|
||||||
* The height of the product variant.
|
* The height of the product variant.
|
||||||
*/
|
*/
|
||||||
height?: number
|
height?: number | null
|
||||||
/**
|
/**
|
||||||
* The width of the product variant.
|
* The width of the product variant.
|
||||||
*/
|
*/
|
||||||
width?: number
|
width?: number | null
|
||||||
/**
|
/**
|
||||||
* The product variant options to associate with the product variant.
|
* The product variant options to associate with the product variant.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -126,21 +126,21 @@ export type AdminCreateProductVariantType = z.infer<
|
|||||||
>
|
>
|
||||||
export const AdminCreateProductVariant = z.object({
|
export const AdminCreateProductVariant = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
sku: z.string().optional(),
|
sku: z.string().nullable().optional(),
|
||||||
ean: z.string().optional(),
|
ean: z.string().nullable().optional(),
|
||||||
upc: z.string().optional(),
|
upc: z.string().nullable().optional(),
|
||||||
barcode: z.string().optional(),
|
barcode: z.string().nullable().optional(),
|
||||||
hs_code: z.string().optional(),
|
hs_code: z.string().nullable().optional(),
|
||||||
mid_code: z.string().optional(),
|
mid_code: z.string().nullable().optional(),
|
||||||
allow_backorder: z.boolean().optional().default(false),
|
allow_backorder: z.boolean().optional().default(false),
|
||||||
manage_inventory: z.boolean().optional().default(true),
|
manage_inventory: z.boolean().optional().default(true),
|
||||||
variant_rank: z.number().optional(),
|
variant_rank: z.number().optional(),
|
||||||
weight: z.number().optional(),
|
weight: z.number().nullable().optional(),
|
||||||
length: z.number().optional(),
|
length: z.number().nullable().optional(),
|
||||||
height: z.number().optional(),
|
height: z.number().nullable().optional(),
|
||||||
width: z.number().optional(),
|
width: z.number().nullable().optional(),
|
||||||
origin_country: z.string().optional(),
|
origin_country: z.string().nullable().optional(),
|
||||||
material: z.string().optional(),
|
material: z.string().nullable().optional(),
|
||||||
metadata: z.record(z.unknown()).optional(),
|
metadata: z.record(z.unknown()).optional(),
|
||||||
prices: z.array(AdminCreateVariantPrice),
|
prices: z.array(AdminCreateVariantPrice),
|
||||||
options: z.record(z.string()).optional(),
|
options: z.record(z.string()).optional(),
|
||||||
@@ -172,29 +172,38 @@ export type AdminCreateProductType = z.infer<typeof AdminCreateProduct>
|
|||||||
export const AdminCreateProduct = z
|
export const AdminCreateProduct = z
|
||||||
.object({
|
.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
subtitle: z.string().optional(),
|
subtitle: z.string().nullable().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().nullable().optional(),
|
||||||
is_giftcard: z.boolean().optional().default(false),
|
is_giftcard: z.boolean().optional().default(false),
|
||||||
discountable: z.boolean().optional().default(true),
|
discountable: z.boolean().optional().default(true),
|
||||||
images: z.array(z.object({ url: z.string() })).optional(),
|
images: z
|
||||||
thumbnail: z.string().optional(),
|
.array(z.object({ url: z.string() }))
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
thumbnail: z.string().nullable().optional(),
|
||||||
handle: z.string().optional(),
|
handle: z.string().optional(),
|
||||||
status: statusEnum.optional().default(ProductStatus.DRAFT),
|
status: statusEnum.optional().default(ProductStatus.DRAFT),
|
||||||
type_id: z.string().nullable().optional(),
|
type_id: z.string().nullable().optional(),
|
||||||
collection_id: z.string().nullable().optional(),
|
collection_id: z.string().nullable().optional(),
|
||||||
categories: z.array(AdminCreateProductProductCategory).optional(),
|
categories: z
|
||||||
tags: z.array(AdminUpdateProductTag).optional(),
|
.array(AdminCreateProductProductCategory)
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
tags: z.array(AdminUpdateProductTag).nullable().optional(),
|
||||||
options: z.array(AdminCreateProductOption).optional(),
|
options: z.array(AdminCreateProductOption).optional(),
|
||||||
variants: z.array(AdminCreateProductVariant).optional(),
|
variants: z.array(AdminCreateProductVariant).optional(),
|
||||||
sales_channels: z.array(z.object({ id: z.string() })).optional(),
|
sales_channels: z
|
||||||
weight: z.number().optional(),
|
.array(z.object({ id: z.string() }))
|
||||||
length: z.number().optional(),
|
.nullable()
|
||||||
height: z.number().optional(),
|
.optional(),
|
||||||
width: z.number().optional(),
|
weight: z.number().nullable().optional(),
|
||||||
hs_code: z.string().optional(),
|
length: z.number().nullable().optional(),
|
||||||
mid_code: z.string().optional(),
|
height: z.number().nullable().optional(),
|
||||||
origin_country: z.string().optional(),
|
width: z.number().nullable().optional(),
|
||||||
material: z.string().optional(),
|
hs_code: z.string().nullable().optional(),
|
||||||
|
mid_code: z.string().nullable().optional(),
|
||||||
|
origin_country: z.string().nullable().optional(),
|
||||||
|
material: z.string().nullable().optional(),
|
||||||
metadata: z.record(z.unknown()).optional(),
|
metadata: z.record(z.unknown()).optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|||||||
Reference in New Issue
Block a user