2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Card } from '../ui/card';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { Input } from '../ui/input';
|
|
|
|
|
import { Radio, Plus, Trash2, Zap, AlertTriangle } from 'lucide-react';
|
|
|
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
|
|
|
|
|
|
interface Webhook {
|
2026-01-13 18:47:57 +00:00
|
|
|
id: string;
|
|
|
|
|
url: string;
|
|
|
|
|
events: string[];
|
|
|
|
|
status: 'active' | 'failed';
|
|
|
|
|
lastTriggered: string;
|
2026-01-07 09:31:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MOCK_WEBHOOKS: Webhook[] = [
|
2026-01-13 18:47:57 +00:00
|
|
|
{
|
|
|
|
|
id: 'w1',
|
|
|
|
|
url: 'https://api.myapp.com/veza-hook',
|
|
|
|
|
events: ['track.upload', 'user.update'],
|
|
|
|
|
status: 'active',
|
|
|
|
|
lastTriggered: '2 hours ago',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'w2',
|
|
|
|
|
url: 'https://hooks.slack.com/services/T000...',
|
|
|
|
|
events: ['sales.new'],
|
|
|
|
|
status: 'failed',
|
|
|
|
|
lastTriggered: '1 day ago',
|
|
|
|
|
},
|
2026-01-07 09:31:02 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export const WebhooksView: React.FC = () => {
|
|
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [webhooks, setWebhooks] = useState<Webhook[]>(MOCK_WEBHOOKS);
|
|
|
|
|
const [newUrl, setNewUrl] = useState('');
|
|
|
|
|
|
|
|
|
|
const handleCreate = () => {
|
2026-01-13 18:47:57 +00:00
|
|
|
if (!newUrl) return;
|
|
|
|
|
const newHook: Webhook = {
|
|
|
|
|
id: `w-${Date.now()}`,
|
|
|
|
|
url: newUrl,
|
|
|
|
|
events: ['all'],
|
|
|
|
|
status: 'active',
|
|
|
|
|
lastTriggered: 'Never',
|
|
|
|
|
};
|
|
|
|
|
setWebhooks([...webhooks, newHook]);
|
|
|
|
|
setNewUrl('');
|
|
|
|
|
addToast('Webhook created', 'success');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleTest = (_id: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
addToast(`Sending test payload to webhook...`, 'info');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDelete = (id: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
setWebhooks(webhooks.filter((w) => w.id !== id));
|
|
|
|
|
addToast('Webhook deleted', 'info');
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-8 animate-fadeIn pb-20">
|
2026-01-13 18:47:57 +00:00
|
|
|
<div>
|
|
|
|
|
<h2 className="text-2xl font-bold text-white mb-2">WEBHOOKS</h2>
|
|
|
|
|
<p className="text-gray-400 font-mono text-sm">
|
|
|
|
|
Subscribe to real-time events.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Card variant="default">
|
|
|
|
|
<h3 className="font-bold text-white mb-4 flex items-center gap-2">
|
|
|
|
|
<Plus className="w-4 h-4 text-kodo-cyan" /> Add Endpoint
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="flex gap-4">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="https://your-domain.com/webhook"
|
|
|
|
|
value={newUrl}
|
|
|
|
|
onChange={(e) => setNewUrl(e.target.value)}
|
|
|
|
|
className="flex-1"
|
|
|
|
|
/>
|
|
|
|
|
<Button variant="primary" onClick={handleCreate} disabled={!newUrl}>
|
|
|
|
|
Create
|
|
|
|
|
</Button>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
</Card>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="space-y-4">
|
|
|
|
|
{webhooks.map((hook) => (
|
|
|
|
|
<Card
|
|
|
|
|
key={hook.id}
|
|
|
|
|
variant="gaming"
|
|
|
|
|
className="flex flex-col md:flex-row items-start md:items-center justify-between p-4 gap-4"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-start gap-4">
|
|
|
|
|
<div
|
|
|
|
|
className={`p-2 rounded-full ${hook.status === 'active' ? 'bg-kodo-lime/10 text-kodo-lime' : 'bg-kodo-red/10 text-kodo-red'}`}
|
|
|
|
|
>
|
|
|
|
|
{hook.status === 'active' ? (
|
|
|
|
|
<Radio className="w-5 h-5" />
|
|
|
|
|
) : (
|
|
|
|
|
<AlertTriangle className="w-5 h-5" />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="font-bold text-white font-mono text-sm break-all">
|
|
|
|
|
{hook.url}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-400 mt-1 flex flex-wrap gap-2">
|
|
|
|
|
<span className="text-gray-500">Events:</span>
|
|
|
|
|
{hook.events.map((e) => (
|
|
|
|
|
<span
|
|
|
|
|
key={e}
|
|
|
|
|
className="bg-white/5 px-1.5 rounded text-gray-300"
|
|
|
|
|
>
|
|
|
|
|
{e}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
<span className="text-gray-500 ml-2">
|
|
|
|
|
Last Triggered: {hook.lastTriggered}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="flex gap-2 w-full md:w-auto">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="flex-1 md:flex-none border border-kodo-steel"
|
|
|
|
|
onClick={() => handleTest(hook.id)}
|
|
|
|
|
>
|
|
|
|
|
<Zap className="w-4 h-4 mr-2" /> Test
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="flex-1 md:flex-none text-kodo-red hover:bg-kodo-red/10"
|
|
|
|
|
onClick={() => handleDelete(hook.id)}
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|