mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat(coupons): раздел оптовых купонов + публичная страница активации
UI для купонов из BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot#3042 (API: PR #3044 бота): - /admin/coupons — список партий с погашениями и offset-пагинацией; - /admin/coupons/create — мастер партии (тариф, дни, количество, учётная оптовая цена, срок), после создания — экран со ссылками (копирование всех, скачивание .txt); - /admin/coupons/:id — карточка со статистикой, экспорт актуальных ссылок, отзыв непогашенных за PermissionGate coupons:edit с подтверждением и тостом; - /coupon/:token — публичная страница купона: тариф/срок, кнопка активации в боте, для залогиненных — активация прямо в кабинете с маппингом структурированных кодов ошибок {code, message}; - пункт меню в разделе тарифов (гейт coupons:read), лениво загружаемые роуты, локали ru/en (zh/fa падают на ru-фолбэк).
This commit is contained in:
112
src/api/coupons.ts
Normal file
112
src/api/coupons.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import apiClient from './client';
|
||||
|
||||
// ============== Types ==============
|
||||
|
||||
export interface CouponBatch {
|
||||
id: number;
|
||||
name: string;
|
||||
tariff_id: number | null;
|
||||
tariff_name: string | null;
|
||||
period_days: number;
|
||||
coupons_total: number;
|
||||
wholesale_price_kopeks: number;
|
||||
valid_until: string | null;
|
||||
is_revoked: boolean;
|
||||
created_at: string;
|
||||
active_count: number;
|
||||
redeemed_count: number;
|
||||
revoked_count: number;
|
||||
}
|
||||
|
||||
export interface CouponBatchListResponse {
|
||||
items: CouponBatch[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface CouponBatchCreateRequest {
|
||||
name: string;
|
||||
tariff_id: number;
|
||||
period_days: number;
|
||||
coupons_count: number;
|
||||
wholesale_price_kopeks?: number;
|
||||
valid_days?: number;
|
||||
}
|
||||
|
||||
export interface CouponBatchCreated extends CouponBatch {
|
||||
links: string[];
|
||||
tokens: string[];
|
||||
}
|
||||
|
||||
export interface CouponBatchLinks {
|
||||
batch_id: number;
|
||||
count: number;
|
||||
links: string[];
|
||||
tokens: string[];
|
||||
}
|
||||
|
||||
export interface CouponBatchRevokeResponse {
|
||||
revoked_count: number;
|
||||
batch: CouponBatch;
|
||||
}
|
||||
|
||||
export interface CouponRedeemResponse {
|
||||
success: boolean;
|
||||
tariff_name: string;
|
||||
period_days: number;
|
||||
renewed: boolean;
|
||||
end_date: string | null;
|
||||
}
|
||||
|
||||
export interface CouponStatus {
|
||||
tariff_name: string;
|
||||
period_days: number;
|
||||
valid_until: string | null;
|
||||
bot_link: string | null;
|
||||
}
|
||||
|
||||
// ============== API ==============
|
||||
|
||||
export const couponsApi = {
|
||||
// Admin: batches
|
||||
getBatches: async (params?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<CouponBatchListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/coupons', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getBatch: async (id: number): Promise<CouponBatch> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/coupons/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
createBatch: async (data: CouponBatchCreateRequest): Promise<CouponBatchCreated> => {
|
||||
const response = await apiClient.post('/cabinet/admin/coupons', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getBatchLinks: async (id: number): Promise<CouponBatchLinks> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/coupons/${id}/links`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
revokeBatch: async (id: number): Promise<CouponBatchRevokeResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/coupons/${id}/revoke`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// User: redeem a coupon for the current cabinet user
|
||||
redeemCoupon: async (token: string): Promise<CouponRedeemResponse> => {
|
||||
const response = await apiClient.post('/cabinet/coupon/redeem', { token });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Public: coupon info by token (no auth)
|
||||
getCouponStatus: async (token: string): Promise<CouponStatus> => {
|
||||
const response = await apiClient.get(`/cabinet/coupon/${token}/status`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user