Skip to content

Commit a2f9ebd

Browse files
bchapuisclaude
andcommitted
Unify Discord and Telegram bots into a single Bots page
Replace separate Discord/Telegram bot pages with a unified /bots page that lists all bots in one table with platform-specific detail pages. Adds a step-based create wizard with platform selection and redirects old URLs to the new unified page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 30f4a77 commit a2f9ebd

8 files changed

Lines changed: 794 additions & 719 deletions

File tree

apps/app/src/components/org-layout.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import Logs from "lucide-react/icons/logs";
1212
import Mail from "lucide-react/icons/mail";
1313
import MessageSquareText from "lucide-react/icons/message-square-text";
1414
import Plug from "lucide-react/icons/plug";
15-
import Send from "lucide-react/icons/send";
1615
import SquareTerminal from "lucide-react/icons/square-terminal";
1716
import Target from "lucide-react/icons/target";
1817
import Users from "lucide-react/icons/users";
@@ -89,15 +88,10 @@ export const getDashboardSidebarGroups = (
8988
icon: Inbox,
9089
},
9190
{
92-
title: "Discord",
93-
url: `/org/${orgHandle}/discord-bots`,
91+
title: "Bots",
92+
url: `/org/${orgHandle}/bots`,
9493
icon: Bot,
9594
},
96-
{
97-
title: "Telegram",
98-
url: `/org/${orgHandle}/telegram-bots`,
99-
icon: Send,
100-
},
10195
],
10296
},
10397
{
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import Copy from "lucide-react/icons/copy";
2+
import ExternalLink from "lucide-react/icons/external-link";
3+
import { useEffect } from "react";
4+
import { useParams } from "react-router";
5+
import { toast } from "sonner";
6+
7+
import { InsetError } from "@/components/inset-error";
8+
import { InsetLoading } from "@/components/inset-loading";
9+
import { InsetLayout } from "@/components/layouts/inset-layout";
10+
import { Button } from "@/components/ui/button";
11+
import { getApiBaseUrl } from "@/config/api";
12+
import { useOrgUrl } from "@/hooks/use-org-url";
13+
import { usePageBreadcrumbs } from "@/hooks/use-page";
14+
import { useDiscordBot } from "@/services/discord-bot-service";
15+
16+
export function BotDiscordDetailPage() {
17+
const { id } = useParams<{ id: string }>();
18+
const { setBreadcrumbs } = usePageBreadcrumbs([]);
19+
const { getOrgUrl } = useOrgUrl();
20+
21+
const { discordBot, discordBotError, isDiscordBotLoading } = useDiscordBot(
22+
id || null
23+
);
24+
25+
useEffect(() => {
26+
setBreadcrumbs([
27+
{ label: "Bots", to: getOrgUrl("bots") },
28+
{ label: discordBot?.name || id || "" },
29+
]);
30+
}, [id, discordBot?.name, setBreadcrumbs, getOrgUrl]);
31+
32+
if (isDiscordBotLoading) {
33+
return <InsetLoading title="Bot Details" />;
34+
} else if (discordBotError) {
35+
return (
36+
<InsetError title="Bot Details" errorMessage={discordBotError.message} />
37+
);
38+
} else if (!discordBot) {
39+
return <InsetError title="Bot Details" errorMessage="Bot not found" />;
40+
}
41+
42+
const webhookUrl = `${getApiBaseUrl()}/discord/webhook/${discordBot.id}`;
43+
const inviteUrl = `https://discord.com/oauth2/authorize?client_id=${discordBot.applicationId}&scope=bot+applications.commands&permissions=2048`;
44+
45+
const copyToClipboard = (text: string, label: string) => {
46+
navigator.clipboard.writeText(text);
47+
toast.success(`${label} copied to clipboard`);
48+
};
49+
50+
return (
51+
<InsetLayout title="Bot Details">
52+
<div className="space-y-6 max-w-2xl">
53+
<div className="space-y-4">
54+
<DetailRow label="Name" value={discordBot.name || "Untitled Bot"} />
55+
<DetailRow label="Application ID" value={discordBot.applicationId} mono />
56+
<DetailRow label="Public Key" value={discordBot.publicKey} mono />
57+
<DetailRow
58+
label="Token"
59+
value={`****${discordBot.tokenLastFour}`}
60+
mono
61+
/>
62+
<div className="grid grid-cols-[180px_1fr] gap-2 items-start">
63+
<span className="text-sm font-medium text-muted-foreground">
64+
Webhook URL
65+
</span>
66+
<div className="flex items-center gap-2">
67+
<span className="text-sm font-mono break-all">{webhookUrl}</span>
68+
<Button
69+
variant="ghost"
70+
size="icon"
71+
className="h-6 w-6 shrink-0"
72+
onClick={() => copyToClipboard(webhookUrl, "Webhook URL")}
73+
>
74+
<Copy className="h-3 w-3" />
75+
</Button>
76+
</div>
77+
</div>
78+
<div className="grid grid-cols-[180px_1fr] gap-2 items-center">
79+
<span className="text-sm font-medium text-muted-foreground">
80+
Invite Link
81+
</span>
82+
<a
83+
href={inviteUrl}
84+
target="_blank"
85+
rel="noopener noreferrer"
86+
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
87+
>
88+
Invite to Server
89+
<ExternalLink className="h-3 w-3" />
90+
</a>
91+
</div>
92+
</div>
93+
</div>
94+
</InsetLayout>
95+
);
96+
}
97+
98+
function DetailRow({
99+
label,
100+
value,
101+
mono,
102+
}: {
103+
label: string;
104+
value: string;
105+
mono?: boolean;
106+
}) {
107+
return (
108+
<div className="grid grid-cols-[180px_1fr] gap-2 items-center">
109+
<span className="text-sm font-medium text-muted-foreground">{label}</span>
110+
<span className={`text-sm ${mono ? "font-mono" : ""}`}>{value}</span>
111+
</div>
112+
);
113+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import ExternalLink from "lucide-react/icons/external-link";
2+
import { useEffect } from "react";
3+
import { useParams } from "react-router";
4+
5+
import { InsetError } from "@/components/inset-error";
6+
import { InsetLoading } from "@/components/inset-loading";
7+
import { InsetLayout } from "@/components/layouts/inset-layout";
8+
import { useOrgUrl } from "@/hooks/use-org-url";
9+
import { usePageBreadcrumbs } from "@/hooks/use-page";
10+
import { useTelegramBot } from "@/services/telegram-bot-service";
11+
12+
export function BotTelegramDetailPage() {
13+
const { id } = useParams<{ id: string }>();
14+
const { setBreadcrumbs } = usePageBreadcrumbs([]);
15+
const { getOrgUrl } = useOrgUrl();
16+
17+
const { telegramBot, telegramBotError, isTelegramBotLoading } =
18+
useTelegramBot(id || null);
19+
20+
useEffect(() => {
21+
setBreadcrumbs([
22+
{ label: "Bots", to: getOrgUrl("bots") },
23+
{ label: telegramBot?.name || id || "" },
24+
]);
25+
}, [id, telegramBot?.name, setBreadcrumbs, getOrgUrl]);
26+
27+
if (isTelegramBotLoading) {
28+
return <InsetLoading title="Bot Details" />;
29+
} else if (telegramBotError) {
30+
return (
31+
<InsetError
32+
title="Bot Details"
33+
errorMessage={telegramBotError.message}
34+
/>
35+
);
36+
} else if (!telegramBot) {
37+
return <InsetError title="Bot Details" errorMessage="Bot not found" />;
38+
}
39+
40+
return (
41+
<InsetLayout title="Bot Details">
42+
<div className="space-y-6 max-w-2xl">
43+
<div className="space-y-4">
44+
<DetailRow label="Name" value={telegramBot.name || "Untitled Bot"} />
45+
<DetailRow
46+
label="Bot Username"
47+
value={
48+
telegramBot.botUsername ? `@${telegramBot.botUsername}` : "---"
49+
}
50+
/>
51+
<DetailRow
52+
label="Token"
53+
value={`****${telegramBot.tokenLastFour}`}
54+
mono
55+
/>
56+
{telegramBot.botUsername && (
57+
<div className="grid grid-cols-[180px_1fr] gap-2 items-center">
58+
<span className="text-sm font-medium text-muted-foreground">
59+
Telegram Link
60+
</span>
61+
<a
62+
href={`https://t.me/${telegramBot.botUsername}`}
63+
target="_blank"
64+
rel="noopener noreferrer"
65+
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
66+
>
67+
@{telegramBot.botUsername}
68+
<ExternalLink className="h-3 w-3" />
69+
</a>
70+
</div>
71+
)}
72+
</div>
73+
</div>
74+
</InsetLayout>
75+
);
76+
}
77+
78+
function DetailRow({
79+
label,
80+
value,
81+
mono,
82+
}: {
83+
label: string;
84+
value: string;
85+
mono?: boolean;
86+
}) {
87+
return (
88+
<div className="grid grid-cols-[180px_1fr] gap-2 items-center">
89+
<span className="text-sm font-medium text-muted-foreground">{label}</span>
90+
<span className={`text-sm ${mono ? "font-mono" : ""}`}>{value}</span>
91+
</div>
92+
);
93+
}

0 commit comments

Comments
 (0)