mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
Add admin features for broadcasts, campaigns, and users
- Introduced new pages and API integrations for managing broadcasts, campaigns, and users in the admin panel. - Added localization support for new features in English, Persian, Russian, and Chinese. - Enhanced the AdminPanel to include navigation for the new sections, improving overall admin experience.
This commit is contained in:
168
src/api/adminBroadcasts.ts
Normal file
168
src/api/adminBroadcasts.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// Types
|
||||
export interface BroadcastFilter {
|
||||
key: string
|
||||
label: string
|
||||
count: number | null
|
||||
group: string | null
|
||||
}
|
||||
|
||||
export interface TariffFilter {
|
||||
key: string
|
||||
label: string
|
||||
tariff_id: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface BroadcastFiltersResponse {
|
||||
filters: BroadcastFilter[]
|
||||
tariff_filters: TariffFilter[]
|
||||
custom_filters: BroadcastFilter[]
|
||||
}
|
||||
|
||||
export interface TariffForBroadcast {
|
||||
id: number
|
||||
name: string
|
||||
filter_key: string
|
||||
active_users_count: number
|
||||
}
|
||||
|
||||
export interface BroadcastTariffsResponse {
|
||||
tariffs: TariffForBroadcast[]
|
||||
}
|
||||
|
||||
export interface BroadcastButton {
|
||||
key: string
|
||||
label: string
|
||||
default: boolean
|
||||
}
|
||||
|
||||
export interface BroadcastButtonsResponse {
|
||||
buttons: BroadcastButton[]
|
||||
}
|
||||
|
||||
export interface BroadcastMedia {
|
||||
type: 'photo' | 'video' | 'document'
|
||||
file_id: string
|
||||
caption?: string
|
||||
}
|
||||
|
||||
export interface BroadcastCreateRequest {
|
||||
target: string
|
||||
message_text: string
|
||||
selected_buttons: string[]
|
||||
media?: BroadcastMedia
|
||||
}
|
||||
|
||||
export interface Broadcast {
|
||||
id: number
|
||||
target_type: string
|
||||
message_text: string
|
||||
has_media: boolean
|
||||
media_type: string | null
|
||||
media_file_id: string | null
|
||||
media_caption: string | null
|
||||
total_count: number
|
||||
sent_count: number
|
||||
failed_count: number
|
||||
status: 'queued' | 'in_progress' | 'completed' | 'partial' | 'failed' | 'cancelled' | 'cancelling'
|
||||
admin_id: number | null
|
||||
admin_name: string | null
|
||||
created_at: string
|
||||
completed_at: string | null
|
||||
progress_percent: number
|
||||
}
|
||||
|
||||
export interface BroadcastListResponse {
|
||||
items: Broadcast[]
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface BroadcastPreviewRequest {
|
||||
target: string
|
||||
}
|
||||
|
||||
export interface BroadcastPreviewResponse {
|
||||
target: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface MediaUploadResponse {
|
||||
media_type: string
|
||||
file_id: string
|
||||
file_unique_id: string | null
|
||||
media_url: string
|
||||
}
|
||||
|
||||
export const adminBroadcastsApi = {
|
||||
// Get all available filters with counts
|
||||
getFilters: async (): Promise<BroadcastFiltersResponse> => {
|
||||
const response = await apiClient.get<BroadcastFiltersResponse>('/cabinet/admin/broadcasts/filters')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get tariffs for filtering
|
||||
getTariffs: async (): Promise<BroadcastTariffsResponse> => {
|
||||
const response = await apiClient.get<BroadcastTariffsResponse>('/cabinet/admin/broadcasts/tariffs')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get available buttons
|
||||
getButtons: async (): Promise<BroadcastButtonsResponse> => {
|
||||
const response = await apiClient.get<BroadcastButtonsResponse>('/cabinet/admin/broadcasts/buttons')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Preview broadcast (get recipients count)
|
||||
preview: async (target: string): Promise<BroadcastPreviewResponse> => {
|
||||
const response = await apiClient.post<BroadcastPreviewResponse>('/cabinet/admin/broadcasts/preview', {
|
||||
target,
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Create and start broadcast
|
||||
create: async (data: BroadcastCreateRequest): Promise<Broadcast> => {
|
||||
const response = await apiClient.post<Broadcast>('/cabinet/admin/broadcasts', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get list of broadcasts
|
||||
list: async (limit = 20, offset = 0): Promise<BroadcastListResponse> => {
|
||||
const response = await apiClient.get<BroadcastListResponse>('/cabinet/admin/broadcasts', {
|
||||
params: { limit, offset },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get broadcast details
|
||||
get: async (id: number): Promise<Broadcast> => {
|
||||
const response = await apiClient.get<Broadcast>(`/cabinet/admin/broadcasts/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Stop broadcast
|
||||
stop: async (id: number): Promise<Broadcast> => {
|
||||
const response = await apiClient.post<Broadcast>(`/cabinet/admin/broadcasts/${id}/stop`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Upload media (uses existing media endpoint)
|
||||
uploadMedia: async (file: File, mediaType: 'photo' | 'video' | 'document'): Promise<MediaUploadResponse> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('media_type', mediaType)
|
||||
|
||||
const response = await apiClient.post<MediaUploadResponse>('/cabinet/media/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
||||
export default adminBroadcastsApi
|
||||
418
src/api/adminUsers.ts
Normal file
418
src/api/adminUsers.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// ============ Types ============
|
||||
|
||||
export interface UserSubscriptionInfo {
|
||||
id: number
|
||||
status: string
|
||||
is_trial: boolean
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
traffic_limit_gb: number
|
||||
traffic_used_gb: number
|
||||
device_limit: number
|
||||
tariff_id: number | null
|
||||
tariff_name: string | null
|
||||
autopay_enabled: boolean
|
||||
is_active: boolean
|
||||
days_remaining: number
|
||||
}
|
||||
|
||||
export interface UserPromoGroupInfo {
|
||||
id: number
|
||||
name: string
|
||||
is_default: boolean
|
||||
}
|
||||
|
||||
export interface UserListItem {
|
||||
id: number
|
||||
telegram_id: number
|
||||
username: string | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
full_name: string
|
||||
status: string
|
||||
balance_kopeks: number
|
||||
balance_rubles: number
|
||||
created_at: string
|
||||
last_activity: string | null
|
||||
has_subscription: boolean
|
||||
subscription_status: string | null
|
||||
subscription_is_trial: boolean
|
||||
subscription_end_date: string | null
|
||||
promo_group_id: number | null
|
||||
promo_group_name: string | null
|
||||
total_spent_kopeks: number
|
||||
purchase_count: number
|
||||
has_restrictions: boolean
|
||||
restriction_topup: boolean
|
||||
restriction_subscription: boolean
|
||||
}
|
||||
|
||||
export interface UsersListResponse {
|
||||
users: UserListItem[]
|
||||
total: number
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
|
||||
export interface UserTransactionItem {
|
||||
id: number
|
||||
type: string
|
||||
amount_kopeks: number
|
||||
amount_rubles: number
|
||||
description: string | null
|
||||
payment_method: string | null
|
||||
is_completed: boolean
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface UserReferralInfo {
|
||||
referral_code: string
|
||||
referrals_count: number
|
||||
total_earnings_kopeks: number
|
||||
commission_percent: number | null
|
||||
referred_by_id: number | null
|
||||
referred_by_username: string | null
|
||||
}
|
||||
|
||||
export interface UserDetailResponse {
|
||||
id: number
|
||||
telegram_id: number
|
||||
username: string | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
full_name: string
|
||||
status: string
|
||||
language: string
|
||||
balance_kopeks: number
|
||||
balance_rubles: number
|
||||
email: string | null
|
||||
email_verified: boolean
|
||||
created_at: string
|
||||
updated_at: string | null
|
||||
last_activity: string | null
|
||||
cabinet_last_login: string | null
|
||||
subscription: UserSubscriptionInfo | null
|
||||
promo_group: UserPromoGroupInfo | null
|
||||
referral: UserReferralInfo
|
||||
total_spent_kopeks: number
|
||||
purchase_count: number
|
||||
used_promocodes: number
|
||||
has_had_paid_subscription: boolean
|
||||
lifetime_used_traffic_bytes: number
|
||||
restriction_topup: boolean
|
||||
restriction_subscription: boolean
|
||||
restriction_reason: string | null
|
||||
promo_offer_discount_percent: number
|
||||
promo_offer_discount_source: string | null
|
||||
promo_offer_discount_expires_at: string | null
|
||||
recent_transactions: UserTransactionItem[]
|
||||
remnawave_uuid: string | null
|
||||
}
|
||||
|
||||
export interface UsersStatsResponse {
|
||||
total_users: number
|
||||
active_users: number
|
||||
blocked_users: number
|
||||
deleted_users: number
|
||||
new_today: number
|
||||
new_week: number
|
||||
new_month: number
|
||||
users_with_subscription: number
|
||||
users_with_active_subscription: number
|
||||
users_with_trial: number
|
||||
users_with_expired_subscription: number
|
||||
total_balance_kopeks: number
|
||||
total_balance_rubles: number
|
||||
avg_balance_kopeks: number
|
||||
active_today: number
|
||||
active_week: number
|
||||
active_month: number
|
||||
}
|
||||
|
||||
// Available tariffs
|
||||
export interface PeriodPriceInfo {
|
||||
days: number
|
||||
price_kopeks: number
|
||||
price_rubles: number
|
||||
}
|
||||
|
||||
export interface UserAvailableTariff {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
is_active: boolean
|
||||
is_trial_available: boolean
|
||||
traffic_limit_gb: number
|
||||
device_limit: number
|
||||
tier_level: number
|
||||
display_order: number
|
||||
period_prices: PeriodPriceInfo[]
|
||||
is_daily: boolean
|
||||
daily_price_kopeks: number
|
||||
custom_days_enabled: boolean
|
||||
price_per_day_kopeks: number
|
||||
min_days: number
|
||||
max_days: number
|
||||
is_available: boolean
|
||||
requires_promo_group: boolean
|
||||
}
|
||||
|
||||
export interface UserAvailableTariffsResponse {
|
||||
user_id: number
|
||||
promo_group_id: number | null
|
||||
promo_group_name: string | null
|
||||
tariffs: UserAvailableTariff[]
|
||||
total: number
|
||||
current_tariff_id: number | null
|
||||
current_tariff_name: string | null
|
||||
}
|
||||
|
||||
// Sync types
|
||||
export interface PanelUserInfo {
|
||||
uuid: string | null
|
||||
short_uuid: string | null
|
||||
username: string | null
|
||||
status: string | null
|
||||
expire_at: string | null
|
||||
traffic_limit_gb: number
|
||||
traffic_used_gb: number
|
||||
device_limit: number
|
||||
subscription_url: string | null
|
||||
active_squads: string[]
|
||||
}
|
||||
|
||||
export interface SyncFromPanelResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
panel_user: PanelUserInfo | null
|
||||
changes: Record<string, unknown>
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export interface SyncToPanelResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
action: string
|
||||
panel_uuid: string | null
|
||||
changes: Record<string, unknown>
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export interface PanelSyncStatusResponse {
|
||||
user_id: number
|
||||
telegram_id: number
|
||||
remnawave_uuid: string | null
|
||||
last_sync: string | null
|
||||
bot_subscription_status: string | null
|
||||
bot_subscription_end_date: string | null
|
||||
bot_traffic_limit_gb: number
|
||||
bot_traffic_used_gb: number
|
||||
bot_device_limit: number
|
||||
bot_squads: string[]
|
||||
panel_found: boolean
|
||||
panel_status: string | null
|
||||
panel_expire_at: string | null
|
||||
panel_traffic_limit_gb: number
|
||||
panel_traffic_used_gb: number
|
||||
panel_device_limit: number
|
||||
panel_squads: string[]
|
||||
has_differences: boolean
|
||||
differences: string[]
|
||||
}
|
||||
|
||||
// Update types
|
||||
export interface UpdateBalanceRequest {
|
||||
amount_kopeks: number
|
||||
description?: string
|
||||
create_transaction?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateBalanceResponse {
|
||||
success: boolean
|
||||
old_balance_kopeks: number
|
||||
new_balance_kopeks: number
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface UpdateSubscriptionRequest {
|
||||
action: 'extend' | 'set_end_date' | 'change_tariff' | 'set_traffic' | 'toggle_autopay' | 'cancel' | 'activate' | 'create'
|
||||
days?: number
|
||||
end_date?: string
|
||||
tariff_id?: number
|
||||
traffic_limit_gb?: number
|
||||
traffic_used_gb?: number
|
||||
autopay_enabled?: boolean
|
||||
is_trial?: boolean
|
||||
device_limit?: number
|
||||
}
|
||||
|
||||
export interface UpdateSubscriptionResponse {
|
||||
success: boolean
|
||||
message: string
|
||||
subscription: UserSubscriptionInfo | null
|
||||
}
|
||||
|
||||
export interface UpdateUserStatusResponse {
|
||||
success: boolean
|
||||
old_status: string
|
||||
new_status: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface UpdateRestrictionsRequest {
|
||||
restriction_topup?: boolean
|
||||
restriction_subscription?: boolean
|
||||
restriction_reason?: string
|
||||
}
|
||||
|
||||
export interface UpdateRestrictionsResponse {
|
||||
success: boolean
|
||||
restriction_topup: boolean
|
||||
restriction_subscription: boolean
|
||||
restriction_reason: string | null
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface SyncFromPanelRequest {
|
||||
update_subscription?: boolean
|
||||
update_traffic?: boolean
|
||||
create_if_missing?: boolean
|
||||
}
|
||||
|
||||
export interface SyncToPanelRequest {
|
||||
create_if_missing?: boolean
|
||||
update_status?: boolean
|
||||
update_traffic_limit?: boolean
|
||||
update_expire_date?: boolean
|
||||
update_squads?: boolean
|
||||
}
|
||||
|
||||
// ============ API ============
|
||||
|
||||
export const adminUsersApi = {
|
||||
// List users
|
||||
getUsers: async (params: {
|
||||
offset?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
status?: 'active' | 'blocked' | 'deleted'
|
||||
sort_by?: 'created_at' | 'balance' | 'traffic' | 'last_activity' | 'total_spent' | 'purchase_count'
|
||||
} = {}): Promise<UsersListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/users', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get users stats
|
||||
getStats: async (): Promise<UsersStatsResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/users/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get user detail
|
||||
getUser: async (userId: number): Promise<UserDetailResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get user by telegram ID
|
||||
getUserByTelegram: async (telegramId: number): Promise<UserDetailResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/by-telegram/${telegramId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get available tariffs for user
|
||||
getAvailableTariffs: async (userId: number, includeInactive = false): Promise<UserAvailableTariffsResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/available-tariffs`, {
|
||||
params: { include_inactive: includeInactive }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update balance
|
||||
updateBalance: async (userId: number, data: UpdateBalanceRequest): Promise<UpdateBalanceResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/balance`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update subscription
|
||||
updateSubscription: async (userId: number, data: UpdateSubscriptionRequest): Promise<UpdateSubscriptionResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/subscription`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update status
|
||||
updateStatus: async (userId: number, status: 'active' | 'blocked' | 'deleted', reason?: string): Promise<UpdateUserStatusResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/status`, { status, reason })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Block user
|
||||
blockUser: async (userId: number, reason?: string): Promise<UpdateUserStatusResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/block`, null, { params: { reason } })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Unblock user
|
||||
unblockUser: async (userId: number): Promise<UpdateUserStatusResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/unblock`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update restrictions
|
||||
updateRestrictions: async (userId: number, data: UpdateRestrictionsRequest): Promise<UpdateRestrictionsResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/restrictions`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update promo group
|
||||
updatePromoGroup: async (userId: number, promoGroupId: number | null): Promise<{ success: boolean; message: string }> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/promo-group`, { promo_group_id: promoGroupId })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Delete user
|
||||
deleteUser: async (userId: number, softDelete = true, reason?: string): Promise<{ success: boolean; message: string }> => {
|
||||
const response = await apiClient.delete(`/cabinet/admin/users/${userId}`, {
|
||||
data: { soft_delete: softDelete, reason }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get referrals
|
||||
getReferrals: async (userId: number, offset = 0, limit = 50): Promise<UsersListResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/referrals`, {
|
||||
params: { offset, limit }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get transactions
|
||||
getTransactions: async (userId: number, params: {
|
||||
offset?: number
|
||||
limit?: number
|
||||
transaction_type?: string
|
||||
} = {}): Promise<{ transactions: UserTransactionItem[]; total: number; offset: number; limit: number }> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/transactions`, { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Sync status
|
||||
getSyncStatus: async (userId: number): Promise<PanelSyncStatusResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/users/${userId}/sync/status`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Sync from panel
|
||||
syncFromPanel: async (userId: number, data: SyncFromPanelRequest = {}): Promise<SyncFromPanelResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/from-panel`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Sync to panel
|
||||
syncToPanel: async (userId: number, data: SyncToPanelRequest = {}): Promise<SyncToPanelResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/users/${userId}/sync/to-panel`, data)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
230
src/api/campaigns.ts
Normal file
230
src/api/campaigns.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// Types
|
||||
export type CampaignBonusType = 'balance' | 'subscription' | 'none' | 'tariff'
|
||||
|
||||
export interface TariffInfo {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface CampaignListItem {
|
||||
id: number
|
||||
name: string
|
||||
start_parameter: string
|
||||
bonus_type: CampaignBonusType
|
||||
is_active: boolean
|
||||
registrations_count: number
|
||||
total_revenue_kopeks: number
|
||||
conversion_rate: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface CampaignListResponse {
|
||||
campaigns: CampaignListItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface CampaignDetail {
|
||||
id: number
|
||||
name: string
|
||||
start_parameter: string
|
||||
bonus_type: CampaignBonusType
|
||||
is_active: boolean
|
||||
balance_bonus_kopeks: number
|
||||
balance_bonus_rubles: number
|
||||
subscription_duration_days: number | null
|
||||
subscription_traffic_gb: number | null
|
||||
subscription_device_limit: number | null
|
||||
subscription_squads: string[]
|
||||
tariff_id: number | null
|
||||
tariff_duration_days: number | null
|
||||
tariff: TariffInfo | null
|
||||
created_by: number | null
|
||||
created_at: string
|
||||
updated_at: string | null
|
||||
deep_link: string | null
|
||||
}
|
||||
|
||||
export interface CampaignCreateRequest {
|
||||
name: string
|
||||
start_parameter: string
|
||||
bonus_type: CampaignBonusType
|
||||
is_active?: boolean
|
||||
balance_bonus_kopeks?: number
|
||||
subscription_duration_days?: number
|
||||
subscription_traffic_gb?: number
|
||||
subscription_device_limit?: number
|
||||
subscription_squads?: string[]
|
||||
tariff_id?: number
|
||||
tariff_duration_days?: number
|
||||
}
|
||||
|
||||
export interface CampaignUpdateRequest {
|
||||
name?: string
|
||||
start_parameter?: string
|
||||
bonus_type?: CampaignBonusType
|
||||
is_active?: boolean
|
||||
balance_bonus_kopeks?: number
|
||||
subscription_duration_days?: number
|
||||
subscription_traffic_gb?: number
|
||||
subscription_device_limit?: number
|
||||
subscription_squads?: string[]
|
||||
tariff_id?: number
|
||||
tariff_duration_days?: number
|
||||
}
|
||||
|
||||
export interface CampaignToggleResponse {
|
||||
id: number
|
||||
is_active: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface CampaignStatistics {
|
||||
id: number
|
||||
name: string
|
||||
start_parameter: string
|
||||
bonus_type: CampaignBonusType
|
||||
is_active: boolean
|
||||
registrations: number
|
||||
balance_issued_kopeks: number
|
||||
balance_issued_rubles: number
|
||||
subscription_issued: number
|
||||
last_registration: string | null
|
||||
total_revenue_kopeks: number
|
||||
total_revenue_rubles: number
|
||||
avg_revenue_per_user_kopeks: number
|
||||
avg_revenue_per_user_rubles: number
|
||||
avg_first_payment_kopeks: number
|
||||
avg_first_payment_rubles: number
|
||||
trial_users_count: number
|
||||
active_trials_count: number
|
||||
conversion_count: number
|
||||
paid_users_count: number
|
||||
conversion_rate: number
|
||||
trial_conversion_rate: number
|
||||
deep_link: string | null
|
||||
}
|
||||
|
||||
export interface CampaignRegistrationItem {
|
||||
id: number
|
||||
user_id: number
|
||||
telegram_id: number
|
||||
username: string | null
|
||||
first_name: string | null
|
||||
bonus_type: string
|
||||
balance_bonus_kopeks: number
|
||||
subscription_duration_days: number | null
|
||||
tariff_id: number | null
|
||||
tariff_duration_days: number | null
|
||||
created_at: string
|
||||
user_balance_kopeks: number
|
||||
has_subscription: boolean
|
||||
has_paid: boolean
|
||||
}
|
||||
|
||||
export interface CampaignRegistrationsResponse {
|
||||
registrations: CampaignRegistrationItem[]
|
||||
total: number
|
||||
page: number
|
||||
per_page: number
|
||||
}
|
||||
|
||||
export interface CampaignsOverview {
|
||||
total: number
|
||||
active: number
|
||||
inactive: number
|
||||
total_registrations: number
|
||||
total_balance_issued_kopeks: number
|
||||
total_balance_issued_rubles: number
|
||||
total_subscription_issued: number
|
||||
total_tariff_issued: number
|
||||
}
|
||||
|
||||
export interface ServerSquadInfo {
|
||||
id: number
|
||||
squad_uuid: string
|
||||
display_name: string
|
||||
country_code: string | null
|
||||
}
|
||||
|
||||
export interface TariffListItem {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
is_active: boolean
|
||||
traffic_limit_gb: number
|
||||
device_limit: number
|
||||
}
|
||||
|
||||
export const campaignsApi = {
|
||||
// Get campaigns overview
|
||||
getOverview: async (): Promise<CampaignsOverview> => {
|
||||
const response = await apiClient.get('/cabinet/admin/campaigns/overview')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get all campaigns
|
||||
getCampaigns: async (includeInactive = true, offset = 0, limit = 50): Promise<CampaignListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/campaigns', {
|
||||
params: { include_inactive: includeInactive, offset, limit }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get single campaign
|
||||
getCampaign: async (campaignId: number): Promise<CampaignDetail> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get campaign statistics
|
||||
getCampaignStats: async (campaignId: number): Promise<CampaignStatistics> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}/stats`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get campaign registrations
|
||||
getCampaignRegistrations: async (campaignId: number, page = 1, perPage = 50): Promise<CampaignRegistrationsResponse> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}/registrations`, {
|
||||
params: { page, per_page: perPage }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Create campaign
|
||||
createCampaign: async (data: CampaignCreateRequest): Promise<CampaignDetail> => {
|
||||
const response = await apiClient.post('/cabinet/admin/campaigns', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Update campaign
|
||||
updateCampaign: async (campaignId: number, data: CampaignUpdateRequest): Promise<CampaignDetail> => {
|
||||
const response = await apiClient.put(`/cabinet/admin/campaigns/${campaignId}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Delete campaign
|
||||
deleteCampaign: async (campaignId: number): Promise<{ message: string }> => {
|
||||
const response = await apiClient.delete(`/cabinet/admin/campaigns/${campaignId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Toggle campaign active status
|
||||
toggleCampaign: async (campaignId: number): Promise<CampaignToggleResponse> => {
|
||||
const response = await apiClient.post(`/cabinet/admin/campaigns/${campaignId}/toggle`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get available servers for subscription bonus
|
||||
getAvailableServers: async (): Promise<ServerSquadInfo[]> => {
|
||||
const response = await apiClient.get('/cabinet/admin/campaigns/available-servers')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Get available tariffs for tariff bonus
|
||||
getAvailableTariffs: async (): Promise<TariffListItem[]> => {
|
||||
const response = await apiClient.get('/cabinet/admin/campaigns/available-tariffs')
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
181
src/api/promocodes.ts
Normal file
181
src/api/promocodes.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// ============== Types ==============
|
||||
|
||||
export type PromoCodeType = 'balance' | 'subscription_days' | 'trial_subscription' | 'promo_group'
|
||||
|
||||
export interface PromoCode {
|
||||
id: number
|
||||
code: string
|
||||
type: PromoCodeType
|
||||
balance_bonus_kopeks: number
|
||||
balance_bonus_rubles: number
|
||||
subscription_days: number
|
||||
max_uses: number
|
||||
current_uses: number
|
||||
uses_left: number
|
||||
is_active: boolean
|
||||
is_valid: boolean
|
||||
first_purchase_only: boolean
|
||||
valid_from: string
|
||||
valid_until: string | null
|
||||
promo_group_id: number | null
|
||||
created_by: number | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface PromoCodeRecentUse {
|
||||
id: number
|
||||
user_id: number
|
||||
user_username: string | null
|
||||
user_full_name: string | null
|
||||
user_telegram_id: number | null
|
||||
used_at: string
|
||||
}
|
||||
|
||||
export interface PromoCodeDetail extends PromoCode {
|
||||
total_uses: number
|
||||
today_uses: number
|
||||
recent_uses: PromoCodeRecentUse[]
|
||||
}
|
||||
|
||||
export interface PromoCodeListResponse {
|
||||
items: PromoCode[]
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface PromoCodeCreateRequest {
|
||||
code: string
|
||||
type: PromoCodeType
|
||||
balance_bonus_kopeks?: number
|
||||
subscription_days?: number
|
||||
max_uses?: number
|
||||
valid_from?: string
|
||||
valid_until?: string | null
|
||||
is_active?: boolean
|
||||
first_purchase_only?: boolean
|
||||
promo_group_id?: number | null
|
||||
}
|
||||
|
||||
export interface PromoCodeUpdateRequest {
|
||||
code?: string
|
||||
type?: PromoCodeType
|
||||
balance_bonus_kopeks?: number
|
||||
subscription_days?: number
|
||||
max_uses?: number
|
||||
valid_from?: string
|
||||
valid_until?: string | null
|
||||
is_active?: boolean
|
||||
first_purchase_only?: boolean
|
||||
promo_group_id?: number | null
|
||||
}
|
||||
|
||||
// ============== PromoGroup Types ==============
|
||||
|
||||
export interface PromoGroup {
|
||||
id: number
|
||||
name: string
|
||||
server_discount_percent: number
|
||||
traffic_discount_percent: number
|
||||
device_discount_percent: number
|
||||
period_discounts: Record<number, number>
|
||||
auto_assign_total_spent_kopeks: number | null
|
||||
apply_discounts_to_addons: boolean
|
||||
is_default: boolean
|
||||
members_count: number
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface PromoGroupListResponse {
|
||||
items: PromoGroup[]
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
export interface PromoGroupCreateRequest {
|
||||
name: string
|
||||
server_discount_percent?: number
|
||||
traffic_discount_percent?: number
|
||||
device_discount_percent?: number
|
||||
period_discounts?: Record<number, number>
|
||||
auto_assign_total_spent_kopeks?: number | null
|
||||
apply_discounts_to_addons?: boolean
|
||||
is_default?: boolean
|
||||
}
|
||||
|
||||
export interface PromoGroupUpdateRequest {
|
||||
name?: string
|
||||
server_discount_percent?: number
|
||||
traffic_discount_percent?: number
|
||||
device_discount_percent?: number
|
||||
period_discounts?: Record<number, number>
|
||||
auto_assign_total_spent_kopeks?: number | null
|
||||
apply_discounts_to_addons?: boolean
|
||||
is_default?: boolean
|
||||
}
|
||||
|
||||
// ============== API ==============
|
||||
|
||||
export const promocodesApi = {
|
||||
// Promocodes
|
||||
getPromocodes: async (params?: {
|
||||
limit?: number
|
||||
offset?: number
|
||||
is_active?: boolean
|
||||
}): Promise<PromoCodeListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/promocodes', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
getPromocode: async (id: number): Promise<PromoCodeDetail> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/promocodes/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
createPromocode: async (data: PromoCodeCreateRequest): Promise<PromoCode> => {
|
||||
const response = await apiClient.post('/cabinet/admin/promocodes', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
updatePromocode: async (id: number, data: PromoCodeUpdateRequest): Promise<PromoCode> => {
|
||||
const response = await apiClient.patch(`/cabinet/admin/promocodes/${id}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
deletePromocode: async (id: number): Promise<void> => {
|
||||
await apiClient.delete(`/cabinet/admin/promocodes/${id}`)
|
||||
},
|
||||
|
||||
// Promo Groups
|
||||
getPromoGroups: async (params?: {
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<PromoGroupListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/promo-groups', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
getPromoGroup: async (id: number): Promise<PromoGroup> => {
|
||||
const response = await apiClient.get(`/cabinet/admin/promo-groups/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
createPromoGroup: async (data: PromoGroupCreateRequest): Promise<PromoGroup> => {
|
||||
const response = await apiClient.post('/cabinet/admin/promo-groups', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
updatePromoGroup: async (id: number, data: PromoGroupUpdateRequest): Promise<PromoGroup> => {
|
||||
const response = await apiClient.patch(`/cabinet/admin/promo-groups/${id}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
deletePromoGroup: async (id: number): Promise<void> => {
|
||||
await apiClient.delete(`/cabinet/admin/promo-groups/${id}`)
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user