first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
790
frontend/src/lib/api.ts
Normal file
790
frontend/src/lib/api.ts
Normal file
|
|
@ -0,0 +1,790 @@
|
|||
const API_BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8000'
|
||||
|
||||
export type DashboardResponse = {
|
||||
metrics: {
|
||||
voice_calls: number
|
||||
messages_sent: number
|
||||
delivery_rate: number
|
||||
api_requests: number
|
||||
balance: number
|
||||
}
|
||||
activity: Array<{
|
||||
id: string
|
||||
type: string
|
||||
title: string
|
||||
detail: string
|
||||
status: string
|
||||
occurred_at: string
|
||||
}>
|
||||
}
|
||||
|
||||
export async function getDashboard(signal?: AbortSignal): Promise<DashboardResponse> {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/dashboard`, {
|
||||
signal,
|
||||
credentials: 'include',
|
||||
})
|
||||
if (!response.ok) throw new Error(`Dashboard request failed: ${response.status}`)
|
||||
return response.json() as Promise<DashboardResponse>
|
||||
}
|
||||
|
||||
export type AuthUser = {
|
||||
id: string
|
||||
email: string
|
||||
name: string
|
||||
avatar_url: string | null
|
||||
email_verified: boolean
|
||||
has_password: boolean
|
||||
providers: string[]
|
||||
}
|
||||
|
||||
async function authRequest(path: string, init?: RequestInit): Promise<AuthUser> {
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/auth${path}`, {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
||||
})
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => null)
|
||||
throw new Error(body?.detail ?? 'Authentication request failed')
|
||||
}
|
||||
return response.json() as Promise<AuthUser>
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
me: () => authRequest('/me'),
|
||||
login: (email: string, password: string) =>
|
||||
authRequest('/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
|
||||
register: (name: string, email: string, password: string) =>
|
||||
authRequest('/register', { method: 'POST', body: JSON.stringify({ name, email, password }) }),
|
||||
setPassword: (password: string, currentPassword?: string) =>
|
||||
authRequest('/password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ password, current_password: currentPassword || null }),
|
||||
}),
|
||||
logout: () =>
|
||||
fetch(`${API_BASE_URL}/api/v1/auth/logout`, { method: 'POST', credentials: 'include' }),
|
||||
googleUrl: `${API_BASE_URL}/api/v1/auth/google/start`,
|
||||
}
|
||||
|
||||
export type KycDocument = {
|
||||
id: string
|
||||
document_type: string
|
||||
original_filename: string | null
|
||||
status: 'missing' | 'pending' | 'approved' | 'rejected'
|
||||
review_note: string | null
|
||||
uploaded_at: string | null
|
||||
reviewed_at: string | null
|
||||
}
|
||||
|
||||
export type KycApplication = {
|
||||
id: string
|
||||
pan_number: string | null
|
||||
gst_number: string | null
|
||||
status: string
|
||||
completed: boolean
|
||||
submitted_at: string | null
|
||||
approved_at: string | null
|
||||
documents: KycDocument[]
|
||||
}
|
||||
|
||||
export type DltDocument = {
|
||||
id: string
|
||||
document_type: string
|
||||
original_filename: string | null
|
||||
status: 'missing' | 'pending' | 'approved' | 'rejected'
|
||||
review_note: string | null
|
||||
uploaded_at: string | null
|
||||
reviewed_at: string | null
|
||||
}
|
||||
|
||||
export type DltApplication = {
|
||||
id: string
|
||||
status: string
|
||||
completed: boolean
|
||||
submitted_at: string | null
|
||||
approved_at: string | null
|
||||
pe_tm_chain_binding_id: string
|
||||
documents: DltDocument[]
|
||||
}
|
||||
|
||||
export type ComplianceStatus = {
|
||||
kyc_completed: boolean
|
||||
dlt_completed: boolean
|
||||
console_unlocked: boolean
|
||||
}
|
||||
|
||||
async function jsonRequest<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
...init,
|
||||
credentials: 'include',
|
||||
headers: init?.body instanceof FormData ? init.headers : { 'Content-Type': 'application/json', ...init?.headers },
|
||||
})
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => null)
|
||||
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Request failed')
|
||||
}
|
||||
if (response.status === 204) return undefined as T
|
||||
return response.json() as Promise<T>
|
||||
}
|
||||
|
||||
export const kycApi = {
|
||||
me: () => jsonRequest<KycApplication>('/api/v1/kyc/me'),
|
||||
update: (pan_number: string, gst_number: string) =>
|
||||
jsonRequest<KycApplication>('/api/v1/kyc/me', { method: 'PUT', body: JSON.stringify({ pan_number, gst_number }) }),
|
||||
upload: (documentType: string, file: File) => {
|
||||
const body = new FormData()
|
||||
body.append('file', file)
|
||||
return jsonRequest<KycApplication>(`/api/v1/kyc/documents/${documentType}`, { method: 'POST', body })
|
||||
},
|
||||
submit: () => jsonRequest<KycApplication>('/api/v1/kyc/submit', { method: 'POST' }),
|
||||
templateUrl: `${API_BASE_URL}/api/v1/kyc/template`,
|
||||
}
|
||||
|
||||
export const dltApi = {
|
||||
me: () => jsonRequest<DltApplication>('/api/v1/dlt/me'),
|
||||
loaUrl: `${API_BASE_URL}/api/v1/dlt/loa`,
|
||||
upload: (documentType: string, file: File) => {
|
||||
const body = new FormData()
|
||||
body.append('file', file)
|
||||
return jsonRequest<DltApplication>(`/api/v1/dlt/documents/${documentType}`, { method: 'POST', body })
|
||||
},
|
||||
confirmChainBinding: () =>
|
||||
jsonRequest<DltApplication>('/api/v1/dlt/chain-binding/confirm', { method: 'POST' }),
|
||||
submit: () => jsonRequest<DltApplication>('/api/v1/dlt/submit', { method: 'POST' }),
|
||||
}
|
||||
|
||||
export const complianceApi = {
|
||||
status: () => jsonRequest<ComplianceStatus>('/api/v1/compliance/status'),
|
||||
}
|
||||
|
||||
export type AdminKycApplication = KycApplication & {
|
||||
user: { id: string; name: string; email: string }
|
||||
}
|
||||
|
||||
export type AdminDltApplication = DltApplication & {
|
||||
user: { id: string; name: string; email: string }
|
||||
}
|
||||
|
||||
export type SenderConfig = {
|
||||
id: string
|
||||
sender_id: string
|
||||
sender_type: 'promotional' | 'transactional'
|
||||
purpose: string
|
||||
dlt_entity_id: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
review_note: string | null
|
||||
created_at: string
|
||||
reviewed_at: string | null
|
||||
}
|
||||
|
||||
export type AdminSenderConfig = SenderConfig & {
|
||||
user: { id: string; name: string; email: string }
|
||||
}
|
||||
|
||||
export type TemplateConfig = {
|
||||
id: string
|
||||
sender_config_id: string
|
||||
sender_id: string
|
||||
template_name: string
|
||||
template_text: string
|
||||
dlt_template_id: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
review_note: string | null
|
||||
created_at: string
|
||||
reviewed_at: string | null
|
||||
}
|
||||
|
||||
export const messagingApi = {
|
||||
listSenders: () => jsonRequest<SenderConfig[]>('/api/v1/messaging/senders'),
|
||||
createSender: (payload: {
|
||||
sender_id: string
|
||||
sender_type: string
|
||||
purpose: string
|
||||
dlt_entity_id: string
|
||||
}) => jsonRequest<SenderConfig>('/api/v1/messaging/senders', { method: 'POST', body: JSON.stringify(payload) }),
|
||||
listTemplates: () => jsonRequest<TemplateConfig[]>('/api/v1/messaging/templates'),
|
||||
createTemplate: (payload: {
|
||||
sender_config_id: string
|
||||
template_name: string
|
||||
template_text: string
|
||||
dlt_template_id: string
|
||||
}) => jsonRequest<TemplateConfig>('/api/v1/messaging/templates', { method: 'POST', body: JSON.stringify(payload) }),
|
||||
send: (payload: {
|
||||
sender_config_id: string
|
||||
template_id: string
|
||||
numbers: string
|
||||
message: string
|
||||
campaign_name?: string | null
|
||||
}) => jsonRequest<ComposeSendResponse>('/api/v1/messaging/send', { method: 'POST', body: JSON.stringify(payload) }),
|
||||
}
|
||||
|
||||
export type CampaignRunMode = 'manual' | 'on_upload' | 'scheduled'
|
||||
|
||||
export type CampaignCsvUpload = {
|
||||
id: string
|
||||
original_filename: string
|
||||
total_rows: number
|
||||
status: 'validated' | 'running' | 'completed' | 'failed'
|
||||
run_started_at: string | null
|
||||
run_completed_at: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type Campaign = {
|
||||
id: string
|
||||
name: string
|
||||
sender_config_id: string
|
||||
sender_id: string
|
||||
sender_type: 'promotional' | 'transactional'
|
||||
template_id: string
|
||||
template_name: string
|
||||
template_text: string
|
||||
number_column: string
|
||||
variable_columns: string[]
|
||||
run_mode: CampaignRunMode
|
||||
scheduled_at: string | null
|
||||
status: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
csv_upload_count: number
|
||||
csv_uploads: CampaignCsvUpload[]
|
||||
}
|
||||
|
||||
export const campaignsApi = {
|
||||
list: () => jsonRequest<Campaign[]>('/api/v1/campaigns'),
|
||||
get: (campaignId: string) => jsonRequest<Campaign>(`/api/v1/campaigns/${campaignId}`),
|
||||
create: (payload: {
|
||||
name: string
|
||||
sender_config_id: string
|
||||
template_id: string
|
||||
number_column: string
|
||||
variable_columns: string[]
|
||||
run_mode: CampaignRunMode
|
||||
scheduled_at?: string | null
|
||||
}) => jsonRequest<Campaign>('/api/v1/campaigns', { method: 'POST', body: JSON.stringify(payload) }),
|
||||
listCsvUploads: (campaignId: string) =>
|
||||
jsonRequest<CampaignCsvUpload[]>(`/api/v1/campaigns/${campaignId}/csv`),
|
||||
uploadCsv: (campaignId: string, file: File) => {
|
||||
const body = new FormData()
|
||||
body.append('file', file)
|
||||
return jsonRequest<CampaignCsvUpload>(`/api/v1/campaigns/${campaignId}/csv`, { method: 'POST', body })
|
||||
},
|
||||
runAutomation: (campaignId: string) =>
|
||||
jsonRequest<{ processed_uploads: number; note: string }>(`/api/v1/campaigns/${campaignId}/run`, { method: 'POST' }),
|
||||
runCsv: (campaignId: string, uploadId: string) =>
|
||||
jsonRequest<CampaignCsvUpload & { note?: string }>(
|
||||
`/api/v1/campaigns/${campaignId}/csv/${uploadId}/run`,
|
||||
{ method: 'POST' },
|
||||
),
|
||||
}
|
||||
|
||||
export type ComposeSendResultItem = {
|
||||
to: string
|
||||
success: boolean
|
||||
job_id: string | null
|
||||
message_id: string | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export type ComposeSendResponse = {
|
||||
success: boolean
|
||||
campaign_name: string | null
|
||||
sent: number
|
||||
failed: number
|
||||
sender_id: string
|
||||
template_id: string
|
||||
template_name: string
|
||||
dlt_template_id: string
|
||||
results: ComposeSendResultItem[]
|
||||
}
|
||||
|
||||
export type ApiKey = {
|
||||
id: string
|
||||
name: string
|
||||
key_prefix: string
|
||||
created_at: string
|
||||
last_used_at: string | null
|
||||
revoked_at: string | null
|
||||
status: 'active' | 'revoked'
|
||||
}
|
||||
|
||||
export type ApiKeyWithToken = ApiKey & {
|
||||
token: string
|
||||
}
|
||||
|
||||
export const apiKeysApi = {
|
||||
list: () => jsonRequest<ApiKey[]>('/api/v1/api-keys'),
|
||||
create: (name: string) =>
|
||||
jsonRequest<ApiKeyWithToken>('/api/v1/api-keys', { method: 'POST', body: JSON.stringify({ name }) }),
|
||||
regenerate: (keyId: string) =>
|
||||
jsonRequest<ApiKeyWithToken>(`/api/v1/api-keys/${keyId}/regenerate`, { method: 'POST' }),
|
||||
revoke: (keyId: string) =>
|
||||
fetch(`${API_BASE_URL}/api/v1/api-keys/${keyId}`, { method: 'DELETE', credentials: 'include' }).then((response) => {
|
||||
if (!response.ok) throw new Error('Unable to revoke API key')
|
||||
}),
|
||||
}
|
||||
|
||||
export type MessageApiDoc = {
|
||||
id: string
|
||||
method: string
|
||||
path: string
|
||||
title: string
|
||||
summary: string
|
||||
authentication: {
|
||||
type: string
|
||||
header: string
|
||||
format: string
|
||||
alternate_header: string
|
||||
}
|
||||
request_body: Record<string, {
|
||||
type: string
|
||||
required: boolean
|
||||
description: string
|
||||
example?: string
|
||||
}>
|
||||
responses: Record<string, { description: string; example?: Record<string, unknown> }>
|
||||
}
|
||||
|
||||
export type SendMessageResponse = {
|
||||
success: boolean
|
||||
job_id: string | null
|
||||
message_id: string | null
|
||||
to: string | null
|
||||
sender_id: string
|
||||
template_id: string
|
||||
template_name: string
|
||||
dlt_template_id: string
|
||||
}
|
||||
|
||||
export const messagesApi = {
|
||||
docs: () => jsonRequest<MessageApiDoc>('/api/v1/messages/docs/send'),
|
||||
send: (apiKey: string, payload: {
|
||||
sender_id: string
|
||||
template_id: string
|
||||
to: string
|
||||
message: string
|
||||
}) =>
|
||||
fetch(`${API_BASE_URL}/api/v1/messages/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
}).then(async (response) => {
|
||||
const body = await response.json().catch(() => null)
|
||||
if (!response.ok) {
|
||||
throw new Error(typeof body?.detail === 'string' ? body.detail : 'SMS send request failed')
|
||||
}
|
||||
return body as SendMessageResponse
|
||||
}),
|
||||
}
|
||||
|
||||
export type UrmiOtpProject = {
|
||||
id: string
|
||||
project_name: string
|
||||
message_label: string
|
||||
app_type: 'web' | 'mobile'
|
||||
app_url: string
|
||||
purpose: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
review_note: string | null
|
||||
terms_accepted_at: string
|
||||
created_at: string
|
||||
reviewed_at: string | null
|
||||
}
|
||||
|
||||
export type OtpTemplateDefinition = {
|
||||
type: string
|
||||
name: string
|
||||
content: string
|
||||
sample: string
|
||||
uses: string[]
|
||||
variables: string[]
|
||||
}
|
||||
|
||||
export type UrmiOtpDocumentation = {
|
||||
project: UrmiOtpProject
|
||||
templates: OtpTemplateDefinition[]
|
||||
api: {
|
||||
method: string
|
||||
path: string
|
||||
implemented: boolean
|
||||
summary: string
|
||||
parameters: Record<string, string>
|
||||
example: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export type AdminUrmiOtpProject = UrmiOtpProject & {
|
||||
user: { id: string; name: string; email: string }
|
||||
}
|
||||
|
||||
export const urmiOtpApi = {
|
||||
listProjects: () => jsonRequest<UrmiOtpProject[]>('/api/v1/urmiotp/projects'),
|
||||
createProject: (payload: {
|
||||
project_name: string
|
||||
message_label: string
|
||||
app_type: 'web' | 'mobile'
|
||||
app_url: string
|
||||
purpose: string
|
||||
terms_accepted: boolean
|
||||
}) => jsonRequest<UrmiOtpProject>('/api/v1/urmiotp/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
documentation: (projectId: string) =>
|
||||
jsonRequest<UrmiOtpDocumentation>(`/api/v1/urmiotp/projects/${projectId}/documentation`),
|
||||
generate: (payload: {
|
||||
project_name: string
|
||||
template_type: string
|
||||
to: string
|
||||
variables: { otp: string; validity_minutes: string }
|
||||
}) => jsonRequest<{
|
||||
success: boolean
|
||||
log_id: string
|
||||
project_name: string
|
||||
template_type: string
|
||||
to: string
|
||||
message_preview: string
|
||||
status: string
|
||||
}>('/api/v1/urmiotp/generate', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
}
|
||||
|
||||
export type PhoneNumber = {
|
||||
id: string
|
||||
phone_number: string
|
||||
monthly_number_price: number
|
||||
inbound_price: number
|
||||
outbound_price: number
|
||||
currency: string
|
||||
status: 'available' | 'assigned'
|
||||
acquisition_type: 'admin_assigned' | 'purchased' | null
|
||||
assigned_at: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type IntegrationEndpoint = {
|
||||
id: string
|
||||
name: string
|
||||
phone_number_id: string
|
||||
phone_number: string
|
||||
url: string
|
||||
enabled: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
signing_secret?: string
|
||||
}
|
||||
|
||||
export type VoiceSocketEndpoint = IntegrationEndpoint
|
||||
|
||||
export type SmsDeliveryWebhook = IntegrationEndpoint & {
|
||||
description: string | null
|
||||
event: 'message.delivery.updated'
|
||||
}
|
||||
|
||||
export type IntegrationsDocs = {
|
||||
title: string
|
||||
summary: string
|
||||
relay: {
|
||||
principle: string
|
||||
latency: string
|
||||
}
|
||||
socket: {
|
||||
title: string
|
||||
description: string
|
||||
events: string[]
|
||||
authentication: {
|
||||
headers: string[]
|
||||
signature: string
|
||||
replay_protection: string
|
||||
}
|
||||
}
|
||||
webhook: {
|
||||
title: string
|
||||
description: string
|
||||
event: string
|
||||
statuses: string[]
|
||||
authentication: {
|
||||
headers: string[]
|
||||
signature: string
|
||||
}
|
||||
example: Record<string, unknown>
|
||||
}
|
||||
api_authentication: {
|
||||
header: string
|
||||
format: string
|
||||
note: string
|
||||
}
|
||||
endpoints: Array<{ method: string; path: string; description: string }>
|
||||
}
|
||||
|
||||
type EndpointPayload = {
|
||||
name: string
|
||||
phone_number_id: string
|
||||
url: string
|
||||
description?: string | null
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export const integrationsApi = {
|
||||
docs: () => jsonRequest<IntegrationsDocs>('/api/v1/integrations/docs'),
|
||||
listSockets: () => jsonRequest<VoiceSocketEndpoint[]>('/api/v1/integrations/sockets'),
|
||||
createSocket: (payload: EndpointPayload) =>
|
||||
jsonRequest<VoiceSocketEndpoint>('/api/v1/integrations/sockets', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
updateSocket: (id: string, payload: Partial<EndpointPayload>) =>
|
||||
jsonRequest<VoiceSocketEndpoint>(`/api/v1/integrations/sockets/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
deleteSocket: (id: string) =>
|
||||
jsonRequest<void>(`/api/v1/integrations/sockets/${id}`, { method: 'DELETE' }),
|
||||
listWebhooks: () => jsonRequest<SmsDeliveryWebhook[]>('/api/v1/integrations/webhooks'),
|
||||
createWebhook: (payload: EndpointPayload) =>
|
||||
jsonRequest<SmsDeliveryWebhook>('/api/v1/integrations/webhooks', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
updateWebhook: (id: string, payload: Partial<EndpointPayload>) =>
|
||||
jsonRequest<SmsDeliveryWebhook>(`/api/v1/integrations/webhooks/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
deleteWebhook: (id: string) =>
|
||||
jsonRequest<void>(`/api/v1/integrations/webhooks/${id}`, { method: 'DELETE' }),
|
||||
testWebhook: (id: string) =>
|
||||
jsonRequest<{ delivered: boolean; response_code: number }>(
|
||||
`/api/v1/integrations/webhooks/${id}/test`,
|
||||
{ method: 'POST' },
|
||||
),
|
||||
}
|
||||
|
||||
export type VoiceCall = {
|
||||
id: string
|
||||
direction: string
|
||||
from: string
|
||||
to: string
|
||||
status: string
|
||||
stream_id: string | null
|
||||
call_id: string | null
|
||||
bridge_mode: string
|
||||
upstream: string | null
|
||||
error_message: string | null
|
||||
answered_at: string | null
|
||||
ended_at: string | null
|
||||
created_at: string
|
||||
media_url?: string
|
||||
}
|
||||
|
||||
export type VoiceApiDocs = {
|
||||
title: string
|
||||
summary: string
|
||||
method: string
|
||||
path: string
|
||||
authentication: {
|
||||
header: string
|
||||
format: string
|
||||
alternate_header?: string
|
||||
}
|
||||
request_body: Record<string, {
|
||||
type: string
|
||||
required: boolean
|
||||
example: string
|
||||
description?: string
|
||||
}>
|
||||
response_example: Record<string, unknown>
|
||||
relay: {
|
||||
principle: string
|
||||
latency: string
|
||||
}
|
||||
outbound_flow: string[]
|
||||
endpoints: Array<{ method: string; path: string; description: string }>
|
||||
}
|
||||
|
||||
export const voiceApi = {
|
||||
docs: () => jsonRequest<VoiceApiDocs>('/api/v1/voice/docs'),
|
||||
createCall: (payload: { to: string; from: string; bridge_mode?: 'console' | 'upstream'; upstream?: string }) =>
|
||||
jsonRequest<VoiceCall>('/api/v1/voice/calls', { method: 'POST', body: JSON.stringify(payload) }),
|
||||
getCall: (callId: string) => jsonRequest<VoiceCall>(`/api/v1/voice/calls/${callId}`),
|
||||
createCallWithApiKey: (
|
||||
apiKey: string,
|
||||
payload: { to: string; from: string; bridge_mode?: 'console' | 'upstream'; upstream?: string },
|
||||
) =>
|
||||
fetch(`${API_BASE_URL}/api/v1/voice/calls`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
}).then(async (response) => {
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => null)
|
||||
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Request failed')
|
||||
}
|
||||
return response.json() as Promise<VoiceCall>
|
||||
}),
|
||||
}
|
||||
|
||||
export type AdminPhoneNumber = PhoneNumber & {
|
||||
user: { id: string; name: string; email: string } | null
|
||||
}
|
||||
|
||||
export const numbersApi = {
|
||||
catalog: () => jsonRequest<PhoneNumber[]>('/api/v1/numbers/catalog'),
|
||||
mine: () => jsonRequest<PhoneNumber[]>('/api/v1/numbers/mine'),
|
||||
purchase: (numberId: string) =>
|
||||
jsonRequest<PhoneNumber>(`/api/v1/numbers/${numberId}/purchase`, { method: 'POST' }),
|
||||
}
|
||||
|
||||
export type AdminTemplateConfig = TemplateConfig & {
|
||||
user: { id: string; name: string; email: string }
|
||||
}
|
||||
|
||||
export const adminApi = {
|
||||
me: () => jsonRequest<{ username: string }>('/api/v1/admin/me'),
|
||||
login: (username: string, password: string) =>
|
||||
jsonRequest<{ username: string }>('/api/v1/admin/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password }),
|
||||
}),
|
||||
logout: () => fetch(`${API_BASE_URL}/api/v1/admin/logout`, { method: 'POST', credentials: 'include' }),
|
||||
listKyc: () => jsonRequest<AdminKycApplication[]>('/api/v1/admin/compliance/kyc'),
|
||||
reviewDocument: (applicationId: string, documentId: string, status: 'approved' | 'rejected', review_note?: string) =>
|
||||
jsonRequest<AdminKycApplication>(`/api/v1/admin/compliance/kyc/${applicationId}/documents/${documentId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status, review_note: review_note || null }),
|
||||
}),
|
||||
listDlt: () => jsonRequest<AdminDltApplication[]>('/api/v1/admin/compliance/dlt'),
|
||||
reviewDltDocument: (applicationId: string, documentId: string, status: 'approved' | 'rejected', review_note?: string) =>
|
||||
jsonRequest<AdminDltApplication>(`/api/v1/admin/compliance/dlt/${applicationId}/documents/${documentId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status, review_note: review_note || null }),
|
||||
}),
|
||||
listSenders: () => jsonRequest<AdminSenderConfig[]>('/api/v1/admin/compliance/senders'),
|
||||
reviewSender: (senderId: string, status: 'approved' | 'rejected', review_note?: string) =>
|
||||
jsonRequest<AdminSenderConfig>(`/api/v1/admin/compliance/senders/${senderId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status, review_note: review_note || null }),
|
||||
}),
|
||||
listTemplates: () => jsonRequest<AdminTemplateConfig[]>('/api/v1/admin/compliance/templates'),
|
||||
reviewTemplate: (templateId: string, status: 'approved' | 'rejected', review_note?: string) =>
|
||||
jsonRequest<AdminTemplateConfig>(`/api/v1/admin/compliance/templates/${templateId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status, review_note: review_note || null }),
|
||||
}),
|
||||
listUrmiOtpProjects: () =>
|
||||
jsonRequest<AdminUrmiOtpProject[]>('/api/v1/admin/compliance/urmiotp-projects'),
|
||||
reviewUrmiOtpProject: (projectId: string, status: 'approved' | 'rejected', review_note?: string) =>
|
||||
jsonRequest<AdminUrmiOtpProject>(`/api/v1/admin/compliance/urmiotp-projects/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status, review_note: review_note || null }),
|
||||
}),
|
||||
listPhoneNumbers: () => jsonRequest<AdminPhoneNumber[]>('/api/v1/admin/phone-numbers'),
|
||||
createPhoneNumber: (payload: {
|
||||
phone_number: string
|
||||
monthly_number_price: number
|
||||
inbound_price: number
|
||||
outbound_price: number
|
||||
user_email?: string | null
|
||||
}) => jsonRequest<AdminPhoneNumber>('/api/v1/admin/phone-numbers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
updatePhoneNumber: (numberId: string, payload: {
|
||||
monthly_number_price?: number
|
||||
inbound_price?: number
|
||||
outbound_price?: number
|
||||
user_email?: string | null
|
||||
release?: boolean
|
||||
}) => jsonRequest<AdminPhoneNumber>(`/api/v1/admin/phone-numbers/${numberId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
}
|
||||
|
||||
export type LogType = 'outbound' | 'inbound' | 'sms' | 'otp'
|
||||
|
||||
export type LogEntry = {
|
||||
id: string
|
||||
from?: string
|
||||
to?: string
|
||||
template?: string
|
||||
otp_type?: string
|
||||
project?: string
|
||||
socket_name?: string
|
||||
status?: string
|
||||
time: string
|
||||
}
|
||||
|
||||
export type LogsResponse = {
|
||||
type: LogType
|
||||
page: number
|
||||
page_size: number
|
||||
total: number
|
||||
total_pages: number
|
||||
from: string
|
||||
to: string
|
||||
items: LogEntry[]
|
||||
}
|
||||
|
||||
export const logsApi = {
|
||||
list: (params: { type: LogType; from: string; to: string; page?: number }) => {
|
||||
const query = new URLSearchParams({
|
||||
type: params.type,
|
||||
from: params.from,
|
||||
to: params.to,
|
||||
page: String(params.page ?? 1),
|
||||
})
|
||||
return jsonRequest<LogsResponse>(`/api/v1/logs?${query.toString()}`)
|
||||
},
|
||||
}
|
||||
|
||||
export type AnalyticsPeriod = 'weekly' | 'monthly' | 'quarterly'
|
||||
export type AnalyticsService = 'sms' | 'inbound' | 'outbound' | 'otp'
|
||||
export type AnalyticsGroupBy = 'dlt_sender' | 'dlt_template' | 'did' | 'socket' | 'project' | 'otp_type'
|
||||
|
||||
export type UsageAnalytics = {
|
||||
period: AnalyticsPeriod
|
||||
service: AnalyticsService
|
||||
group_by: AnalyticsGroupBy
|
||||
from: string
|
||||
to: string
|
||||
totals: {
|
||||
sms: number
|
||||
inbound: number
|
||||
outbound: number
|
||||
otp: number
|
||||
}
|
||||
timeline: Array<{
|
||||
label: string
|
||||
period_start: string
|
||||
sms: number
|
||||
inbound: number
|
||||
outbound: number
|
||||
otp: number
|
||||
}>
|
||||
breakdown: Array<{
|
||||
label: string
|
||||
detail: string
|
||||
count: number
|
||||
}>
|
||||
}
|
||||
|
||||
export const analyticsApi = {
|
||||
usage: (params: { period: AnalyticsPeriod; service: AnalyticsService; group_by: AnalyticsGroupBy }) => {
|
||||
const query = new URLSearchParams({
|
||||
period: params.period,
|
||||
service: params.service,
|
||||
group_by: params.group_by,
|
||||
})
|
||||
return jsonRequest<UsageAnalytics>(`/api/v1/analytics/usage?${query.toString()}`)
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue