Skip to content

Commit 9cc0fb1

Browse files
bchapuisclaude
andcommitted
Add command name and server invite steps to Discord bot creation wizard
Expand the wizard from 3 to 5 steps for a fully guided setup flow: 1. Application Details (App ID, Public Key) 2. Bot Token 3. Interactions Endpoint (webhook URL copy) 4. Slash Command (name input synced to node via onCommandNameSet) 5. Add Bot to Server (invite link with permissions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d566a4c commit 9cc0fb1

2 files changed

Lines changed: 111 additions & 7 deletions

File tree

apps/app/src/components/workflow/widgets/input/discord-bot-create-dialog.tsx

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,49 @@ import { createDiscordBot } from "@/services/discord-bot-service";
1616

1717
import { DiscordBotSetupInfo } from "../../bot-setup-info";
1818

19-
type Step = "application" | "bot-token" | "setup";
19+
type Step = "application" | "bot-token" | "webhook" | "command" | "invite";
2020

2121
const 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

2729
const 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

3642
interface DiscordBotCreateDialogProps {
3743
isOpen: boolean;
3844
onClose: () => void;
3945
onCreated: (botId: string) => void;
46+
onCommandNameSet?: (commandName: string) => void;
4047
}
4148

4249
export 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>

apps/app/src/components/workflow/widgets/input/discord-trigger-input.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,19 @@ function DiscordTriggerInputWidget({
8181
edges,
8282
deleteEdge
8383
);
84-
setIsCreateDialogOpen(false);
84+
};
85+
86+
const handleCommandNameSet = (value: string) => {
87+
setLocalCommand(value);
88+
updateNodeInput(
89+
nodeId,
90+
"commandName",
91+
value,
92+
inputs,
93+
updateNodeData,
94+
edges,
95+
deleteEdge
96+
);
8597
};
8698

8799
const handleCommandChange = (value: string) => {
@@ -122,6 +134,7 @@ function DiscordTriggerInputWidget({
122134
isOpen={isCreateDialogOpen}
123135
onClose={() => setIsCreateDialogOpen(false)}
124136
onCreated={handleBotCreated}
137+
onCommandNameSet={handleCommandNameSet}
125138
/>
126139
</div>
127140
);

0 commit comments

Comments
 (0)