mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement: cleanup style and display for webhooks
This commit is contained in:
+101
-10
@@ -1,24 +1,85 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { CheckCircle2, ExternalLink } from 'lucide-react'
|
||||
import { GithubIcon, StripeIcon, WhatsAppIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { WebhookModal } from '@/components/ui/webhook-modal'
|
||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||
|
||||
export interface WebhookProvider {
|
||||
id: string
|
||||
name: string
|
||||
icon: (props: { className?: string }) => JSX.Element
|
||||
configFields: {
|
||||
[key: string]: {
|
||||
type: 'string' | 'boolean' | 'select'
|
||||
label: string
|
||||
placeholder?: string
|
||||
options?: string[]
|
||||
defaultValue?: string | boolean
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define provider-specific configuration types
|
||||
interface WhatsAppConfig {
|
||||
export interface WhatsAppConfig {
|
||||
verificationToken: string
|
||||
}
|
||||
|
||||
interface GitHubConfig {
|
||||
export interface GitHubConfig {
|
||||
contentType: string
|
||||
}
|
||||
|
||||
interface StripeConfig {
|
||||
export interface StripeConfig {
|
||||
// Any Stripe-specific fields would go here
|
||||
}
|
||||
|
||||
type ProviderConfig = WhatsAppConfig | GitHubConfig | StripeConfig | Record<string, never>
|
||||
// Union type for all provider configurations
|
||||
export type ProviderConfig = WhatsAppConfig | GitHubConfig | StripeConfig | Record<string, never>
|
||||
|
||||
// Define available webhook providers
|
||||
export const WEBHOOK_PROVIDERS: { [key: string]: WebhookProvider } = {
|
||||
whatsapp: {
|
||||
id: 'whatsapp',
|
||||
name: 'WhatsApp',
|
||||
icon: (props) => <WhatsAppIcon {...props} />,
|
||||
configFields: {
|
||||
verificationToken: {
|
||||
type: 'string',
|
||||
label: 'Verification Token',
|
||||
placeholder: 'Enter a verification token for WhatsApp',
|
||||
description: 'This token will be used to verify your webhook with WhatsApp.',
|
||||
},
|
||||
},
|
||||
},
|
||||
github: {
|
||||
id: 'github',
|
||||
name: 'GitHub',
|
||||
icon: (props) => <GithubIcon {...props} />,
|
||||
configFields: {
|
||||
contentType: {
|
||||
type: 'string',
|
||||
label: 'Content Type',
|
||||
placeholder: 'application/json',
|
||||
defaultValue: 'application/json',
|
||||
description: 'The content type for GitHub webhook payloads.',
|
||||
},
|
||||
},
|
||||
},
|
||||
stripe: {
|
||||
id: 'stripe',
|
||||
name: 'Stripe',
|
||||
icon: (props) => <StripeIcon {...props} />,
|
||||
configFields: {},
|
||||
},
|
||||
generic: {
|
||||
id: 'generic',
|
||||
name: 'Generic',
|
||||
icon: (props) => <CheckCircle2 {...props} />,
|
||||
configFields: {},
|
||||
},
|
||||
}
|
||||
|
||||
interface WebhookConfigProps {
|
||||
blockId: string
|
||||
@@ -36,7 +97,7 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
const workflowId = params.id as string
|
||||
|
||||
// Get the webhook provider from the block state
|
||||
const [webhookProvider] = useSubBlockValue(blockId, 'webhookProvider')
|
||||
const [webhookProvider, setWebhookProvider] = useSubBlockValue(blockId, 'webhookProvider')
|
||||
|
||||
// Store the webhook path
|
||||
const [webhookPath, setWebhookPath] = useSubBlockValue(blockId, 'webhookPath')
|
||||
@@ -44,6 +105,9 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
// Store provider-specific configuration
|
||||
const [providerConfig, setProviderConfig] = useSubBlockValue(blockId, 'providerConfig')
|
||||
|
||||
// Store the actual provider from the database
|
||||
const [actualProvider, setActualProvider] = useState<string | null>(null)
|
||||
|
||||
// Check if webhook exists in the database
|
||||
useEffect(() => {
|
||||
const checkWebhook = async () => {
|
||||
@@ -56,12 +120,21 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
const webhook = data.webhooks[0].webhook
|
||||
setWebhookId(webhook.id)
|
||||
|
||||
// Update the provider in the block state if it's different
|
||||
if (webhook.provider && webhook.provider !== webhookProvider) {
|
||||
setWebhookProvider(webhook.provider)
|
||||
}
|
||||
|
||||
// Store the actual provider from the database
|
||||
setActualProvider(webhook.provider)
|
||||
|
||||
// Update the path in the block state if it's different
|
||||
if (webhook.path && webhook.path !== webhookPath) {
|
||||
setWebhookPath(webhook.path)
|
||||
}
|
||||
} else {
|
||||
setWebhookId(null)
|
||||
setActualProvider(null)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -70,7 +143,7 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
}
|
||||
|
||||
checkWebhook()
|
||||
}, [webhookPath, workflowId, setWebhookPath])
|
||||
}, [webhookPath, webhookProvider, workflowId, setWebhookPath, setWebhookProvider])
|
||||
|
||||
const handleOpenModal = () => {
|
||||
setIsModalOpen(true)
|
||||
@@ -116,6 +189,9 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
const data = await response.json()
|
||||
setWebhookId(data.webhook.id)
|
||||
|
||||
// Update the actual provider after saving
|
||||
setActualProvider(webhookProvider || 'generic')
|
||||
|
||||
console.log('Webhook configuration saved successfully')
|
||||
return true
|
||||
} catch (error: any) {
|
||||
@@ -143,8 +219,9 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
throw new Error(errorData.error || 'Failed to delete webhook')
|
||||
}
|
||||
|
||||
// Clear the webhook ID
|
||||
// Clear the webhook ID and actual provider
|
||||
setWebhookId(null)
|
||||
setActualProvider(null)
|
||||
|
||||
console.log('Webhook deleted successfully')
|
||||
return true
|
||||
@@ -157,9 +234,23 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
}
|
||||
}
|
||||
|
||||
// Get provider icon based on the current provider
|
||||
const getProviderIcon = () => {
|
||||
// Only show provider icon if the webhook is connected and the selected provider matches the actual provider
|
||||
if (!webhookId || webhookProvider !== actualProvider) {
|
||||
return null
|
||||
}
|
||||
|
||||
const provider = WEBHOOK_PROVIDERS[webhookProvider || 'generic']
|
||||
return provider.icon({ className: 'h-4 w-4 mr-2 text-green-500 dark:text-green-400' })
|
||||
}
|
||||
|
||||
// Check if the webhook is connected for the selected provider
|
||||
const isWebhookConnected = webhookId && webhookProvider === actualProvider
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{error && <div className="text-sm text-red-500 mb-2">{error}</div>}
|
||||
{error && <div className="text-sm text-red-500 dark:text-red-400 mb-2">{error}</div>}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -167,9 +258,9 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
|
||||
onClick={handleOpenModal}
|
||||
disabled={isConnecting || isSaving || isDeleting}
|
||||
>
|
||||
{webhookId ? (
|
||||
{isWebhookConnected ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
||||
{getProviderIcon()}
|
||||
{isSaving ? 'Saving...' : isDeleting ? 'Deleting...' : 'Webhook Connected'}
|
||||
</>
|
||||
) : (
|
||||
|
||||
+134
-76
@@ -23,21 +23,11 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
// Define provider-specific configuration types
|
||||
interface WhatsAppConfig {
|
||||
verificationToken: string
|
||||
}
|
||||
|
||||
interface GitHubConfig {
|
||||
contentType: string
|
||||
}
|
||||
|
||||
interface StripeConfig {
|
||||
// Any Stripe-specific fields would go here
|
||||
}
|
||||
|
||||
type ProviderConfig = WhatsAppConfig | GitHubConfig | StripeConfig | Record<string, never>
|
||||
import {
|
||||
ProviderConfig,
|
||||
WEBHOOK_PROVIDERS,
|
||||
WebhookProvider,
|
||||
} from '@/app/w/[id]/components/workflow-block/components/sub-block/components/webhook-config'
|
||||
|
||||
interface WebhookModalProps {
|
||||
isOpen: boolean
|
||||
@@ -64,6 +54,7 @@ export function WebhookModal({
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [isTesting, setIsTesting] = useState(false)
|
||||
const [isLoadingToken, setIsLoadingToken] = useState(false)
|
||||
const [testResult, setTestResult] = useState<{ success: boolean; message?: string } | null>(null)
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const isConfigured = Boolean(webhookId)
|
||||
@@ -72,13 +63,21 @@ export function WebhookModal({
|
||||
const [whatsappVerificationToken, setWhatsappVerificationToken] = useState('')
|
||||
const [githubContentType, setGithubContentType] = useState('application/json')
|
||||
|
||||
// Get the current provider configuration
|
||||
const provider = WEBHOOK_PROVIDERS[webhookProvider] || WEBHOOK_PROVIDERS.generic
|
||||
|
||||
// Generate a random verification token if none exists
|
||||
useEffect(() => {
|
||||
if (webhookProvider === 'whatsapp' && !whatsappVerificationToken) {
|
||||
if (
|
||||
webhookProvider === 'whatsapp' &&
|
||||
!whatsappVerificationToken &&
|
||||
!webhookId &&
|
||||
!isLoadingToken
|
||||
) {
|
||||
const randomToken = Math.random().toString(36).substring(2, 10)
|
||||
setWhatsappVerificationToken(randomToken)
|
||||
}
|
||||
}, [webhookProvider, whatsappVerificationToken])
|
||||
}, [webhookProvider, whatsappVerificationToken, webhookId, isLoadingToken])
|
||||
|
||||
// Load existing configuration values
|
||||
useEffect(() => {
|
||||
@@ -86,6 +85,7 @@ export function WebhookModal({
|
||||
// If we have a webhook ID, try to fetch the existing configuration
|
||||
const fetchWebhookConfig = async () => {
|
||||
try {
|
||||
setIsLoadingToken(true)
|
||||
const response = await fetch(`/api/webhooks/${webhookId}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
@@ -102,10 +102,16 @@ export function WebhookModal({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching webhook config:', error)
|
||||
} finally {
|
||||
setIsLoadingToken(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchWebhookConfig()
|
||||
} else {
|
||||
// If we don't have a webhook ID, we're creating a new one
|
||||
// Reset the loading state
|
||||
setIsLoadingToken(false)
|
||||
}
|
||||
}, [webhookId, webhookProvider])
|
||||
|
||||
@@ -208,7 +214,8 @@ export function WebhookModal({
|
||||
if (data.success) {
|
||||
setTestResult({
|
||||
success: true,
|
||||
message: data.message || 'Webhook configuration is valid.',
|
||||
message:
|
||||
data.message || 'Webhook configuration is valid. You can now use this URL in WhatsApp.',
|
||||
})
|
||||
} else {
|
||||
setTestResult({
|
||||
@@ -229,16 +236,12 @@ export function WebhookModal({
|
||||
|
||||
// Get provider icon
|
||||
const getProviderIcon = () => {
|
||||
switch (webhookProvider) {
|
||||
case 'whatsapp':
|
||||
return <WhatsAppIcon className="h-6 w-6 text-green-500" />
|
||||
case 'github':
|
||||
return <GithubIcon className="h-6 w-6" />
|
||||
case 'stripe':
|
||||
return <StripeIcon className="h-6 w-6 text-purple-500" />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
return provider.icon({ className: 'h-5 w-5 text-green-500 dark:text-green-400' })
|
||||
}
|
||||
|
||||
// Get provider-specific title
|
||||
const getProviderTitle = () => {
|
||||
return `${provider.name} Integration`
|
||||
}
|
||||
|
||||
// Provider-specific setup instructions and configuration fields
|
||||
@@ -250,18 +253,25 @@ export function WebhookModal({
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="whatsapp-verification-token">Verification Token</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input
|
||||
id="whatsapp-verification-token"
|
||||
value={whatsappVerificationToken}
|
||||
onChange={(e) => setWhatsappVerificationToken(e.target.value)}
|
||||
placeholder="Enter a verification token for WhatsApp"
|
||||
className="flex-1"
|
||||
/>
|
||||
{isLoadingToken ? (
|
||||
<div className="flex-1 h-10 px-3 py-2 rounded-md border border-input bg-background flex items-center">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
id="whatsapp-verification-token"
|
||||
value={whatsappVerificationToken}
|
||||
onChange={(e) => setWhatsappVerificationToken(e.target.value)}
|
||||
placeholder="Enter a verification token for WhatsApp"
|
||||
className="flex-1"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => copyToClipboard(whatsappVerificationToken, 'token')}
|
||||
disabled={isLoadingToken}
|
||||
>
|
||||
{copied === 'token' ? (
|
||||
<Check className="h-4 w-4" />
|
||||
@@ -277,7 +287,11 @@ export function WebhookModal({
|
||||
|
||||
{testResult && (
|
||||
<div
|
||||
className={`p-3 rounded-md ${testResult.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'}`}
|
||||
className={`p-3 rounded-md ${
|
||||
testResult.success
|
||||
? 'bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300 border border-green-200 dark:border-green-800'
|
||||
: 'bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300 border border-red-200 dark:border-red-800'
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm">{testResult.message}</p>
|
||||
</div>
|
||||
@@ -287,56 +301,58 @@ export function WebhookModal({
|
||||
<h4 className="font-medium">Setup Instructions</h4>
|
||||
<ol className="space-y-2">
|
||||
<li className="flex items-start">
|
||||
<span className="text-gray-500 mr-2">1.</span>
|
||||
<span className="text-gray-500 dark:text-gray-400 mr-2">1.</span>
|
||||
<span className="text-sm">Go to WhatsApp Business Platform dashboard</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-gray-500 mr-2">2.</span>
|
||||
<span className="text-gray-500 dark:text-gray-400 mr-2">2.</span>
|
||||
<span className="text-sm">Navigate to "Configuration" in the sidebar</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-gray-500 mr-2">3.</span>
|
||||
<span className="text-gray-500 dark:text-gray-400 mr-2">3.</span>
|
||||
<span className="text-sm">
|
||||
Enter the URL above as "Callback URL" (exactly as shown)
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-gray-500 mr-2">4.</span>
|
||||
<span className="text-gray-500 dark:text-gray-400 mr-2">4.</span>
|
||||
<span className="text-sm">Enter your token as "Verify token"</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-gray-500 mr-2">5.</span>
|
||||
<span className="text-gray-500 dark:text-gray-400 mr-2">5.</span>
|
||||
<span className="text-sm">
|
||||
Click "Verify and save" and subscribe to "messages"
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
<div className="bg-blue-50 p-3 rounded-md mt-3">
|
||||
<h5 className="text-sm font-medium text-blue-800">Requirements</h5>
|
||||
<div className="bg-blue-50 dark:bg-blue-950 p-3 rounded-md mt-3 border border-blue-200 dark:border-blue-800">
|
||||
<h5 className="text-sm font-medium text-blue-800 dark:text-blue-300">
|
||||
Requirements
|
||||
</h5>
|
||||
<ul className="mt-1 space-y-1">
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700">
|
||||
<span className="text-blue-500 dark:text-blue-400 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700 dark:text-blue-300">
|
||||
URL must be publicly accessible with HTTPS
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700">
|
||||
<span className="text-blue-500 dark:text-blue-400 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700 dark:text-blue-300">
|
||||
Self-signed SSL certificates not supported
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-500 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700">
|
||||
<span className="text-blue-500 dark:text-blue-400 mr-2">•</span>
|
||||
<span className="text-sm text-blue-700 dark:text-blue-300">
|
||||
For local testing, use ngrok to expose your server
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-md mt-3">
|
||||
<p className="text-sm text-gray-700 flex items-center">
|
||||
<span className="text-gray-400 mr-2">💡</span>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md mt-3 border border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 flex items-center">
|
||||
<span className="text-gray-400 dark:text-gray-500 mr-2">💡</span>
|
||||
After saving, use "Test" to verify your webhook configuration.
|
||||
</p>
|
||||
</div>
|
||||
@@ -348,13 +364,19 @@ export function WebhookModal({
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="github-content-type">Content Type</Label>
|
||||
<Input
|
||||
id="github-content-type"
|
||||
value={githubContentType}
|
||||
onChange={(e) => setGithubContentType(e.target.value)}
|
||||
placeholder="application/json"
|
||||
className="flex-1"
|
||||
/>
|
||||
{isLoadingToken ? (
|
||||
<div className="h-10 px-3 py-2 rounded-md border border-input bg-background flex items-center">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
id="github-content-type"
|
||||
value={githubContentType}
|
||||
onChange={(e) => setGithubContentType(e.target.value)}
|
||||
placeholder="application/json"
|
||||
className="flex-1"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -369,6 +391,13 @@ export function WebhookModal({
|
||||
<li>Ensure "Active" is checked and save</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md mt-3 border border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 flex items-center">
|
||||
<span className="text-gray-400 dark:text-gray-500 mr-2">💡</span>
|
||||
After saving, GitHub will send a ping event to verify your webhook.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
case 'stripe':
|
||||
@@ -383,6 +412,13 @@ export function WebhookModal({
|
||||
<li>Select the events you want to listen for</li>
|
||||
<li>Add the endpoint</li>
|
||||
</ol>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md mt-3 border border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 flex items-center">
|
||||
<span className="text-gray-400 dark:text-gray-500 mr-2">💡</span>
|
||||
Stripe will send a test event to verify your webhook endpoint.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
@@ -390,6 +426,13 @@ export function WebhookModal({
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Generic Webhook Setup</h4>
|
||||
<p className="text-sm">Use the URL above to send webhook events to this workflow.</p>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md mt-3 border border-gray-200 dark:border-gray-700">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 flex items-center">
|
||||
<span className="text-gray-400 dark:text-gray-500 mr-2">💡</span>
|
||||
You can test your webhook by sending a POST request to the URL.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -399,32 +442,47 @@ export function WebhookModal({
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader className="flex flex-row items-start">
|
||||
<div className="mr-3">{getProviderIcon()}</div>
|
||||
<div>
|
||||
<DialogTitle>Webhook Configuration</DialogTitle>
|
||||
<DialogDescription>Configure your WhatsApp integration</DialogDescription>
|
||||
<DialogHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-3 flex items-center">{getProviderIcon()}</div>
|
||||
<div>
|
||||
<DialogTitle>{getProviderTitle()}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{webhookProvider === 'generic'
|
||||
? 'Configure your webhook integration'
|
||||
: `Configure your ${provider.name.toLowerCase()} integration`}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{webhookId && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="bg-green-50 text-green-700 border-green-200 dark:bg-green-950 dark:text-green-300 dark:border-green-800"
|
||||
>
|
||||
Connected
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{webhookId && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-auto bg-green-50 text-green-700 border-green-200"
|
||||
>
|
||||
Connected
|
||||
</Badge>
|
||||
)}
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="webhook-url">Webhook URL</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input id="webhook-url" value={webhookUrl} readOnly className="flex-1" />
|
||||
{isLoadingToken ? (
|
||||
<div className="flex-1 h-10 px-3 py-2 rounded-md border border-input bg-background flex items-center">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<Input id="webhook-url" value={webhookUrl} readOnly className="flex-1" />
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => copyToClipboard(webhookUrl, 'url')}
|
||||
disabled={isLoadingToken}
|
||||
>
|
||||
{copied === 'url' ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
@@ -440,7 +498,7 @@ export function WebhookModal({
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
disabled={isDeleting}
|
||||
disabled={isDeleting || isLoadingToken}
|
||||
className="w-full sm:w-auto"
|
||||
size="sm"
|
||||
>
|
||||
@@ -460,7 +518,7 @@ export function WebhookModal({
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={testWebhook}
|
||||
disabled={isTesting}
|
||||
disabled={isTesting || isLoadingToken}
|
||||
className="w-full sm:w-auto"
|
||||
size="sm"
|
||||
>
|
||||
@@ -482,7 +540,7 @@ export function WebhookModal({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
disabled={isSaving || isLoadingToken}
|
||||
className="w-full sm:w-auto"
|
||||
size="sm"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user