Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
import type {
|
|
BulkCreateDiscountCodesResponseDto,
|
|
CreateDiscountCodesDto,
|
|
DiscountCodeResponseDto,
|
|
DiscountPreviewResponseDto,
|
|
DiscountRedemptionResponseDto,
|
|
DiscountReportSummaryDto,
|
|
} from '@/api/generated/models'
|
|
import { getDiscountCodes } from '@/api/generated/discount-codes/discount-codes'
|
|
import { getFinancial } from '@/api/generated/financial/financial'
|
|
import axiosInstance from '@/config/axios'
|
|
import { parseRemittanceList, unwrapApiPayload } from '@/helpers/listResponse'
|
|
import { texts } from '@/texts'
|
|
import {
|
|
createServiceError,
|
|
errorResult,
|
|
handleServiceError,
|
|
type ServiceCallOptions,
|
|
type ServiceResult,
|
|
shouldBubbleErrorToParent,
|
|
successResult,
|
|
} from '@/services/errorHandler'
|
|
|
|
export type DiscountCode = DiscountCodeResponseDto
|
|
export type DiscountRedemption = DiscountRedemptionResponseDto
|
|
export type DiscountReport = DiscountReportSummaryDto
|
|
export type DiscountBulkResult = BulkCreateDiscountCodesResponseDto
|
|
export type DiscountPreview = DiscountPreviewResponseDto
|
|
export type DiscountType = CreateDiscountCodesDto['type']
|
|
export type DiscountBearer = NonNullable<CreateDiscountCodesDto['bearer']>
|
|
|
|
export interface DiscountCodesPage {
|
|
items: DiscountCode[]
|
|
totalItemsCount: number
|
|
totalPages: number
|
|
}
|
|
|
|
export interface DiscountRedemptionsPage {
|
|
items: DiscountRedemption[]
|
|
totalItemsCount: number
|
|
totalPages: number
|
|
}
|
|
|
|
export interface DiscountManagementBootstrap {
|
|
codes: { items: DiscountCode[] }
|
|
report: DiscountReport
|
|
redemptions: { items: DiscountRedemption[] }
|
|
}
|
|
|
|
export interface BulkCreateDiscountInput {
|
|
type: DiscountType
|
|
value: number
|
|
quantity: number
|
|
maxUses?: number
|
|
maxUsesPerUser?: number
|
|
validFrom?: string
|
|
validUntil?: string
|
|
bearer?: DiscountBearer
|
|
}
|
|
|
|
const discountApi = getDiscountCodes()
|
|
const financialApi = getFinancial()
|
|
|
|
const wrap = async <T>(run: () => Promise<T>, options?: ServiceCallOptions): Promise<ServiceResult<T>> => {
|
|
try {
|
|
return successResult(await run())
|
|
} catch (error) {
|
|
const normalizedError = handleServiceError(error, options)
|
|
|
|
if (shouldBubbleErrorToParent(options)) {
|
|
throw normalizedError
|
|
}
|
|
|
|
return errorResult(normalizedError)
|
|
}
|
|
}
|
|
|
|
export const GET_DISCOUNT_MANAGEMENT_BOOTSTRAP = (
|
|
eventId: string,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<DiscountManagementBootstrap>> =>
|
|
wrap(async () => {
|
|
const response = await axiosInstance.get(`events/${eventId}/discount-codes/management-bootstrap`)
|
|
|
|
return unwrapApiPayload<DiscountManagementBootstrap>(response.data)
|
|
}, options)
|
|
|
|
export const LIST_DISCOUNT_CODES = (
|
|
eventId: string,
|
|
params: { page?: number; pageSize?: number } = {},
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<DiscountCodesPage>> =>
|
|
wrap(async () => {
|
|
const res = await discountApi.discountCodesControllerList(eventId, {
|
|
page: params.page ?? 1,
|
|
pageSize: params.pageSize ?? 50,
|
|
sort: '-createdAt',
|
|
})
|
|
const parsed = parseRemittanceList<DiscountCode>(res.data)
|
|
|
|
return {
|
|
items: parsed.items,
|
|
totalItemsCount: parsed.pagination.totalItemsCount,
|
|
totalPages: parsed.pagination.totalPages ?? 0,
|
|
}
|
|
}, options)
|
|
|
|
export const LIST_DISCOUNT_REDEMPTIONS = (
|
|
eventId: string,
|
|
params: { page?: number; pageSize?: number } = {},
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<DiscountRedemptionsPage>> =>
|
|
wrap(async () => {
|
|
const res = await discountApi.discountCodesControllerRedemptions(eventId, {
|
|
page: params.page ?? 1,
|
|
pageSize: params.pageSize ?? 50,
|
|
sort: '-createdAt',
|
|
})
|
|
const parsed = parseRemittanceList<DiscountRedemption>(res.data)
|
|
|
|
return {
|
|
items: parsed.items,
|
|
totalItemsCount: parsed.pagination.totalItemsCount,
|
|
totalPages: parsed.pagination.totalPages ?? 0,
|
|
}
|
|
}, options)
|
|
|
|
export const GET_DISCOUNT_REPORT = (eventId: string, options?: ServiceCallOptions): Promise<ServiceResult<DiscountReport>> =>
|
|
wrap(async () => {
|
|
const res = await discountApi.discountCodesControllerReport(eventId)
|
|
|
|
return unwrapApiPayload<DiscountReport>(res.data)
|
|
}, options)
|
|
|
|
export const BULK_CREATE_DISCOUNT_CODES = (
|
|
eventId: string,
|
|
input: BulkCreateDiscountInput,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<DiscountBulkResult>> =>
|
|
wrap(async () => {
|
|
const res = await discountApi.discountCodesControllerCreate(eventId, {
|
|
type: input.type,
|
|
value: input.value,
|
|
quantity: input.quantity,
|
|
maxUses: input.maxUses,
|
|
maxUsesPerUser: input.maxUsesPerUser,
|
|
validFrom: input.validFrom,
|
|
validUntil: input.validUntil,
|
|
bearer: input.bearer,
|
|
})
|
|
const data = unwrapApiPayload<DiscountBulkResult>(res.data)
|
|
|
|
if (!data?.codes) throw createServiceError(texts.events.discountCreateFailed)
|
|
|
|
return data
|
|
}, options)
|
|
|
|
export const SET_DISCOUNT_CODE_ACTIVE = (
|
|
codeId: string,
|
|
isActive: boolean,
|
|
options?: ServiceCallOptions
|
|
): Promise<ServiceResult<DiscountCode>> =>
|
|
wrap(async () => {
|
|
const res = await discountApi.discountCodesControllerUpdate(codeId, { isActive })
|
|
const data = unwrapApiPayload<DiscountCode>(res.data)
|
|
|
|
if (!data?.id) throw createServiceError(texts.events.discountUpdateFailed)
|
|
|
|
return data
|
|
}, options)
|
|
|
|
export const DELETE_DISCOUNT_CODE = (codeId: string, options?: ServiceCallOptions): Promise<ServiceResult<void>> =>
|
|
wrap(async () => {
|
|
await discountApi.discountCodesControllerRemove(codeId)
|
|
}, options)
|
|
|
|
export const PREVIEW_DISCOUNT = (bookingId: string, code: string, options?: ServiceCallOptions): Promise<ServiceResult<DiscountPreview>> =>
|
|
wrap(async () => {
|
|
const res = await financialApi.paymentsControllerPreviewDiscount(bookingId, { code: code.trim() })
|
|
const data = unwrapApiPayload<DiscountPreview>(res.data)
|
|
|
|
if (!data?.code) throw createServiceError(texts.events.discountCodeInvalid)
|
|
|
|
return data
|
|
}, options)
|