mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-01 08:59:46 +02:00
feat: add vonage telephony (#35)
* refactor: telephony integration * feat: add vonage telephony
This commit is contained in:
parent
6503d806c5
commit
4cfdc3d420
39 changed files with 3382 additions and 335 deletions
|
|
@ -25,10 +25,18 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
|
||||
// TODO: Make UI provider-agnostic
|
||||
interface TelephonyConfigForm {
|
||||
provider: string;
|
||||
account_sid: string;
|
||||
auth_token: string;
|
||||
// Twilio fields
|
||||
account_sid?: string;
|
||||
auth_token?: string;
|
||||
// Vonage fields
|
||||
application_id?: string;
|
||||
private_key?: string;
|
||||
api_key?: string;
|
||||
api_secret?: string;
|
||||
// Common field
|
||||
from_number: string;
|
||||
}
|
||||
|
||||
|
|
@ -70,13 +78,26 @@ export default function ConfigureTelephonyPage() {
|
|||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
if (!response.error && response.data?.twilio) {
|
||||
setHasExistingConfig(true);
|
||||
// Masked values like "****************def0" from backend
|
||||
setValue("account_sid", response.data.twilio.account_sid);
|
||||
setValue("auth_token", response.data.twilio.auth_token);
|
||||
if (response.data.twilio.from_numbers?.length > 0) {
|
||||
setValue("from_number", response.data.twilio.from_numbers[0]);
|
||||
if (!response.error) {
|
||||
// Simple single provider config
|
||||
if (response.data?.twilio) {
|
||||
setHasExistingConfig(true);
|
||||
setValue("provider", "twilio");
|
||||
setValue("account_sid", response.data.twilio.account_sid);
|
||||
setValue("auth_token", response.data.twilio.auth_token);
|
||||
if (response.data.twilio.from_numbers?.length > 0) {
|
||||
setValue("from_number", response.data.twilio.from_numbers[0]);
|
||||
}
|
||||
} else if (response.data?.vonage) {
|
||||
setHasExistingConfig(true);
|
||||
setValue("provider", "vonage");
|
||||
setValue("application_id", response.data.vonage.application_id);
|
||||
setValue("private_key", response.data.vonage.private_key);
|
||||
setValue("api_key", response.data.vonage.api_key || "");
|
||||
setValue("api_secret", response.data.vonage.api_secret || "");
|
||||
if (response.data.vonage.from_numbers?.length > 0) {
|
||||
setValue("from_number", response.data.vonage.from_numbers[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -92,14 +113,26 @@ export default function ConfigureTelephonyPage() {
|
|||
|
||||
try {
|
||||
const accessToken = await getAccessToken();
|
||||
|
||||
// Build the request body based on provider
|
||||
let requestBody: any = {
|
||||
provider: data.provider,
|
||||
from_numbers: [data.from_number],
|
||||
};
|
||||
|
||||
if (data.provider === "twilio") {
|
||||
requestBody.account_sid = data.account_sid;
|
||||
requestBody.auth_token = data.auth_token;
|
||||
} else if (data.provider === "vonage") {
|
||||
requestBody.application_id = data.application_id;
|
||||
requestBody.private_key = data.private_key;
|
||||
requestBody.api_key = data.api_key;
|
||||
requestBody.api_secret = data.api_secret;
|
||||
}
|
||||
|
||||
const response = await saveTelephonyConfigurationApiV1OrganizationsTelephonyConfigPost({
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
body: {
|
||||
provider: data.provider,
|
||||
account_sid: data.account_sid,
|
||||
auth_token: data.auth_token,
|
||||
from_numbers: [data.from_number],
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
|
|
@ -177,8 +210,14 @@ export default function ConfigureTelephonyPage() {
|
|||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="twilio">Twilio</SelectItem>
|
||||
<SelectItem value="vonage">Vonage</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{hasExistingConfig && (
|
||||
<p className="text-sm text-amber-600">
|
||||
⚠️ Switching providers will require entering new credentials
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Twilio-specific fields */}
|
||||
|
|
@ -249,6 +288,87 @@ export default function ConfigureTelephonyPage() {
|
|||
</>
|
||||
)}
|
||||
|
||||
{/* Vonage-specific fields */}
|
||||
{selectedProvider === "vonage" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="application_id">Application ID</Label>
|
||||
<Input
|
||||
id="application_id"
|
||||
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
{...register("application_id", {
|
||||
required: selectedProvider === "vonage" ? "Application ID is required" : false,
|
||||
})}
|
||||
/>
|
||||
{errors.application_id && (
|
||||
<p className="text-sm text-red-500">
|
||||
{errors.application_id.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="private_key">Private Key</Label>
|
||||
<textarea
|
||||
id="private_key"
|
||||
className="w-full min-h-[100px] px-3 py-2 text-sm border rounded-md"
|
||||
placeholder="-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----"
|
||||
{...register("private_key", {
|
||||
required: selectedProvider === "vonage" && !hasExistingConfig
|
||||
? "Private key is required"
|
||||
: false,
|
||||
})}
|
||||
/>
|
||||
{errors.private_key && (
|
||||
<p className="text-sm text-red-500">
|
||||
{errors.private_key.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api_key">API Key (Optional)</Label>
|
||||
<Input
|
||||
id="api_key"
|
||||
placeholder="Optional - for some operations"
|
||||
{...register("api_key")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api_secret">API Secret (Optional)</Label>
|
||||
<Input
|
||||
id="api_secret"
|
||||
type="password"
|
||||
placeholder="Optional - for webhook verification"
|
||||
{...register("api_secret")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="from_number">From Phone Number</Label>
|
||||
<Input
|
||||
id="from_number"
|
||||
autoComplete="tel"
|
||||
placeholder="14155551234 (no + prefix for Vonage)"
|
||||
{...register("from_number", {
|
||||
required: "Phone number is required",
|
||||
pattern: {
|
||||
value: /^[1-9]\d{1,14}$/,
|
||||
message:
|
||||
"Enter a valid phone number without + prefix (e.g., 14155551234)",
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{errors.from_number && (
|
||||
<p className="text-sm text-red-500">
|
||||
{errors.from_number.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useRouter } from "next/navigation";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { PhoneInput } from 'react-international-phone';
|
||||
|
||||
import { getTelephonyConfigurationApiV1OrganizationsTelephonyConfigGet, initiateCallApiV1TwilioInitiateCallPost } from '@/client/sdk.gen';
|
||||
import { getTelephonyConfigurationApiV1OrganizationsTelephonyConfigGet, initiateCallApiV1TelephonyInitiateCallPost } from '@/client/sdk.gen';
|
||||
import { WorkflowError } from '@/client/types.gen';
|
||||
import { FlowEdge, FlowNode } from "@/components/flow/types";
|
||||
import { OnboardingTooltip } from '@/components/onboarding/OnboardingTooltip';
|
||||
|
|
@ -117,7 +117,8 @@ const WorkflowHeader = ({ isDirty, workflowName, rfInstance, onRun, workflowId,
|
|||
});
|
||||
|
||||
// If no configuration exists, show configure dialog
|
||||
if (configResponse.error || !configResponse.data?.twilio) {
|
||||
// Check if any telephony provider is configured (Twilio or Vonage)
|
||||
if (configResponse.error || (!configResponse.data?.twilio && !configResponse.data?.vonage)) {
|
||||
setConfigureDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
|
|
@ -151,8 +152,11 @@ const WorkflowHeader = ({ isDirty, workflowName, rfInstance, onRun, workflowId,
|
|||
}
|
||||
|
||||
// Configuration exists, proceed with call initiation
|
||||
const response = await initiateCallApiV1TwilioInitiateCallPost({
|
||||
body: { workflow_id: workflowId },
|
||||
const response = await initiateCallApiV1TelephonyInitiateCallPost({
|
||||
body: {
|
||||
workflow_id: workflowId,
|
||||
phone_number: phoneNumber
|
||||
},
|
||||
headers: { 'Authorization': `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue