198 lines
8.2 KiB
TypeScript
198 lines
8.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Bot, Eye, EyeOff, Loader2, CheckCircle2, XCircle, Plug, Building2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface CloseBotCardProps {
|
|
settings: Record<string, string>;
|
|
onSave: (updates: Record<string, string>) => Promise<void>;
|
|
connected: boolean | null;
|
|
onTestConnection: () => Promise<void>;
|
|
testing: boolean;
|
|
agencyName?: string;
|
|
sourcesCount?: number;
|
|
}
|
|
|
|
function maskApiKey(val: string): string {
|
|
if (!val || val.length < 10) return val ? '••••••••' : '';
|
|
const prefix = val.slice(0, 3);
|
|
const suffix = val.slice(-4);
|
|
return `${prefix}••••••••${suffix}`;
|
|
}
|
|
|
|
export default function CloseBotCard({ settings, onSave, connected, onTestConnection, testing, agencyName, sourcesCount }: CloseBotCardProps) {
|
|
const [apiKey, setApiKey] = useState('');
|
|
const [webhookSourceId, setWebhookSourceId] = useState('');
|
|
const [showKey, setShowKey] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
const [editing, setEditing] = useState(false);
|
|
|
|
const envKey = settings.closebot_api_key || '';
|
|
const envSourceId = settings.closebot_webhook_source_id || '';
|
|
|
|
async function handleSave() {
|
|
setSaving(true);
|
|
try {
|
|
const updates: Record<string, string> = {};
|
|
if (apiKey) updates.closebot_api_key = apiKey;
|
|
if (webhookSourceId) updates.closebot_webhook_source_id = webhookSourceId;
|
|
await onSave(updates);
|
|
setEditing(false);
|
|
setApiKey('');
|
|
setWebhookSourceId('');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="card p-6 space-y-5">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-500/10 border border-purple-500/20">
|
|
<Bot className="h-5 w-5 text-purple-400" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-white">CloseBot Connection</h3>
|
|
<p className="text-xs text-slate-500">AI chatbot API credentials</p>
|
|
</div>
|
|
</div>
|
|
{/* Connection status */}
|
|
<div className="flex items-center gap-2">
|
|
{connected === null ? (
|
|
<span className="text-xs text-slate-500">Not tested</span>
|
|
) : connected ? (
|
|
<>
|
|
<div className="h-2.5 w-2.5 rounded-full bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.5)]" />
|
|
<span className="text-xs font-medium text-green-400">Connected</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="h-2.5 w-2.5 rounded-full bg-red-500 shadow-[0_0_6px_rgba(239,68,68,0.5)]" />
|
|
<span className="text-xs font-medium text-red-400">Disconnected</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Agency Info — shown when connected */}
|
|
{connected && (agencyName || sourcesCount !== undefined) && (
|
|
<div className="rounded-lg border border-green-500/20 bg-green-500/5 p-4 space-y-2.5">
|
|
{agencyName && (
|
|
<div className="flex items-center gap-2.5">
|
|
<Building2 className="h-4 w-4 text-green-400 flex-shrink-0" />
|
|
<div>
|
|
<p className="text-[10px] uppercase tracking-wider text-slate-500 font-medium">Agency</p>
|
|
<p className="text-sm font-semibold text-white">{agencyName}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{sourcesCount !== undefined && (
|
|
<div className="flex items-center gap-2.5">
|
|
<Plug className="h-4 w-4 text-cyan-400 flex-shrink-0" />
|
|
<div>
|
|
<p className="text-[10px] uppercase tracking-wider text-slate-500 font-medium">Active Sources</p>
|
|
<p className="text-sm font-semibold text-white">{sourcesCount} connected</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Fields */}
|
|
<div className="space-y-4">
|
|
{/* API Key */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-slate-400 mb-1.5">API Key</label>
|
|
{editing ? (
|
|
<div className="relative">
|
|
<input
|
|
type={showKey ? 'text' : 'password'}
|
|
value={apiKey}
|
|
onChange={(e) => setApiKey(e.target.value)}
|
|
placeholder="cb_xxxxxxxxxxxxxxxxxxxxxxxx"
|
|
className="w-full rounded-lg border border-slate-700/50 bg-slate-800/50 px-3 py-2 pr-10 text-sm text-slate-200 placeholder-slate-600 outline-none transition-colors focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 font-mono"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowKey(!showKey)}
|
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-slate-500 hover:text-slate-300 transition-colors"
|
|
>
|
|
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="w-full rounded-lg border border-slate-700/30 bg-slate-800/30 px-3 py-2 text-sm text-slate-400 font-mono">
|
|
{envKey ? maskApiKey(envKey) : <span className="text-slate-600 italic">Not configured</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Webhook Source ID */}
|
|
<div>
|
|
<label className="block text-xs font-medium text-slate-400 mb-1.5">Webhook Source ID</label>
|
|
{editing ? (
|
|
<input
|
|
type="text"
|
|
value={webhookSourceId}
|
|
onChange={(e) => setWebhookSourceId(e.target.value)}
|
|
placeholder="Source ID from CloseBot webhook settings"
|
|
className="w-full rounded-lg border border-slate-700/50 bg-slate-800/50 px-3 py-2 text-sm text-slate-200 placeholder-slate-600 outline-none transition-colors focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 font-mono"
|
|
/>
|
|
) : (
|
|
<div className="w-full rounded-lg border border-slate-700/30 bg-slate-800/30 px-3 py-2 text-sm text-slate-400 font-mono">
|
|
{envSourceId || <span className="text-slate-600 italic">Not configured</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center gap-3 pt-2">
|
|
<button
|
|
onClick={onTestConnection}
|
|
disabled={testing}
|
|
className="flex items-center gap-2 rounded-lg border border-slate-700/50 bg-slate-800/50 px-4 py-2 text-xs font-medium text-slate-300 transition-all hover:bg-slate-700/50 hover:text-white disabled:opacity-50"
|
|
>
|
|
{testing ? (
|
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
) : connected ? (
|
|
<CheckCircle2 className="h-3.5 w-3.5 text-green-400" />
|
|
) : (
|
|
<XCircle className="h-3.5 w-3.5 text-slate-500" />
|
|
)}
|
|
Test Connection
|
|
</button>
|
|
|
|
{editing ? (
|
|
<div className="flex items-center gap-2 ml-auto">
|
|
<button
|
|
onClick={() => { setEditing(false); setApiKey(''); setWebhookSourceId(''); }}
|
|
className="rounded-lg border border-slate-700/50 px-4 py-2 text-xs font-medium text-slate-400 transition-colors hover:text-white"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={saving || (!apiKey && !webhookSourceId)}
|
|
className="flex items-center gap-2 rounded-lg bg-cyan-500/20 border border-cyan-500/30 px-4 py-2 text-xs font-medium text-cyan-400 transition-all hover:bg-cyan-500/30 disabled:opacity-50"
|
|
>
|
|
{saving && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<button
|
|
onClick={() => setEditing(true)}
|
|
className="ml-auto rounded-lg bg-slate-700/50 border border-slate-600/30 px-4 py-2 text-xs font-medium text-slate-300 transition-all hover:bg-slate-600/50 hover:text-white"
|
|
>
|
|
Edit Credentials
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|