Files
helix-engage/src/pages/integrations.tsx
saridsa2 567f9f2d72 feat: agent detail page, campaign edit slideout, integration config, auth persistence
Agent Detail (/agent/:id):
- Individual agent performance page with KPI cards + call log table
- Clickable agent names in dashboard table link to detail view
- Back button to Team Dashboard

Campaign Edit Slideout:
- Edit button on each campaign card
- Slideout with name, status, budget, dates
- Saves via updateCampaign GraphQL mutation

Integration Config Slideout:
- Configure button on each integration card
- Per-integration form fields (Ozonetel, WhatsApp, Facebook, etc.)
- Copy webhook URL, OAuth placeholder buttons

Auth Persistence:
- User data persisted to localStorage on login
- Session restored on page refresh — no more logout on F5
- Stale tokens cleaned up automatically

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:32:58 +05:30

240 lines
9.8 KiB
TypeScript

import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faPhone,
faCommentDots,
faSquare,
faMagnifyingGlass,
faCamera,
faGlobe,
faEnvelope,
faCopy,
faCircleCheck,
faCircleXmark,
faGear,
} from '@fortawesome/pro-duotone-svg-icons';
import { Badge } from '@/components/base/badges/badges';
import { Button } from '@/components/base/buttons/button';
import { TopBar } from '@/components/layout/top-bar';
import { IntegrationEditSlideout } from '@/components/integrations/integration-edit-slideout';
import { notify } from '@/lib/toast';
type IntegrationType = 'ozonetel' | 'whatsapp' | 'facebook' | 'google' | 'instagram' | 'website' | 'email';
type IntegrationConfig = {
type: IntegrationType;
name: string;
details: { label: string; value: string }[];
webhookUrl?: string;
};
type IntegrationStatus = 'connected' | 'disconnected' | 'configured';
type IntegrationCardProps = {
name: string;
description: string;
icon: any;
iconColor: string;
status: IntegrationStatus;
details: { label: string; value: string }[];
webhookUrl?: string;
onConfigure?: () => void;
};
const statusConfig: Record<IntegrationStatus, { color: 'success' | 'error' | 'warning'; label: string }> = {
connected: { color: 'success', label: 'Connected' },
disconnected: { color: 'error', label: 'Not Connected' },
configured: { color: 'warning', label: 'Configured' },
};
const IntegrationCard = ({ name, description, icon, iconColor, status, details, webhookUrl, onConfigure }: IntegrationCardProps) => {
const statusCfg = statusConfig[status];
const copyWebhook = () => {
if (webhookUrl) {
navigator.clipboard.writeText(webhookUrl);
notify.success('Copied', 'Webhook URL copied to clipboard');
}
};
return (
<div className="rounded-xl border border-secondary bg-primary p-5 shadow-xs">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-lg bg-secondary">
<FontAwesomeIcon icon={icon} className={`size-5 ${iconColor}`} />
</div>
<div>
<h3 className="text-sm font-semibold text-primary">{name}</h3>
<p className="text-xs text-tertiary">{description}</p>
</div>
</div>
<div className="flex items-center gap-2">
{onConfigure && (
<Button
size="sm"
color="secondary"
iconLeading={({ className }: { className?: string }) => (
<FontAwesomeIcon icon={faGear} className={className} />
)}
onClick={onConfigure}
>
Configure
</Button>
)}
<Badge size="sm" color={statusCfg.color} type="pill-color">
<FontAwesomeIcon icon={status === 'connected' ? faCircleCheck : faCircleXmark} className="mr-1 size-3" />
{statusCfg.label}
</Badge>
</div>
</div>
{details.length > 0 && (
<div className="mt-4 space-y-2">
{details.map((d) => (
<div key={d.label} className="flex items-center justify-between text-xs">
<span className="text-tertiary">{d.label}</span>
<span className="font-medium text-secondary">{d.value}</span>
</div>
))}
</div>
)}
{webhookUrl && (
<div className="mt-4 flex items-center gap-2 rounded-lg bg-secondary p-3">
<code className="flex-1 truncate text-xs text-secondary">{webhookUrl}</code>
<Button size="sm" color="secondary" iconLeading={({ className }: { className?: string }) => (
<FontAwesomeIcon icon={faCopy} className={className} />
)} onClick={copyWebhook}>
Copy
</Button>
</div>
)}
</div>
);
};
export const IntegrationsPage = () => {
const webhookBase = 'https://engage-api.srv1477139.hstgr.cloud';
const [editIntegration, setEditIntegration] = useState<IntegrationConfig | null>(null);
const openEdit = (config: IntegrationConfig) => setEditIntegration(config);
return (
<div className="flex flex-1 flex-col overflow-hidden">
<TopBar title="Integrations" subtitle="Manage external service connections" />
<div className="flex-1 overflow-y-auto p-6 space-y-4">
{/* Telephony */}
<IntegrationCard
name="Ozonetel CloudAgent"
description="Cloud telephony — inbound/outbound calls, SIP softphone, IVR"
icon={faPhone}
iconColor="text-fg-brand-primary"
status="connected"
details={[
{ label: 'Account', value: 'global_healthx' },
{ label: 'Agent ID', value: 'global' },
{ label: 'SIP Extension', value: '523590' },
{ label: 'Inbound Campaign', value: 'Inbound_918041763265' },
{ label: 'DID Number', value: '+91 804 176 3265' },
]}
webhookUrl={`${webhookBase}/webhooks/ozonetel/missed-call`}
onConfigure={() => openEdit({
type: 'ozonetel',
name: 'Ozonetel CloudAgent',
details: [
{ label: 'Account', value: 'global_healthx' },
{ label: 'Agent ID', value: 'global' },
{ label: 'SIP Extension', value: '523590' },
{ label: 'Inbound Campaign', value: 'Inbound_918041763265' },
],
})}
/>
{/* WhatsApp */}
<IntegrationCard
name="WhatsApp Business"
description="Send templates, receive messages, automate outreach"
icon={faCommentDots}
iconColor="text-green-600"
status="disconnected"
details={[]}
onConfigure={() => openEdit({ type: 'whatsapp', name: 'WhatsApp Business', details: [] })}
/>
{/* Facebook Lead Ads */}
<IntegrationCard
name="Facebook Lead Ads"
description="Auto-import leads from Facebook ad campaigns"
icon={faSquare}
iconColor="text-blue-600"
status="disconnected"
details={[]}
onConfigure={() => openEdit({ type: 'facebook', name: 'Facebook Lead Ads', details: [] })}
/>
{/* Google Ads */}
<IntegrationCard
name="Google Ads"
description="Sync leads from Google Search and Display campaigns"
icon={faMagnifyingGlass}
iconColor="text-red-500"
status="disconnected"
details={[]}
onConfigure={() => openEdit({ type: 'google', name: 'Google Ads', details: [] })}
/>
{/* Instagram */}
<IntegrationCard
name="Instagram Lead Ads"
description="Capture leads from Instagram ad forms"
icon={faCamera}
iconColor="text-pink-600"
status="disconnected"
details={[]}
onConfigure={() => openEdit({ type: 'instagram', name: 'Instagram Lead Ads', details: [] })}
/>
{/* Website */}
<IntegrationCard
name="Website Lead Forms"
description="Capture leads from website contact forms via webhook"
icon={faGlobe}
iconColor="text-fg-brand-primary"
status="configured"
details={[
{ label: 'Method', value: 'POST webhook' },
]}
webhookUrl={`${webhookBase}/webhooks/website/lead`}
onConfigure={() => openEdit({
type: 'website',
name: 'Website Lead Forms',
details: [{ label: 'Method', value: 'POST webhook' }],
webhookUrl: `${webhookBase}/webhooks/website/lead`,
})}
/>
{/* Email */}
<IntegrationCard
name="Email (SMTP)"
description="Outbound email for campaigns and notifications"
icon={faEnvelope}
iconColor="text-fg-quaternary"
status="disconnected"
details={[]}
onConfigure={() => openEdit({ type: 'email', name: 'Email (SMTP)', details: [] })}
/>
</div>
{editIntegration && (
<IntegrationEditSlideout
isOpen={!!editIntegration}
onOpenChange={(open) => { if (!open) setEditIntegration(null); }}
integration={editIntegration}
/>
)}
</div>
);
};