diff --git a/apps/web/components/app-experience.tsx b/apps/web/components/app-experience.tsx index 42fedec9..77695b10 100644 --- a/apps/web/components/app-experience.tsx +++ b/apps/web/components/app-experience.tsx @@ -146,9 +146,28 @@ export function AppExperience() { } | null>(null) useEffect(() => { const sp = new URLSearchParams(window.location.search) - if (sp.get("slack") !== "connected") return + const slackParam = sp.get("slack") + if (slackParam !== "connected" && slackParam !== "error") return const team = sp.get("team") - if (isCompanyBrain) { + if (slackParam === "error") { + const reason = sp.get("reason") + const org = sp.get("linked_org") + const isOrgMember = sp.get("linked_member") === "1" + const conflictMessage = + org && isOrgMember + ? `That Slack workspace is already connected to your "${org}" organization. Switch to it to manage the connection.` + : org + ? `That Slack workspace is already connected to another Supermemory organization (${org}). Ask the teammate who set it up.` + : "That Slack workspace is already connected to a different Supermemory organization. Disconnect it there first, or switch to that organization." + toast.error( + reason === "workspace_conflict" + ? conflictMessage + : reason === "expired" + ? "That Slack connect link expired. Try connecting again." + : "Slack connection failed. Try again.", + { duration: 10000 }, + ) + } else if (isCompanyBrain) { setSlackHandoff({ team }) } else { toast.success( @@ -159,6 +178,9 @@ export function AppExperience() { } sp.delete("slack") sp.delete("team") + sp.delete("reason") + sp.delete("linked_org") + sp.delete("linked_member") const qs = sp.toString() window.history.replaceState( null, diff --git a/apps/web/components/settings/company-brain-connections.tsx b/apps/web/components/settings/company-brain-connections.tsx index e0e3185f..d7969d3e 100644 --- a/apps/web/components/settings/company-brain-connections.tsx +++ b/apps/web/components/settings/company-brain-connections.tsx @@ -237,19 +237,28 @@ function SlackCard({ status, isAdmin, installHref, + onDisconnect, }: { status: SlackStatus | null isAdmin: boolean installHref: string + onDisconnect: () => Promise }) { const connected = status?.connected ?? false + const [confirming, setConfirming] = useState(false) + const [disconnecting, setDisconnecting] = useState(false) + useEffect(() => { + if (!confirming) return + const timer = setTimeout(() => setConfirming(false), 4000) + return () => clearTimeout(timer) + }, [confirming]) return (
-
+

+ {connected && status?.teamName ? ( + + {status.teamName} + + ) : null}
{isAdmin ? ( - - {connected ? "Reconnect" : "Connect"} - +
+ {connected ? ( + + ) : null} + + {connected ? "Reconnect" : "Connect"} + +
) : null}
@@ -560,6 +603,27 @@ export default function CompanyBrainConnections() { status={slackStatus} isAdmin={isAdmin} installHref={`${BACKEND}/brain/slack/oauth/install`} + onDisconnect={async () => { + try { + const res = await fetch(`${BACKEND}/brain/slack/workspace`, { + method: "DELETE", + credentials: "include", + }) + if (res.status === 403) { + toast.error("Only admins can disconnect Slack.") + return + } + if (!res.ok) { + toast.error("Couldn't disconnect Slack.") + return + } + } catch { + toast.error("Couldn't disconnect Slack.") + return + } + toast.success("Slack disconnected.") + await load().catch(() => undefined) + }} /> {apps.map((entry) => (