mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat: add partner management and withdrawal admin pages
- Admin partners page: list partners, view applications, approve/reject - Admin partner detail: stats, commission management, campaign assignment - Admin withdrawals page: list with risk scoring, status filters including cancelled - Admin withdrawal detail: fraud analysis, approve/reject/complete actions - Partner API client with full TypeScript interfaces - Withdrawal API client with admin and user endpoints - Referral page: partner application form, withdrawal balance and history - Navigation: partners and withdrawals in admin panel marketing group - i18n: full translations for ru, en, zh, fa locales - Neutral fallbacks for unknown status badges
This commit is contained in:
181
src/api/partners.ts
Normal file
181
src/api/partners.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import apiClient from './client';
|
||||
|
||||
// ==================== User-facing types ====================
|
||||
|
||||
export interface PartnerApplicationInfo {
|
||||
id: number;
|
||||
status: string;
|
||||
company_name: string | null;
|
||||
website_url: string | null;
|
||||
telegram_channel: string | null;
|
||||
description: string | null;
|
||||
expected_monthly_referrals: number | null;
|
||||
admin_comment: string | null;
|
||||
approved_commission_percent: number | null;
|
||||
created_at: string;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export interface PartnerStatusResponse {
|
||||
partner_status: string;
|
||||
commission_percent: number | null;
|
||||
latest_application: PartnerApplicationInfo | null;
|
||||
}
|
||||
|
||||
export interface PartnerApplicationRequest {
|
||||
company_name?: string;
|
||||
website_url?: string;
|
||||
telegram_channel?: string;
|
||||
description?: string;
|
||||
expected_monthly_referrals?: number;
|
||||
}
|
||||
|
||||
// ==================== Admin-facing types ====================
|
||||
|
||||
export interface AdminPartnerApplicationItem {
|
||||
id: number;
|
||||
user_id: number;
|
||||
username: string | null;
|
||||
first_name: string | null;
|
||||
telegram_id: number | null;
|
||||
company_name: string | null;
|
||||
website_url: string | null;
|
||||
telegram_channel: string | null;
|
||||
description: string | null;
|
||||
expected_monthly_referrals: number | null;
|
||||
status: string;
|
||||
admin_comment: string | null;
|
||||
approved_commission_percent: number | null;
|
||||
created_at: string;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export interface AdminPartnerApplicationsResponse {
|
||||
items: AdminPartnerApplicationItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface AdminPartnerItem {
|
||||
user_id: number;
|
||||
username: string | null;
|
||||
first_name: string | null;
|
||||
telegram_id: number | null;
|
||||
commission_percent: number | null;
|
||||
total_referrals: number;
|
||||
total_earnings_kopeks: number;
|
||||
balance_kopeks: number;
|
||||
partner_status: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface AdminPartnerListResponse {
|
||||
items: AdminPartnerItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface AdminPartnerDetailResponse {
|
||||
user_id: number;
|
||||
username: string | null;
|
||||
first_name: string | null;
|
||||
telegram_id: number | null;
|
||||
commission_percent: number | null;
|
||||
partner_status: string;
|
||||
balance_kopeks: number;
|
||||
total_referrals: number;
|
||||
paid_referrals: number;
|
||||
active_referrals: number;
|
||||
earnings_all_time: number;
|
||||
earnings_today: number;
|
||||
earnings_week: number;
|
||||
earnings_month: number;
|
||||
conversion_to_paid: number;
|
||||
campaigns: { id: number; name: string; start_parameter: string; is_active: boolean }[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface PartnerStats {
|
||||
total_partners: number;
|
||||
pending_applications: number;
|
||||
total_referrals: number;
|
||||
total_earnings_kopeks: number;
|
||||
}
|
||||
|
||||
export const partnerApi = {
|
||||
// User endpoints
|
||||
getStatus: async (): Promise<PartnerStatusResponse> => {
|
||||
const response = await apiClient.get<PartnerStatusResponse>('/cabinet/referral/partner/status');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
apply: async (data: PartnerApplicationRequest): Promise<PartnerApplicationInfo> => {
|
||||
const response = await apiClient.post<PartnerApplicationInfo>(
|
||||
'/cabinet/referral/partner/apply',
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Admin endpoints
|
||||
getStats: async (): Promise<PartnerStats> => {
|
||||
const response = await apiClient.get<PartnerStats>('/cabinet/admin/partners/stats');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getApplications: async (params?: {
|
||||
status?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}): Promise<AdminPartnerApplicationsResponse> => {
|
||||
const response = await apiClient.get<AdminPartnerApplicationsResponse>(
|
||||
'/cabinet/admin/partners/applications',
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
approveApplication: async (
|
||||
applicationId: number,
|
||||
data: { commission_percent: number; comment?: string },
|
||||
): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/partners/applications/${applicationId}/approve`, data);
|
||||
},
|
||||
|
||||
rejectApplication: async (applicationId: number, data: { comment?: string }): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/partners/applications/${applicationId}/reject`, data);
|
||||
},
|
||||
|
||||
getPartners: async (params?: {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}): Promise<AdminPartnerListResponse> => {
|
||||
const response = await apiClient.get<AdminPartnerListResponse>('/cabinet/admin/partners', {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getPartnerDetail: async (userId: number): Promise<AdminPartnerDetailResponse> => {
|
||||
const response = await apiClient.get<AdminPartnerDetailResponse>(
|
||||
`/cabinet/admin/partners/${userId}`,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateCommission: async (userId: number, commissionPercent: number): Promise<void> => {
|
||||
await apiClient.patch(`/cabinet/admin/partners/${userId}/commission`, {
|
||||
commission_percent: commissionPercent,
|
||||
});
|
||||
},
|
||||
|
||||
revokePartner: async (userId: number): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/partners/${userId}/revoke`);
|
||||
},
|
||||
|
||||
assignCampaign: async (userId: number, campaignId: number): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/partners/${userId}/campaigns/${campaignId}/assign`);
|
||||
},
|
||||
|
||||
unassignCampaign: async (userId: number, campaignId: number): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/partners/${userId}/campaigns/${campaignId}/unassign`);
|
||||
},
|
||||
};
|
||||
152
src/api/withdrawals.ts
Normal file
152
src/api/withdrawals.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import apiClient from './client';
|
||||
|
||||
// ==================== User-facing types ====================
|
||||
|
||||
export interface WithdrawalBalanceResponse {
|
||||
total_earned: number;
|
||||
referral_spent: number;
|
||||
withdrawn: number;
|
||||
pending: number;
|
||||
available_referral: number;
|
||||
available_total: number;
|
||||
only_referral_mode: boolean;
|
||||
min_amount_kopeks: number;
|
||||
is_withdrawal_enabled: boolean;
|
||||
can_request: boolean;
|
||||
cannot_request_reason: string | null;
|
||||
}
|
||||
|
||||
export interface WithdrawalCreateRequest {
|
||||
amount_kopeks: number;
|
||||
payment_details: string;
|
||||
}
|
||||
|
||||
export interface WithdrawalItem {
|
||||
id: number;
|
||||
amount_kopeks: number;
|
||||
amount_rubles: number;
|
||||
status: string;
|
||||
payment_details: string | null;
|
||||
admin_comment: string | null;
|
||||
created_at: string;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export interface WithdrawalListResponse {
|
||||
items: WithdrawalItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface WithdrawalCreateResponse {
|
||||
id: number;
|
||||
amount_kopeks: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
// ==================== Admin-facing types ====================
|
||||
|
||||
export interface AdminWithdrawalItem {
|
||||
id: number;
|
||||
user_id: number;
|
||||
username: string | null;
|
||||
first_name: string | null;
|
||||
telegram_id: number | null;
|
||||
amount_kopeks: number;
|
||||
amount_rubles: number;
|
||||
status: string;
|
||||
risk_score: number;
|
||||
risk_level: string;
|
||||
payment_details: string | null;
|
||||
admin_comment: string | null;
|
||||
created_at: string;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export interface AdminWithdrawalListResponse {
|
||||
items: AdminWithdrawalItem[];
|
||||
total: number;
|
||||
pending_count: number;
|
||||
pending_total_kopeks: number;
|
||||
}
|
||||
|
||||
export interface AdminWithdrawalDetailResponse {
|
||||
id: number;
|
||||
user_id: number;
|
||||
username: string | null;
|
||||
first_name: string | null;
|
||||
telegram_id: number | null;
|
||||
amount_kopeks: number;
|
||||
amount_rubles: number;
|
||||
status: string;
|
||||
risk_score: number;
|
||||
risk_level: string;
|
||||
risk_analysis: Record<string, unknown> | null;
|
||||
payment_details: string | null;
|
||||
admin_comment: string | null;
|
||||
balance_kopeks: number;
|
||||
total_referrals: number;
|
||||
total_earnings_kopeks: number;
|
||||
created_at: string;
|
||||
processed_at: string | null;
|
||||
}
|
||||
|
||||
export const withdrawalApi = {
|
||||
// User endpoints
|
||||
getBalance: async (): Promise<WithdrawalBalanceResponse> => {
|
||||
const response = await apiClient.get<WithdrawalBalanceResponse>(
|
||||
'/cabinet/referral/withdrawal/balance',
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: WithdrawalCreateRequest): Promise<WithdrawalCreateResponse> => {
|
||||
const response = await apiClient.post<WithdrawalCreateResponse>(
|
||||
'/cabinet/referral/withdrawal/create',
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getHistory: async (): Promise<WithdrawalListResponse> => {
|
||||
const response = await apiClient.get<WithdrawalListResponse>(
|
||||
'/cabinet/referral/withdrawal/history',
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
cancel: async (requestId: number): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/referral/withdrawal/${requestId}/cancel`);
|
||||
},
|
||||
|
||||
// Admin endpoints
|
||||
getAll: async (params?: {
|
||||
status?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}): Promise<AdminWithdrawalListResponse> => {
|
||||
const response = await apiClient.get<AdminWithdrawalListResponse>(
|
||||
'/cabinet/admin/withdrawals',
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getDetail: async (withdrawalId: number): Promise<AdminWithdrawalDetailResponse> => {
|
||||
const response = await apiClient.get<AdminWithdrawalDetailResponse>(
|
||||
`/cabinet/admin/withdrawals/${withdrawalId}`,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
approve: async (withdrawalId: number, comment?: string): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/withdrawals/${withdrawalId}/approve`, { comment });
|
||||
},
|
||||
|
||||
reject: async (withdrawalId: number, comment?: string): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/withdrawals/${withdrawalId}/reject`, { comment });
|
||||
},
|
||||
|
||||
complete: async (withdrawalId: number): Promise<void> => {
|
||||
await apiClient.post(`/cabinet/admin/withdrawals/${withdrawalId}/complete`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user