diff --git a/packages/admin-next/dashboard/public/locales/en-US/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json index 250e9b82c4..effe2c4628 100644 --- a/packages/admin-next/dashboard/public/locales/en-US/translation.json +++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json @@ -288,9 +288,9 @@ "editConditions": "Edit conditions", "conditionsHint": "Create conditions to apply on the discount", "isTemplateDiscount": "Is this a template discount?", - "percentageDescription" : "Discount applied in %", - "fixedDescription" : "Amount discount", - "shippingDescription" : "Override delivery amount", + "percentageDescription": "Discount applied in %", + "fixedDescription": "Amount discount", + "shippingDescription": "Override delivery amount", "selectRegionFirst": "Select region first", "templateHint": "Template discounts allow you to define a set of rules that can be used across a group of discounts. This is useful in campaigns that should generate unique codes for each user, but where the rules for all unique codes should be the same.", "conditions": { @@ -505,6 +505,15 @@ "createdBy": "Created by", "revokedBy": "Revoked by" }, + "returnReasons": { + "domain": "Return Reasons", + "calloutHint": "Manage the reasons to categorize returns.", + "deleteReasonWarning": "You are about to delete the return reason {{label}}. This action cannot be undone.", + "createReason": "Create Return Reason", + "createReasonHint": "Create a new return reason to categorize returns.", + "editReason": "Edit Return Reason", + "valueTooltip": "The value should be a unique identifier for the return reason." + }, "login": { "forgotPassword": "Forgot password? - <0>Reset0>", "title": "Log in", @@ -696,6 +705,7 @@ "maxSubtotal": "Max. Subtotal", "shippingProfile": "Shipping Profile", "summary": "Summary", + "label": "Label", "rate": "Rate", "requiresShipping": "Requires shipping" }, diff --git a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx index 9702645749..69b15651f6 100644 --- a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx +++ b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx @@ -37,6 +37,10 @@ const useSettingRoutes = (): NavItemProps[] => { label: t("regions.domain"), to: "/settings/regions", }, + { + label: t("returnReasons.domain"), + to: "/settings/return-reasons", + }, { label: "Taxes", to: "/settings/taxes", diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 77d0986f53..a0a8d76262 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -494,6 +494,34 @@ const router = createBrowserRouter([ }, ], }, + { + path: "return-reasons", + element: , + handle: { + crumb: () => "Return Reasons", + }, + children: [ + { + path: "", + lazy: () => + import("../../routes/return-reasons/return-reason-list"), + children: [ + { + path: "create", + lazy: () => + import( + "../../routes/return-reasons/return-reason-create" + ), + }, + { + path: ":id/edit", + lazy: () => + import("../../routes/return-reasons/return-reason-edit"), + }, + ], + }, + ], + }, { path: "regions", element: , diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/create-return-reason-form.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/create-return-reason-form.tsx new file mode 100644 index 0000000000..4b9efc5b1b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/create-return-reason-form.tsx @@ -0,0 +1,130 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { Button, Heading, Input, Text, Textarea, clx } from "@medusajs/ui" +import { useAdminCreateReturnReason } 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 { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/route-modal" + +const CreateReturnReasonSchema = z.object({ + value: z.string().min(1, "Value is required"), + label: z.string().min(1, "Label is required"), + description: z.string().optional(), +}) + +export const CreateReturnReasonForm = () => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + value: "", + label: "", + description: "", + }, + resolver: zodResolver(CreateReturnReasonSchema), + }) + + const { mutateAsync, isLoading } = useAdminCreateReturnReason() + + const handleSubmit = form.handleSubmit(async (data) => { + await mutateAsync(data, { + onSuccess: () => { + handleSuccess() + }, + }) + }) + + return ( + + + + + + + {t("actions.cancel")} + + + + {t("actions.save")} + + + + + + + + {t("returnReasons.createReason")} + + {t("returnReasons.createReasonHint")} + + + + { + return ( + + {t("fields.label")} + + + + + + ) + }} + /> + { + return ( + + + {t("fields.value")} + + + + + + + ) + }} + /> + + { + return ( + + + {t("fields.description")} + + + + + + + ) + }} + /> + + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/index.ts new file mode 100644 index 0000000000..fa77b49a80 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/components/create-return-reason-form/index.ts @@ -0,0 +1 @@ +export * from "./create-return-reason-form" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/index.ts new file mode 100644 index 0000000000..22d4ee4ca9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/index.ts @@ -0,0 +1 @@ +export { ReturnReasonCreate as Component } from "./return-reason-create" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/return-reason-create.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/return-reason-create.tsx new file mode 100644 index 0000000000..5f37ae51b5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-create/return-reason-create.tsx @@ -0,0 +1,10 @@ +import { RouteFocusModal } from "../../../components/route-modal" +import { CreateReturnReasonForm } from "./components/create-return-reason-form" + +export const ReturnReasonCreate = () => { + return ( + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/edit-return-reason-form.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/edit-return-reason-form.tsx new file mode 100644 index 0000000000..c5630b81b5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/edit-return-reason-form.tsx @@ -0,0 +1,119 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { ReturnReason } from "@medusajs/medusa" +import { Button, Input, Textarea } from "@medusajs/ui" +import { useAdminUpdateReturnReason } 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 EditReturnReasonFormProps = { + reason: ReturnReason +} + +const EditReturnReasonSchema = z.object({ + value: z.string().min(1, "Value is required"), + label: z.string().min(1, "Label is required"), + description: z.string().optional(), +}) + +export const EditReturnReasonForm = ({ reason }: EditReturnReasonFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + value: reason.value, + label: reason.label, + description: reason.description || "", + }, + resolver: zodResolver(EditReturnReasonSchema), + }) + + const { mutateAsync, isLoading } = useAdminUpdateReturnReason(reason.id) + + const handleSubmit = form.handleSubmit(async (data) => { + await mutateAsync(data, { + onSuccess: () => { + handleSuccess() + }, + }) + }) + + return ( + + + + + { + return ( + + {t("fields.label")} + + + + + + ) + }} + /> + { + return ( + + + {t("fields.value")} + + + + + + + ) + }} + /> + + { + return ( + + {t("fields.description")} + + + + + + ) + }} + /> + + + + + + {t("actions.cancel")} + + + + {t("actions.save")} + + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/index.ts new file mode 100644 index 0000000000..7f1c8a898d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/components/edit-return-reason-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-return-reason-form" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/index.ts new file mode 100644 index 0000000000..e0f51b1985 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/index.ts @@ -0,0 +1 @@ +export { ReturnReasonEdit as Component } from "./return-reason-edit" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/return-reason-edit.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/return-reason-edit.tsx new file mode 100644 index 0000000000..7ac2cf9e5e --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-edit/return-reason-edit.tsx @@ -0,0 +1,28 @@ +import { Heading } from "@medusajs/ui" +import { useAdminReturnReason } from "medusa-react" +import { useTranslation } from "react-i18next" +import { useParams } from "react-router-dom" +import { RouteDrawer } from "../../../components/route-modal" +import { EditReturnReasonForm } from "./components/edit-return-reason-form" + +export const ReturnReasonEdit = () => { + const { t } = useTranslation() + const { id } = useParams() + + const { return_reason, isLoading, isError, error } = useAdminReturnReason(id!) + + const ready = !isLoading && return_reason + + if (isError) { + throw error + } + + return ( + + + {t("returnReasons.editReason")} + + {ready && } + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/index.ts new file mode 100644 index 0000000000..ced0612ff8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/index.ts @@ -0,0 +1 @@ +export * from "./return-reason-callout" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/return-reason-callout.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/return-reason-callout.tsx new file mode 100644 index 0000000000..67c8a264f4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-callout/return-reason-callout.tsx @@ -0,0 +1,21 @@ +import { Button, Container, Heading, Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" + +export const ReturnReasonCallout = () => { + const { t } = useTranslation() + + return ( + + + Return Reasons + + Manage reasons for returned items. + + + + {t("actions.create")} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/index.ts new file mode 100644 index 0000000000..674c40498b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/index.ts @@ -0,0 +1 @@ +export * from "./return-reason-overview" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/return-reason-overview.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/return-reason-overview.tsx new file mode 100644 index 0000000000..f462538810 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/components/return-reason-overview/return-reason-overview.tsx @@ -0,0 +1,121 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { ReturnReason } from "@medusajs/medusa" +import { Badge, Container, Text, usePrompt } from "@medusajs/ui" +import { useAdminDeleteReturnReason, useAdminReturnReasons } from "medusa-react" +import { useTranslation } from "react-i18next" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { NoRecords } from "../../../../../components/common/empty-table-content" +import { Skeleton } from "../../../../../components/common/skeleton" + +export const ReturnReasonOverview = () => { + const { return_reasons, isLoading, isError, error } = useAdminReturnReasons() + + if (isLoading) { + return ( + + {Array.from({ length: 5 }).map((_, i) => ( + + ))} + + ) + } + + if (!return_reasons || !return_reasons.length) { + return ( + + + + ) + } + + if (isError) { + throw error + } + + return ( + + {return_reasons.map((reason) => ( + + ))} + + ) +} + +const Item = ({ reason }: { reason: ReturnReason }) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminDeleteReturnReason(reason.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("returnReasons.deleteReasonWarning", { + label: reason.label, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + + + {reason.value} + + + + + {reason.label} + + + {reason.description} + + + , + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + onClick: handleDelete, + icon: , + }, + ], + }, + ]} + /> + + + ) +} + +const ItemSkeleton = () => { + return ( + + + + + + + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts new file mode 100644 index 0000000000..77433ebf6d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/index.ts @@ -0,0 +1 @@ +export { ReturnReasonList as Component } from "./return-reason-list" diff --git a/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx new file mode 100644 index 0000000000..e5304f0124 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/return-reasons/return-reason-list/return-reason-list.tsx @@ -0,0 +1,13 @@ +import { Outlet } from "react-router-dom" +import { ReturnReasonCallout } from "./components/return-reason-callout" +import { ReturnReasonOverview } from "./components/return-reason-overview" + +export const ReturnReasonList = () => { + return ( + + + + + + ) +}