fix(dashboard): Fixes to campaign and promotions domains (#9022)
This commit is contained in:
committed by
GitHub
parent
c27aa46939
commit
e5b90b2d97
@@ -1,3 +1,5 @@
|
||||
import { Badge } from "@medusajs/ui"
|
||||
|
||||
type CellProps = {
|
||||
code: string
|
||||
}
|
||||
@@ -9,10 +11,9 @@ type HeaderProps = {
|
||||
export const CodeCell = ({ code }: CellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
{/* // TODO: border color inversion*/}
|
||||
<span className="bg-ui-tag-neutral-bg truncate rounded-md border border-neutral-200 p-1 text-xs">
|
||||
<Badge size="2xsmall" className="truncate">
|
||||
{code}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
export const usePromotionTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
let filters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at", type: "date" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at", type: "date" },
|
||||
]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -1694,8 +1694,15 @@
|
||||
"title": "Edit buy rules"
|
||||
}
|
||||
},
|
||||
"addToCampaign": {
|
||||
"title": "Promotion's Campaign"
|
||||
"campaign": {
|
||||
"header": "Campaign",
|
||||
"edit": {
|
||||
"header": "Edit Campaign",
|
||||
"successToast": "Successfully updated the campaign of the promotion."
|
||||
},
|
||||
"actions": {
|
||||
"goToCampaign": "Go to campaign"
|
||||
}
|
||||
},
|
||||
"campaign_currency": {
|
||||
"tooltip": "This is the promotion's currency. Change it from the Details tab."
|
||||
@@ -1800,6 +1807,14 @@
|
||||
"header": "Edit Campaign",
|
||||
"successToast": "Campaign '{{name}}' was successfully updated."
|
||||
},
|
||||
"configuration": {
|
||||
"header": "Configuration",
|
||||
"edit": {
|
||||
"header": "Edit Campaign Configuration",
|
||||
"description": "Edit the configuration of the campaign.",
|
||||
"successToast": "Campaign configuration was successfully updated."
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"hint": "Create a promotional campaign.",
|
||||
"header": "Create Campaign",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export enum PromotionStatus {
|
||||
SCHEDULED = "SCHEDULED",
|
||||
@@ -7,7 +7,7 @@ export enum PromotionStatus {
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export const getPromotionStatus = (promotion: PromotionDTO) => {
|
||||
export const getPromotionStatus = (promotion: HttpTypes.AdminPromotion) => {
|
||||
const date = new Date()
|
||||
const campaign = promotion.campaign
|
||||
|
||||
|
||||
@@ -355,6 +355,11 @@ export const RouteMap: RouteObject[] = [
|
||||
path: "edit",
|
||||
lazy: () => import("../../routes/campaigns/campaign-edit"),
|
||||
},
|
||||
{
|
||||
path: "configuration",
|
||||
lazy: () =>
|
||||
import("../../routes/campaigns/campaign-configuration"),
|
||||
},
|
||||
{
|
||||
path: "edit-budget",
|
||||
lazy: () =>
|
||||
|
||||
@@ -15,9 +15,9 @@ import { RouteFocusModal, useRouteModal } from "../../../../components/modals"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { useAddOrRemoveCampaignPromotions } from "../../../../hooks/api/campaigns"
|
||||
import { usePromotions } from "../../../../hooks/api/promotions"
|
||||
import { usePromotionTableColumns } from "../../../../hooks/table/columns-v2/use-promotion-table-columns"
|
||||
import { usePromotionTableColumns } from "../../../../hooks/table/columns/use-promotion-table-columns"
|
||||
import { usePromotionTableFilters } from "../../../../hooks/table/filters/use-promotion-table-filters"
|
||||
import { usePromotionTableQuery } from "../../../../hooks/table/query-v2/use-promotion-table-query"
|
||||
import { usePromotionTableQuery } from "../../../../hooks/table/query/use-promotion-table-query"
|
||||
import { useDataTable } from "../../../../hooks/use-data-table"
|
||||
|
||||
type AddCampaignPromotionsFormProps = {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/modals"
|
||||
import { useCampaign } from "../../../hooks/api/campaigns"
|
||||
import { CampaignConfigurationForm } from "./components/campaign-configuration-form"
|
||||
|
||||
export const CampaignConfiguration = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { id } = useParams()
|
||||
const { campaign, isLoading, isError, error } = useCampaign(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<RouteDrawer.Title asChild>
|
||||
<Heading>{t("campaigns.configuration.edit.header")}</Heading>
|
||||
</RouteDrawer.Title>
|
||||
<RouteDrawer.Description className="sr-only">
|
||||
{t("campaigns.configuration.edit.description")}
|
||||
</RouteDrawer.Description>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && campaign && (
|
||||
<CampaignConfigurationForm campaign={campaign} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { AdminCampaign } from "@medusajs/types"
|
||||
import { Button, DatePicker, toast } from "@medusajs/ui"
|
||||
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/modals"
|
||||
import { useUpdateCampaign } from "../../../../../hooks/api/campaigns"
|
||||
|
||||
type CampaignConfigurationFormProps = {
|
||||
campaign: AdminCampaign
|
||||
}
|
||||
|
||||
const CampaignConfigurationSchema = z.object({
|
||||
starts_at: z.date().nullable(),
|
||||
ends_at: z.date().nullable(),
|
||||
})
|
||||
|
||||
export const CampaignConfigurationForm = ({
|
||||
campaign,
|
||||
}: CampaignConfigurationFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<z.infer<typeof CampaignConfigurationSchema>>({
|
||||
defaultValues: {
|
||||
starts_at: campaign.starts_at ? new Date(campaign.starts_at) : undefined,
|
||||
ends_at: campaign.ends_at ? new Date(campaign.ends_at) : undefined,
|
||||
},
|
||||
resolver: zodResolver(CampaignConfigurationSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isPending } = useUpdateCampaign(campaign.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
starts_at: data.starts_at || null,
|
||||
ends_at: data.ends_at || null,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ campaign }) => {
|
||||
toast.success(
|
||||
t("campaigns.configuration.edit.successToast", {
|
||||
name: campaign.name,
|
||||
})
|
||||
)
|
||||
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="starts_at"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("campaigns.fields.start_date")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<DatePicker
|
||||
granularity="minute"
|
||||
hourCycle={12}
|
||||
shouldCloseOnSelect={false}
|
||||
{...field}
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="ends_at"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("campaigns.fields.end_date")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<DatePicker
|
||||
granularity="minute"
|
||||
shouldCloseOnSelect={false}
|
||||
{...field}
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</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
|
||||
isLoading={isPending}
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="small"
|
||||
>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./campaign-configuration-form"
|
||||
@@ -0,0 +1 @@
|
||||
export { CampaignConfiguration as Component } from "./campaign-configuration"
|
||||
@@ -78,8 +78,12 @@ export const CreateCampaignForm = () => {
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<RouteFocusModal.Header>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header />
|
||||
<RouteFocusModal.Body className="flex size-full flex-col items-center overflow-auto py-16">
|
||||
<CreateCampaignFormFields form={form} />
|
||||
</RouteFocusModal.Body>
|
||||
<RouteFocusModal.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
@@ -95,11 +99,7 @@ export const CreateCampaignForm = () => {
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
|
||||
<RouteFocusModal.Body className="flex flex-col items-center py-16">
|
||||
<CreateCampaignFormFields form={form} />
|
||||
</RouteFocusModal.Body>
|
||||
</RouteFocusModal.Footer>
|
||||
</form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
import { useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { useCampaign } from "../../../hooks/api/campaigns"
|
||||
import { CampaignBudget } from "./components/campaign-budget"
|
||||
import { CampaignGeneralSection } from "./components/campaign-general-section"
|
||||
@@ -12,6 +11,9 @@ import after from "virtual:medusa/widgets/campaign/details/after"
|
||||
import before from "virtual:medusa/widgets/campaign/details/before"
|
||||
import sideAfter from "virtual:medusa/widgets/campaign/details/side/after"
|
||||
import sideBefore from "virtual:medusa/widgets/campaign/details/side/before"
|
||||
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||
import { CampaignConfigurationSection } from "./components/campaign-configuration-section"
|
||||
|
||||
export const CampaignDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -26,7 +28,14 @@ export const CampaignDetail = () => {
|
||||
)
|
||||
|
||||
if (isLoading || !campaign) {
|
||||
return <div>Loading...</div>
|
||||
return (
|
||||
<TwoColumnPageSkeleton
|
||||
mainSections={2}
|
||||
sidebarSections={3}
|
||||
showJSON
|
||||
showMetadata
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
@@ -34,54 +43,27 @@ export const CampaignDetail = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={campaign} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<CampaignGeneralSection campaign={campaign} />
|
||||
<CampaignPromotionSection campaign={campaign} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={campaign} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="hidden xl:block">
|
||||
<JsonViewSection data={campaign} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 xl:mt-0 xl:max-w-[400px]">
|
||||
{sideBefore.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={campaign} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<CampaignSpend campaign={campaign} />
|
||||
<CampaignBudget campaign={campaign} />
|
||||
{sideAfter.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={campaign} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="xl:hidden">
|
||||
<JsonViewSection data={campaign} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Outlet />
|
||||
</div>
|
||||
<TwoColumnPage
|
||||
widgets={{
|
||||
after,
|
||||
before,
|
||||
sideAfter,
|
||||
sideBefore,
|
||||
}}
|
||||
hasOutlet
|
||||
showJSON
|
||||
showMetadata
|
||||
data={campaign}
|
||||
>
|
||||
<TwoColumnPage.Main>
|
||||
<CampaignGeneralSection campaign={campaign} />
|
||||
<CampaignPromotionSection campaign={campaign} />
|
||||
</TwoColumnPage.Main>
|
||||
<TwoColumnPage.Sidebar>
|
||||
<CampaignConfigurationSection campaign={campaign} />
|
||||
<CampaignSpend campaign={campaign} />
|
||||
<CampaignBudget campaign={campaign} />
|
||||
</TwoColumnPage.Sidebar>
|
||||
</TwoColumnPage>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
|
||||
import { PencilSquare } from "@medusajs/icons"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DateRangeDisplay } from "../../../../../components/common/date-range-display"
|
||||
|
||||
type CampaignConfigurationSectionProps = {
|
||||
campaign: HttpTypes.AdminCampaign
|
||||
}
|
||||
|
||||
export const CampaignConfigurationSection = ({
|
||||
campaign,
|
||||
}: CampaignConfigurationSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Heading level="h2">{t("campaigns.configuration.header")}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: "configuration",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DateRangeDisplay
|
||||
startsAt={campaign.starts_at}
|
||||
endsAt={campaign.ends_at}
|
||||
showTime
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./campaign-configuration-section";
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { formatDate } from "../../../../../components/common/date"
|
||||
import { useDeleteCampaign } from "../../../../../hooks/api/campaigns"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import {
|
||||
@@ -133,26 +132,6 @@ export const CampaignGeneralSection = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("campaigns.fields.start_date")}
|
||||
</Text>
|
||||
|
||||
<Text size="small" leading="compact">
|
||||
{campaign.starts_at ? formatDate(campaign.starts_at) : "-"}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("campaigns.fields.end_date")}
|
||||
</Text>
|
||||
|
||||
<Text size="small" leading="compact">
|
||||
{campaign.ends_at ? formatDate(campaign.ends_at) : "-"}
|
||||
</Text>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useAddOrRemoveCampaignPromotions } from "../../../../../hooks/api/campaigns"
|
||||
import { usePromotions } from "../../../../../hooks/api/promotions"
|
||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns-v2/use-promotion-table-columns"
|
||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns/use-promotion-table-columns"
|
||||
import { usePromotionTableFilters } from "../../../../../hooks/table/filters/use-promotion-table-filters"
|
||||
import { usePromotionTableQuery } from "../../../../../hooks/table/query-v2/use-promotion-table-query"
|
||||
import { usePromotionTableQuery } from "../../../../../hooks/table/query/use-promotion-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
|
||||
type CampaignPromotionSectionProps = {
|
||||
|
||||
@@ -74,13 +74,10 @@ export const ProductCreateForm = ({
|
||||
return {}
|
||||
}
|
||||
|
||||
return regions.reduce(
|
||||
(acc, reg) => {
|
||||
acc[reg.id] = reg.currency_code
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string>
|
||||
)
|
||||
return regions.reduce((acc, reg) => {
|
||||
acc[reg.id] = reg.currency_code
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
}, [regions])
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { AdminCampaign, AdminPromotion } from "@medusajs/types"
|
||||
import { Button, RadioGroup, Select, Text } from "@medusajs/ui"
|
||||
import { Button, RadioGroup, Select, Text, toast } from "@medusajs/ui"
|
||||
import { useEffect } from "react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
@@ -44,7 +44,7 @@ export const AddCampaignPromotionFields = ({
|
||||
const selectedCampaign = campaigns.find((c) => c.id === watchCampaignId)
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-y-8">
|
||||
<div className="flex flex-col gap-y-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="campaign_choice"
|
||||
@@ -55,7 +55,7 @@ export const AddCampaignPromotionFields = ({
|
||||
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
className="grid grid-cols-1 gap-3"
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
@@ -154,6 +154,8 @@ export const AddCampaignPromotionForm = ({
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const { campaign } = promotion
|
||||
|
||||
const originalId = campaign?.id
|
||||
|
||||
const form = useForm<zod.infer<typeof EditPromotionSchema>>({
|
||||
defaultValues: {
|
||||
campaign_id: campaign?.id,
|
||||
@@ -162,11 +164,21 @@ export const AddCampaignPromotionForm = ({
|
||||
resolver: zodResolver(EditPromotionSchema),
|
||||
})
|
||||
|
||||
const { setValue } = form
|
||||
|
||||
const { mutateAsync, isPending } = useUpdatePromotion(promotion.id)
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
{ campaign_id: data.campaign_id },
|
||||
{ onSuccess: () => handleSuccess() }
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("promotions.campaign.edit.successToast"))
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -177,18 +189,21 @@ export const AddCampaignPromotionForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (watchCampaignChoice === "none") {
|
||||
form.setValue("campaign_id", null)
|
||||
setValue("campaign_id", null)
|
||||
}
|
||||
|
||||
if (watchCampaignChoice === "existing") {
|
||||
form.setValue("campaign_id", campaign?.id)
|
||||
setValue("campaign_id", originalId)
|
||||
}
|
||||
}, [watchCampaignChoice])
|
||||
}, [watchCampaignChoice, setValue, originalId])
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<RouteDrawer.Body>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex size-full flex-col overflow-hidden"
|
||||
>
|
||||
<RouteDrawer.Body className="size-full overflow-auto">
|
||||
<AddCampaignPromotionFields
|
||||
form={form}
|
||||
campaigns={campaigns}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const PromotionAddCampaign = () => {
|
||||
return (
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("promotions.addToCampaign.title")}</Heading>
|
||||
<Heading>{t("promotions.campaign.edit.header")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
|
||||
{!isPending && !areCampaignsLoading && promotion && campaigns && (
|
||||
|
||||
@@ -326,17 +326,15 @@ export const CreatePromotionForm = () => {
|
||||
|
||||
return (
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
className="flex h-full flex-col overflow-scroll"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<form className="flex h-full flex-col" onSubmit={handleSubmit}>
|
||||
<ProgressTabs
|
||||
value={tab}
|
||||
onValueChange={(tab) => handleTabChange(tab as Tab)}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex w-full items-center justify-between gap-x-4">
|
||||
<div className="-my-2 w-full max-w-[400px] border-l">
|
||||
<div className="-my-2 w-full max-w-[600px] border-l">
|
||||
<ProgressTabs.List className="grid w-full grid-cols-3">
|
||||
<ProgressTabs.Trigger
|
||||
className="w-full"
|
||||
@@ -358,427 +356,114 @@ export const CreatePromotionForm = () => {
|
||||
</ProgressTabs.Trigger>
|
||||
</ProgressTabs.List>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
|
||||
{tab === Tab.CAMPAIGN ? (
|
||||
<Button
|
||||
key="save-btn"
|
||||
type="submit"
|
||||
size="small"
|
||||
isLoading={false}
|
||||
>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
key="continue-btn"
|
||||
type="button"
|
||||
onClick={handleContinue}
|
||||
size="small"
|
||||
>
|
||||
{t("actions.continue")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
|
||||
<RouteFocusModal.Body className="mx-auto my-20 w-[800px]">
|
||||
<ProgressTabs.Content value={Tab.TYPE}>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="template_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("promotions.fields.type")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
key={"template_id"}
|
||||
className="flex-col gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{templates.map((template) => {
|
||||
return (
|
||||
<RadioGroup.ChoiceBox
|
||||
key={template.id}
|
||||
value={template.id}
|
||||
label={template.title}
|
||||
description={template.description}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</ProgressTabs.Content>
|
||||
|
||||
<RouteFocusModal.Body className="size-full overflow-hidden">
|
||||
<ProgressTabs.Content
|
||||
value={Tab.PROMOTION}
|
||||
className="flex flex-1 flex-col gap-8"
|
||||
value={Tab.TYPE}
|
||||
className="size-full overflow-y-auto"
|
||||
>
|
||||
<Heading level="h1" className="text-fg-base">
|
||||
{t(`promotions.sections.details`)}
|
||||
|
||||
{currentTemplate?.title && (
|
||||
<Badge
|
||||
className="ml-2 align-middle"
|
||||
color="grey"
|
||||
size="2xsmall"
|
||||
rounded="full"
|
||||
>
|
||||
{currentTemplate?.title}
|
||||
</Badge>
|
||||
)}
|
||||
</Heading>
|
||||
|
||||
{form.formState.errors.root && (
|
||||
<Alert
|
||||
variant="error"
|
||||
dismissible={false}
|
||||
className="text-balance"
|
||||
>
|
||||
{form.formState.errors.root.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="is_automatic"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>Method</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"false"}
|
||||
label={t("promotions.form.method.code.title")}
|
||||
description={t(
|
||||
"promotions.form.method.code.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"true"}
|
||||
label={t("promotions.form.method.automatic.title")}
|
||||
description={t(
|
||||
"promotions.form.method.automatic.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="code"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label>
|
||||
{t("promotions.form.code.title")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input {...field} placeholder="SUMMER15" />
|
||||
</Form.Control>
|
||||
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="promotions.form.code.description"
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!currentTemplate?.hiddenFields?.includes("type") && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("promotions.fields.type")}</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"standard"}
|
||||
label={t("promotions.form.type.standard.title")}
|
||||
description={t(
|
||||
"promotions.form.type.standard.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"buyget"}
|
||||
label={t("promotions.form.type.buyget.title")}
|
||||
description={t(
|
||||
"promotions.form.type.buyget.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<RulesFormField form={form} ruleType={"rules"} />
|
||||
|
||||
<Divider />
|
||||
|
||||
{!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.type"
|
||||
) && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.type"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("promotions.fields.value_type")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"fixed"}
|
||||
label={t(
|
||||
"promotions.form.value_type.fixed.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.value_type.fixed.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"percentage"}
|
||||
label={t(
|
||||
"promotions.form.value_type.percentage.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.value_type.percentage.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex gap-x-2 gap-y-4">
|
||||
{!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.value"
|
||||
) && (
|
||||
<div className="flex size-full flex-col items-center">
|
||||
<div className="w-full max-w-[720px] py-16">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.value"
|
||||
render={({ field: { onChange, value, ...field } }) => {
|
||||
const currencyCode =
|
||||
form.getValues().application_method.currency_code
|
||||
|
||||
name="template_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label
|
||||
tooltip={
|
||||
currencyCode || !isFixedValueType
|
||||
? undefined
|
||||
: t("promotions.fields.amount.tooltip")
|
||||
}
|
||||
>
|
||||
{t("promotions.form.value.title")}
|
||||
</Form.Label>
|
||||
<Form.Item>
|
||||
<Form.Label>{t("promotions.fields.type")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
{isFixedValueType ? (
|
||||
<CurrencyInput
|
||||
{...field}
|
||||
min={0}
|
||||
onValueChange={(value) => {
|
||||
onChange(value ? parseInt(value) : "")
|
||||
}}
|
||||
code={currencyCode}
|
||||
symbol={
|
||||
currencyCode
|
||||
? getCurrencySymbol(currencyCode)
|
||||
: ""
|
||||
}
|
||||
value={value}
|
||||
disabled={!currencyCode}
|
||||
/>
|
||||
) : (
|
||||
<DeprecatedPercentageInput
|
||||
key="amount"
|
||||
className="text-right"
|
||||
min={0}
|
||||
max={100}
|
||||
{...field}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(
|
||||
e.target.value === ""
|
||||
? null
|
||||
: parseInt(e.target.value)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<RadioGroup
|
||||
key={"template_id"}
|
||||
className="flex-col gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
{templates.map((template) => {
|
||||
return (
|
||||
<RadioGroup.ChoiceBox
|
||||
key={template.id}
|
||||
value={template.id}
|
||||
label={template.title}
|
||||
description={template.description}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey={
|
||||
isFixedValueType
|
||||
? "promotions.form.value_type.fixed.description"
|
||||
: "promotions.form.value_type.percentage.description"
|
||||
}
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTypeStandard && watchAllocation === "each" && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.max_quantity"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label>
|
||||
{t("promotions.form.max_quantity.title")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input
|
||||
{...form.register(
|
||||
"application_method.max_quantity",
|
||||
{ valueAsNumber: true }
|
||||
)}
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder="3"
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="promotions.form.max_quantity.description"
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ProgressTabs.Content>
|
||||
|
||||
<ProgressTabs.Content
|
||||
value={Tab.PROMOTION}
|
||||
className="size-full overflow-y-auto"
|
||||
>
|
||||
<div className="flex size-full flex-col items-center">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 py-16">
|
||||
<Heading level="h1" className="text-fg-base">
|
||||
{t(`promotions.sections.details`)}
|
||||
|
||||
{currentTemplate?.title && (
|
||||
<Badge
|
||||
className="ml-2 align-middle"
|
||||
color="grey"
|
||||
size="2xsmall"
|
||||
rounded="full"
|
||||
>
|
||||
{currentTemplate?.title}
|
||||
</Badge>
|
||||
)}
|
||||
</Heading>
|
||||
|
||||
{form.formState.errors.root && (
|
||||
<Alert
|
||||
variant="error"
|
||||
dismissible={false}
|
||||
className="text-balance"
|
||||
>
|
||||
{form.formState.errors.root.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isTypeStandard &&
|
||||
!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.allocation"
|
||||
) && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.allocation"
|
||||
name="is_automatic"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("promotions.fields.allocation")}
|
||||
</Form.Label>
|
||||
<Form.Label>Method</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"each"}
|
||||
label={t(
|
||||
"promotions.form.allocation.each.title"
|
||||
)}
|
||||
value={"false"}
|
||||
label={t("promotions.form.method.code.title")}
|
||||
description={t(
|
||||
"promotions.form.allocation.each.description"
|
||||
"promotions.form.method.code.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"across"}
|
||||
value={"true"}
|
||||
label={t(
|
||||
"promotions.form.allocation.across.title"
|
||||
"promotions.form.method.automatic.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.allocation.across.description"
|
||||
"promotions.form.method.automatic.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
@@ -789,41 +474,376 @@ export const CreatePromotionForm = () => {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isTypeStandard && (
|
||||
<>
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"buy-rules"}
|
||||
scope="application_method.buy_rules"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="code"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label>
|
||||
{t("promotions.form.code.title")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input {...field} placeholder="SUMMER15" />
|
||||
</Form.Control>
|
||||
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="promotions.form.code.description"
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!currentTemplate?.hiddenFields?.includes("type") && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("promotions.fields.type")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"standard"}
|
||||
label={t(
|
||||
"promotions.form.type.standard.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.type.standard.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"buyget"}
|
||||
label={t("promotions.form.type.buyget.title")}
|
||||
description={t(
|
||||
"promotions.form.type.buyget.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isTargetTypeOrder && (
|
||||
<>
|
||||
<Divider />
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"target-rules"}
|
||||
scope="application_method.target_rules"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<RulesFormField form={form} ruleType={"rules"} />
|
||||
|
||||
<Divider />
|
||||
|
||||
{!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.type"
|
||||
) && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.type"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("promotions.fields.value_type")}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"fixed"}
|
||||
label={t(
|
||||
"promotions.form.value_type.fixed.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.value_type.fixed.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"percentage"}
|
||||
label={t(
|
||||
"promotions.form.value_type.percentage.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.value_type.percentage.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex gap-x-2 gap-y-4">
|
||||
{!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.value"
|
||||
) && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.value"
|
||||
render={({ field: { onChange, value, ...field } }) => {
|
||||
const currencyCode =
|
||||
form.getValues().application_method.currency_code
|
||||
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label
|
||||
tooltip={
|
||||
currencyCode || !isFixedValueType
|
||||
? undefined
|
||||
: t("promotions.fields.amount.tooltip")
|
||||
}
|
||||
>
|
||||
{t("promotions.form.value.title")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
{isFixedValueType ? (
|
||||
<CurrencyInput
|
||||
{...field}
|
||||
min={0}
|
||||
onValueChange={(value) => {
|
||||
onChange(value ? parseInt(value) : "")
|
||||
}}
|
||||
code={currencyCode || "USD"}
|
||||
symbol={
|
||||
currencyCode
|
||||
? getCurrencySymbol(currencyCode)
|
||||
: "$"
|
||||
}
|
||||
value={value}
|
||||
disabled={!currencyCode}
|
||||
/>
|
||||
) : (
|
||||
<DeprecatedPercentageInput
|
||||
key="amount"
|
||||
className="text-right"
|
||||
min={0}
|
||||
max={100}
|
||||
{...field}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(
|
||||
e.target.value === ""
|
||||
? null
|
||||
: parseInt(e.target.value)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Form.Control>
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey={
|
||||
isFixedValueType
|
||||
? "promotions.form.value_type.fixed.description"
|
||||
: "promotions.form.value_type.percentage.description"
|
||||
}
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTypeStandard && watchAllocation === "each" && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.max_quantity"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Label>
|
||||
{t("promotions.form.max_quantity.title")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input
|
||||
{...form.register(
|
||||
"application_method.max_quantity",
|
||||
{ valueAsNumber: true }
|
||||
)}
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder="3"
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="promotions.form.max_quantity.description"
|
||||
components={[<br key="break" />]}
|
||||
/>
|
||||
</Text>
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isTypeStandard &&
|
||||
!currentTemplate?.hiddenFields?.includes(
|
||||
"application_method.allocation"
|
||||
) && (
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="application_method.allocation"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("promotions.fields.allocation")}
|
||||
</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<RadioGroup
|
||||
className="flex gap-y-3"
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"each"}
|
||||
label={t(
|
||||
"promotions.form.allocation.each.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.allocation.each.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"across"}
|
||||
label={t(
|
||||
"promotions.form.allocation.across.title"
|
||||
)}
|
||||
description={t(
|
||||
"promotions.form.allocation.across.description"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isTypeStandard && (
|
||||
<>
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"buy-rules"}
|
||||
scope="application_method.buy_rules"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isTargetTypeOrder && (
|
||||
<>
|
||||
<Divider />
|
||||
<RulesFormField
|
||||
form={form}
|
||||
ruleType={"target-rules"}
|
||||
scope="application_method.target_rules"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ProgressTabs.Content>
|
||||
|
||||
<ProgressTabs.Content
|
||||
value={Tab.CAMPAIGN}
|
||||
className="flex flex-col items-center"
|
||||
className="size-full overflow-auto"
|
||||
>
|
||||
<AddCampaignPromotionFields
|
||||
form={form}
|
||||
campaigns={campaigns || []}
|
||||
/>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 py-16">
|
||||
<AddCampaignPromotionFields
|
||||
form={form}
|
||||
campaigns={campaigns || []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ProgressTabs.Content>
|
||||
</RouteFocusModal.Body>
|
||||
</ProgressTabs>
|
||||
<RouteFocusModal.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
|
||||
{tab === Tab.CAMPAIGN ? (
|
||||
<Button
|
||||
key="save-btn"
|
||||
type="submit"
|
||||
size="small"
|
||||
isLoading={false}
|
||||
>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
key="continue-btn"
|
||||
type="button"
|
||||
onClick={handleContinue}
|
||||
size="small"
|
||||
>
|
||||
{t("actions.continue")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</RouteFocusModal.Footer>
|
||||
</form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
|
||||
@@ -2,5 +2,9 @@ import { RouteFocusModal } from "../../../components/modals"
|
||||
import { CreatePromotionForm } from "./components/create-promotion-form/create-promotion-form"
|
||||
|
||||
export const PromotionCreate = () => {
|
||||
return <RouteFocusModal>{<CreatePromotionForm />}</RouteFocusModal>
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
<CreatePromotionForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,97 +1,73 @@
|
||||
import { PencilSquare } from "@medusajs/icons"
|
||||
import { CampaignDTO } from "@medusajs/types"
|
||||
import { ArrowUpRightOnBox, PencilSquare } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading, Text } from "@medusajs/ui"
|
||||
import { format } from "date-fns"
|
||||
import { Fragment } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DateRangeDisplay } from "../../../../../components/common/date-range-display"
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
|
||||
function formatDate(date?: string | Date) {
|
||||
if (!date) {
|
||||
return "-"
|
||||
}
|
||||
|
||||
return format(new Date(date), "dd MMM yyyy")
|
||||
}
|
||||
|
||||
const CampaignDetailSection = ({ campaign }: { campaign: CampaignDTO }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const CampaignDetailSection = ({
|
||||
campaign,
|
||||
}: {
|
||||
campaign: HttpTypes.AdminCampaign
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{t("campaigns.fields.name")}
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<div className="text-ui-fg-muted flex items-center gap-x-1.5">
|
||||
<Text size="small" weight="plus" className="text-ui-fg-base">
|
||||
{campaign.name}
|
||||
</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Text size="small" leading="compact" className="text-pretty">
|
||||
{campaign?.name}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{t("campaigns.fields.identifier")}
|
||||
<Text size="small" weight="plus">
|
||||
·
|
||||
</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Text size="small" leading="compact" className="text-pretty">
|
||||
{campaign?.campaign_identifier}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{t("campaigns.fields.start_date")}
|
||||
<Text size="small" weight="plus">
|
||||
{campaign.campaign_identifier}
|
||||
</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Text size="small" leading="compact" className="text-pretty">
|
||||
{formatDate(campaign?.starts_at)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-center px-6 py-4">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{t("campaigns.fields.end_date") || "-"}
|
||||
</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Text size="small" leading="compact" className="text-pretty">
|
||||
{formatDate(campaign?.ends_at)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
<DateRangeDisplay
|
||||
startsAt={campaign.starts_at}
|
||||
endsAt={campaign.ends_at}
|
||||
showTime
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CampaignSection = ({ campaign }: { campaign: CampaignDTO }) => {
|
||||
export const CampaignSection = ({
|
||||
campaign,
|
||||
}: {
|
||||
campaign: HttpTypes.AdminCampaign | null
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: "add-to-campaign",
|
||||
icon: <PencilSquare />,
|
||||
},
|
||||
]
|
||||
|
||||
if (campaign) {
|
||||
actions.unshift({
|
||||
label: t("promotions.campaign.actions.goToCampaign"),
|
||||
to: `/campaigns/${campaign.id}`,
|
||||
icon: <ArrowUpRightOnBox />,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Container>
|
||||
<div className="flex items-center justify-between">
|
||||
<Heading level="h2">{t("promotions.fields.campaign")}</Heading>
|
||||
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: "add-to-campaign",
|
||||
icon: <PencilSquare />,
|
||||
},
|
||||
],
|
||||
actions,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -101,7 +77,7 @@ export const CampaignSection = ({ campaign }: { campaign: CampaignDTO }) => {
|
||||
<CampaignDetailSection campaign={campaign} />
|
||||
) : (
|
||||
<NoRecords
|
||||
className="h-[180px] p-6 text-center"
|
||||
className="h-[180px] pt-4 text-center"
|
||||
title="Not part of a campaign"
|
||||
message="Add this promotion to an existing campaign"
|
||||
action={{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PencilSquare } from "@medusajs/icons"
|
||||
import { AdminGetPromotionRulesRes, PromotionRuleTypes } from "@medusajs/types"
|
||||
import { HttpTypes, PromotionRuleTypes } from "@medusajs/types"
|
||||
import { Badge, Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BadgeListSummary } from "../../../../../components/common/badge-list-su
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
|
||||
type RuleProps = {
|
||||
rule: AdminGetPromotionRulesRes
|
||||
rule: HttpTypes.AdminPromotionRule
|
||||
}
|
||||
|
||||
function RuleBlock({ rule }: RuleProps) {
|
||||
@@ -42,7 +42,7 @@ function RuleBlock({ rule }: RuleProps) {
|
||||
}
|
||||
|
||||
type PromotionConditionsSectionProps = {
|
||||
rules: AdminGetPromotionRulesRes
|
||||
rules: HttpTypes.AdminPromotionRule[]
|
||||
ruleType: PromotionRuleTypes
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Badge,
|
||||
Container,
|
||||
@@ -14,13 +14,37 @@ import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { useDeletePromotion } from "../../../../../hooks/api/promotions"
|
||||
import { formatCurrency } from "../../../../../lib/format-currency"
|
||||
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
||||
import {
|
||||
getPromotionStatus,
|
||||
PromotionStatus,
|
||||
} from "../../../../../lib/promotions"
|
||||
|
||||
type PromotionGeneralSectionProps = {
|
||||
promotion: PromotionDTO
|
||||
promotion: HttpTypes.AdminPromotion
|
||||
}
|
||||
|
||||
function getDisplayValue(promotion: HttpTypes.AdminPromotion) {
|
||||
const value = promotion.application_method?.value
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (promotion.application_method?.type === "fixed") {
|
||||
const currency = promotion.application_method?.currency_code
|
||||
|
||||
if (!currency) {
|
||||
return null
|
||||
}
|
||||
|
||||
return formatCurrency(value, currency)
|
||||
} else if (promotion.application_method?.type === "percentage") {
|
||||
return formatPercentage(value)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const PromotionGeneralSection = ({
|
||||
@@ -64,6 +88,8 @@ export const PromotionGeneralSection = ({
|
||||
string
|
||||
]
|
||||
|
||||
const displayValue = getDisplayValue(promotion)
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
@@ -113,18 +139,15 @@ export const PromotionGeneralSection = ({
|
||||
{t("fields.code")}
|
||||
</Text>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Text
|
||||
size="small"
|
||||
weight="plus"
|
||||
leading="compact"
|
||||
className="text-pretty"
|
||||
<Copy content={promotion.code!} asChild>
|
||||
<Badge
|
||||
size="2xsmall"
|
||||
rounded="full"
|
||||
className="cursor-pointer text-pretty"
|
||||
>
|
||||
{promotion.code}
|
||||
</Text>
|
||||
|
||||
<Copy content={promotion.code!} variant="mini" />
|
||||
</div>
|
||||
</Badge>
|
||||
</Copy>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-start px-6 py-4">
|
||||
@@ -142,17 +165,16 @@ export const PromotionGeneralSection = ({
|
||||
{t("promotions.fields.value")}
|
||||
</Text>
|
||||
|
||||
<Text size="small" leading="compact" className="text-pretty">
|
||||
<Text className="inline pr-3" size="small" leading="compact">
|
||||
{promotion.application_method?.value}
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Text className="inline" size="small" leading="compact">
|
||||
{displayValue || "-"}
|
||||
</Text>
|
||||
|
||||
{promotion?.application_method?.type === "fixed" && (
|
||||
<Badge size="xsmall">
|
||||
{promotion?.application_method?.currency_code}
|
||||
<Badge size="2xsmall" rounded="full">
|
||||
{promotion?.application_method?.currency_code?.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-subtle grid grid-cols-2 items-start px-6 py-4">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||
import { useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||
import { usePromotion, usePromotionRules } from "../../../hooks/api/promotions"
|
||||
import { CampaignSection } from "./components/campaign-section"
|
||||
import { PromotionConditionsSection } from "./components/promotion-conditions-section"
|
||||
@@ -9,6 +10,8 @@ import { promotionLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/promotion/details/after"
|
||||
import before from "virtual:medusa/widgets/promotion/details/before"
|
||||
import sideAfter from "virtual:medusa/widgets/promotion/details/side/after"
|
||||
import sideBefore from "virtual:medusa/widgets/promotion/details/side/before"
|
||||
|
||||
export const PromotionDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -28,51 +31,40 @@ export const PromotionDetail = () => {
|
||||
const { rules: buyRules } = usePromotionRules(id!, "buy-rules", query)
|
||||
|
||||
if (isLoading || !promotion) {
|
||||
return <div>Loading...</div>
|
||||
return (
|
||||
<TwoColumnPageSkeleton mainSections={3} sidebarSections={1} showJSON />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={promotion} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<PromotionGeneralSection promotion={promotion} />
|
||||
<PromotionConditionsSection rules={rules || []} ruleType={"rules"} />
|
||||
<TwoColumnPage
|
||||
data={promotion}
|
||||
widgets={{
|
||||
after,
|
||||
before,
|
||||
sideAfter,
|
||||
sideBefore,
|
||||
}}
|
||||
hasOutlet
|
||||
showJSON
|
||||
>
|
||||
<TwoColumnPage.Main>
|
||||
<PromotionGeneralSection promotion={promotion} />
|
||||
<PromotionConditionsSection rules={rules || []} ruleType={"rules"} />
|
||||
<PromotionConditionsSection
|
||||
rules={targetRules || []}
|
||||
ruleType={"target-rules"}
|
||||
/>
|
||||
{promotion.type === "buyget" && (
|
||||
<PromotionConditionsSection
|
||||
rules={targetRules || []}
|
||||
ruleType={"target-rules"}
|
||||
rules={buyRules || []}
|
||||
ruleType={"buy-rules"}
|
||||
/>
|
||||
{promotion.type === "buyget" && (
|
||||
<PromotionConditionsSection
|
||||
rules={buyRules || []}
|
||||
ruleType={"buy-rules"}
|
||||
/>
|
||||
)}
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<w.Component data={promotion} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="hidden xl:block">
|
||||
<JsonViewSection data={promotion} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 xl:mt-0 xl:max-w-[400px]">
|
||||
<CampaignSection campaign={promotion.campaign!} />
|
||||
<div className="xl:hidden">
|
||||
<JsonViewSection data={promotion} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
)}
|
||||
</TwoColumnPage.Main>
|
||||
<TwoColumnPage.Sidebar>
|
||||
<CampaignSection campaign={promotion.campaign!} />
|
||||
</TwoColumnPage.Sidebar>
|
||||
</TwoColumnPage>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
clx,
|
||||
CurrencyInput,
|
||||
Input,
|
||||
RadioGroup,
|
||||
Text,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, CurrencyInput, Input, RadioGroup, Text } from "@medusajs/ui"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||
import { useUpdatePromotion } from "../../../../../hooks/api/promotions"
|
||||
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||
|
||||
@@ -80,9 +70,12 @@ export const EditPromotionDetailsForm = ({
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex h-full flex-col gap-y-8">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<div className="flex flex-col gap-y-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="is_automatic"
|
||||
@@ -98,10 +91,6 @@ export const EditPromotionDetailsForm = ({
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"false" === field.value,
|
||||
})}
|
||||
value={"false"}
|
||||
label={t("promotions.form.method.code.title")}
|
||||
description={t(
|
||||
@@ -109,10 +98,6 @@ export const EditPromotionDetailsForm = ({
|
||||
)}
|
||||
/>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"true" === field.value,
|
||||
})}
|
||||
value={"true"}
|
||||
label={t("promotions.form.method.automatic.title")}
|
||||
description={t(
|
||||
@@ -171,10 +156,6 @@ export const EditPromotionDetailsForm = ({
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"fixed" === field.value,
|
||||
})}
|
||||
value={"fixed"}
|
||||
label={t("promotions.form.value_type.fixed.title")}
|
||||
description={t(
|
||||
@@ -183,10 +164,6 @@ export const EditPromotionDetailsForm = ({
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"percentage" === field.value,
|
||||
})}
|
||||
value={"percentage"}
|
||||
label={t(
|
||||
"promotions.form.value_type.percentage.title"
|
||||
@@ -263,10 +240,6 @@ export const EditPromotionDetailsForm = ({
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"each" === field.value,
|
||||
})}
|
||||
value={"each"}
|
||||
label={t("promotions.form.allocation.each.title")}
|
||||
description={t(
|
||||
@@ -275,10 +248,6 @@ export const EditPromotionDetailsForm = ({
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-ui-border-interactive border-2":
|
||||
"across" === field.value,
|
||||
})}
|
||||
value={"across"}
|
||||
label={t("promotions.form.allocation.across.title")}
|
||||
description={t(
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
useDeletePromotion,
|
||||
usePromotions,
|
||||
} from "../../../../../hooks/api/promotions"
|
||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns-v2/use-promotion-table-columns"
|
||||
import { usePromotionTableFilters } from "../../../../../hooks/table/filters-v2/use-promotion-table-filters"
|
||||
import { usePromotionTableQuery } from "../../../../../hooks/table/query-v2/use-promotion-table-query"
|
||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns/use-promotion-table-columns"
|
||||
import { usePromotionTableFilters } from "../../../../../hooks/table/filters/use-promotion-table-filters"
|
||||
import { usePromotionTableQuery } from "../../../../../hooks/table/query/use-promotion-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { promotionsLoader } from "../../loader"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user