feat(medusa-react,medusa,types,dashboard): added empty state + table for promotions list page (#6827)
what: - adds empty state for promotions list page - lists all promotions with pagination <img width="1663" alt="Screenshot 2024-03-26 at 14 19 27" src="https://github.com/medusajs/medusa/assets/5105988/ed0d5c65-d003-40f5-b899-540970d892f5"> <img width="1664" alt="Screenshot 2024-03-27 at 20 46 17" src="https://github.com/medusajs/medusa/assets/5105988/4aa40f09-fe3f-4f34-af7a-f5c183254c76">
This commit is contained in:
7
.changeset/angry-fans-collect.md
Normal file
7
.changeset/angry-fans-collect.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"medusa-react": patch
|
||||
"@medusajs/medusa": patch
|
||||
"@medusajs/types": patch
|
||||
---
|
||||
|
||||
feat(medusa-react,medusa,types,dashboard): added empty state for promotions list page
|
||||
@@ -46,6 +46,11 @@ export const injectionZones = [
|
||||
"discount.details.after",
|
||||
"discount.list.before",
|
||||
"discount.list.after",
|
||||
// Promotion injection zones
|
||||
"promotion.details.before",
|
||||
"promotion.details.after",
|
||||
"promotion.list.before",
|
||||
"promotion.list.after",
|
||||
// Gift card injection zones
|
||||
"gift_card.details.before",
|
||||
"gift_card.details.after",
|
||||
|
||||
@@ -131,6 +131,15 @@
|
||||
},
|
||||
"required": ["domain"]
|
||||
},
|
||||
"promotions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["domain"]
|
||||
},
|
||||
"giftCards": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -176,6 +185,23 @@
|
||||
},
|
||||
"required": ["domain", "role", "roles"]
|
||||
},
|
||||
"statuses": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scheduled": {
|
||||
"type": "string"
|
||||
},
|
||||
"expired": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "string"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -502,6 +502,16 @@
|
||||
"disabled": "Disabled"
|
||||
}
|
||||
},
|
||||
"promotions": {
|
||||
"domain": "Promotions",
|
||||
"fields": {
|
||||
"method": "Method",
|
||||
"campaign": "Campaign"
|
||||
},
|
||||
"deleteWarning": "You are about to delete the promotion {{code}}. This action cannot be undone.",
|
||||
"createPromotionTitle": "Create Promotion",
|
||||
"type": "Promotion type"
|
||||
},
|
||||
"pricing": {
|
||||
"domain": "Pricing",
|
||||
"deletePriceListWarning": "You are about to delete the price list {{name}}. This action cannot be undone.",
|
||||
@@ -801,6 +811,12 @@
|
||||
"serverError": "Server error - Try again later.",
|
||||
"invalidCredentials": "Wrong email or password"
|
||||
},
|
||||
"statuses": {
|
||||
"scheduled": "Scheduled",
|
||||
"expired": "Expired",
|
||||
"active": "Active",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"fields": {
|
||||
"amount": "Amount",
|
||||
"name": "Name",
|
||||
|
||||
@@ -135,8 +135,8 @@ const useCoreRoutes = (): Omit<NavItemProps, "pathname">[] => {
|
||||
},
|
||||
{
|
||||
icon: <ReceiptPercent />,
|
||||
label: t("discounts.domain"),
|
||||
to: "/discounts",
|
||||
label: t("promotions.domain"),
|
||||
to: "/promotions",
|
||||
},
|
||||
{
|
||||
icon: <CurrencyDollar />,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
type CellProps = {
|
||||
code: string
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
text: string
|
||||
}
|
||||
|
||||
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">
|
||||
{code}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CodeHeader = ({ text }: HeaderProps) => {
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center ">
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./code-cell"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./text-cell"
|
||||
@@ -0,0 +1,23 @@
|
||||
type CellProps = {
|
||||
text?: string | number
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export const TextCell = ({ text }: CellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
<span className="truncate">{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const TextHeader = ({ text }: HeaderProps) => {
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center">
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./status-cell"
|
||||
@@ -0,0 +1,28 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
getPromotionStatus,
|
||||
PromotionStatus,
|
||||
} from "../../../../../lib/promotions"
|
||||
import { StatusCell as StatusCell_ } from "../../common/status-cell"
|
||||
|
||||
type PromotionCellProps = {
|
||||
promotion: PromotionDTO
|
||||
}
|
||||
type StatusColors = "grey" | "orange" | "green" | "red"
|
||||
type StatusMap = Record<string, [StatusColors, string]>
|
||||
|
||||
export const StatusCell = ({ promotion }: PromotionCellProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const statusMap: StatusMap = {
|
||||
[PromotionStatus.DISABLED]: ["grey", t("statuses.disabled")],
|
||||
[PromotionStatus.ACTIVE]: ["green", t("statuses.active")],
|
||||
[PromotionStatus.SCHEDULED]: ["orange", t("statuses.scheduled")],
|
||||
[PromotionStatus.EXPIRED]: ["red", t("statuses.expired")],
|
||||
}
|
||||
|
||||
const [color, text] = statusMap[getPromotionStatus(promotion)]
|
||||
|
||||
return <StatusCell_ color={color}>{text}</StatusCell_>
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import {
|
||||
CodeCell,
|
||||
CodeHeader,
|
||||
} from "../../../components/table/table-cells/common/code-cell"
|
||||
import {
|
||||
TextCell,
|
||||
TextHeader,
|
||||
} from "../../../components/table/table-cells/common/text-cell"
|
||||
import { StatusCell } from "../../../components/table/table-cells/promotion/status-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<PromotionDTO>()
|
||||
|
||||
export const usePromotionTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "code",
|
||||
header: () => <CodeHeader text={t("fields.code")} />,
|
||||
cell: ({ row }) => <CodeCell code={row.original.code!} />,
|
||||
}),
|
||||
|
||||
columnHelper.display({
|
||||
id: "campaign",
|
||||
header: () => <TextHeader text={t("promotions.fields.campaign")} />,
|
||||
cell: ({ row }) => <TextCell text={row.original.campaign?.name} />,
|
||||
}),
|
||||
|
||||
columnHelper.display({
|
||||
id: "method",
|
||||
header: () => <TextHeader text={t("promotions.fields.method")} />,
|
||||
cell: ({ row }) => {
|
||||
const text = row.original.is_automatic
|
||||
? "Automatic"
|
||||
: "Promotion Code"
|
||||
|
||||
return <TextCell text={text} />
|
||||
},
|
||||
}),
|
||||
|
||||
columnHelper.display({
|
||||
id: "status",
|
||||
header: () => <TextHeader text={t("fields.status")} />,
|
||||
cell: ({ row }) => <StatusCell promotion={row.original} />,
|
||||
}),
|
||||
],
|
||||
[]
|
||||
) as ColumnDef<PromotionDTO>[]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { AdminGetPromotionsParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UsePromotionTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const usePromotionTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UsePromotionTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
["offset", "q", "created_at", "updated_at"],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, q, created_at, updated_at } = queryObject
|
||||
|
||||
const searchParams: AdminGetPromotionsParams = {
|
||||
limit: pageSize,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./auth"
|
||||
export * from "./promotion"
|
||||
export * from "./store"
|
||||
|
||||
23
packages/admin-next/dashboard/src/lib/api-v2/promotion.ts
Normal file
23
packages/admin-next/dashboard/src/lib/api-v2/promotion.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
AdminGetPromotionsParams,
|
||||
AdminPromotionsListRes,
|
||||
} from "@medusajs/medusa"
|
||||
import { queryKeysFactory, useAdminCustomQuery } from "medusa-react"
|
||||
|
||||
const QUERY_KEY = "admin_promotions"
|
||||
export const adminPromotionKeys = queryKeysFactory<
|
||||
typeof QUERY_KEY,
|
||||
AdminGetPromotionsParams
|
||||
>(QUERY_KEY)
|
||||
|
||||
export const useV2Promotions = (
|
||||
query?: AdminGetPromotionsParams,
|
||||
options?: object
|
||||
) => {
|
||||
const { data, ...rest } = useAdminCustomQuery<
|
||||
AdminGetPromotionsParams,
|
||||
AdminPromotionsListRes
|
||||
>("/admin/promotions", adminPromotionKeys.list(query), query, options)
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
31
packages/admin-next/dashboard/src/lib/promotions.ts
Normal file
31
packages/admin-next/dashboard/src/lib/promotions.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
|
||||
export enum PromotionStatus {
|
||||
SCHEDULED = "SCHEDULED",
|
||||
EXPIRED = "EXPIRED",
|
||||
ACTIVE = "ACTIVE",
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export const getPromotionStatus = (promotion: PromotionDTO) => {
|
||||
const date = new Date()
|
||||
const campaign = promotion.campaign
|
||||
|
||||
if (!campaign) {
|
||||
return PromotionStatus.ACTIVE
|
||||
}
|
||||
|
||||
if (new Date(campaign.starts_at!) > date) {
|
||||
return PromotionStatus.SCHEDULED
|
||||
}
|
||||
|
||||
const campaignBudget = campaign.budget
|
||||
const overBudget =
|
||||
campaignBudget && campaignBudget.used! > campaignBudget.limit!
|
||||
|
||||
if ((campaign.ends_at && new Date(campaign.ends_at) < date) || overBudget) {
|
||||
return PromotionStatus.EXPIRED
|
||||
}
|
||||
|
||||
return PromotionStatus.ACTIVE
|
||||
}
|
||||
@@ -77,6 +77,18 @@ export const v2Routes: RouteObject[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/promotions",
|
||||
handle: {
|
||||
crumb: () => "Promotions",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () => import("../../v2-routes/promotions/promotion-list"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./promotion-list-table"
|
||||
@@ -0,0 +1,147 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useAdminDeleteDiscount } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, Outlet, useLoaderData } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
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 { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useV2Promotions } from "../../../../../lib/api-v2"
|
||||
import { promotionsLoader } from "../../loader"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export const PromotionListTable = () => {
|
||||
const { t } = useTranslation()
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
ReturnType<ReturnType<typeof promotionsLoader>>
|
||||
>
|
||||
|
||||
const { searchParams, raw } = usePromotionTableQuery({ pageSize: PAGE_SIZE })
|
||||
const { promotions, count, isLoading, isError, error } = useV2Promotions(
|
||||
{ ...searchParams },
|
||||
{
|
||||
initialData,
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
|
||||
const filters = usePromotionTableFilters()
|
||||
const columns = useColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: (promotions ?? []) as PromotionDTO[],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
pageSize: PAGE_SIZE,
|
||||
getRowId: (row) => row.id,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("promotions.domain")}</Heading>
|
||||
|
||||
<Button size="small" variant="secondary" asChild>
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
filters={filters}
|
||||
search
|
||||
pagination
|
||||
isLoading={isLoading}
|
||||
queryObject={raw}
|
||||
navigateTo={(row) => `${row.original.id}`}
|
||||
orderBy={["created_at", "updated_at"]}
|
||||
/>
|
||||
<Outlet />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const PromotionActions = ({ promotion }: { promotion: PromotionDTO }) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
// TODO: change to promotions delete endpoint
|
||||
const { mutateAsync } = useAdminDeleteDiscount(promotion.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("promotions.deleteWarning", {
|
||||
code: promotion.code,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: handle error scenario here
|
||||
await mutateAsync()
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("actions.edit"),
|
||||
to: `/promotions/${promotion.id}/edit`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Trash />,
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<PromotionDTO>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = usePromotionTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return <PromotionActions promotion={row.original} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { promotionsLoader } from "./loader"
|
||||
export { PromotionsList as Component } from "./promotions-list.tsx"
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AdminPromotionsListRes } from "@medusajs/medusa"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
import { QueryClient } from "@tanstack/react-query"
|
||||
import { adminPromotionKeys, useV2Promotions } from "../../../lib/api-v2"
|
||||
import { queryClient } from "../../../lib/medusa"
|
||||
|
||||
const promotionsListQuery = () => ({
|
||||
queryKey: adminPromotionKeys.list(),
|
||||
queryFn: async () => useV2Promotions({ limit: 20, offset: 0 }),
|
||||
})
|
||||
|
||||
export const promotionsLoader = (client: QueryClient) => {
|
||||
return async () => {
|
||||
const query = promotionsListQuery()
|
||||
|
||||
return (
|
||||
queryClient.getQueryData<Response<AdminPromotionsListRes>>(
|
||||
query.queryKey
|
||||
) ?? (await client.fetchQuery(query))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import after from "medusa-admin:widgets/promotion/list/after"
|
||||
import before from "medusa-admin:widgets/promotion/list/before"
|
||||
|
||||
import { PromotionListTable } from "./components/promotion-list-table"
|
||||
|
||||
export const PromotionsList = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => (
|
||||
<w.Component key={i} />
|
||||
))}
|
||||
|
||||
<PromotionListTable />
|
||||
|
||||
{after.widgets.map((w, i) => (
|
||||
<w.Component key={i} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./store/"
|
||||
export * from "./admin/"
|
||||
export * from "./admin"
|
||||
export * from "./store"
|
||||
export * from "./utils"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./contexts"
|
||||
export * from "./hooks/"
|
||||
export * from "./helpers"
|
||||
export * from "./hooks"
|
||||
export * from "./types"
|
||||
|
||||
1
packages/medusa/src/api-v2/admin/index.ts
Normal file
1
packages/medusa/src/api-v2/admin/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./promotions"
|
||||
2
packages/medusa/src/api-v2/admin/promotions/index.ts
Normal file
2
packages/medusa/src/api-v2/admin/promotions/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./types"
|
||||
export * from "./validators"
|
||||
@@ -1,12 +1,11 @@
|
||||
import { createPromotionsWorkflow } from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { CreatePromotionDTO, IPromotionModuleService } from "@medusajs/types"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../types/routing"
|
||||
|
||||
import { createPromotionsWorkflow } from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
|
||||
5
packages/medusa/src/api-v2/admin/promotions/types.ts
Normal file
5
packages/medusa/src/api-v2/admin/promotions/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PaginatedResponse, PromotionDTO } from "@medusajs/types"
|
||||
|
||||
export type AdminPromotionsListRes = PaginatedResponse<{
|
||||
promotions: PromotionDTO[]
|
||||
}>
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import {
|
||||
DateComparisonOperator,
|
||||
FindParams,
|
||||
extendedFindParamsMixin,
|
||||
} from "../../../types/common"
|
||||
import { XorConstraint } from "../../../types/validators/xor"
|
||||
import { AdminPostCampaignsReq } from "../campaigns/validators"
|
||||
|
||||
@@ -33,6 +37,29 @@ export class AdminGetPromotionsParams extends extendedFindParamsMixin({
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
code?: string
|
||||
|
||||
/**
|
||||
* Search terms to search promotions' code fields.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
q?: string
|
||||
|
||||
/**
|
||||
* Date filters to apply on the promotions' `created_at` date.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
created_at?: DateComparisonOperator
|
||||
|
||||
/**
|
||||
* Date filters to apply on the promotions' `updated_at` date.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
updated_at?: DateComparisonOperator
|
||||
}
|
||||
|
||||
export class AdminPostPromotionsReq {
|
||||
|
||||
1
packages/medusa/src/api-v2/index.ts
Normal file
1
packages/medusa/src/api-v2/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./admin"
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./api"
|
||||
export * from "./api-v2"
|
||||
export * from "./api/middlewares"
|
||||
export * from "./interfaces"
|
||||
export * from "./joiner-config"
|
||||
|
||||
@@ -230,7 +230,7 @@ export type RequestQueryFields = {
|
||||
*
|
||||
* Fields included in the response if it's paginated.
|
||||
*/
|
||||
export type PaginatedResponse = {
|
||||
export type PaginatedResponse<T = unknown> = {
|
||||
/**
|
||||
* The limit applied on the retrieved items.
|
||||
*/
|
||||
@@ -245,7 +245,7 @@ export type PaginatedResponse = {
|
||||
* The total count of items.
|
||||
*/
|
||||
count: number
|
||||
}
|
||||
} & T
|
||||
|
||||
/**
|
||||
* The fields returned in the response of a DELETE request.
|
||||
|
||||
Reference in New Issue
Block a user