feat(dashboard): Add forms for edit and create product options (#6907)

This commit is contained in:
Kasper Fabricius Kristensen
2024-04-04 18:14:05 +02:00
committed by GitHub
parent fd3fc1384b
commit 880bbbd4af
14 changed files with 252 additions and 56 deletions

View File

@@ -154,7 +154,6 @@
"editAttributes": "Edit Attributes",
"organization": "Organize",
"editOrganization": "Edit Organization",
"options": "Options",
"editOptions": "Edit Options",
"media": {
"label": "Media",
@@ -250,6 +249,15 @@
"allowBackordersLabel": "Allow backorders",
"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": {

View File

@@ -231,8 +231,14 @@ export const v1Routes: RouteObject[] = [
import("../../routes/products/product-attributes"),
},
{
path: "options",
lazy: () => import("../../routes/products/product-options"),
path: "options/create",
lazy: () =>
import("../../routes/products/product-create-option"),
},
{
path: "options/:option_id/edit",
lazy: () =>
import("../../routes/products/product-edit-option"),
},
{
path: "media",

View File

@@ -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>
)
}

View File

@@ -0,0 +1 @@
export * from "./create-product-option-form"

View File

@@ -0,0 +1 @@
export { ProductCreateOption as Component } from "./product-create-option"

View File

@@ -3,9 +3,9 @@ import { useAdminProduct } from "medusa-react"
import { useTranslation } from "react-i18next"
import { useParams } from "react-router-dom"
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 { t } = useTranslation()
@@ -18,9 +18,9 @@ export const ProductOptions = () => {
return (
<RouteDrawer>
<RouteDrawer.Header>
<Heading>{t("products.editOptions")}</Heading>
<Heading>{t("products.options.create.header")}</Heading>
</RouteDrawer.Header>
{!isLoading && product && <EditProductOptionsForm product={product} />}
{!isLoading && product && <CreateProductOptionForm product={product} />}
</RouteDrawer>
)
}

View File

@@ -1,4 +1,4 @@
import { PencilSquare } from "@medusajs/icons"
import { PencilSquare, Plus } from "@medusajs/icons"
import { Product, ProductOption } from "@medusajs/medusa"
import { Badge, Container, Heading, Text } from "@medusajs/ui"
import { useTranslation } from "react-i18next"
@@ -16,15 +16,15 @@ export const ProductOptionSection = ({
return (
<Container className="divide-y p-0">
<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
groups={[
{
actions: [
{
label: t("actions.edit"),
to: "options",
icon: <PencilSquare />,
label: t("actions.create"),
to: "options/create",
icon: <Plus />,
},
],
},
@@ -35,7 +35,7 @@ export const ProductOptionSection = ({
return (
<div
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">
{option.title}
@@ -53,6 +53,19 @@ export const ProductOptionSection = ({
)
})}
</div>
<ActionMenu
groups={[
{
actions: [
{
label: t("actions.edit"),
to: `options/${option.id}/edit`,
icon: <PencilSquare />,
},
],
},
]}
/>
</div>
)
})}

View File

@@ -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>
)
}

View File

@@ -0,0 +1 @@
export * from "./edit-product-option-form"

View File

@@ -0,0 +1 @@
export { ProductEditOption as Component } from "./product-edit-option"

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -1 +0,0 @@
export { ProductOptions as Component } from "./product-options"