feat(dashboard): Add forms for edit and create product options (#6907)
This commit is contained in:
1 parent
fd3fc1384b
commit
880bbbd4af
14 files changed
+252
-56
No files matched your search
@@ -154,7 +154,6 @@
|
|||||||
"editAttributes": "Edit Attributes",
|
"editAttributes": "Edit Attributes",
|
||||||
"organization": "Organize",
|
"organization": "Organize",
|
||||||
"editOrganization": "Edit Organization",
|
"editOrganization": "Edit Organization",
|
||||||
"options": "Options",
|
|
||||||
"editOptions": "Edit Options",
|
"editOptions": "Edit Options",
|
||||||
"media": {
|
"media": {
|
||||||
"label": "Media",
|
"label": "Media",
|
||||||
@@ -250,6 +249,15 @@
|
|||||||
"allowBackordersLabel": "Allow backorders",
|
"allowBackordersLabel": "Allow backorders",
|
||||||
"allowBackordersHint": "When enabled the variant can be sold even if the inventory level is below zero."
|
"allowBackordersHint": "When enabled the variant can be sold even if the inventory level is below zero."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"header": "Options",
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Option"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"header": "Create Option"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"collections": {
|
"collections": {
|
||||||
|
|||||||
@@ -231,8 +231,14 @@ export const v1Routes: RouteObject[] = [
|
|||||||
import("../../routes/products/product-attributes"),
|
import("../../routes/products/product-attributes"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "options",
|
path: "options/create",
|
||||||
lazy: () => import("../../routes/products/product-options"),
|
lazy: () =>
|
||||||
|
import("../../routes/products/product-create-option"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "options/:option_id/edit",
|
||||||
|
lazy: () =>
|
||||||
|
import("../../routes/products/product-edit-option"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "media",
|
path: "media",
|
||||||
|
|||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { Button, Input } from "@medusajs/ui"
|
||||||
|
import { useAdminCreateProductOption } from "medusa-react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import {
|
||||||
|
RouteDrawer,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../../components/route-modal"
|
||||||
|
|
||||||
|
type EditProductOptionsFormProps = {
|
||||||
|
product: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateProductOptionSchema = z.object({
|
||||||
|
title: z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CreateProductOptionForm = ({
|
||||||
|
product,
|
||||||
|
}: EditProductOptionsFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof CreateProductOptionSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(CreateProductOptionSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isLoading } = useAdminCreateProductOption(product.id)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (values) => {
|
||||||
|
mutateAsync(values, {
|
||||||
|
onSuccess: () => {
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex flex-1 flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.title")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
<Button type="submit" size="small" isLoading={isLoading}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</form>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-product-option-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProductCreateOption as Component } from "./product-create-option"
|
||||||
+4
-4
@@ -3,9 +3,9 @@ import { useAdminProduct } from "medusa-react"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useParams } from "react-router-dom"
|
import { useParams } from "react-router-dom"
|
||||||
import { RouteDrawer } from "../../../components/route-modal"
|
import { RouteDrawer } from "../../../components/route-modal"
|
||||||
import { EditProductOptionsForm } from "./components/edit-product-options-form"
|
import { CreateProductOptionForm } from "./components/create-product-option-form"
|
||||||
|
|
||||||
export const ProductOptions = () => {
|
export const ProductCreateOption = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ export const ProductOptions = () => {
|
|||||||
return (
|
return (
|
||||||
<RouteDrawer>
|
<RouteDrawer>
|
||||||
<RouteDrawer.Header>
|
<RouteDrawer.Header>
|
||||||
<Heading>{t("products.editOptions")}</Heading>
|
<Heading>{t("products.options.create.header")}</Heading>
|
||||||
</RouteDrawer.Header>
|
</RouteDrawer.Header>
|
||||||
{!isLoading && product && <EditProductOptionsForm product={product} />}
|
{!isLoading && product && <CreateProductOptionForm product={product} />}
|
||||||
</RouteDrawer>
|
</RouteDrawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+19
-6
@@ -1,4 +1,4 @@
|
|||||||
import { PencilSquare } from "@medusajs/icons"
|
import { PencilSquare, Plus } from "@medusajs/icons"
|
||||||
import { Product, ProductOption } from "@medusajs/medusa"
|
import { Product, ProductOption } from "@medusajs/medusa"
|
||||||
import { Badge, Container, Heading, Text } from "@medusajs/ui"
|
import { Badge, Container, Heading, Text } from "@medusajs/ui"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
@@ -16,15 +16,15 @@ export const ProductOptionSection = ({
|
|||||||
return (
|
return (
|
||||||
<Container className="divide-y p-0">
|
<Container className="divide-y p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Heading level="h2">{t("products.options")}</Heading>
|
<Heading level="h2">{t("products.options.header")}</Heading>
|
||||||
<ActionMenu
|
<ActionMenu
|
||||||
groups={[
|
groups={[
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: t("actions.edit"),
|
label: t("actions.create"),
|
||||||
to: "options",
|
to: "options/create",
|
||||||
icon: <PencilSquare />,
|
icon: <Plus />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -35,7 +35,7 @@ export const ProductOptionSection = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={option.id}
|
key={option.id}
|
||||||
className="text-ui-fg-subtle grid grid-cols-2 items-start px-6 py-4"
|
className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_28px] items-start gap-4 px-6 py-4"
|
||||||
>
|
>
|
||||||
<Text size="small" leading="compact" weight="plus">
|
<Text size="small" leading="compact" weight="plus">
|
||||||
{option.title}
|
{option.title}
|
||||||
@@ -53,6 +53,19 @@ export const ProductOptionSection = ({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<ActionMenu
|
||||||
|
groups={[
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: t("actions.edit"),
|
||||||
|
to: `options/${option.id}/edit`,
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { ProductOption } from "@medusajs/medusa"
|
||||||
|
import { Button, Input } from "@medusajs/ui"
|
||||||
|
import { useAdminUpdateProductOption } from "medusa-react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Form } from "../../../../../components/common/form"
|
||||||
|
import {
|
||||||
|
RouteDrawer,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../../components/route-modal"
|
||||||
|
|
||||||
|
type EditProductOptionFormProps = {
|
||||||
|
option: ProductOption
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateProductOptionSchema = z.object({
|
||||||
|
title: z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CreateProductOptionForm = ({
|
||||||
|
option,
|
||||||
|
}: EditProductOptionFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof CreateProductOptionSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
title: option.title,
|
||||||
|
},
|
||||||
|
resolver: zodResolver(CreateProductOptionSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isLoading } = useAdminUpdateProductOption(
|
||||||
|
option.product_id
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (values) => {
|
||||||
|
mutateAsync(
|
||||||
|
{
|
||||||
|
option_id: option.id,
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer.Form form={form}>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex flex-1 flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.title")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RouteDrawer.Body>
|
||||||
|
<RouteDrawer.Footer>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
<RouteDrawer.Close asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteDrawer.Close>
|
||||||
|
<Button type="submit" size="small" isLoading={isLoading}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteDrawer.Footer>
|
||||||
|
</form>
|
||||||
|
</RouteDrawer.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-product-option-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ProductEditOption as Component } from "./product-edit-option"
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import { Heading } from "@medusajs/ui"
|
||||||
|
import { useAdminProduct } from "medusa-react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { json, useParams } from "react-router-dom"
|
||||||
|
import { RouteDrawer } from "../../../components/route-modal"
|
||||||
|
import { CreateProductOptionForm } from "./components/edit-product-option-form"
|
||||||
|
|
||||||
|
export const ProductEditOption = () => {
|
||||||
|
const { id, option_id } = useParams()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||||
|
|
||||||
|
const option = product?.options.find((o) => o.id === option_id)
|
||||||
|
|
||||||
|
const ready = !isLoading && option
|
||||||
|
|
||||||
|
if (!isLoading && !option) {
|
||||||
|
throw json({ message: `An option with ID ${option_id} was not found` }, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteDrawer>
|
||||||
|
<RouteDrawer.Header>
|
||||||
|
<Heading>{t("products.options.edit.header")}</Heading>
|
||||||
|
</RouteDrawer.Header>
|
||||||
|
{ready && <CreateProductOptionForm option={option} />}
|
||||||
|
</RouteDrawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
|
||||||
import { Product } from "@medusajs/medusa"
|
|
||||||
import { Button } from "@medusajs/ui"
|
|
||||||
import { useForm } from "react-hook-form"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import * as zod from "zod"
|
|
||||||
import { RouteDrawer } from "../../../../../components/route-modal"
|
|
||||||
|
|
||||||
type EditProductOptionsFormProps = {
|
|
||||||
product: Product
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditProductOptionsSchema = zod.object({})
|
|
||||||
|
|
||||||
export const EditProductOptionsForm = (props: EditProductOptionsFormProps) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof EditProductOptionsSchema>>({
|
|
||||||
resolver: zodResolver(EditProductOptionsSchema),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouteDrawer.Form form={form}>
|
|
||||||
<form className="flex flex-1 flex-col overflow-hidden">
|
|
||||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto"></RouteDrawer.Body>
|
|
||||||
<RouteDrawer.Footer>
|
|
||||||
<div className="flex items-center justify-end gap-x-2">
|
|
||||||
<RouteDrawer.Close asChild>
|
|
||||||
<Button variant="secondary" size="small">
|
|
||||||
{t("actions.cancel")}
|
|
||||||
</Button>
|
|
||||||
</RouteDrawer.Close>
|
|
||||||
<Button type="submit" size="small">
|
|
||||||
{t("actions.save")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</RouteDrawer.Footer>
|
|
||||||
</form>
|
|
||||||
</RouteDrawer.Form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
export * from "./edit-product-options-form"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { ProductOptions as Component } from "./product-options"
|
|
||||||
Reference in new issue
Block a user