From 75c79cbe9e582f5fdeead1c6f55084a28c330f1c Mon Sep 17 00:00:00 2001
From: Philipinho <16838612+Philipinho@users.noreply.github.com>
Date: Sat, 23 May 2026 02:23:11 +0100
Subject: [PATCH] feat(slack): add browser-side identity link confirmation page
---
apps/client/src/App.tsx | 2 +
.../features/integration/pages/slack-link.tsx | 112 ++++++++++++++++++
.../services/slack-link-service.ts | 23 ++++
3 files changed, 137 insertions(+)
create mode 100644 apps/client/src/features/integration/pages/slack-link.tsx
create mode 100644 apps/client/src/features/integration/services/slack-link-service.ts
diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx
index f8394b9e7..ff36f0f52 100644
--- a/apps/client/src/App.tsx
+++ b/apps/client/src/App.tsx
@@ -40,6 +40,7 @@ import WorkspaceApiKeys from "@/ee/api-key/pages/workspace-api-keys";
import AiSettings from "@/ee/ai/pages/ai-settings.tsx";
import Integrations from "@/features/integration/pages/integrations.tsx";
import Connections from "@/features/integration/pages/connections.tsx";
+import SlackLinkPage from "@/features/integration/pages/slack-link.tsx";
import AuditLogs from "@/ee/audit/pages/audit-logs.tsx";
import VerifiedPages from "@/ee/page-verification/pages/verified-pages.tsx";
import TemplateList from "@/ee/template/pages/template-list";
@@ -88,6 +89,7 @@ export default function App() {
} />
} />
} />
+ } />
}>
} />
diff --git a/apps/client/src/features/integration/pages/slack-link.tsx b/apps/client/src/features/integration/pages/slack-link.tsx
new file mode 100644
index 000000000..f9cec389b
--- /dev/null
+++ b/apps/client/src/features/integration/pages/slack-link.tsx
@@ -0,0 +1,112 @@
+import { useEffect, useState } from "react";
+import { useSearchParams, useNavigate } from "react-router-dom";
+import { Alert, Button, Card, Group, Loader, Stack, Text } from "@mantine/core";
+import { useTranslation } from "react-i18next";
+import { useAtomValue } from "jotai";
+import {
+ decodeSlackLinkState,
+ confirmSlackLink,
+ SlackLinkStateInfo,
+} from "../services/slack-link-service";
+import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
+import APP_ROUTE from "@/lib/app-route";
+
+export default function SlackLinkPage() {
+ const { t } = useTranslation();
+ const [searchParams] = useSearchParams();
+ const navigate = useNavigate();
+ const state = searchParams.get("state");
+ const currentUser = useAtomValue(currentUserAtom);
+
+ const [info, setInfo] = useState(null);
+ const [error, setError] = useState(null);
+ const [submitting, setSubmitting] = useState(false);
+ const [done, setDone] = useState(false);
+
+ useEffect(() => {
+ if (!currentUser) {
+ const redirectPath = window.location.pathname + window.location.search;
+ navigate(`${APP_ROUTE.AUTH.LOGIN}?redirect=${encodeURIComponent(redirectPath)}`);
+ return;
+ }
+
+ if (!state) {
+ setError(t("Missing state parameter"));
+ return;
+ }
+
+ decodeSlackLinkState(state)
+ .then(setInfo)
+ .catch((e) => setError(e?.response?.data?.message ?? e.message));
+ }, [state, t, currentUser, navigate]);
+
+ async function onConfirm() {
+ if (!state) return;
+ setSubmitting(true);
+ try {
+ await confirmSlackLink(state);
+ setDone(true);
+ } catch (e: any) {
+ setError(e?.response?.data?.message ?? e.message);
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ if (done) {
+ return (
+
+
+ {t("Connected")}
+ {t("You can close this tab and return to Slack.")}
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+ {error}
+
+
+ );
+ }
+
+ if (!info || !currentUser) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ {t("Link your Docmost account to Slack")}
+
+ {t("Connect Docmost account")}{" "}
+ {currentUser.user.email}{" "}
+ {t("to Slack user")} @{info.slackUserName}
+ {info.slackTeamName && (
+ <>
+ {" "}
+ {t("in")} {info.slackTeamName}
+ >
+ )}
+ ?
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/client/src/features/integration/services/slack-link-service.ts b/apps/client/src/features/integration/services/slack-link-service.ts
new file mode 100644
index 000000000..ea761dba0
--- /dev/null
+++ b/apps/client/src/features/integration/services/slack-link-service.ts
@@ -0,0 +1,23 @@
+import api from "@/lib/api-client";
+
+export type SlackLinkStateInfo = {
+ slackUserName: string;
+ slackUserId: string;
+ slackTeamId: string;
+ slackTeamName: string | null;
+ integrationWorkspaceId: string | undefined;
+};
+
+export async function decodeSlackLinkState(
+ state: string,
+): Promise {
+ const req = await api.post(
+ "/integrations/slack/link/state",
+ { state },
+ );
+ return req.data;
+}
+
+export async function confirmSlackLink(state: string): Promise {
+ await api.post("/integrations/slack/link", { state });
+}