From 2f313187ab4dc6e43d99cae153ccf366cef291c9 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 23 May 2026 12:22:26 +0100 Subject: [PATCH] feat(integrations): one-step install for workspace-scoped providers --- .../integration/components/connection-row.tsx | 60 ++++++++++++------- .../integration/pages/integrations.tsx | 18 +++++- .../integration/types/integration.types.ts | 8 +++ 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/apps/client/src/features/integration/components/connection-row.tsx b/apps/client/src/features/integration/components/connection-row.tsx index 017616bd8..632f0c93d 100644 --- a/apps/client/src/features/integration/components/connection-row.tsx +++ b/apps/client/src/features/integration/components/connection-row.tsx @@ -19,6 +19,7 @@ export default function ConnectionRow({ isDisconnecting, }: ConnectionRowProps) { const { t } = useTranslation(); + const isWorkspaceScoped = definition.oauth?.connectionScope === 'workspace'; return ( - {connection ? ( + {isWorkspaceScoped ? ( <> - - {t("Connected")} - {connection.providerUserId && ` (${connection.providerUserId})`} - - + {connection ? ( + + {t("Linked")} + {connection.providerUserId && ` as @${connection.providerUserId}`} + + ) : ( + + {t("Use")} /docmost help {t("in")} {definition.name} {t("to link your account.")} + + )} ) : ( - + <> + {connection ? ( + <> + + {t("Connected")} + {connection.providerUserId && ` (${connection.providerUserId})`} + + + + ) : ( + + )} + )} diff --git a/apps/client/src/features/integration/pages/integrations.tsx b/apps/client/src/features/integration/pages/integrations.tsx index 85cbfe637..4141d5f0a 100644 --- a/apps/client/src/features/integration/pages/integrations.tsx +++ b/apps/client/src/features/integration/pages/integrations.tsx @@ -14,6 +14,7 @@ import { useUpdateIntegrationSettings, } from "../queries/integration-query"; import { Integration } from "../types/integration.types"; +import { getOAuthAuthorizeUrl } from "../services/integration-service"; export default function Integrations() { const { t } = useTranslation(); @@ -28,10 +29,21 @@ export default function Integrations() { const [configuring, setConfiguring] = useState(null); const handleInstall = useCallback( - (type: string) => { - installMutation.mutate({ type }); + async (type: string) => { + const definition = available?.find((d) => d.type === type); + try { + const integration = await installMutation.mutateAsync({ type }); + if (definition?.oauth?.connectionScope === 'workspace') { + const { authorizationUrl } = await getOAuthAuthorizeUrl({ + integrationId: integration.id, + }); + window.location.href = authorizationUrl; + } + } catch (err) { + // installMutation's onError already shows a notification + } }, - [installMutation], + [installMutation, available], ); const handleUninstall = useCallback( diff --git a/apps/client/src/features/integration/types/integration.types.ts b/apps/client/src/features/integration/types/integration.types.ts index aec8a8491..3092b9f01 100644 --- a/apps/client/src/features/integration/types/integration.types.ts +++ b/apps/client/src/features/integration/types/integration.types.ts @@ -1,11 +1,19 @@ export type IntegrationCapability = "oauth" | "unfurl" | "actions" | "webhooks"; +export type OAuthConfig = { + authUrl: string; + tokenUrl: string; + scopes: string[]; + connectionScope?: 'workspace' | 'user'; +}; + export type IntegrationDefinition = { type: string; name: string; description: string; icon: string; capabilities: IntegrationCapability[]; + oauth?: OAuthConfig; }; export type Integration = {