@@ -16,40 +16,49 @@ import { createDiscordBot } from "@/services/discord-bot-service";
1616
1717import { DiscordBotSetupInfo } from "../../bot-setup-info" ;
1818
19- type Step = "application" | "bot-token" | "setup " ;
19+ type Step = "application" | "bot-token" | "webhook" | "command" | "invite ";
2020
2121const STEP_TITLES : Record < Step , string > = {
2222 application : "Create a Discord Application" ,
2323 "bot-token" : "Bot Token" ,
24- setup : "Bot Created" ,
24+ webhook : "Interactions Endpoint" ,
25+ command : "Slash Command" ,
26+ invite : "Add Bot to Server" ,
2527} ;
2628
2729const STEP_DESCRIPTIONS : Record < Step , string > = {
2830 application :
2931 "Create a new application in the Discord Developer Portal, then copy the Application ID and Public Key from the General Information page." ,
3032 "bot-token" :
3133 "Copy the token from the Bot page in the Discord Developer Portal." ,
32- setup :
33- "Configure the Interactions Endpoint URL to receive slash commands." ,
34+ webhook :
35+ "Copy the webhook URL below and paste it as the Interactions Endpoint URL in the Discord Developer Portal." ,
36+ command :
37+ "Choose a name for the slash command that will trigger this workflow." ,
38+ invite :
39+ "Add the bot to a Discord server so it can receive slash commands." ,
3440} ;
3541
3642interface DiscordBotCreateDialogProps {
3743 isOpen : boolean ;
3844 onClose : ( ) => void ;
3945 onCreated : ( botId : string ) => void ;
46+ onCommandNameSet ?: ( commandName : string ) => void ;
4047}
4148
4249export function DiscordBotCreateDialog ( {
4350 isOpen,
4451 onClose,
4552 onCreated,
53+ onCommandNameSet,
4654} : DiscordBotCreateDialogProps ) {
4755 const { organization } = useAuth ( ) ;
4856 const [ step , setStep ] = useState < Step > ( "application" ) ;
4957 const [ name , setName ] = useState ( "" ) ;
5058 const [ applicationId , setApplicationId ] = useState ( "" ) ;
5159 const [ publicKey , setPublicKey ] = useState ( "" ) ;
5260 const [ botToken , setBotToken ] = useState ( "" ) ;
61+ const [ commandName , setCommandName ] = useState ( "" ) ;
5362 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
5463 const [ error , setError ] = useState < string | null > ( null ) ;
5564 const [ createdBotId , setCreatedBotId ] = useState < string | null > ( null ) ;
@@ -60,6 +69,7 @@ export function DiscordBotCreateDialog({
6069 setApplicationId ( "" ) ;
6170 setPublicKey ( "" ) ;
6271 setBotToken ( "" ) ;
72+ setCommandName ( "" ) ;
6373 setError ( null ) ;
6474 setCreatedBotId ( null ) ;
6575 } ;
@@ -81,7 +91,7 @@ export function DiscordBotCreateDialog({
8191 organization . handle
8292 ) ;
8393 setCreatedBotId ( response . id ) ;
84- setStep ( "setup " ) ;
94+ setStep ( "webhook " ) ;
8595 onCreated ( response . id ) ;
8696 } catch ( err ) {
8797 setError (
@@ -92,6 +102,13 @@ export function DiscordBotCreateDialog({
92102 }
93103 } ;
94104
105+ const handleCommandNext = ( ) => {
106+ if ( commandName . trim ( ) && onCommandNameSet ) {
107+ onCommandNameSet ( commandName . trim ( ) ) ;
108+ }
109+ setStep ( "invite" ) ;
110+ } ;
111+
95112 const generalInfoUrl = applicationId
96113 ? `https://discord.com/developers/applications/${ applicationId } /information`
97114 : "https://discord.com/developers/applications" ;
@@ -100,6 +117,10 @@ export function DiscordBotCreateDialog({
100117 ? `https://discord.com/developers/applications/${ applicationId } /bot`
101118 : "https://discord.com/developers/applications" ;
102119
120+ const inviteUrl = applicationId
121+ ? `https://discord.com/oauth2/authorize?client_id=${ applicationId } &scope=bot+applications.commands&permissions=2048`
122+ : null ;
123+
103124 const canAdvanceToToken =
104125 name . trim ( ) !== "" &&
105126 applicationId . trim ( ) !== "" &&
@@ -241,7 +262,7 @@ export function DiscordBotCreateDialog({
241262 </ div >
242263 ) }
243264
244- { step === "setup " && (
265+ { step === "webhook " && (
245266 < div className = "space-y-4" >
246267 < div className = "flex items-center gap-2 text-sm" >
247268 < span className = "text-xs px-2 py-0.5 bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 rounded-md font-medium" >
@@ -258,6 +279,76 @@ export function DiscordBotCreateDialog({
258279 ) }
259280
260281 < div className = "flex justify-end" >
282+ < Button onClick = { ( ) => setStep ( "command" ) } > Next</ Button >
283+ </ div >
284+ </ div >
285+ ) }
286+
287+ { step === "command" && (
288+ < div className = "space-y-3" >
289+ < div className = "space-y-1.5" >
290+ < Label htmlFor = "discord-command" > Command Name</ Label >
291+ < div className = "flex items-center gap-1" >
292+ < span className = "text-sm text-muted-foreground" > /</ span >
293+ < Input
294+ id = "discord-command"
295+ value = { commandName }
296+ onChange = { ( e ) => setCommandName ( e . target . value ) }
297+ placeholder = "ask"
298+ className = "font-mono"
299+ />
300+ </ div >
301+ < p className = "text-xs text-muted-foreground" >
302+ Users will type /{ commandName || "command" } in Discord to
303+ trigger this workflow.
304+ </ p >
305+ </ div >
306+
307+ < div className = "flex justify-end gap-2 pt-1" >
308+ < Button
309+ type = "button"
310+ variant = "outline"
311+ onClick = { ( ) => setStep ( "webhook" ) }
312+ >
313+ Back
314+ </ Button >
315+ < Button
316+ onClick = { handleCommandNext }
317+ disabled = { commandName . trim ( ) === "" }
318+ >
319+ Next
320+ </ Button >
321+ </ div >
322+ </ div >
323+ ) }
324+
325+ { step === "invite" && (
326+ < div className = "space-y-4" >
327+ { inviteUrl && (
328+ < div className = "bg-muted/50 p-3 rounded-md" >
329+ < a
330+ href = { inviteUrl }
331+ target = "_blank"
332+ rel = "noopener noreferrer"
333+ className = "inline-flex items-center gap-1 text-sm text-primary hover:underline"
334+ >
335+ Add { name } to a Server
336+ < ExternalLink className = "w-3 h-3" />
337+ </ a >
338+ < p className = "text-xs text-muted-foreground mt-1" >
339+ This will request the bot and slash commands permissions.
340+ </ p >
341+ </ div >
342+ ) }
343+
344+ < div className = "flex justify-end gap-2 pt-1" >
345+ < Button
346+ type = "button"
347+ variant = "outline"
348+ onClick = { ( ) => setStep ( "command" ) }
349+ >
350+ Back
351+ </ Button >
261352 < Button onClick = { handleClose } > Done</ Button >
262353 </ div >
263354 </ div >
0 commit comments