@campxdev/campx-web-utils 2.0.14 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/types/src/components/Exotel/ExotelPhone.d.ts +2 -2
- package/dist/cjs/types/src/services/exotel/api.d.ts +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/Exotel/ExotelPhone.d.ts +2 -2
- package/dist/esm/types/src/services/exotel/api.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/Exotel/CallButton.tsx +35 -1
- package/src/components/Exotel/CallDispositionForm.tsx +1 -1
- package/src/components/Exotel/ExotelPhone.tsx +109 -104
- package/src/providers/ExotelProvider.tsx +10 -2
- package/src/services/exotel/api.ts +131 -159
- package/src/utils/constants.ts +1 -0
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import { axios } from '../../config/axios'
|
|
2
|
-
import { workspace } from '../../utils/constants'
|
|
1
|
+
import { axios } from '../../config/axios';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Voice config from notification-configs API
|
|
6
5
|
*/
|
|
7
6
|
export interface VoiceConfig {
|
|
8
|
-
id: string
|
|
9
|
-
isEnabled: boolean
|
|
10
|
-
name: string
|
|
11
|
-
channelType: string
|
|
7
|
+
id: string;
|
|
8
|
+
isEnabled: boolean;
|
|
9
|
+
name: string;
|
|
10
|
+
channelType: string;
|
|
12
11
|
EXOTEL?: {
|
|
13
|
-
appId: string
|
|
14
|
-
appSecret: string
|
|
15
|
-
token?: string
|
|
16
|
-
tokenExpiresAt?: string
|
|
17
|
-
}
|
|
12
|
+
appId: string;
|
|
13
|
+
appSecret: string;
|
|
14
|
+
token?: string;
|
|
15
|
+
tokenExpiresAt?: string;
|
|
16
|
+
};
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -22,17 +21,17 @@ export interface VoiceConfig {
|
|
|
22
21
|
*/
|
|
23
22
|
export interface NotificationConfigResponse {
|
|
24
23
|
config: {
|
|
25
|
-
id: string
|
|
26
|
-
tenantId: string
|
|
27
|
-
institutionUniqueId: number
|
|
28
|
-
enableVoice?: boolean
|
|
29
|
-
voiceConfigs?: VoiceConfig[]
|
|
24
|
+
id: string;
|
|
25
|
+
tenantId: string;
|
|
26
|
+
institutionUniqueId: number;
|
|
27
|
+
enableVoice?: boolean;
|
|
28
|
+
voiceConfigs?: VoiceConfig[];
|
|
30
29
|
// ... other fields not relevant to VoIP
|
|
31
|
-
}
|
|
30
|
+
};
|
|
32
31
|
options: {
|
|
33
|
-
voiceChannels?: string[]
|
|
32
|
+
voiceChannels?: string[];
|
|
34
33
|
// ... other fields
|
|
35
|
-
}
|
|
34
|
+
};
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
@@ -42,28 +41,28 @@ export interface NotificationConfigResponse {
|
|
|
42
41
|
* - voiceConfigs has at least one enabled config
|
|
43
42
|
*/
|
|
44
43
|
export const checkVoIPEnabled = async (): Promise<{
|
|
45
|
-
enabled: boolean
|
|
46
|
-
hasValidToken: boolean
|
|
44
|
+
enabled: boolean;
|
|
45
|
+
hasValidToken: boolean;
|
|
47
46
|
}> => {
|
|
48
47
|
try {
|
|
49
48
|
const { data } = await axios.get<NotificationConfigResponse>(
|
|
50
49
|
'/notifications/notification-configs',
|
|
51
|
-
)
|
|
50
|
+
);
|
|
52
51
|
|
|
53
|
-
const config = data?.config
|
|
52
|
+
const config = data?.config;
|
|
54
53
|
|
|
55
54
|
// Check if voice is enabled at tenant level
|
|
56
55
|
if (!config?.enableVoice) {
|
|
57
|
-
return { enabled: false, hasValidToken: false }
|
|
56
|
+
return { enabled: false, hasValidToken: false };
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
// Check if there's at least one enabled voice config
|
|
61
60
|
const enabledVoiceConfig = config.voiceConfigs?.find(
|
|
62
61
|
(vc) => vc.isEnabled === true,
|
|
63
|
-
)
|
|
62
|
+
);
|
|
64
63
|
|
|
65
64
|
if (!enabledVoiceConfig) {
|
|
66
|
-
return { enabled: false, hasValidToken: false }
|
|
65
|
+
return { enabled: false, hasValidToken: false };
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
// Check if token exists and is not expired
|
|
@@ -71,116 +70,117 @@ export const checkVoIPEnabled = async (): Promise<{
|
|
|
71
70
|
enabledVoiceConfig.EXOTEL?.token &&
|
|
72
71
|
enabledVoiceConfig.EXOTEL?.tokenExpiresAt &&
|
|
73
72
|
new Date(enabledVoiceConfig.EXOTEL.tokenExpiresAt) > new Date()
|
|
74
|
-
)
|
|
73
|
+
);
|
|
75
74
|
|
|
76
|
-
return { enabled: true, hasValidToken }
|
|
75
|
+
return { enabled: true, hasValidToken };
|
|
77
76
|
} catch (error) {
|
|
78
|
-
console.error('Failed to check VoIP config:', error)
|
|
79
|
-
return { enabled: false, hasValidToken: false }
|
|
77
|
+
console.error('Failed to check VoIP config:', error);
|
|
78
|
+
return { enabled: false, hasValidToken: false };
|
|
80
79
|
}
|
|
81
|
-
}
|
|
80
|
+
};
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* Get the active Exotel voice token from the backend.
|
|
85
84
|
* The backend manages credentials securely and auto-refreshes tokens.
|
|
86
85
|
*/
|
|
87
86
|
export const getActiveVoiceToken = async (): Promise<{
|
|
88
|
-
token: string
|
|
89
|
-
expiresAt: string
|
|
87
|
+
token: string;
|
|
88
|
+
expiresAt: string;
|
|
90
89
|
} | null> => {
|
|
91
90
|
try {
|
|
92
91
|
const { data } = await axios.get(
|
|
93
92
|
'/notifications/notification-configs/voice-config/active-token',
|
|
94
|
-
)
|
|
95
|
-
return data
|
|
93
|
+
);
|
|
94
|
+
return data;
|
|
96
95
|
} catch (error) {
|
|
97
|
-
console.error('Failed to fetch Exotel token:', error)
|
|
98
|
-
return null
|
|
96
|
+
console.error('Failed to fetch Exotel token:', error);
|
|
97
|
+
return null;
|
|
99
98
|
}
|
|
100
|
-
}
|
|
99
|
+
};
|
|
101
100
|
|
|
102
101
|
/**
|
|
103
102
|
* @deprecated Use getActiveVoiceToken instead.
|
|
104
103
|
* This function is kept for backwards compatibility but will be removed.
|
|
105
104
|
*/
|
|
106
105
|
export const createExotelAppToken = async () => {
|
|
107
|
-
const tokenData = await getActiveVoiceToken()
|
|
106
|
+
const tokenData = await getActiveVoiceToken();
|
|
108
107
|
if (!tokenData) {
|
|
109
|
-
throw new Error('No active voice configuration found')
|
|
108
|
+
throw new Error('No active voice configuration found');
|
|
110
109
|
}
|
|
111
|
-
return { Data: tokenData.token }
|
|
112
|
-
}
|
|
110
|
+
return { Data: tokenData.token };
|
|
111
|
+
};
|
|
113
112
|
|
|
114
113
|
// ============ Call Activity APIs ============
|
|
115
114
|
|
|
116
115
|
export interface CallActivity {
|
|
117
|
-
id: string
|
|
118
|
-
callSid: string
|
|
119
|
-
prospectId: string
|
|
120
|
-
direction: 'inbound' | 'outbound'
|
|
121
|
-
status: string
|
|
122
|
-
durationSeconds?: number
|
|
123
|
-
createdAt: string
|
|
124
|
-
startTime?: string
|
|
125
|
-
endTime?: string
|
|
126
|
-
fromNumber?: string
|
|
127
|
-
toNumber?: string
|
|
128
|
-
userId?: number
|
|
129
|
-
dispositionCategory?: string
|
|
130
|
-
dispositionReason?: string
|
|
131
|
-
dispositionNotes?: string
|
|
132
|
-
callStatusDisposition?: 'connected' | 'not_connected'
|
|
133
|
-
recordingUrl?: string
|
|
116
|
+
id: string;
|
|
117
|
+
callSid: string;
|
|
118
|
+
prospectId: string;
|
|
119
|
+
direction: 'inbound' | 'outbound';
|
|
120
|
+
status: string;
|
|
121
|
+
durationSeconds?: number;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
startTime?: string;
|
|
124
|
+
endTime?: string;
|
|
125
|
+
fromNumber?: string;
|
|
126
|
+
toNumber?: string;
|
|
127
|
+
userId?: number;
|
|
128
|
+
dispositionCategory?: string;
|
|
129
|
+
dispositionReason?: string;
|
|
130
|
+
dispositionNotes?: string;
|
|
131
|
+
callStatusDisposition?: 'connected' | 'not_connected';
|
|
132
|
+
recordingUrl?: string;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
export interface InitiateCallInput {
|
|
137
|
-
prospectId: string
|
|
138
|
-
toNumber: string
|
|
139
|
-
fromNumber?: string
|
|
140
|
-
userId?: number
|
|
141
|
-
callSid?: string // Exotel CallSid from SDK response
|
|
136
|
+
prospectId: string;
|
|
137
|
+
toNumber: string;
|
|
138
|
+
fromNumber?: string;
|
|
139
|
+
userId?: number;
|
|
140
|
+
callSid?: string; // Exotel CallSid from SDK response
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
export interface SaveDispositionInput {
|
|
145
|
-
callId: string
|
|
146
|
-
prospectId?: string
|
|
147
|
-
callStatusDisposition?: 'connected' | 'not_connected'
|
|
148
|
-
dispositionCategory: string
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
callId: string;
|
|
145
|
+
prospectId?: string;
|
|
146
|
+
callStatusDisposition?: 'connected' | 'not_connected'; // Step 1: Call Status
|
|
147
|
+
dispositionCategory: string; // Step 2: Call Disposition
|
|
148
|
+
prospectStatus?: string; // Step 3: Prospect Status (optional)
|
|
149
|
+
dispositionReason?: string; // Deprecated
|
|
150
|
+
dispositionNotes?: string; // Step 4: Notes/Remarks
|
|
151
|
+
callbackScheduledAt?: string;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
export interface CancelCallInput {
|
|
155
|
-
callSid: string
|
|
156
|
-
reason?: string // 'declined' | 'cancelled' | 'missed' | 'timeout'
|
|
155
|
+
callSid: string;
|
|
156
|
+
reason?: string; // 'declined' | 'cancelled' | 'missed' | 'timeout'
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
export interface CallActivityResponse {
|
|
160
|
-
success: boolean
|
|
161
|
-
message: string
|
|
162
|
-
data: CallActivity
|
|
160
|
+
success: boolean;
|
|
161
|
+
message: string;
|
|
162
|
+
data: CallActivity;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export interface RegisterIncomingCallInput {
|
|
166
|
-
callSid: string
|
|
167
|
-
fromNumber: string
|
|
168
|
-
toNumber?: string
|
|
169
|
-
prospectId?: string
|
|
170
|
-
userId?: number
|
|
166
|
+
callSid: string;
|
|
167
|
+
fromNumber: string;
|
|
168
|
+
toNumber?: string;
|
|
169
|
+
prospectId?: string;
|
|
170
|
+
userId?: number;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export interface RegisterIncomingCallResponse {
|
|
174
|
-
success: boolean
|
|
175
|
-
message: string
|
|
176
|
-
data: CallActivity
|
|
174
|
+
success: boolean;
|
|
175
|
+
message: string;
|
|
176
|
+
data: CallActivity;
|
|
177
177
|
prospect?: {
|
|
178
|
-
id: string
|
|
179
|
-
uniqueId?: number
|
|
180
|
-
prospectId?: string // Formatted ID like PRSP-000001
|
|
181
|
-
name: string
|
|
182
|
-
mobile: string
|
|
183
|
-
}
|
|
178
|
+
id: string;
|
|
179
|
+
uniqueId?: number;
|
|
180
|
+
prospectId?: string; // Formatted ID like PRSP-000001
|
|
181
|
+
name: string;
|
|
182
|
+
mobile: string;
|
|
183
|
+
};
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
@@ -189,14 +189,12 @@ export interface RegisterIncomingCallResponse {
|
|
|
189
189
|
export const initiateCallActivity = async (
|
|
190
190
|
input: InitiateCallInput,
|
|
191
191
|
): Promise<{ success: boolean; message: string; data: CallActivity }> => {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return data
|
|
199
|
-
}
|
|
192
|
+
const { data } = await axios.post(
|
|
193
|
+
`/communication-activities/initiate`,
|
|
194
|
+
input,
|
|
195
|
+
);
|
|
196
|
+
return data;
|
|
197
|
+
};
|
|
200
198
|
|
|
201
199
|
/**
|
|
202
200
|
* Register an incoming call - creates call activity record
|
|
@@ -204,14 +202,12 @@ export const initiateCallActivity = async (
|
|
|
204
202
|
export const registerIncomingCall = async (
|
|
205
203
|
input: RegisterIncomingCallInput,
|
|
206
204
|
): Promise<RegisterIncomingCallResponse> => {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return data
|
|
214
|
-
}
|
|
205
|
+
const { data } = await axios.post(
|
|
206
|
+
`/communication-activities/register-incoming`,
|
|
207
|
+
input,
|
|
208
|
+
);
|
|
209
|
+
return data;
|
|
210
|
+
};
|
|
215
211
|
|
|
216
212
|
/**
|
|
217
213
|
* Fetch call history for a prospect
|
|
@@ -219,16 +215,11 @@ export const registerIncomingCall = async (
|
|
|
219
215
|
export const fetchCallHistory = async (
|
|
220
216
|
prospectId: string,
|
|
221
217
|
): Promise<CallActivity[]> => {
|
|
222
|
-
const
|
|
223
|
-
workspace === 'common-workspace'
|
|
224
|
-
? `/paymentx/call-activities`
|
|
225
|
-
: `/call-activities`
|
|
226
|
-
|
|
227
|
-
const { data } = await axios.get(`${endpoint}/by-prospect`, {
|
|
218
|
+
const { data } = await axios.get(`/communication-activities/by-prospect`, {
|
|
228
219
|
params: { prospectId },
|
|
229
|
-
})
|
|
230
|
-
return data
|
|
231
|
-
}
|
|
220
|
+
});
|
|
221
|
+
return data;
|
|
222
|
+
};
|
|
232
223
|
|
|
233
224
|
/**
|
|
234
225
|
* Get call activity by ID
|
|
@@ -236,14 +227,9 @@ export const fetchCallHistory = async (
|
|
|
236
227
|
export const getCallActivityById = async (
|
|
237
228
|
id: string,
|
|
238
229
|
): Promise<CallActivity> => {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
: `/call-activities`
|
|
243
|
-
|
|
244
|
-
const { data } = await axios.get(`${endpoint}/${id}`)
|
|
245
|
-
return data
|
|
246
|
-
}
|
|
230
|
+
const { data } = await axios.get(`/communication-activities/${id}`);
|
|
231
|
+
return data;
|
|
232
|
+
};
|
|
247
233
|
|
|
248
234
|
/**
|
|
249
235
|
* Get call activity by CallSid
|
|
@@ -251,16 +237,11 @@ export const getCallActivityById = async (
|
|
|
251
237
|
export const getCallActivityByCallSid = async (
|
|
252
238
|
callSid: string,
|
|
253
239
|
): Promise<CallActivity> => {
|
|
254
|
-
const
|
|
255
|
-
workspace === 'common-workspace'
|
|
256
|
-
? `/paymentx/call-activities`
|
|
257
|
-
: `/call-activities`
|
|
258
|
-
|
|
259
|
-
const { data } = await axios.get(`${endpoint}/by-call-sid`, {
|
|
240
|
+
const { data } = await axios.get(`/communication-activities/by-call-sid`, {
|
|
260
241
|
params: { callSid },
|
|
261
|
-
})
|
|
262
|
-
return data
|
|
263
|
-
}
|
|
242
|
+
});
|
|
243
|
+
return data;
|
|
244
|
+
};
|
|
264
245
|
|
|
265
246
|
/**
|
|
266
247
|
* Get call count for a prospect
|
|
@@ -268,16 +249,14 @@ export const getCallActivityByCallSid = async (
|
|
|
268
249
|
export const getCallCountByProspect = async (
|
|
269
250
|
prospectId: string,
|
|
270
251
|
): Promise<number> => {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return data?.count || 0
|
|
280
|
-
}
|
|
252
|
+
const { data } = await axios.get(
|
|
253
|
+
`/communication-activities/count-by-prospect`,
|
|
254
|
+
{
|
|
255
|
+
params: { prospectId },
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
return data?.count || 0;
|
|
259
|
+
};
|
|
281
260
|
|
|
282
261
|
/**
|
|
283
262
|
* Save call disposition
|
|
@@ -285,21 +264,20 @@ export const getCallCountByProspect = async (
|
|
|
285
264
|
export const saveCallDisposition = async (
|
|
286
265
|
input: SaveDispositionInput,
|
|
287
266
|
): Promise<CallActivity> => {
|
|
288
|
-
const endpoint =
|
|
289
|
-
workspace === 'common-workspace'
|
|
290
|
-
? `/paymentx/call-activities`
|
|
291
|
-
: `/call-activities`
|
|
292
|
-
|
|
293
267
|
const requestBody = {
|
|
294
268
|
callStatusDisposition: input.callStatusDisposition,
|
|
295
269
|
dispositionCategory: input.dispositionCategory,
|
|
270
|
+
prospectStatus: input.prospectStatus,
|
|
296
271
|
dispositionNotes: input.dispositionNotes,
|
|
297
272
|
callbackScheduledAt: input.callbackScheduledAt,
|
|
298
273
|
};
|
|
299
274
|
|
|
300
|
-
const { data } = await axios.post(
|
|
301
|
-
|
|
302
|
-
|
|
275
|
+
const { data } = await axios.post(
|
|
276
|
+
`/communication-activities/${input.callId}/disposition`,
|
|
277
|
+
requestBody,
|
|
278
|
+
);
|
|
279
|
+
return data;
|
|
280
|
+
};
|
|
303
281
|
|
|
304
282
|
/**
|
|
305
283
|
* Cancel/decline a call before it was connected
|
|
@@ -308,12 +286,6 @@ export const saveCallDisposition = async (
|
|
|
308
286
|
export const cancelCall = async (
|
|
309
287
|
input: CancelCallInput,
|
|
310
288
|
): Promise<{ success: boolean; message: string }> => {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
: `/call-activities`
|
|
315
|
-
|
|
316
|
-
const { data } = await axios.post(`${endpoint}/cancel`, input)
|
|
317
|
-
return data
|
|
318
|
-
}
|
|
319
|
-
|
|
289
|
+
const { data } = await axios.post(`/communication-activities/cancel`, input);
|
|
290
|
+
return data;
|
|
291
|
+
};
|
package/src/utils/constants.ts
CHANGED
|
@@ -26,6 +26,7 @@ export const workspaceApiMapping: Record<string, string> = {
|
|
|
26
26
|
'finance-officer-workspace': 'finance-officer-api',
|
|
27
27
|
'outreach-agent-workspace': 'outreach-agent-api',
|
|
28
28
|
'prospect-workspace': 'prospect-api',
|
|
29
|
+
'external-expert-workspace': 'external-expert-api',
|
|
29
30
|
'research-coordinator-workspace': 'research-coordinator-api',
|
|
30
31
|
'tele-counsellor-workspace': 'tele-counsellor-api',
|
|
31
32
|
'team-owner-workspace': '/team-owner-api',
|