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 }); +}