feat(dashboard): add promotions to campaign UI (#7283)
* feat(core-flows,types,medusa): API to add promotions to campaign * chore: consolidate specs * chore: split workflows step into 2 * chore: fix tests * chore: fix specs * chore: add promotions to campaign UI * chore: fix bug wrt to not refreshing * chore: address review comments
This commit is contained in:
1 parent
ea872cbda9
commit
6ec5ded6c8
13 files changed
+374
-24
No files matched your search
@@ -1086,6 +1086,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"promotions": {
|
||||||
|
"remove": {
|
||||||
|
"title": "Remove promotion from campaign",
|
||||||
|
"description": "You are about to remove {{count}} promotion(s) from the campaign. This action cannot be undone."
|
||||||
|
},
|
||||||
|
"alreadyAdded": "This promotion has already been added to the campaign.",
|
||||||
|
"toast": {
|
||||||
|
"success": "Successfully added {{count}} promotion(s) to campaign"
|
||||||
|
}
|
||||||
|
},
|
||||||
"deleteCampaignWarning": "You are about to delete the campaign {{name}}. This action cannot be undone.",
|
"deleteCampaignWarning": "You are about to delete the campaign {{name}}. This action cannot be undone.",
|
||||||
"totalSpend": "<0>{{amount}}</0> <1>{{currency}}</1>"
|
"totalSpend": "<0>{{amount}}</0> <1>{{currency}}</1>"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AdminCampaignListResponse,
|
AdminCampaignListResponse,
|
||||||
AdminCampaignResponse,
|
AdminCampaignResponse,
|
||||||
|
LinkMethodRequest,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
QueryKey,
|
QueryKey,
|
||||||
@@ -14,6 +15,7 @@ import { queryClient } from "../../lib/medusa"
|
|||||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||||
import { CreateCampaignReq, UpdateCampaignReq } from "../../types/api-payloads"
|
import { CreateCampaignReq, UpdateCampaignReq } from "../../types/api-payloads"
|
||||||
import { CampaignDeleteRes } from "../../types/api-responses"
|
import { CampaignDeleteRes } from "../../types/api-responses"
|
||||||
|
import { promotionsQueryKeys } from "./promotions"
|
||||||
|
|
||||||
const REGIONS_QUERY_KEY = "campaigns" as const
|
const REGIONS_QUERY_KEY = "campaigns" as const
|
||||||
export const campaignsQueryKeys = queryKeysFactory(REGIONS_QUERY_KEY)
|
export const campaignsQueryKeys = queryKeysFactory(REGIONS_QUERY_KEY)
|
||||||
@@ -82,7 +84,7 @@ export const useUpdateCampaign = (
|
|||||||
mutationFn: (payload) => client.campaigns.update(id, payload),
|
mutationFn: (payload) => client.campaigns.update(id, payload),
|
||||||
onSuccess: (data, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.detail(id) })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.details() })
|
||||||
|
|
||||||
options?.onSuccess?.(data, variables, context)
|
options?.onSuccess?.(data, variables, context)
|
||||||
},
|
},
|
||||||
@@ -98,10 +100,26 @@ export const useDeleteCampaign = (
|
|||||||
mutationFn: () => client.campaigns.delete(id),
|
mutationFn: () => client.campaigns.delete(id),
|
||||||
onSuccess: (data, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.lists() })
|
||||||
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.detail(id) })
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.details() })
|
||||||
|
|
||||||
options?.onSuccess?.(data, variables, context)
|
options?.onSuccess?.(data, variables, context)
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAddOrRemoveCampaignPromotions = (
|
||||||
|
id: string,
|
||||||
|
options?: UseMutationOptions<AdminCampaignResponse, Error, LinkMethodRequest>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload) =>
|
||||||
|
client.campaigns.addOrRemovePromotions(id, payload),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: campaignsQueryKeys.details() })
|
||||||
|
queryClient.invalidateQueries({ queryKey: promotionsQueryKeys.lists() })
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
AdminCampaignListResponse,
|
AdminCampaignListResponse,
|
||||||
AdminCampaignResponse,
|
AdminCampaignResponse,
|
||||||
CreateCampaignDTO,
|
CreateCampaignDTO,
|
||||||
|
LinkMethodRequest,
|
||||||
UpdateCampaignDTO,
|
UpdateCampaignDTO,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { CampaignDeleteRes } from "../../types/api-responses"
|
import { CampaignDeleteRes } from "../../types/api-responses"
|
||||||
@@ -27,10 +28,21 @@ async function deleteCampaign(id: string) {
|
|||||||
return deleteRequest<CampaignDeleteRes>(`/admin/campaigns/${id}`)
|
return deleteRequest<CampaignDeleteRes>(`/admin/campaigns/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addOrRemoveCampaignPromotions(
|
||||||
|
id: string,
|
||||||
|
payload: LinkMethodRequest
|
||||||
|
) {
|
||||||
|
return postRequest<AdminCampaignResponse>(
|
||||||
|
`/admin/campaigns/${id}/promotions`,
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const campaigns = {
|
export const campaigns = {
|
||||||
retrieve: retrieveCampaign,
|
retrieve: retrieveCampaign,
|
||||||
list: listCampaigns,
|
list: listCampaigns,
|
||||||
create: createCampaign,
|
create: createCampaign,
|
||||||
update: updateCampaign,
|
update: updateCampaign,
|
||||||
delete: deleteCampaign,
|
delete: deleteCampaign,
|
||||||
|
addOrRemovePromotions: addOrRemoveCampaignPromotions,
|
||||||
}
|
}
|
||||||
@@ -237,6 +237,13 @@ export const RouteMap: RouteObject[] = [
|
|||||||
lazy: () =>
|
lazy: () =>
|
||||||
import("../../v2-routes/campaigns/campaign-budget-edit"),
|
import("../../v2-routes/campaigns/campaign-budget-edit"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "add-promotions",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../v2-routes/campaigns/add-campaign-promotions"
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
import { RouteFocusModal } from "../../../components/route-modal"
|
||||||
|
import { useCampaign } from "../../../hooks/api/campaigns"
|
||||||
|
import { AddCampaignPromotionsForm } from "./components"
|
||||||
|
|
||||||
|
export const AddCampaignPromotions = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
const { campaign, isError, error } = useCampaign(id!)
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
{campaign && <AddCampaignPromotionsForm campaign={campaign} />}
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { CampaignResponse, PromotionDTO } from "@medusajs/types"
|
||||||
|
import { Button, Checkbox, Hint, toast, Tooltip } from "@medusajs/ui"
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
|
import {
|
||||||
|
createColumnHelper,
|
||||||
|
OnChangeFn,
|
||||||
|
RowSelectionState,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import { useMemo, useState } from "react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import * as zod from "zod"
|
||||||
|
import {
|
||||||
|
RouteFocusModal,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../components/route-modal"
|
||||||
|
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 { usePromotionTableFilters } from "../../../../hooks/table/filters/use-promotion-table-filters"
|
||||||
|
import { usePromotionTableQuery } from "../../../../hooks/table/query-v2/use-promotion-table-query"
|
||||||
|
import { useDataTable } from "../../../../hooks/use-data-table"
|
||||||
|
|
||||||
|
type AddCampaignPromotionsFormProps = {
|
||||||
|
campaign: CampaignResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddCampaignPromotionsSchema = zod.object({
|
||||||
|
promotion_ids: zod.array(zod.string()).min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
const PAGE_SIZE = 50
|
||||||
|
|
||||||
|
export const AddCampaignPromotionsForm = ({
|
||||||
|
campaign,
|
||||||
|
}: AddCampaignPromotionsFormProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof AddCampaignPromotionsSchema>>({
|
||||||
|
defaultValues: { promotion_ids: [] },
|
||||||
|
resolver: zodResolver(AddCampaignPromotionsSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { setValue } = form
|
||||||
|
const { mutateAsync, isPending } = useAddOrRemoveCampaignPromotions(
|
||||||
|
campaign.id
|
||||||
|
)
|
||||||
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
|
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||||
|
const state = typeof fn === "function" ? fn(rowSelection) : fn
|
||||||
|
const ids = Object.keys(state)
|
||||||
|
|
||||||
|
setValue("promotion_ids", ids, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldTouch: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
setRowSelection(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { searchParams, raw } = usePromotionTableQuery({ pageSize: PAGE_SIZE })
|
||||||
|
const {
|
||||||
|
promotions,
|
||||||
|
count,
|
||||||
|
isPending: isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = usePromotions(
|
||||||
|
{ ...searchParams, campaign_id: "null" },
|
||||||
|
{ placeholderData: keepPreviousData }
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = useColumns()
|
||||||
|
const filters = usePromotionTableFilters()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
data: promotions ?? [],
|
||||||
|
columns,
|
||||||
|
enableRowSelection: (row) => {
|
||||||
|
return row.original.campaign_id !== campaign.id
|
||||||
|
},
|
||||||
|
enablePagination: true,
|
||||||
|
getRowId: (row) => row.id,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
count,
|
||||||
|
rowSelection: {
|
||||||
|
state: rowSelection,
|
||||||
|
updater,
|
||||||
|
},
|
||||||
|
meta: { campaignId: campaign.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (values) => {
|
||||||
|
await mutateAsync(
|
||||||
|
{ add: values.promotion_ids },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("general.success"), {
|
||||||
|
description: t("campaigns.promotions.toast.success", {
|
||||||
|
count: values.promotion_ids.length,
|
||||||
|
}),
|
||||||
|
dismissLabel: t("actions.close"),
|
||||||
|
})
|
||||||
|
handleSuccess()
|
||||||
|
},
|
||||||
|
onError: (error) =>
|
||||||
|
toast.error(t("general.error"), {
|
||||||
|
description: error.message,
|
||||||
|
dismissLabel: t("actions.close"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal.Form form={form}>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className="flex h-full flex-col overflow-hidden"
|
||||||
|
>
|
||||||
|
<RouteFocusModal.Header>
|
||||||
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
|
{form.formState.errors.promotion_ids && (
|
||||||
|
<Hint variant="error">
|
||||||
|
{form.formState.errors.promotion_ids.message}
|
||||||
|
</Hint>
|
||||||
|
)}
|
||||||
|
<RouteFocusModal.Close asChild>
|
||||||
|
<Button size="small" variant="secondary">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</RouteFocusModal.Close>
|
||||||
|
<Button size="small" type="submit" isLoading={isPending}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Header>
|
||||||
|
<RouteFocusModal.Body className="flex size-full flex-col overflow-y-auto">
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
count={count}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
isLoading={isLoading}
|
||||||
|
filters={filters}
|
||||||
|
orderBy={["title", "status", "created_at", "updated_at"]}
|
||||||
|
queryObject={raw}
|
||||||
|
layout="fill"
|
||||||
|
pagination
|
||||||
|
search
|
||||||
|
/>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
</form>
|
||||||
|
</RouteFocusModal.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<PromotionDTO>()
|
||||||
|
|
||||||
|
const useColumns = () => {
|
||||||
|
const base = usePromotionTableColumns()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsSomePageRowsSelected()
|
||||||
|
? "indeterminate"
|
||||||
|
: table.getIsAllPageRowsSelected()
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
table.toggleAllPageRowsSelected(!!value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cell: ({ row, table }) => {
|
||||||
|
const { campaignId } = table.options.meta as {
|
||||||
|
campaignId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdded = row.original.campaign_id === campaignId
|
||||||
|
const isSelected = row.getIsSelected() || isAdded
|
||||||
|
|
||||||
|
const Component = (
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
disabled={isAdded}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAdded) {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={t("campaigns.promotions.alreadyAdded")}
|
||||||
|
side="right"
|
||||||
|
>
|
||||||
|
{Component}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Component
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...base,
|
||||||
|
],
|
||||||
|
[t, base]
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./add-campaign-promotions-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AddCampaignPromotions as Component } from "./add-campaign-promotions"
|
||||||
+2
-2
@@ -3,6 +3,7 @@ import { JsonViewSection } from "../../../components/common/json-view-section"
|
|||||||
import { useCampaign } from "../../../hooks/api/campaigns"
|
import { useCampaign } from "../../../hooks/api/campaigns"
|
||||||
import { CampaignBudget } from "./components/campaign-budget"
|
import { CampaignBudget } from "./components/campaign-budget"
|
||||||
import { CampaignGeneralSection } from "./components/campaign-general-section"
|
import { CampaignGeneralSection } from "./components/campaign-general-section"
|
||||||
|
import { CampaignPromotionSection } from "./components/campaign-promotion-section"
|
||||||
import { CampaignSpend } from "./components/campaign-spend"
|
import { CampaignSpend } from "./components/campaign-spend"
|
||||||
import { campaignLoader } from "./loader"
|
import { campaignLoader } from "./loader"
|
||||||
|
|
||||||
@@ -31,8 +32,7 @@ export const CampaignDetail = () => {
|
|||||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||||
<div className="flex w-full flex-col gap-y-2">
|
<div className="flex w-full flex-col gap-y-2">
|
||||||
<CampaignGeneralSection campaign={campaign} />
|
<CampaignGeneralSection campaign={campaign} />
|
||||||
{/* TODO: enable this when missing APIs are available */}
|
<CampaignPromotionSection campaign={campaign} />
|
||||||
{/* <CampaignPromotionSection campaign={campaign} /> */}
|
|
||||||
<JsonViewSection data={campaign} />
|
<JsonViewSection data={campaign} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+9
-19
@@ -8,7 +8,7 @@ import { Link } from "react-router-dom"
|
|||||||
|
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||||
import { DataTable } from "../../../../../components/table/data-table"
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
import { useRemovePromotionsFromCampaign } from "../../../../../hooks/api/campaigns"
|
import { useAddOrRemoveCampaignPromotions } from "../../../../../hooks/api/campaigns"
|
||||||
import { usePromotions } from "../../../../../hooks/api/promotions"
|
import { usePromotions } from "../../../../../hooks/api/promotions"
|
||||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns-v2/use-promotion-table-columns"
|
import { usePromotionTableColumns } from "../../../../../hooks/table/columns-v2/use-promotion-table-columns"
|
||||||
import { usePromotionTableFilters } from "../../../../../hooks/table/filters/use-promotion-table-filters"
|
import { usePromotionTableFilters } from "../../../../../hooks/table/filters/use-promotion-table-filters"
|
||||||
@@ -27,16 +27,14 @@ export const CampaignPromotionSection = ({
|
|||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
const columns = useColumns()
|
||||||
|
const filters = usePromotionTableFilters()
|
||||||
const { searchParams, raw } = usePromotionTableQuery({ pageSize: PAGE_SIZE })
|
const { searchParams, raw } = usePromotionTableQuery({ pageSize: PAGE_SIZE })
|
||||||
const { promotions, count, isLoading, isError, error } = usePromotions({
|
const { promotions, count, isLoading, isError, error } = usePromotions({
|
||||||
...searchParams,
|
...searchParams,
|
||||||
// TODO: This should be scoped by currency_code
|
campaign_id: campaign.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns = useColumns()
|
|
||||||
const filters = usePromotionTableFilters()
|
|
||||||
|
|
||||||
const { table } = useDataTable({
|
const { table } = useDataTable({
|
||||||
data: promotions ?? [],
|
data: promotions ?? [],
|
||||||
columns,
|
columns,
|
||||||
@@ -49,16 +47,14 @@ export const CampaignPromotionSection = ({
|
|||||||
state: rowSelection,
|
state: rowSelection,
|
||||||
updater: setRowSelection,
|
updater: setRowSelection,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: { campaignId: campaign.id },
|
||||||
campaignId: campaign.id,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mutateAsync } = useRemovePromotionsFromCampaign(campaign.id)
|
const { mutateAsync } = useAddOrRemoveCampaignPromotions(campaign.id)
|
||||||
|
|
||||||
const handleRemove = async () => {
|
const handleRemove = async () => {
|
||||||
const keys = Object.keys(rowSelection)
|
const keys = Object.keys(rowSelection)
|
||||||
@@ -77,14 +73,8 @@ export const CampaignPromotionSection = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{ remove: keys },
|
||||||
remove: keys,
|
{ onSuccess: () => setRowSelection({}) }
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
setRowSelection({})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +128,7 @@ const PromotionActions = ({
|
|||||||
campaignId: string
|
campaignId: string
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { mutateAsync } = useRemovePromotionsFromCampaign(campaignId)
|
const { mutateAsync } = useAddOrRemoveCampaignPromotions(campaignId)
|
||||||
|
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPromotionModuleService, LinkWorkflowInput } from "@medusajs/types"
|
||||||
|
import { StepResponse, WorkflowData, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const addOrRemoveCampaignPromotionsStepId =
|
||||||
|
"add-or-remove-campaign-promotions"
|
||||||
|
export const addOrRemoveCampaignPromotionsStep = createStep(
|
||||||
|
addOrRemoveCampaignPromotionsStepId,
|
||||||
|
async (input: WorkflowData<LinkWorkflowInput>, { container }) => {
|
||||||
|
const {
|
||||||
|
id: campaignId,
|
||||||
|
add: promotionIdsToAdd = [],
|
||||||
|
remove: promotionIdsToRemove = [],
|
||||||
|
} = input
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToAdd.length) {
|
||||||
|
await promotionModule.addPromotionsToCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToAdd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promotionIdsToRemove.length) {
|
||||||
|
await promotionModule.removePromotionsFromCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(null, input)
|
||||||
|
},
|
||||||
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
id: campaignId,
|
||||||
|
add: promotionIdsToRemove = [],
|
||||||
|
remove: promotionIdsToAdd = [],
|
||||||
|
} = data
|
||||||
|
|
||||||
|
const promotionModule = container.resolve<IPromotionModuleService>(
|
||||||
|
ModuleRegistrationName.PROMOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if (promotionIdsToAdd.length) {
|
||||||
|
promotionModule.addPromotionsToCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToAdd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promotionIdsToRemove.length) {
|
||||||
|
promotionModule.removePromotionsFromCampaign({
|
||||||
|
id: campaignId,
|
||||||
|
promotion_ids: promotionIdsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -51,6 +51,11 @@ export interface PromotionDTO {
|
|||||||
*/
|
*/
|
||||||
rules?: PromotionRuleDTO[]
|
rules?: PromotionRuleDTO[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associated campaign.
|
||||||
|
*/
|
||||||
|
campaign_id?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The associated campaign.
|
* The associated campaign.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
} from "../../../../../types/routing"
|
} from "../../../../../types/routing"
|
||||||
|
|
||||||
import { addOrRemoveCampaignPromotionsWorkflow } from "@medusajs/core-flows"
|
import { addOrRemoveCampaignPromotionsWorkflow } from "@medusajs/core-flows"
|
||||||
import { LinkMethodRequest } from "@medusajs/types/src"
|
import { LinkMethodRequest } from "@medusajs/types"
|
||||||
import { refetchCampaign } from "../../helpers"
|
import { refetchCampaign } from "../../helpers"
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
|
|||||||
Reference in new issue
Block a user