Skip to content

Commit bf6ebb1

Browse files
committed
Add Teams bot frontend components
1 parent 7e13c06 commit bf6ebb1

17 files changed

+776
-0
lines changed

Diff for: web/src/app/admin/bots/teams/ErrorState.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Button } from "@/components/ui/button";
2+
3+
interface ErrorStateProps {
4+
error: Error;
5+
reset: () => void;
6+
}
7+
8+
export function ErrorState({ error, reset }: ErrorStateProps) {
9+
return (
10+
<div className="flex flex-col items-center justify-center space-y-4">
11+
<h2 className="text-2xl font-bold">Something went wrong!</h2>
12+
<p className="text-muted-foreground">{error.message}</p>
13+
<Button onClick={reset}>Try again</Button>
14+
</div>
15+
);
16+
}

Diff for: web/src/app/admin/bots/teams/LoadingState.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

Diff for: web/src/app/admin/bots/teams/NotFoundState.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Button } from "@/components/ui/button";
2+
import Link from "next/link";
3+
4+
export function NotFoundState() {
5+
return (
6+
<div className="flex flex-col items-center justify-center space-y-4">
7+
<h2 className="text-2xl font-bold">Teams Bot Not Found</h2>
8+
<p className="text-muted-foreground">
9+
The Teams bot you are looking for does not exist.
10+
</p>
11+
<Button asChild>
12+
<Link href="/admin/bots/teams">Back to Teams Bots</Link>
13+
</Button>
14+
</div>
15+
);
16+
}
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useState } from "react";
2+
import { useRouter } from "next/navigation";
3+
import { Button } from "@/components/ui/button";
4+
import { Input } from "@/components/ui/input";
5+
import { Label } from "@/components/ui/label";
6+
import { createTeamsBot } from "./lib";
7+
import { TeamsBotCreationRequest } from "@/lib/types";
8+
9+
export const TeamsBotCreationForm = () => {
10+
const router = useRouter();
11+
const [name, setName] = useState("");
12+
const [teamId, setTeamId] = useState("");
13+
const [webhookUrl, setWebhookUrl] = useState("");
14+
const [clientId, setClientId] = useState("");
15+
const [clientSecret, setClientSecret] = useState("");
16+
const [tenantId, setTenantId] = useState("");
17+
const [isLoading, setIsLoading] = useState(false);
18+
19+
const handleSubmit = async (e: React.FormEvent) => {
20+
e.preventDefault();
21+
setIsLoading(true);
22+
23+
try {
24+
const request: TeamsBotCreationRequest = {
25+
name,
26+
team_id: teamId,
27+
webhook_url: webhookUrl,
28+
tokens: {
29+
client_id: clientId,
30+
client_secret: clientSecret,
31+
tenant_id: tenantId,
32+
},
33+
};
34+
35+
await createTeamsBot(request);
36+
router.push("/admin/bots/teams");
37+
} catch (error) {
38+
console.error("Failed to create Teams bot:", error);
39+
} finally {
40+
setIsLoading(false);
41+
}
42+
};
43+
44+
return (
45+
<form onSubmit={handleSubmit} className="space-y-4">
46+
<div className="space-y-2">
47+
<Label htmlFor="name">Name</Label>
48+
<Input
49+
id="name"
50+
value={name}
51+
onChange={(e) => setName(e.target.value)}
52+
required
53+
/>
54+
</div>
55+
<div className="space-y-2">
56+
<Label htmlFor="teamId">Team ID</Label>
57+
<Input
58+
id="teamId"
59+
value={teamId}
60+
onChange={(e) => setTeamId(e.target.value)}
61+
required
62+
/>
63+
</div>
64+
<div className="space-y-2">
65+
<Label htmlFor="webhookUrl">Webhook URL</Label>
66+
<Input
67+
id="webhookUrl"
68+
value={webhookUrl}
69+
onChange={(e) => setWebhookUrl(e.target.value)}
70+
required
71+
/>
72+
</div>
73+
<div className="space-y-2">
74+
<Label htmlFor="clientId">Client ID</Label>
75+
<Input
76+
id="clientId"
77+
value={clientId}
78+
onChange={(e) => setClientId(e.target.value)}
79+
required
80+
/>
81+
</div>
82+
<div className="space-y-2">
83+
<Label htmlFor="clientSecret">Client Secret</Label>
84+
<Input
85+
id="clientSecret"
86+
type="password"
87+
value={clientSecret}
88+
onChange={(e) => setClientSecret(e.target.value)}
89+
required
90+
/>
91+
</div>
92+
<div className="space-y-2">
93+
<Label htmlFor="tenantId">Tenant ID</Label>
94+
<Input
95+
id="tenantId"
96+
value={tenantId}
97+
onChange={(e) => setTenantId(e.target.value)}
98+
required
99+
/>
100+
</div>
101+
<Button type="submit" disabled={isLoading}>
102+
{isLoading ? "Creating..." : "Create Teams Bot"}
103+
</Button>
104+
</form>
105+
);
106+
};

Diff for: web/src/app/admin/bots/teams/TeamsBotTable.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { TeamsBot } from "@/lib/types";
2+
import {
3+
Table,
4+
TableBody,
5+
TableCell,
6+
TableHead,
7+
TableHeader,
8+
TableRow,
9+
} from "@/components/ui/table";
10+
import { Badge } from "@/components/ui/badge";
11+
import { Button } from "@/components/ui/button";
12+
import { Switch } from "@/components/ui/switch";
13+
import { updateTeamsBotField } from "./lib";
14+
import { useState } from "react";
15+
import { useRouter } from "next/navigation";
16+
17+
const NUM_IN_PAGE = 10;
18+
19+
export const TeamsBotTable = ({ teamsBots }: { teamsBots: TeamsBot[] }) => {
20+
const router = useRouter();
21+
const [currentPage, setCurrentPage] = useState(1);
22+
23+
const teamsBotsForPage = teamsBots.slice(
24+
(currentPage - 1) * NUM_IN_PAGE,
25+
currentPage * NUM_IN_PAGE
26+
);
27+
28+
return (
29+
<div className="space-y-4">
30+
<Table>
31+
<TableHeader>
32+
<TableRow>
33+
<TableHead>Name</TableHead>
34+
<TableHead>Status</TableHead>
35+
<TableHead>Channel Configs</TableHead>
36+
<TableHead>Actions</TableHead>
37+
</TableRow>
38+
</TableHeader>
39+
<TableBody>
40+
{teamsBotsForPage.map((teamsBot) => {
41+
return (
42+
<TableRow
43+
key={teamsBot.id}
44+
className="cursor-pointer hover:bg-muted/50"
45+
onClick={() => {
46+
router.push(`/admin/bots/teams/${teamsBot.id}`);
47+
}}
48+
>
49+
<TableCell className="font-medium">{teamsBot.name}</TableCell>
50+
<TableCell>
51+
<Switch
52+
checked={teamsBot.enabled}
53+
onCheckedChange={async (checked) => {
54+
await updateTeamsBotField(teamsBot, "enabled", checked);
55+
}}
56+
/>
57+
</TableCell>
58+
<TableCell>{teamsBot.configs_count}</TableCell>
59+
<TableCell>
60+
<Button
61+
variant="ghost"
62+
onClick={(e) => {
63+
e.stopPropagation();
64+
router.push(`/admin/bots/teams/${teamsBot.id}/channels`);
65+
}}
66+
>
67+
Configure
68+
</Button>
69+
</TableCell>
70+
</TableRow>
71+
);
72+
})}
73+
{teamsBots.length === 0 && (
74+
<TableRow>
75+
<TableCell colSpan={4} className="text-center">
76+
No Teams bots found
77+
</TableCell>
78+
</TableRow>
79+
)}
80+
</TableBody>
81+
</Table>
82+
{teamsBots.length > NUM_IN_PAGE && (
83+
<div className="flex justify-center">
84+
<Button
85+
variant="outline"
86+
onClick={() => setCurrentPage(currentPage - 1)}
87+
disabled={currentPage === 1}
88+
>
89+
Previous
90+
</Button>
91+
<Button
92+
variant="outline"
93+
onClick={() => setCurrentPage(currentPage + 1)}
94+
disabled={currentPage * NUM_IN_PAGE >= teamsBots.length}
95+
>
96+
Next
97+
</Button>
98+
</div>
99+
)}
100+
</div>
101+
);
102+
};
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useState } from "react";
2+
import { useRouter } from "next/navigation";
3+
import { Button } from "@/components/ui/button";
4+
import { Input } from "@/components/ui/input";
5+
import { Label } from "@/components/ui/label";
6+
import { Switch } from "@/components/ui/switch";
7+
import { createTeamsChannelConfig } from "./lib";
8+
import { TeamsChannelConfig } from "@/lib/types";
9+
10+
interface TeamsChannelConfigFormProps {
11+
botId: string;
12+
initialConfig?: TeamsChannelConfig;
13+
}
14+
15+
export const TeamsChannelConfigForm = ({
16+
botId,
17+
initialConfig,
18+
}: TeamsChannelConfigFormProps) => {
19+
const router = useRouter();
20+
const [channelName, setChannelName] = useState(initialConfig?.channel_name || "");
21+
const [channelId, setChannelId] = useState(initialConfig?.channel_id || "");
22+
const [enabled, setEnabled] = useState(initialConfig?.enabled || true);
23+
const [isLoading, setIsLoading] = useState(false);
24+
25+
const handleSubmit = async (e: React.FormEvent) => {
26+
e.preventDefault();
27+
setIsLoading(true);
28+
29+
try {
30+
await createTeamsChannelConfig(botId, {
31+
channel_name: channelName,
32+
channel_id: channelId,
33+
enabled,
34+
});
35+
router.push(`/admin/bots/teams/${botId}/channels`);
36+
} catch (error) {
37+
console.error("Failed to create channel configuration:", error);
38+
} finally {
39+
setIsLoading(false);
40+
}
41+
};
42+
43+
return (
44+
<form onSubmit={handleSubmit} className="space-y-4">
45+
<div className="space-y-2">
46+
<Label htmlFor="channelName">Channel Name</Label>
47+
<Input
48+
id="channelName"
49+
value={channelName}
50+
onChange={(e) => setChannelName(e.target.value)}
51+
required
52+
/>
53+
</div>
54+
<div className="space-y-2">
55+
<Label htmlFor="channelId">Channel ID</Label>
56+
<Input
57+
id="channelId"
58+
value={channelId}
59+
onChange={(e) => setChannelId(e.target.value)}
60+
required
61+
/>
62+
</div>
63+
<div className="flex items-center space-x-2">
64+
<Switch
65+
id="enabled"
66+
checked={enabled}
67+
onCheckedChange={setEnabled}
68+
/>
69+
<Label htmlFor="enabled">Enabled</Label>
70+
</div>
71+
<Button type="submit" disabled={isLoading}>
72+
{isLoading ? "Creating..." : "Create Channel Configuration"}
73+
</Button>
74+
</form>
75+
);
76+
};

0 commit comments

Comments
 (0)