import { useState } from 'react' import { createFileRoute, Link } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { Alert, Badge, Button, EmptyState, IconButton, LoadingBlock, Modal, MultiSelect, PageHeader, Panel, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, TextArea, TextInput, type SelectOption, } from '@nstart/ui' import { CopyIcon, PlusIcon, TrashIcon, } from '@phosphor-icons/react' import { useWebhookSubscriptions } from '@/api/queries' import { useCreateWebhook, useDeleteWebhook, } from '@/api/mutations' import type { CreateWebhookSubscriptionRequest, DataScope, WebhookSubscription, } from '@/api/client' import { SCOPE_DOT } from '@/lib/scope-style' export const Route = createFileRoute('/webhooks/')({ component: WebhooksPage, }) const SCOPE_OPTIONS: SelectOption[] = [ { id: 'PUBLIC', label: 'PUBLIC' }, { id: 'INTERNAL', label: 'INTERNAL' }, { id: 'RESTRICTED', label: 'RESTRICTED' }, ] function WebhooksPage() { const { t } = useTranslation() const { data, isLoading, error } = useWebhookSubscriptions() const [createOpen, setCreateOpen] = useState(false) const [revealedSecret, setRevealedSecret] = useState<{ name: string secret: string } | null>(null) const deleteMut = useDeleteWebhook() const handleDelete = (sub: WebhookSubscription) => { if (!window.confirm(t('webhooks.confirmDelete', { name: sub.name }))) return deleteMut.mutate(sub.id) } if (isLoading) return if (error) { return ( {String(error)} ) } return (
} onClick={() => setCreateOpen(true)} > {t('webhooks.action.create')} } /> {!data || data.length === 0 ? ( ) : ( {t('webhooks.col.name')} {t('webhooks.col.url')} {t('webhooks.col.scopes')} {t('webhooks.col.dictionaries')} {t('webhooks.col.status')} {t('dict.col.actions')} {data.map((sub) => ( {sub.name} {sub.url} {sub.dictionaryFilter && sub.dictionaryFilter.length > 0 ? ( {sub.dictionaryFilter.slice(0, 3).join(', ')} {sub.dictionaryFilter.length > 3 && ` +${sub.dictionaryFilter.length - 3}`} ) : ( {t('webhooks.allDicts')} )} {sub.active ? t('webhooks.active') : t('webhooks.inactive')} } onClick={() => handleDelete(sub)} /> ))}
)} setCreateOpen(false)} onSuccess={(sub, secret) => { setCreateOpen(false) setRevealedSecret({ name: sub.name, secret }) }} /> setRevealedSecret(null)} />
) } function ScopeChips({ scopes }: { scopes?: DataScope[] | null }) { const { t } = useTranslation() if (!scopes || scopes.length === 0) { return {t('webhooks.allScopes')} } return (
{scopes.map((s) => ( ))}
) } type CreateDialogProps = { open: boolean onClose: () => void onSuccess: (sub: WebhookSubscription, secret: string) => void } function CreateWebhookDialog({ open, onClose, onSuccess }: CreateDialogProps) { const { t } = useTranslation() const create = useCreateWebhook() const [name, setName] = useState('') const [url, setUrl] = useState('') const [scopeFilter, setScopeFilter] = useState([]) const [dictionaryFilterCsv, setDictionaryFilterCsv] = useState('') const [eventTypeFilterCsv, setEventTypeFilterCsv] = useState('') const [description, setDescription] = useState('') const [errorMsg, setErrorMsg] = useState(null) const reset = () => { setName('') setUrl('') setScopeFilter([]) setDictionaryFilterCsv('') setEventTypeFilterCsv('') setDescription('') setErrorMsg(null) } const csvToArray = (s: string): string[] | undefined => { const arr = s .split(',') .map((x) => x.trim()) .filter(Boolean) return arr.length > 0 ? arr : undefined } const handleSubmit = () => { setErrorMsg(null) if (!name.trim() || !url.trim()) { setErrorMsg(t('webhooks.error.nameUrlRequired')) return } if (!/^https?:\/\//.test(url)) { setErrorMsg(t('webhooks.error.urlScheme')) return } const req: CreateWebhookSubscriptionRequest = { name: name.trim(), url: url.trim(), scopeFilter: scopeFilter.length > 0 ? (scopeFilter as DataScope[]) : undefined, dictionaryFilter: csvToArray(dictionaryFilterCsv), eventTypeFilter: csvToArray(eventTypeFilterCsv), active: true, description: description.trim() || undefined, } create.mutate(req, { onSuccess: (sub) => { const plaintext = sub.hmacSecret reset() onSuccess(sub, plaintext) }, onError: (e) => setErrorMsg(String(e)), }) } return ( { reset() onClose() }} title={t('webhooks.action.create')} maxWidth="max-w-2xl" >
setName(e.target.value)} /> setUrl(e.target.value)} /> setScopeFilter(ids as string[])} hint={t('webhooks.field.scopeFilterHint')} /> setDictionaryFilterCsv(e.target.value)} /> setEventTypeFilterCsv(e.target.value)} />