Skip to content

Commit

Permalink
feat(dashboard): add campaign create to promotion UI
Browse files Browse the repository at this point in the history
  • Loading branch information
riqwan committed May 13, 2024
1 parent 3fd882a commit bc2bec6
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 322 deletions.
2 changes: 2 additions & 0 deletions packages/admin-next/dashboard/src/hooks/api/promotions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
PromotionRulesListRes,
PromotionRuleValuesListRes,
} from "../../types/api-responses"
import { campaignsQueryKeys } from "./campaigns"

const PROMOTIONS_QUERY_KEY = "promotions" as const
export const promotionsQueryKeys = {
Expand Down Expand Up @@ -185,6 +186,7 @@ export const useCreatePromotion = (
mutationFn: (payload) => client.promotions.create(payload),
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
options?.onSuccess?.(data, variables, context)
},
...options,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,50 @@
import { zodResolver } from "@hookform/resolvers/zod"
import {
Button,
clx,
CurrencyInput,
DatePicker,
Heading,
Input,
RadioGroup,
Select,
Text,
toast,
} from "@medusajs/ui"
import { useForm, useWatch } from "react-hook-form"
import { Button, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"

import { Form } from "../../../../../components/common/form"
import { CampaignBudgetTypeValues } from "@medusajs/types"
import {
RouteFocusModal,
useRouteModal,
} from "../../../../../components/route-modal"
import { useCreateCampaign } from "../../../../../hooks/api/campaigns"
import { currencies, getCurrencySymbol } from "../../../../../lib/currencies"
import { CreateCampaignFormFields } from "../../../common/components/create-campaign-form-fields"

const CreateCampaignSchema = zod.object({
name: zod.string(),
export const CreateCampaignSchema = zod.object({
name: zod.string().min(1),
description: zod.string().optional(),
currency: zod.string(),
campaign_identifier: zod.string(),
campaign_identifier: zod.string().min(1),
starts_at: zod.date().optional(),
ends_at: zod.date().optional(),
budget: zod.object({
limit: zod.number().min(0),
type: zod.enum(["spend", "usage"]).optional(),
type: zod.enum(["spend", "usage"]),
}),
})

export const defaultCampaignValues = {
name: undefined,
description: undefined,
currency: undefined,
campaign_identifier: undefined,
starts_at: undefined,
ends_at: undefined,
budget: {
type: "spend" as CampaignBudgetTypeValues,
limit: undefined,
},
}

export const CreateCampaignForm = () => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()

const { mutateAsync, isPending } = useCreateCampaign()

const form = useForm<zod.infer<typeof CreateCampaignSchema>>({
defaultValues: {
name: "",
description: "",
currency: "",
campaign_identifier: "",
starts_at: undefined,
ends_at: undefined,
budget: {
type: "spend",
limit: undefined,
},
},
defaultValues: defaultCampaignValues,
resolver: zodResolver(CreateCampaignSchema),
})

Expand Down Expand Up @@ -92,18 +82,6 @@ export const CreateCampaignForm = () => {
)
})

const watchValueType = useWatch({
control: form.control,
name: "budget.type",
})

const isTypeSpend = watchValueType === "spend"

const currencyValue = useWatch({
control: form.control,
name: "currency",
})

return (
<RouteFocusModal.Form form={form}>
<form onSubmit={handleSubmit}>
Expand All @@ -127,259 +105,7 @@ export const CreateCampaignForm = () => {
</RouteFocusModal.Header>

<RouteFocusModal.Body className="flex flex-col items-center py-16">
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
<div>
<Heading>{t("campaigns.create.header")}</Heading>
<Text size="small" className="text-ui-fg-subtle">
{t("campaigns.create.hint")}
</Text>
</div>

<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Form.Field
control={form.control}
name="name"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.name")}</Form.Label>

<Form.Control>
<Input {...field} />
</Form.Control>

<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<Form.Field
control={form.control}
name="description"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.description")}</Form.Label>

<Form.Control>
<Input {...field} />
</Form.Control>

<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<Form.Field
control={form.control}
name="campaign_identifier"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>
{t("campaigns.fields.identifier")}
</Form.Label>

<Form.Control>
<Input {...field} />
</Form.Control>

<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<Form.Field
control={form.control}
name="currency"
render={({ field: { onChange, ref, ...field } }) => {
return (
<Form.Item>
<Form.Label>{t("fields.currency")}</Form.Label>
<Form.Control>
<Select {...field} onValueChange={onChange}>
<Select.Trigger ref={ref}>
<Select.Value />
</Select.Trigger>

<Select.Content>
{Object.values(currencies).map((currency) => (
<Select.Item
value={currency.code}
key={currency.code}
>
{currency.name}
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<Form.Field
control={form.control}
name="starts_at"
render={({
field: { value, onChange, ref: _ref, ...field },
}) => {
return (
<Form.Item>
<Form.Label>
{t("campaigns.fields.start_date")}
</Form.Label>

<Form.Control>
<DatePicker
showTimePicker
value={value ?? undefined}
onChange={(v) => {
onChange(v ?? null)
}}
{...field}
/>
</Form.Control>

<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<Form.Field
control={form.control}
name="ends_at"
render={({
field: { value, onChange, ref: _ref, ...field },
}) => {
return (
<Form.Item>
<Form.Label>{t("campaigns.fields.end_date")}</Form.Label>

<Form.Control>
<DatePicker
showTimePicker
value={value ?? undefined}
onChange={(v) => onChange(v ?? null)}
{...field}
/>
</Form.Control>

<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div>

<div>
<Heading>{t("campaigns.budget.create.header")}</Heading>
<Text size="small" className="text-ui-fg-subtle">
{t("campaigns.budget.create.hint")}
</Text>
</div>

<Form.Field
control={form.control}
name="budget.type"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("campaigns.budget.fields.type")}</Form.Label>

<Form.Control>
<RadioGroup
className="flex gap-y-3"
{...field}
onValueChange={field.onChange}
>
<RadioGroup.ChoiceBox
className={clx("basis-1/2", {
"border-2 border-ui-border-interactive":
"spend" === field.value,
})}
value={"spend"}
label={t("campaigns.budget.type.spend.title")}
description={t(
"campaigns.budget.type.spend.description"
)}
/>

<RadioGroup.ChoiceBox
className={clx("basis-1/2", {
"border-2 border-ui-border-interactive":
"usage" === field.value,
})}
value={"usage"}
label={t("campaigns.budget.type.usage.title")}
description={t(
"campaigns.budget.type.usage.description"
)}
/>
</RadioGroup>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>

<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Form.Field
control={form.control}
name="budget.limit"
render={({ field: { onChange, value, ...field } }) => {
return (
<Form.Item className="basis-1/2">
<Form.Label>
{t("campaigns.budget.fields.limit")}
</Form.Label>

<Form.Control>
{isTypeSpend ? (
<CurrencyInput
min={0}
onValueChange={(value) =>
onChange(value ? parseInt(value) : "")
}
code={currencyValue}
symbol={
currencyValue
? getCurrencySymbol(currencyValue)
: ""
}
{...field}
value={value}
/>
) : (
<Input
key="usage"
min={0}
{...field}
value={value}
onChange={(e) => {
onChange(
e.target.value === ""
? null
: parseInt(e.target.value)
)
}}
/>
)}
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
</div>
</div>
<CreateCampaignFormFields form={form} />
</RouteFocusModal.Body>
</form>
</RouteFocusModal.Form>
Expand Down
Loading

0 comments on commit bc2bec6

Please sign in to comment.