Skip to content

Commit aed2bbc

Browse files
committed
Added bot impersonation
1 parent be50d04 commit aed2bbc

File tree

11 files changed

+149
-3
lines changed

11 files changed

+149
-3
lines changed

front_end/messages/cs.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,5 +1781,8 @@
17811781
"hideApiKey": "Skrýt API klíč",
17821782
"accessToken": "Přístupový token",
17831783
"copy": "kopírovat",
1784+
"switchToBotAccount": "Přepnout na účet bota",
1785+
"impersonationBannerText": "Momentálně si prohlížíte Metaculus jako váš bot.",
1786+
"stopImpersonating": "Přepnout zpět na můj účet",
17841787
"othersCount": "Ostatní ({count})"
17851788
}

front_end/messages/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,9 @@
17741774
"revealApiKey": "Reveal API Key",
17751775
"hideApiKey": "Hide API Key",
17761776
"accessToken": "Access Token",
1777-
"copy": "copy",
1777+
"copy": "Copy",
1778+
"switchToBotAccount": "Switch to Bot Account",
1779+
"impersonationBannerText": "You are currently viewing Metaculus as your bot.",
1780+
"stopImpersonating": "Switch back to my account",
17781781
"none": "none"
17791782
}

front_end/messages/es.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,5 +1781,8 @@
17811781
"hideApiKey": "Ocultar Clave API",
17821782
"accessToken": "Token de Acceso",
17831783
"copy": "copiar",
1784+
"switchToBotAccount": "Cambiar a cuenta de bot",
1785+
"impersonationBannerText": "Actualmente estás viendo Metaculus como tu bot.",
1786+
"stopImpersonating": "Volver a mi cuenta",
17841787
"othersCount": "Otros ({count})"
17851788
}

front_end/messages/pt.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,5 +1779,8 @@
17791779
"hideApiKey": "Ocultar Chave da API",
17801780
"accessToken": "Token de Acesso",
17811781
"copy": "copiar",
1782+
"switchToBotAccount": "Mudar para Conta de Bot",
1783+
"impersonationBannerText": "Você está visualizando o Metaculus atualmente como seu bot.",
1784+
"stopImpersonating": "Voltar para minha conta",
17821785
"othersCount": "Outros ({count})"
17831786
}

front_end/messages/zh-TW.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,5 +1778,8 @@
17781778
"hideApiKey": "隱藏API密鑰",
17791779
"accessToken": "訪問令牌",
17801780
"copy": "複製",
1781+
"switchToBotAccount": "切換到機器人帳戶",
1782+
"impersonationBannerText": "您目前正在以您的機器人帳戶查看 Metaculus。",
1783+
"stopImpersonating": "切換回我的帳戶",
17811784
"withdrawAfterPercentSetting2": "問題總生命周期後撤回"
17821785
}

front_end/messages/zh.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,5 +1783,8 @@
17831783
"hideApiKey": "隐藏 API 密钥",
17841784
"accessToken": "访问令牌",
17851785
"copy": "复制",
1786+
"switchToBotAccount": "切换到机器人账户",
1787+
"impersonationBannerText": "您当前正在以机器人身份查看 Metaculus。",
1788+
"stopImpersonating": "切换回我的账户",
17861789
"othersCount": "其他({count})"
17871790
}

front_end/src/app/(main)/accounts/settings/actions.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
"use server";
22

33
import { revalidatePath } from "next/cache";
4+
import { redirect } from "next/navigation";
45

56
import ServerProfileApi from "@/services/api/profile/profile.server";
7+
import {
8+
deleteImpersonatorSession,
9+
getImpersonatorSession,
10+
getServerSession,
11+
setImpersonatorSession,
12+
setServerSession,
13+
} from "@/services/session";
614
import { ApiError } from "@/utils/core/errors";
715

816
export async function changePassword(password: string, new_password: string) {
@@ -107,3 +115,37 @@ export async function getBotTokenAction(botId: number) {
107115
};
108116
}
109117
}
118+
119+
export async function stopImpersonatingAction() {
120+
const impersonatorToken = await getImpersonatorSession();
121+
122+
if (impersonatorToken) {
123+
await setServerSession(impersonatorToken);
124+
await deleteImpersonatorSession();
125+
}
126+
127+
redirect("/accounts/settings/bots/");
128+
}
129+
130+
export async function impersonateBotAction(botId: number) {
131+
try {
132+
const userToken = await getServerSession();
133+
const { token: botToken } = await ServerProfileApi.getBotToken(botId);
134+
135+
if (userToken) {
136+
await setImpersonatorSession(userToken);
137+
}
138+
139+
await setServerSession(botToken);
140+
141+
redirect("/");
142+
} catch (err) {
143+
if (!ApiError.isApiError(err)) {
144+
throw err;
145+
}
146+
147+
return {
148+
errors: err.data,
149+
};
150+
}
151+
}

front_end/src/app/(main)/accounts/settings/bots/components/bot_controls.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { useTranslations } from "next-intl";
66
import { FC, useState } from "react";
77
import toast from "react-hot-toast";
88

9-
import { getBotTokenAction } from "@/app/(main)/accounts/settings/actions";
9+
import {
10+
getBotTokenAction,
11+
impersonateBotAction,
12+
} from "@/app/(main)/accounts/settings/actions";
1013
import Button from "@/components/ui/button";
1114
import { CurrentBot } from "@/types/users";
1215
import { extractError } from "@/utils/core/errors";
@@ -42,6 +45,18 @@ const BotControls: FC<Props> = ({ bot }) => {
4245
}
4346
};
4447

48+
const [isImpersonating, setIsImpersonating] = useState(false);
49+
50+
const handleImpersonate = async () => {
51+
setIsImpersonating(true);
52+
const response = await impersonateBotAction(id);
53+
54+
if (response?.errors) {
55+
setIsImpersonating(false);
56+
toast.error(extractError(response.errors));
57+
}
58+
};
59+
4560
return (
4661
<div className="flex flex-col gap-2">
4762
<div className="flex flex-wrap gap-2">
@@ -55,6 +70,16 @@ const BotControls: FC<Props> = ({ bot }) => {
5570
<FontAwesomeIcon icon={faSpinner} spin className="ml-1" />
5671
)}
5772
</Button>
73+
<Button
74+
size="xs"
75+
onClick={handleImpersonate}
76+
disabled={isImpersonating}
77+
>
78+
{t("switchToBotAccount")}
79+
{isImpersonating && (
80+
<FontAwesomeIcon icon={faSpinner} spin className="ml-1" />
81+
)}
82+
</Button>
5883
</div>
5984

6085
{apiToken && (
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import { useTranslations } from "next-intl";
4+
import { FC, useState } from "react";
5+
6+
import { stopImpersonatingAction } from "@/app/(main)/accounts/settings/actions";
7+
import Button from "@/components/ui/button";
8+
import { useAuth } from "@/contexts/auth_context";
9+
10+
const ImpersonationBanner: FC = () => {
11+
const t = useTranslations();
12+
const [isLoading, setIsLoading] = useState(false);
13+
const { user } = useAuth();
14+
15+
const handleStop = async () => {
16+
setIsLoading(true);
17+
await stopImpersonatingAction();
18+
};
19+
20+
if (!user?.is_bot) return;
21+
22+
return (
23+
<div className="text-med -mb-12 mt-12 flex w-full items-center justify-center gap-4 border-b border-t border-orange-400 bg-orange-50 p-2 text-sm leading-6 text-orange-900 dark:border-orange-400-dark dark:bg-orange-50-dark dark:text-orange-900-dark sm:px-6">
24+
<span>{t("impersonationBannerText")}</span>
25+
<Button
26+
onClick={handleStop}
27+
disabled={isLoading}
28+
size="sm"
29+
variant="tertiary"
30+
>
31+
{t("stopImpersonating")}
32+
</Button>
33+
</div>
34+
);
35+
};
36+
37+
export default ImpersonationBanner;

front_end/src/app/(main)/layout.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import "@fortawesome/fontawesome-svg-core/styles.css";
33
import type { Metadata } from "next";
44

55
import { defaultDescription } from "@/constants/metadata";
6+
import { getImpersonatorSession } from "@/services/session";
67
import { getPublicSettings } from "@/utils/public_settings.server";
78

89
import FeedbackFloat from "./(home)/components/feedback_float";
910
import Bulletins from "./components/bulletins";
1011
import CookiesBanner from "./components/cookies_banner";
1112
import Footer from "./components/footer";
1213
import GlobalHeader from "./components/headers/global_header";
14+
import ImpersonationBanner from "./components/impersonation_banner";
1315
import VersionChecker from "./components/version_checker";
1416

1517
config.autoAddCss = false;
@@ -26,9 +28,14 @@ export default async function RootLayout({
2628
}: Readonly<{
2729
children: React.ReactNode;
2830
}>) {
31+
const impersonatorToken = await getImpersonatorSession();
32+
2933
return (
3034
<div className="flex min-h-screen flex-col">
3135
<GlobalHeader />
36+
37+
{impersonatorToken && <ImpersonationBanner />}
38+
3239
<Bulletins />
3340
<div className="flex-grow">{children}</div>
3441
{!PUBLIC_MINIMAL_UI && (

0 commit comments

Comments
 (0)