Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sim/app/blocks/blocks/starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const StarterBlock: BlockConfig<StarterBlockOutput> = {
{ label: 'Generic', id: 'generic' },
{ label: 'WhatsApp', id: 'whatsapp' },
{ label: 'GitHub', id: 'github' },
{ label: 'Discord', id: 'discord' },
// { label: 'Stripe', id: 'stripe' },
],
value: () => 'generic',
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void>
}

export function DiscordConfig({
webhookName,
setWebhookName,
avatarUrl,
setAvatarUrl,
isLoadingToken,
testResult,
copied,
copyToClipboard,
testWebhook,
}: DiscordConfigProps) {
return (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="discord-webhook-name">Webhook Name (Optional)</Label>
<Input
id="discord-webhook-name"
value={webhookName}
onChange={(e) => setWebhookName(e.target.value)}
placeholder="Enter a name for your webhook"
disabled={isLoadingToken}
/>
<p className="text-xs text-muted-foreground">
This name will be displayed as the sender of messages in Discord.
</p>
</div>

<div className="space-y-2">
<Label htmlFor="discord-avatar-url">Avatar URL (Optional)</Label>
<Input
id="discord-avatar-url"
value={avatarUrl}
onChange={(e) => setAvatarUrl(e.target.value)}
placeholder="https://example.com/avatar.png"
disabled={isLoadingToken}
/>
<p className="text-xs text-muted-foreground">
URL to an image that will be used as the webhook's avatar.
</p>
</div>

<TestResultDisplay
testResult={testResult}
copied={copied}
copyToClipboard={copyToClipboard}
showCurlCommand={true}
/>

<div className="space-y-2">
<h4 className="font-medium">Setup Instructions</h4>
<ol className="list-decimal list-inside space-y-1 text-sm">
<li>Open Discord and go to the server where you want to add the webhook</li>
<li>Click the gear icon next to a channel to open Channel Settings</li>
<li>Navigate to "Integrations" {'>'} "Webhooks"</li>
<li>Click "New Webhook"</li>
<li>Give your webhook a name and choose an avatar (optional)</li>
<li>Select the channel the webhook will post to</li>
<li>Click "Copy Webhook URL" and save it for your records</li>
<li>Click "Save"</li>
<li>Use the Webhook URL above to receive messages from Discord</li>
</ol>
</div>

<div className="bg-indigo-50 dark:bg-indigo-950 p-3 rounded-md mt-3 border border-indigo-200 dark:border-indigo-800">
<h5 className="text-sm font-medium text-indigo-800 dark:text-indigo-300">
Discord Webhook Features
</h5>
<ul className="mt-1 space-y-1">
<li className="flex items-start">
<span className="text-indigo-500 dark:text-indigo-400 mr-2">•</span>
<span className="text-sm text-indigo-700 dark:text-indigo-300">
Customize message appearance with embeds and formatting
</span>
</li>
<li className="flex items-start">
<span className="text-indigo-500 dark:text-indigo-400 mr-2">•</span>
<span className="text-sm text-indigo-700 dark:text-indigo-300">
Send messages with different usernames and avatars per request
</span>
</li>
<li className="flex items-start">
<span className="text-indigo-500 dark:text-indigo-400 mr-2">•</span>
<span className="text-sm text-indigo-700 dark:text-indigo-300">
Discord secures webhooks by keeping URLs private - protect your webhook URL
</span>
</li>
</ul>
</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>
You can use this webhook to receive notifications from Discord or to send messages to your
Discord channel.
</p>
</div>

<div className="bg-purple-50 dark:bg-purple-950 p-3 rounded-md mt-3 border border-purple-200 dark:border-purple-800">
<h5 className="text-sm font-medium text-purple-800 dark:text-purple-300">
Example POST Request
</h5>
<pre className="mt-2 text-xs bg-black/5 dark:bg-white/5 p-2 rounded overflow-x-auto">
{`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"
}`}
</pre>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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: '',
Expand All @@ -75,6 +78,8 @@ export function WebhookModal({
secretHeaderName: '',
requireAuth: false,
allowedIps: '',
discordWebhookName: '',
discordAvatarUrl: '',
})

// Get the current provider configuration
Expand Down Expand Up @@ -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 || ''
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -269,6 +294,8 @@ export function WebhookModal({
secretHeaderName,
requireAuth,
allowedIps,
discordWebhookName,
discordAvatarUrl,
})
setHasUnsavedChanges(false)
}
Expand Down Expand Up @@ -385,6 +412,20 @@ export function WebhookModal({
testWebhook={testWebhook}
/>
)
case 'discord':
return (
<DiscordConfig
webhookName={discordWebhookName}
setWebhookName={setDiscordWebhookName}
avatarUrl={discordAvatarUrl}
setAvatarUrl={setDiscordAvatarUrl}
isLoadingToken={isLoadingToken}
testResult={testResult}
copied={copied}
copyToClipboard={copyToClipboard}
testWebhook={testWebhook}
/>
)
case 'stripe':
return (
<StripeConfig
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { useParams } from 'next/navigation'
import { CheckCircle2, ExternalLink } from 'lucide-react'
import { GithubIcon, StripeIcon, WhatsAppIcon } from '@/components/icons'
import { DiscordIcon, GithubIcon, StripeIcon, WhatsAppIcon } from '@/components/icons'
import { Button } from '@/components/ui/button'
import { createLogger } from '@/lib/logs/console-logger'
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
Expand Down Expand Up @@ -34,6 +34,11 @@ export interface GitHubConfig {
contentType: string
}

export interface DiscordConfig {
webhookName?: string
avatarUrl?: string
}

export interface StripeConfig {
// Any Stripe-specific fields would go here
}
Expand All @@ -49,6 +54,7 @@ export interface GeneralWebhookConfig {
export type ProviderConfig =
| WhatsAppConfig
| GitHubConfig
| DiscordConfig
| StripeConfig
| GeneralWebhookConfig
| Record<string, never>
Expand Down Expand Up @@ -82,6 +88,25 @@ export const WEBHOOK_PROVIDERS: { [key: string]: WebhookProvider } = {
},
},
},
discord: {
id: 'discord',
name: 'Discord',
icon: (props) => <DiscordIcon {...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',
Expand Down