feat: add Notion OAuth integration and connector routes

- Introduced Notion OAuth support with new environment variables for client ID, client secret, and redirect URI.
- Implemented Notion connector routes for OAuth flow, including authorization and callback handling.
- Updated existing components to accommodate Notion integration, including validation changes and connector configuration.
- Enhanced the Notion indexer to utilize OAuth access tokens instead of integration tokens.
- Adjusted UI components to reflect the new Notion connector without requiring special configuration.
This commit is contained in:
Anish Sarkar 2026-01-02 20:07:14 +05:30
parent 2b01120c2b
commit c5b184d475
14 changed files with 333 additions and 76 deletions

View file

@ -1,7 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { Info, Webhook } from "lucide-react";
import { Webhook } from "lucide-react";
import type { FC } from "react";
import { useRef } from "react";
import { useForm } from "react-hook-form";

View file

@ -11,7 +11,6 @@ import { JiraConnectForm } from "./components/jira-connect-form";
import { LinearConnectForm } from "./components/linear-connect-form";
import { LinkupApiConnectForm } from "./components/linkup-api-connect-form";
import { LumaConnectForm } from "./components/luma-connect-form";
import { NotionConnectForm } from "./components/notion-connect-form";
import { SearxngConnectForm } from "./components/searxng-connect-form";
import { SlackConnectForm } from "./components/slack-connect-form";
import { TavilyApiConnectForm } from "./components/tavily-api-connect-form";
@ -59,8 +58,6 @@ export function getConnectFormComponent(connectorType: string): ConnectFormCompo
return SlackConnectForm;
case "DISCORD_CONNECTOR":
return DiscordConnectForm;
case "NOTION_CONNECTOR":
return NotionConnectForm;
case "CONFLUENCE_CONNECTOR":
return ConfluenceConnectForm;
case "BOOKSTACK_CONNECTOR":

View file

@ -15,7 +15,6 @@ import { JiraConfig } from "./components/jira-config";
import { LinearConfig } from "./components/linear-config";
import { LinkupApiConfig } from "./components/linkup-api-config";
import { LumaConfig } from "./components/luma-config";
import { NotionConfig } from "./components/notion-config";
import { SearxngConfig } from "./components/searxng-config";
import { SlackConfig } from "./components/slack-config";
import { TavilyApiConfig } from "./components/tavily-api-config";
@ -56,8 +55,6 @@ export function getConnectorConfigComponent(
return SlackConfig;
case "DISCORD_CONNECTOR":
return DiscordConfig;
case "NOTION_CONNECTOR":
return NotionConfig;
case "CONFLUENCE_CONNECTOR":
return ConfluenceConfig;
case "BOOKSTACK_CONNECTOR":
@ -72,7 +69,7 @@ export function getConnectorConfigComponent(
return LumaConfig;
case "CIRCLEBACK_CONNECTOR":
return CirclebackConfig;
// OAuth connectors (Gmail, Calendar, Airtable) and others don't need special config UI
// OAuth connectors (Gmail, Calendar, Airtable, Notion) and others don't need special config UI
default:
return null;
}

View file

@ -55,7 +55,6 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
ELASTICSEARCH_CONNECTOR: "elasticsearch-connect-form",
SLACK_CONNECTOR: "slack-connect-form",
DISCORD_CONNECTOR: "discord-connect-form",
NOTION_CONNECTOR: "notion-connect-form",
CONFLUENCE_CONNECTOR: "confluence-connect-form",
BOOKSTACK_CONNECTOR: "bookstack-connect-form",
GITHUB_CONNECTOR: "github-connect-form",

View file

@ -59,6 +59,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
const [isScrolled, setIsScrolled] = useState(false);
const [hasMoreContent, setHasMoreContent] = useState(false);
const [showDisconnectConfirm, setShowDisconnectConfirm] = useState(false);
const [isQuickIndexing, setIsQuickIndexing] = useState(false);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const checkScrollState = useCallback(() => {
@ -94,6 +95,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
};
}, [checkScrollState]);
// Reset local quick indexing state when indexing completes
useEffect(() => {
if (!isIndexing) {
setIsQuickIndexing(false);
}
}, [isIndexing]);
const handleDisconnectClick = () => {
setShowDisconnectConfirm(true);
};
@ -107,6 +115,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
setShowDisconnectConfirm(false);
};
const handleQuickIndex = useCallback(() => {
if (onQuickIndex) {
setIsQuickIndexing(true);
onQuickIndex();
}
}, [onQuickIndex]);
return (
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Fixed Header */}
@ -146,11 +161,11 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
<Button
variant="secondary"
size="sm"
onClick={onQuickIndex}
disabled={isIndexing || isSaving || isDisconnecting}
onClick={handleQuickIndex}
disabled={isQuickIndexing || isIndexing || isSaving || isDisconnecting}
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
>
{isIndexing ? (
{isQuickIndexing || isIndexing ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Indexing...

View file

@ -2,6 +2,7 @@
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { cn } from "@/lib/utils";
@ -43,6 +44,9 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
onStartIndexing,
onSkip,
}) => {
const searchParams = useSearchParams();
const isFromOAuth = searchParams.get("view") === "configure";
// Get connector-specific config component
const ConnectorConfigComponent = useMemo(
() => (connector ? getConnectorConfigComponent(connector.connector_type) : null),
@ -94,15 +98,17 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
isScrolled && "shadow-sm"
)}
>
{/* Back button */}
<button
type="button"
onClick={onSkip}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
>
<ArrowLeft className="size-4" />
Back to connectors
</button>
{/* Back button - only show if not from OAuth */}
{!isFromOAuth && (
<button
type="button"
onClick={onSkip}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
>
<ArrowLeft className="size-4" />
Back to connectors
</button>
)}
{/* Success header */}
<div className="flex items-center gap-4 mb-6">
@ -187,15 +193,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
</div>
{/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted">
<Button
variant="ghost"
onClick={onSkip}
disabled={isStartingIndexing}
className="text-xs sm:text-sm"
>
Skip for now
</Button>
<div className="flex-shrink-0 flex items-center justify-end px-6 sm:px-12 py-6 bg-muted">
<Button
onClick={onStartIndexing}
disabled={isStartingIndexing}

View file

@ -30,6 +30,13 @@ export const OAUTH_CONNECTORS = [
connectorType: EnumConnectorName.AIRTABLE_CONNECTOR,
authEndpoint: "/api/v1/auth/airtable/connector/add/",
},
{
id: "notion-connector",
title: "Notion",
description: "Search your Notion pages",
connectorType: EnumConnectorName.NOTION_CONNECTOR,
authEndpoint: "/api/v1/auth/notion/connector/add/",
},
] as const;
// Content Sources (tools that extract and import content from external sources)
@ -62,12 +69,6 @@ export const OTHER_CONNECTORS = [
description: "Search Discord messages",
connectorType: EnumConnectorName.DISCORD_CONNECTOR,
},
{
id: "notion-connector",
title: "Notion",
description: "Search Notion pages",
connectorType: EnumConnectorName.NOTION_CONNECTOR,
},
{
id: "confluence-connector",
title: "Confluence",
@ -143,7 +144,7 @@ export const OTHER_CONNECTORS = [
{
id: "circleback-connector",
title: "Circleback",
description: "Receive meeting notes via webhook",
description: "Receive meeting notes, transcripts",
connectorType: EnumConnectorName.CIRCLEBACK_CONNECTOR,
},
] as const;