825 lines
24 KiB
TypeScript
825 lines
24 KiB
TypeScript
import { API_BASE_URL } from './apiBase'
|
|
|
|
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()}`)
|
|
},
|
|
}
|
|
|
|
export type WhatsAppSetup = {
|
|
webhook_url: string
|
|
verify_token_configured: boolean
|
|
app_secret_configured: boolean
|
|
access_token_configured: boolean
|
|
phone_number_id: string | null
|
|
business_account_id: string | null
|
|
meta_app_id: string | null
|
|
api_version: string
|
|
recommended_fields: string[]
|
|
docs_url: string
|
|
}
|
|
|
|
export type WhatsAppWebhookEvent = {
|
|
id: string
|
|
waba_id: string | null
|
|
phone_number_id: string | null
|
|
field: string
|
|
event_type: string
|
|
from_wa_id: string | null
|
|
message_id: string | null
|
|
status: string | null
|
|
created_at: string
|
|
}
|
|
|
|
export const whatsappApi = {
|
|
setup: () => jsonRequest<WhatsAppSetup>('/api/v1/whatsapp/setup'),
|
|
events: () => jsonRequest<WhatsAppWebhookEvent[]>('/api/v1/whatsapp/events'),
|
|
sendText: (payload: { to: string; body: string }) =>
|
|
jsonRequest<Record<string, unknown>>('/api/v1/whatsapp/messages/send', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
}
|