diff --git a/sim/app/blocks/blocks/starter.ts b/sim/app/blocks/blocks/starter.ts index aaa019f3769..5f411ceb0f4 100644 --- a/sim/app/blocks/blocks/starter.ts +++ b/sim/app/blocks/blocks/starter.ts @@ -41,6 +41,7 @@ export const StarterBlock: BlockConfig = { { label: 'Generic', id: 'generic' }, { label: 'WhatsApp', id: 'whatsapp' }, { label: 'GitHub', id: 'github' }, + { label: 'Discord', id: 'discord' }, // { label: 'Stripe', id: 'stripe' }, ], value: () => 'generic', diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/providers/discord-config.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/providers/discord-config.tsx new file mode 100644 index 00000000000..a4ce6cd653c --- /dev/null +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/providers/discord-config.tsx @@ -0,0 +1,136 @@ +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { CopyableField } from '../ui/copyable' +import { TestResultDisplay } from '../ui/test-result' + +interface DiscordConfigProps { + webhookName: string + setWebhookName: (name: string) => void + avatarUrl: string + setAvatarUrl: (url: string) => void + isLoadingToken: boolean + testResult: { + success: boolean + message?: string + test?: any + } | null + copied: string | null + copyToClipboard: (text: string, type: string) => void + testWebhook: () => Promise +} + +export function DiscordConfig({ + webhookName, + setWebhookName, + avatarUrl, + setAvatarUrl, + isLoadingToken, + testResult, + copied, + copyToClipboard, + testWebhook, +}: DiscordConfigProps) { + return ( +
+
+ + setWebhookName(e.target.value)} + placeholder="Enter a name for your webhook" + disabled={isLoadingToken} + /> +

+ This name will be displayed as the sender of messages in Discord. +

+
+ +
+ + setAvatarUrl(e.target.value)} + placeholder="https://example.com/avatar.png" + disabled={isLoadingToken} + /> +

+ URL to an image that will be used as the webhook's avatar. +

+
+ + + +
+

Setup Instructions

+
    +
  1. Open Discord and go to the server where you want to add the webhook
  2. +
  3. Click the gear icon next to a channel to open Channel Settings
  4. +
  5. Navigate to "Integrations" {'>'} "Webhooks"
  6. +
  7. Click "New Webhook"
  8. +
  9. Give your webhook a name and choose an avatar (optional)
  10. +
  11. Select the channel the webhook will post to
  12. +
  13. Click "Copy Webhook URL" and save it for your records
  14. +
  15. Click "Save"
  16. +
  17. Use the Webhook URL above to receive messages from Discord
  18. +
+
+ +
+
+ Discord Webhook Features +
+
    +
  • + + + Customize message appearance with embeds and formatting + +
  • +
  • + + + Send messages with different usernames and avatars per request + +
  • +
  • + + + Discord secures webhooks by keeping URLs private - protect your webhook URL + +
  • +
+
+ +
+

+ 💡 + You can use this webhook to receive notifications from Discord or to send messages to your + Discord channel. +

+
+ +
+
+ Example POST Request +
+
+          {`POST /api/webhooks/{your-webhook-id} HTTP/1.1
+Content-Type: application/json
+
+{
+  "content": "Hello from Sim Studio!",
+  "username": "Custom Bot Name",
+  "avatar_url": "https://example.com/avatar.png"
+}`}
+        
+
+
+ ) +} diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/ui/webhook-header.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/ui/webhook-header.tsx index ba0a74b824b..291b4d38dd1 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/ui/webhook-header.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/ui/webhook-header.tsx @@ -14,7 +14,9 @@ export function WebhookDialogHeader({ webhookProvider, webhookId }: WebhookDialo const getProviderIcon = () => { return provider.icon({ className: - webhookProvider === 'github' ? 'h-5 w-5' : 'h-5 w-5 text-green-500 dark:text-green-400', + webhookProvider === 'github' || webhookProvider === 'discord' + ? 'h-5 w-5' + : 'h-5 w-5 text-green-500 dark:text-green-400', }) } diff --git a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx index 96f6398f00b..5f26f8112b6 100644 --- a/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx +++ b/sim/app/w/[id]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { Dialog, DialogContent } from '@/components/ui/dialog' import { createLogger } from '@/lib/logs/console-logger' import { ProviderConfig, WEBHOOK_PROVIDERS } from '../webhook-config' +import { DiscordConfig } from './providers/discord-config' import { GenericConfig } from './providers/generic-config' import { GithubConfig } from './providers/github-config' import { StripeConfig } from './providers/stripe-config' @@ -57,16 +58,18 @@ export function WebhookModal({ const [showUnsavedChangesConfirm, setShowUnsavedChangesConfirm] = useState(false) const isConfigured = Boolean(webhookId) - // Provider-specific configuration state - const [whatsappVerificationToken, setWhatsappVerificationToken] = useState('') - const [githubContentType, setGithubContentType] = useState('application/json') - - // General webhook configuration state + // Generic webhook state const [generalToken, setGeneralToken] = useState('') const [secretHeaderName, setSecretHeaderName] = useState('') const [requireAuth, setRequireAuth] = useState(false) const [allowedIps, setAllowedIps] = useState('') + // Provider-specific state + const [whatsappVerificationToken, setWhatsappVerificationToken] = useState('') + const [githubContentType, setGithubContentType] = useState('application/json') + const [discordWebhookName, setDiscordWebhookName] = useState('') + const [discordAvatarUrl, setDiscordAvatarUrl] = useState('') + // Original values to track changes const [originalValues, setOriginalValues] = useState({ whatsappVerificationToken: '', @@ -75,6 +78,8 @@ export function WebhookModal({ secretHeaderName: '', requireAuth: false, allowedIps: '', + discordWebhookName: '', + discordAvatarUrl: '', }) // Get the current provider configuration @@ -136,6 +141,18 @@ export function WebhookModal({ const contentType = config.contentType || 'application/json' setGithubContentType(contentType) setOriginalValues((prev) => ({ ...prev, githubContentType: contentType })) + } else if (webhookProvider === 'discord') { + const webhookName = config.webhookName || '' + const avatarUrl = config.avatarUrl || '' + + setDiscordWebhookName(webhookName) + setDiscordAvatarUrl(avatarUrl) + + setOriginalValues((prev) => ({ + ...prev, + discordWebhookName: webhookName, + discordAvatarUrl: avatarUrl, + })) } else if (webhookProvider === 'generic') { // Set general webhook configuration const token = config.token || '' @@ -177,23 +194,26 @@ export function WebhookModal({ // Check for unsaved changes useEffect(() => { - // Compare current values with original values - if (webhookProvider === 'whatsapp') { - setHasUnsavedChanges(whatsappVerificationToken !== originalValues.whatsappVerificationToken) - } else if (webhookProvider === 'github') { - setHasUnsavedChanges(githubContentType !== originalValues.githubContentType) - } else if (webhookProvider === 'generic') { - setHasUnsavedChanges( - generalToken !== originalValues.generalToken || + const hasChanges = + (webhookProvider === 'whatsapp' && + whatsappVerificationToken !== originalValues.whatsappVerificationToken) || + (webhookProvider === 'github' && githubContentType !== originalValues.githubContentType) || + (webhookProvider === 'discord' && + (discordWebhookName !== originalValues.discordWebhookName || + discordAvatarUrl !== originalValues.discordAvatarUrl)) || + (webhookProvider === 'generic' && + (generalToken !== originalValues.generalToken || secretHeaderName !== originalValues.secretHeaderName || requireAuth !== originalValues.requireAuth || - allowedIps !== originalValues.allowedIps - ) - } + allowedIps !== originalValues.allowedIps)) + + setHasUnsavedChanges(hasChanges) }, [ webhookProvider, whatsappVerificationToken, githubContentType, + discordWebhookName, + discordAvatarUrl, generalToken, secretHeaderName, requireAuth, @@ -224,6 +244,11 @@ export function WebhookModal({ return { verificationToken: whatsappVerificationToken } case 'github': return { contentType: githubContentType } + case 'discord': + return { + webhookName: discordWebhookName || undefined, + avatarUrl: discordAvatarUrl || undefined, + } case 'stripe': return {} case 'generic': @@ -269,6 +294,8 @@ export function WebhookModal({ secretHeaderName, requireAuth, allowedIps, + discordWebhookName, + discordAvatarUrl, }) setHasUnsavedChanges(false) } @@ -385,6 +412,20 @@ export function WebhookModal({ testWebhook={testWebhook} /> ) + case 'discord': + return ( + + ) case 'stripe': return ( @@ -82,6 +88,25 @@ export const WEBHOOK_PROVIDERS: { [key: string]: WebhookProvider } = { }, }, }, + discord: { + id: 'discord', + name: 'Discord', + icon: (props) => , + configFields: { + webhookName: { + type: 'string', + label: 'Webhook Name', + placeholder: 'Enter a name for the webhook', + description: 'Custom name that will appear as the message sender in Discord.', + }, + avatarUrl: { + type: 'string', + label: 'Avatar URL', + placeholder: 'https://example.com/avatar.png', + description: 'URL to an image that will be used as the webhook avatar.', + }, + }, + }, stripe: { id: 'stripe', name: 'Stripe',