diff --git a/surfsense_backend/app/routes/airtable_add_connector_route.py b/surfsense_backend/app/routes/airtable_add_connector_route.py
index 423d61fb2..fe359d2f3 100644
--- a/surfsense_backend/app/routes/airtable_add_connector_route.py
+++ b/surfsense_backend/app/routes/airtable_add_connector_route.py
@@ -199,7 +199,7 @@ async def airtable_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=airtable_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=airtable_oauth_denied"
)
else:
return RedirectResponse(
@@ -316,7 +316,7 @@ async def airtable_callback(
f"Duplicate Airtable connector detected for user {user_id} with email {user_email}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=airtable-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=airtable-connector"
)
# Generate a unique, user-friendly connector name
@@ -348,7 +348,7 @@ async def airtable_callback(
# Redirect to the frontend with success params for indexing config
# Using query params to auto-open the popup with config view on new-chat page
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=airtable-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=airtable-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/clickup_add_connector_route.py b/surfsense_backend/app/routes/clickup_add_connector_route.py
index 1b2e6795d..2cd63eca2 100644
--- a/surfsense_backend/app/routes/clickup_add_connector_route.py
+++ b/surfsense_backend/app/routes/clickup_add_connector_route.py
@@ -148,7 +148,7 @@ async def clickup_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=clickup_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=clickup_oauth_denied"
)
else:
return RedirectResponse(
@@ -326,7 +326,7 @@ async def clickup_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=clickup-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=clickup-connector"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/composio_routes.py b/surfsense_backend/app/routes/composio_routes.py
index e0c6c1f65..61076c666 100644
--- a/surfsense_backend/app/routes/composio_routes.py
+++ b/surfsense_backend/app/routes/composio_routes.py
@@ -208,7 +208,7 @@ async def composio_callback(
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=composio_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=composio_oauth_denied"
)
else:
return RedirectResponse(
@@ -370,7 +370,7 @@ async def composio_callback(
toolkit_id, "composio-connector"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector={frontend_connector_id}&connectorId={existing_connector.id}&view=configure"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector={frontend_connector_id}&connectorId={existing_connector.id}"
)
# This is a NEW account - create a new connector
@@ -399,7 +399,7 @@ async def composio_callback(
toolkit_id, "composio-connector"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector={frontend_connector_id}&connectorId={db_connector.id}&view=configure"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector={frontend_connector_id}&connectorId={db_connector.id}"
)
except IntegrityError as e:
diff --git a/surfsense_backend/app/routes/confluence_add_connector_route.py b/surfsense_backend/app/routes/confluence_add_connector_route.py
index 24e0f858a..f50383860 100644
--- a/surfsense_backend/app/routes/confluence_add_connector_route.py
+++ b/surfsense_backend/app/routes/confluence_add_connector_route.py
@@ -170,7 +170,7 @@ async def confluence_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=confluence_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=confluence_oauth_denied"
)
else:
return RedirectResponse(
@@ -310,7 +310,7 @@ async def confluence_callback(
f"Duplicate Confluence connector detected for user {user_id} with instance {connector_identifier}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=confluence-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=confluence-connector"
)
# Generate a unique, user-friendly connector name
@@ -341,7 +341,7 @@ async def confluence_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=confluence-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=confluence-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/discord_add_connector_route.py b/surfsense_backend/app/routes/discord_add_connector_route.py
index e49acf30b..27bfffc90 100644
--- a/surfsense_backend/app/routes/discord_add_connector_route.py
+++ b/surfsense_backend/app/routes/discord_add_connector_route.py
@@ -172,7 +172,7 @@ async def discord_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=discord_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=discord_oauth_denied"
)
else:
return RedirectResponse(
@@ -311,7 +311,7 @@ async def discord_callback(
f"Duplicate Discord connector detected for user {user_id} with server {connector_identifier}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=discord-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=discord-connector"
)
# Generate a unique, user-friendly connector name
@@ -342,7 +342,7 @@ async def discord_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=discord-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=discord-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/google_calendar_add_connector_route.py b/surfsense_backend/app/routes/google_calendar_add_connector_route.py
index 08e5c2f04..6b74bc05d 100644
--- a/surfsense_backend/app/routes/google_calendar_add_connector_route.py
+++ b/surfsense_backend/app/routes/google_calendar_add_connector_route.py
@@ -137,7 +137,7 @@ async def calendar_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=google_calendar_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=google_calendar_oauth_denied"
)
else:
return RedirectResponse(
@@ -210,7 +210,7 @@ async def calendar_callback(
f"Duplicate Google Calendar connector detected for user {user_id} with email {user_email}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=google-calendar-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=google-calendar-connector"
)
try:
@@ -236,7 +236,7 @@ async def calendar_callback(
# Redirect to the frontend with success params for indexing config
# Using query params to auto-open the popup with config view on new-chat page
return RedirectResponse(
- f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=google-calendar-connector&connectorId={db_connector.id}"
+ f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=google-calendar-connector&connectorId={db_connector.id}"
)
except ValidationError as e:
await session.rollback()
diff --git a/surfsense_backend/app/routes/google_drive_add_connector_route.py b/surfsense_backend/app/routes/google_drive_add_connector_route.py
index 9fa83f3a2..23fba8770 100644
--- a/surfsense_backend/app/routes/google_drive_add_connector_route.py
+++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py
@@ -257,7 +257,7 @@ async def drive_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=google_drive_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=google_drive_oauth_denied"
)
else:
return RedirectResponse(
@@ -360,7 +360,7 @@ async def drive_callback(
url=f"{config.NEXT_FRONTEND_URL}{reauth_return_url}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=google-drive-connector&connectorId={db_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=google-drive-connector&connectorId={db_connector.id}"
)
is_duplicate = await check_duplicate_connector(
@@ -375,7 +375,7 @@ async def drive_callback(
f"Duplicate Google Drive connector detected for user {user_id} with email {user_email}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=google-drive-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=google-drive-connector"
)
# Generate a unique, user-friendly connector name
@@ -425,7 +425,7 @@ async def drive_callback(
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=google-drive-connector&connectorId={db_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=google-drive-connector&connectorId={db_connector.id}"
)
except HTTPException:
diff --git a/surfsense_backend/app/routes/google_gmail_add_connector_route.py b/surfsense_backend/app/routes/google_gmail_add_connector_route.py
index 19fa019ce..aafe4d271 100644
--- a/surfsense_backend/app/routes/google_gmail_add_connector_route.py
+++ b/surfsense_backend/app/routes/google_gmail_add_connector_route.py
@@ -168,7 +168,7 @@ async def gmail_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=google_gmail_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=google_gmail_oauth_denied"
)
else:
return RedirectResponse(
@@ -241,7 +241,7 @@ async def gmail_callback(
f"Duplicate Gmail connector detected for user {user_id} with email {user_email}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=google-gmail-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=google-gmail-connector"
)
try:
@@ -272,7 +272,7 @@ async def gmail_callback(
# Redirect to the frontend with success params for indexing config
# Using query params to auto-open the popup with config view on new-chat page
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=google-gmail-connector&connectorId={db_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=google-gmail-connector&connectorId={db_connector.id}"
)
except IntegrityError as e:
diff --git a/surfsense_backend/app/routes/jira_add_connector_route.py b/surfsense_backend/app/routes/jira_add_connector_route.py
index 58903606a..90267bbab 100644
--- a/surfsense_backend/app/routes/jira_add_connector_route.py
+++ b/surfsense_backend/app/routes/jira_add_connector_route.py
@@ -167,7 +167,7 @@ async def jira_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=jira_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=jira_oauth_denied"
)
else:
return RedirectResponse(
@@ -328,7 +328,7 @@ async def jira_callback(
f"Duplicate Jira connector detected for user {user_id} with instance {connector_identifier}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=jira-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=jira-connector"
)
# Generate a unique, user-friendly connector name
@@ -359,7 +359,7 @@ async def jira_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=jira-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=jira-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py
index 119042668..40b800e3b 100644
--- a/surfsense_backend/app/routes/linear_add_connector_route.py
+++ b/surfsense_backend/app/routes/linear_add_connector_route.py
@@ -230,7 +230,7 @@ async def linear_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=linear_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=linear_oauth_denied"
)
else:
return RedirectResponse(
@@ -367,7 +367,7 @@ async def linear_callback(
url=f"{config.NEXT_FRONTEND_URL}{reauth_return_url}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=linear-connector&connectorId={db_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=linear-connector&connectorId={db_connector.id}"
)
# Check for duplicate connector (same organization already connected)
@@ -383,7 +383,7 @@ async def linear_callback(
f"Duplicate Linear connector detected for user {user_id} with org {org_name}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=linear-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=linear-connector"
)
# Generate a unique, user-friendly connector name
@@ -415,7 +415,7 @@ async def linear_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=linear-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=linear-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/notion_add_connector_route.py b/surfsense_backend/app/routes/notion_add_connector_route.py
index 46404acb0..953aa2bb8 100644
--- a/surfsense_backend/app/routes/notion_add_connector_route.py
+++ b/surfsense_backend/app/routes/notion_add_connector_route.py
@@ -227,7 +227,7 @@ async def notion_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=notion_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=notion_oauth_denied"
)
else:
return RedirectResponse(
@@ -365,7 +365,7 @@ async def notion_callback(
url=f"{config.NEXT_FRONTEND_URL}{reauth_return_url}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=notion-connector&connectorId={db_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=notion-connector&connectorId={db_connector.id}"
)
# Extract unique identifier from connector credentials
@@ -386,7 +386,7 @@ async def notion_callback(
f"Duplicate Notion connector detected for user {user_id} with workspace {connector_identifier}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=notion-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=notion-connector"
)
# Generate a unique, user-friendly connector name
@@ -417,7 +417,7 @@ async def notion_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=notion-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=notion-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_backend/app/routes/slack_add_connector_route.py b/surfsense_backend/app/routes/slack_add_connector_route.py
index 0cbfdef44..405ab2c4f 100644
--- a/surfsense_backend/app/routes/slack_add_connector_route.py
+++ b/surfsense_backend/app/routes/slack_add_connector_route.py
@@ -166,7 +166,7 @@ async def slack_callback(
# Redirect to frontend with error parameter
if space_id:
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=slack_oauth_denied"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=slack_oauth_denied"
)
else:
return RedirectResponse(
@@ -296,7 +296,7 @@ async def slack_callback(
f"Duplicate Slack connector detected for user {user_id} with workspace {connector_identifier}"
)
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&error=duplicate_account&connector=slack-connector"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?error=duplicate_account&connector=slack-connector"
)
# Generate a unique, user-friendly connector name
@@ -328,7 +328,7 @@ async def slack_callback(
# Redirect to the frontend with success params
return RedirectResponse(
- url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/new-chat?modal=connectors&tab=all&success=true&connector=slack-connector&connectorId={new_connector.id}"
+ url=f"{config.NEXT_FRONTEND_URL}/dashboard/{space_id}/connectors/callback?success=true&connector=slack-connector&connectorId={new_connector.id}"
)
except ValidationError as e:
diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/page.tsx
new file mode 100644
index 000000000..ef5be4640
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/page.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { Spinner } from "@/components/ui/spinner";
+
+const OAUTH_RESULT_KEY = "connector_oauth_result";
+
+export default function ConnectorCallbackPage({
+ params,
+}: {
+ params: { search_space_id: string };
+}) {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ useEffect(() => {
+ const result = {
+ success: searchParams.get("success"),
+ error: searchParams.get("error"),
+ connector: searchParams.get("connector"),
+ connectorId: searchParams.get("connectorId"),
+ };
+
+ sessionStorage.setItem(OAUTH_RESULT_KEY, JSON.stringify(result));
+ router.replace(`/dashboard/${params.search_space_id}/new-chat`);
+ }, [searchParams, router, params.search_space_id]);
+
+ return (
+
+
+
+ );
+}
diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx
index 4f4bf5cea..e065ce72d 100644
--- a/surfsense_web/components/assistant-ui/connector-popup.tsx
+++ b/surfsense_web/components/assistant-ui/connector-popup.tsx
@@ -2,8 +2,7 @@
import { useAtomValue, useSetAtom } from "jotai";
import { AlertTriangle, Cable, Settings } from "lucide-react";
-import { useSearchParams } from "next/navigation";
-import { type FC, forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
+import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { documentTypeCountsAtom } from "@/atoms/documents/document-query.atoms";
import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom";
import {
@@ -49,7 +48,6 @@ interface ConnectorIndicatorProps {
export const ConnectorIndicator = forwardRef(
({ showTrigger = true }, ref) => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
- const searchParams = useSearchParams();
const setSearchSpaceSettingsDialog = useSetAtom(searchSpaceSettingsDialogAtom);
const { data: currentUser } = useAtomValue(currentUserAtom);
const { data: preferences = {}, isFetching: preferencesLoading } =
@@ -85,9 +83,6 @@ export const ConnectorIndicator = forwardRef
) : indexingConfig ? (
- {
+ if (indexingConfig.connectorId) {
+ startIndexing(indexingConfig.connectorId);
}
- startDate={startDate}
- endDate={endDate}
- periodicEnabled={periodicEnabled}
- frequencyMinutes={frequencyMinutes}
- enableSummary={enableSummary}
- isStartingIndexing={isStartingIndexing}
- onStartDateChange={setStartDate}
- onEndDateChange={setEndDate}
- onPeriodicEnabledChange={setPeriodicEnabled}
- onFrequencyChange={setFrequencyMinutes}
- onEnableSummaryChange={setEnableSummary}
- onConfigChange={setIndexingConnectorConfig}
- onStartIndexing={() => {
- if (indexingConfig.connectorId) {
- startIndexing(indexingConfig.connectorId);
- }
- handleStartIndexing(() => refreshConnectors());
- }}
- onSkip={handleSkipIndexing}
- />
+ handleStartIndexing(() => refreshConnectors());
+ }}
+ onSkip={handleSkipIndexing}
+ />
) : (
void;
onEndDateChange: (date: Date | undefined) => void;
onPeriodicEnabledChange: (enabled: boolean) => void;
@@ -43,6 +43,7 @@ export const IndexingConfigurationView: FC = ({
frequencyMinutes,
enableSummary,
isStartingIndexing,
+ isFromOAuth = false,
onStartDateChange,
onEndDateChange,
onPeriodicEnabledChange,
@@ -52,9 +53,6 @@ export const IndexingConfigurationView: FC = ({
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),
diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-popup.schemas.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-popup.schemas.ts
index 5a0a8e8c8..b03ad893b 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-popup.schemas.ts
+++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-popup.schemas.ts
@@ -1,24 +1,6 @@
import { z } from "zod";
import { searchSourceConnectorTypeEnum } from "@/contracts/types/connector.types";
-/**
- * Schema for URL query parameters used by the connector popup
- */
-export const connectorPopupQueryParamsSchema = z.object({
- modal: z.enum(["connectors"]).optional(),
- tab: z.enum(["all", "active"]).optional(),
- view: z
- .enum(["configure", "edit", "connect", "youtube", "accounts", "mcp-list", "composio"])
- .optional(),
- connector: z.string().optional(),
- connectorId: z.string().optional(),
- connectorType: z.string().optional(),
- success: z.enum(["true", "false"]).optional(),
- error: z.string().optional(),
-});
-
-export type ConnectorPopupQueryParams = z.infer;
-
/**
* Schema for OAuth API response (auth_url)
*/
@@ -72,31 +54,10 @@ export const dateRangeSchema = z
export type DateRange = z.infer;
/**
- * Schema for connector ID validation (used in URL params)
+ * Schema for connector ID validation
*/
export const connectorIdSchema = z.string().min(1, "Connector ID is required");
-/**
- * Helper function to safely parse query params
- */
-export function parseConnectorPopupQueryParams(
- params: URLSearchParams | Record
-): ConnectorPopupQueryParams {
- const obj: Record = {};
-
- if (params instanceof URLSearchParams) {
- params.forEach((value, key) => {
- obj[key] = value || undefined;
- });
- } else {
- Object.entries(params).forEach(([key, value]) => {
- obj[key] = value || undefined;
- });
- }
-
- return connectorPopupQueryParamsSchema.parse(obj);
-}
-
/**
* Helper function to safely parse OAuth response
*/
diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts
index 14183ec75..74cea667c 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts
+++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts
@@ -1,6 +1,5 @@
import { format } from "date-fns";
import { useAtom, useAtomValue } from "jotai";
-import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
@@ -37,14 +36,13 @@ import {
import {
dateRangeSchema,
frequencyMinutesSchema,
- parseConnectorPopupQueryParams,
parseOAuthAuthResponse,
validateIndexingConfigState,
} from "../constants/connector-popup.schemas";
+const OAUTH_RESULT_KEY = "connector_oauth_result";
+
export const useConnectorDialog = () => {
- const router = useRouter();
- const searchParams = useSearchParams();
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const { data: allConnectors, refetch: refetchAllConnectors } = useAtomValue(connectorsAtom);
const { mutateAsync: indexConnector } = useAtomValue(indexConnectorMutationAtom);
@@ -102,6 +100,9 @@ export const useConnectorDialog = () => {
// Track if we came from MCP list view when entering edit mode
const [cameFromMCPList, setCameFromMCPList] = useState(false);
+ // Track if we came from MCP list view when entering connect mode
+ const [connectCameFromMCPList, setConnectCameFromMCPList] = useState(false);
+
// Helper function to get frequency label
const getFrequencyLabel = useCallback((minutes: string): string => {
switch (minutes) {
@@ -181,352 +182,146 @@ export const useConnectorDialog = () => {
[searchSpaceId, indexConnector, updateConnector, refetchAllConnectors]
);
- // When the dialog is opened externally (via setConnectorDialogOpen atom from
- // thread.tsx / DocumentsSidebar.tsx), the URL is not updated. Sync it here
- // so that other handlers that read window.location.href see modal=connectors.
- const activeTabRef = useRef(activeTab);
- activeTabRef.current = activeTab;
- useEffect(() => {
- if (isOpen) {
- const url = new URL(window.location.href);
- const modalParam = url.searchParams.get("modal");
- const tabParam = url.searchParams.get("tab");
- if (modalParam !== "connectors" || (tabParam !== "all" && tabParam !== "active")) {
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("tab", activeTabRef.current);
- window.history.replaceState({ modal: true }, "", url.toString());
- }
- }
- }, [isOpen]);
+ // YouTube view state
+ const [isYouTubeView, setIsYouTubeView] = useState(false);
- // Synchronize state with URL query params
+ // Track whether the current indexing config came from an OAuth redirect
+ const [isFromOAuth, setIsFromOAuth] = useState(false);
+
+ // Consume OAuth result from sessionStorage (set by /connectors/callback page)
useEffect(() => {
+ const raw = sessionStorage.getItem(OAUTH_RESULT_KEY);
+ if (!raw || !searchSpaceId) return;
+ sessionStorage.removeItem(OAUTH_RESULT_KEY);
+
+ let result: {
+ success: string | null;
+ error: string | null;
+ connector: string | null;
+ connectorId: string | null;
+ };
try {
- const params = parseConnectorPopupQueryParams(searchParams);
+ result = JSON.parse(raw);
+ } catch {
+ return;
+ }
- if (params.modal === "connectors") {
- setIsOpen(true);
+ if (result.error) {
+ const oauthConnector = result.connector
+ ? OAUTH_CONNECTORS.find((c) => c.id === result.connector)
+ : null;
+ const name = oauthConnector?.title || "connector";
- if (params.tab === "active" || params.tab === "all") {
- setActiveTab(params.tab);
- }
-
- // Clear indexing config if view is not "configure" anymore
- if (params.view !== "configure" && indexingConfig) {
- setIndexingConfig(null);
- }
-
- // Clear editing connector if view is not "edit" anymore
- if (params.view !== "edit" && editingConnector) {
- setEditingConnector(null);
- setConnectorName(null);
- }
-
- // Clear connecting connector type if view is not "connect" anymore
- if (params.view !== "connect" && connectingConnectorType) {
- setConnectingConnectorType(null);
- }
-
- // Clear viewing accounts type if view is not "accounts" anymore
- if (params.view !== "accounts" && viewingAccountsType) {
- setViewingAccountsType(null);
- }
-
- // Clear MCP list view if view is not "mcp-list" anymore
- if (params.view !== "mcp-list" && viewingMCPList) {
- setViewingMCPList(false);
- }
-
- // Handle MCP list view
- if (params.view === "mcp-list" && !viewingMCPList) {
- setViewingMCPList(true);
- }
-
- // Handle connect view
- if (params.view === "connect" && params.connectorType && !connectingConnectorType) {
- setConnectingConnectorType(params.connectorType);
- }
-
- // Handle accounts view
- if (params.view === "accounts" && params.connectorType) {
- // Update state if not set, or if connectorType has changed
- const needsUpdate =
- !viewingAccountsType || viewingAccountsType.connectorType !== params.connectorType;
-
- if (needsUpdate) {
- // Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
- const oauthConnector =
- OAUTH_CONNECTORS.find((c) => c.connectorType === params.connectorType) ||
- COMPOSIO_CONNECTORS.find((c) => c.connectorType === params.connectorType);
- if (oauthConnector) {
- setViewingAccountsType({
- connectorType: oauthConnector.connectorType,
- connectorTitle: oauthConnector.title,
- });
- }
- }
- }
-
- // Handle YouTube view
- if (params.view === "youtube") {
- // YouTube view is active - no additional state needed
- }
-
- // Handle configure view (for page refresh support)
- if (params.view === "configure" && params.connector && !indexingConfig && allConnectors) {
- // Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
- const oauthConnector =
- OAUTH_CONNECTORS.find((c) => c.id === params.connector) ||
- COMPOSIO_CONNECTORS.find((c) => c.id === params.connector);
- if (oauthConnector) {
- let existingConnector: SearchSourceConnector | undefined;
- if (params.connectorId) {
- const connectorId = parseInt(params.connectorId, 10);
- existingConnector = allConnectors.find(
- (c: SearchSourceConnector) => c.id === connectorId
- );
- } else {
- existingConnector = allConnectors.find(
- (c: SearchSourceConnector) => c.connector_type === oauthConnector.connectorType
- );
- }
- if (existingConnector) {
- const connectorValidation = searchSourceConnector.safeParse(existingConnector);
- if (connectorValidation.success) {
- const config = validateIndexingConfigState({
- connectorType: oauthConnector.connectorType,
- connectorId: existingConnector.id,
- connectorTitle: oauthConnector.title,
- });
- setIndexingConfig(config);
- setIndexingConnector(existingConnector);
- setIndexingConnectorConfig(existingConnector.config);
- }
- }
- }
- }
-
- // Handle edit view
- if (params.view === "edit" && params.connectorId && allConnectors && !editingConnector) {
- const connectorId = parseInt(params.connectorId, 10);
- const connector = allConnectors.find((c: SearchSourceConnector) => c.id === connectorId);
- if (connector) {
- const connectorValidation = searchSourceConnector.safeParse(connector);
- if (connectorValidation.success) {
- setEditingConnector(connector);
- setConnectorConfig(connector.config);
- setConnectorName(connector.name);
- // Load existing periodic sync settings (disabled for non-indexable connectors)
- setPeriodicEnabled(
- !connector.is_indexable ? false : connector.periodic_indexing_enabled
- );
- setFrequencyMinutes(connector.indexing_frequency_minutes?.toString() || "1440");
- setEnableSummary(connector.enable_summary ?? false);
- // Reset dates - user can set new ones for re-indexing
- setStartDate(undefined);
- setEndDate(undefined);
- }
- }
- }
+ if (result.error === "duplicate_account") {
+ toast.error(`This ${name} account is already connected`, {
+ description: "Please use a different account or manage the existing connection.",
+ });
} else {
- // Do NOT call setIsOpen(false) here. Closing the dialog is handled
- // explicitly by handleOpenChange and the individual action handlers.
- // Relying on URL params to close the dialog caused a race condition
- // where Next.js router updates from tab switches briefly produced
- // stale searchParams without the "modal" key, closing the popup.
-
- // Still clean up sub-view state when the modal param is gone
- // (e.g. after browser back navigation or explicit handler URL cleanup).
- if (indexingConfig) {
- setIndexingConfig(null);
- setIndexingConnector(null);
- setIndexingConnectorConfig(null);
- setStartDate(undefined);
- setEndDate(undefined);
- setPeriodicEnabled(false);
- setFrequencyMinutes("1440");
- setEnableSummary(false);
- setIsScrolled(false);
- setSearchQuery("");
- }
- if (editingConnector) {
- setEditingConnector(null);
- setConnectorName(null);
- setConnectorConfig(null);
- setStartDate(undefined);
- setEndDate(undefined);
- setPeriodicEnabled(false);
- setFrequencyMinutes("1440");
- setEnableSummary(false);
- setIsScrolled(false);
- setSearchQuery("");
- }
- if (connectingConnectorType) {
- setConnectingConnectorType(null);
- }
- if (viewingAccountsType) {
- setViewingAccountsType(null);
- }
- }
- } catch (error) {
- // Invalid query params - log but don't crash
- console.warn("Invalid connector popup query params:", error);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [
- searchParams,
- allConnectors,
- editingConnector,
- indexingConfig,
- connectingConnectorType,
- viewingAccountsType,
- viewingMCPList,
- setIsOpen,
- ]);
-
- // Detect OAuth success / Failure and transition to config view
- useEffect(() => {
- try {
- const params = parseConnectorPopupQueryParams(searchParams);
-
- // Handle OAuth errors (e.g., duplicate account)
- if (params.error && params.modal === "connectors") {
- const oauthConnector = params.connector
- ? OAUTH_CONNECTORS.find((c) => c.id === params.connector)
- : null;
- const connectorName = oauthConnector?.title || "connector";
-
- if (params.error === "duplicate_account") {
- toast.error(`This ${connectorName} account is already connected`, {
- description: "Please use a different account or manage the existing connection.",
- });
- } else {
- toast.error(`Failed to connect ${connectorName}`, {
- description: params.error.replace(/_/g, " "),
- });
- }
-
- // Clean up error params from URL
- const url = new URL(window.location.href);
- url.searchParams.delete("error");
- url.searchParams.delete("connector");
- window.history.replaceState({}, "", url.toString());
-
- // Open the popup to show the connectors
- setIsOpen(true);
- return;
- }
-
- if (params.success === "true" && searchSpaceId && params.modal === "connectors") {
- // For auto-index connectors: close modal and show loading toast before refetch
- const earlyConnector = params.connector
- ? OAUTH_CONNECTORS.find((c) => c.id === params.connector) ||
- COMPOSIO_CONNECTORS.find((c) => c.id === params.connector)
- : null;
-
- if (earlyConnector && AUTO_INDEX_CONNECTOR_TYPES.has(earlyConnector.connectorType)) {
- toast.loading(`Setting up ${earlyConnector.title}...`, { id: "auto-index" });
- setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("success");
- url.searchParams.delete("connector");
- url.searchParams.delete("connectorId");
- url.searchParams.delete("view");
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- router.replace(url.pathname + url.search, { scroll: false });
- }
-
- refetchAllConnectors().then(async (result) => {
- if (!result.data) {
- toast.dismiss("auto-index");
- return;
- }
-
- let newConnector: SearchSourceConnector | undefined;
- let oauthConnector:
- | (typeof OAUTH_CONNECTORS)[number]
- | (typeof COMPOSIO_CONNECTORS)[number]
- | undefined;
-
- // First, try to find connector by connectorId if provided
- if (params.connectorId) {
- const connectorId = parseInt(params.connectorId, 10);
- newConnector = result.data.find((c: SearchSourceConnector) => c.id === connectorId);
-
- // If we found the connector, find the matching OAuth/Composio connector by type
- if (newConnector) {
- const connectorType = newConnector.connector_type;
- oauthConnector =
- OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) ||
- COMPOSIO_CONNECTORS.find((c) => c.connectorType === connectorType);
- }
- }
-
- // If we don't have a connector yet, try to find by connector param
- if (!newConnector && params.connector) {
- oauthConnector =
- OAUTH_CONNECTORS.find((c) => c.id === params.connector) ||
- COMPOSIO_CONNECTORS.find((c) => c.id === params.connector);
-
- if (oauthConnector) {
- const oauthConnectorType = oauthConnector.connectorType;
- newConnector = result.data.find(
- (c: SearchSourceConnector) => c.connector_type === oauthConnectorType
- );
- }
- }
-
- if (newConnector && oauthConnector) {
- const connectorValidation = searchSourceConnector.safeParse(newConnector);
- if (connectorValidation.success) {
- trackConnectorConnected(
- Number(searchSpaceId),
- oauthConnector.connectorType,
- newConnector.id
- );
-
- if (
- newConnector.is_indexable &&
- AUTO_INDEX_CONNECTOR_TYPES.has(oauthConnector.connectorType)
- ) {
- await handleAutoIndex(
- newConnector,
- oauthConnector.title,
- oauthConnector.connectorType
- );
- } else {
- toast.dismiss("auto-index");
- const config = validateIndexingConfigState({
- connectorType: oauthConnector.connectorType,
- connectorId: newConnector.id,
- connectorTitle: oauthConnector.title,
- });
- setIndexingConfig(config);
- setIndexingConnector(newConnector);
- setIndexingConnectorConfig(newConnector.config);
- setIsOpen(true);
- const url = new URL(window.location.href);
- url.searchParams.delete("success");
- url.searchParams.set("connectorId", newConnector.id.toString());
- url.searchParams.set("view", "configure");
- window.history.replaceState({}, "", url.toString());
- }
- } else {
- console.warn("Invalid connector data after OAuth:", connectorValidation.error);
- toast.dismiss("auto-index");
- toast.error("Failed to validate connector data");
- }
- } else {
- toast.dismiss("auto-index");
- }
+ toast.error(`Failed to connect ${name}`, {
+ description: result.error.replace(/_/g, " "),
});
}
- } catch (error) {
- // Invalid query params - log but don't crash
- console.warn("Invalid connector popup query params in OAuth success handler:", error);
+
+ setIsOpen(true);
+ return;
}
- }, [searchParams, searchSpaceId, refetchAllConnectors, setIsOpen, handleAutoIndex, router]);
+
+ if (result.success === "true") {
+ const earlyConnector = result.connector
+ ? OAUTH_CONNECTORS.find((c) => c.id === result.connector) ||
+ COMPOSIO_CONNECTORS.find((c) => c.id === result.connector)
+ : null;
+
+ if (earlyConnector && AUTO_INDEX_CONNECTOR_TYPES.has(earlyConnector.connectorType)) {
+ toast.loading(`Setting up ${earlyConnector.title}...`, { id: "auto-index" });
+ setIsOpen(false);
+ }
+
+ refetchAllConnectors().then(async (fetchResult) => {
+ if (!fetchResult.data) {
+ toast.dismiss("auto-index");
+ return;
+ }
+
+ let newConnector: SearchSourceConnector | undefined;
+ let oauthConnector:
+ | (typeof OAUTH_CONNECTORS)[number]
+ | (typeof COMPOSIO_CONNECTORS)[number]
+ | undefined;
+
+ if (result.connectorId) {
+ const connectorId = parseInt(result.connectorId, 10);
+ newConnector = fetchResult.data.find(
+ (c: SearchSourceConnector) => c.id === connectorId
+ );
+ if (newConnector) {
+ const connectorType = newConnector.connector_type;
+ oauthConnector =
+ OAUTH_CONNECTORS.find(
+ (c) => c.connectorType === connectorType
+ ) ||
+ COMPOSIO_CONNECTORS.find(
+ (c) => c.connectorType === connectorType
+ );
+ }
+ }
+
+ if (!newConnector && result.connector) {
+ oauthConnector =
+ OAUTH_CONNECTORS.find((c) => c.id === result.connector) ||
+ COMPOSIO_CONNECTORS.find((c) => c.id === result.connector);
+ if (oauthConnector) {
+ const oauthType = oauthConnector.connectorType;
+ newConnector = fetchResult.data.find(
+ (c: SearchSourceConnector) =>
+ c.connector_type === oauthType
+ );
+ }
+ }
+
+ if (newConnector && oauthConnector) {
+ const connectorValidation = searchSourceConnector.safeParse(newConnector);
+ if (connectorValidation.success) {
+ trackConnectorConnected(
+ Number(searchSpaceId),
+ oauthConnector.connectorType,
+ newConnector.id
+ );
+
+ if (
+ newConnector.is_indexable &&
+ AUTO_INDEX_CONNECTOR_TYPES.has(oauthConnector.connectorType)
+ ) {
+ await handleAutoIndex(
+ newConnector,
+ oauthConnector.title,
+ oauthConnector.connectorType
+ );
+ } else {
+ toast.dismiss("auto-index");
+ const config = validateIndexingConfigState({
+ connectorType: oauthConnector.connectorType,
+ connectorId: newConnector.id,
+ connectorTitle: oauthConnector.title,
+ });
+ setIndexingConfig(config);
+ setIndexingConnector(newConnector);
+ setIndexingConnectorConfig(newConnector.config);
+ setIsFromOAuth(true);
+ setIsOpen(true);
+ }
+ } else {
+ console.warn("Invalid connector data after OAuth:", connectorValidation.error);
+ toast.dismiss("auto-index");
+ toast.error("Failed to validate connector data");
+ }
+ } else {
+ toast.dismiss("auto-index");
+ }
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [searchSpaceId, handleAutoIndex, refetchAllConnectors, setIsOpen]);
// Handle OAuth connection
const handleConnectOAuth = useCallback(
@@ -572,12 +367,7 @@ export const useConnectorDialog = () => {
// Handle creating YouTube crawler (not a connector, shows view in popup)
const handleCreateYouTubeCrawler = useCallback(() => {
if (!searchSpaceId) return;
-
- // Update URL to show YouTube view
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "youtube");
- window.history.pushState({ modal: true }, "", url.toString());
+ setIsYouTubeView(true);
}, [searchSpaceId]);
// Handle creating webcrawler connector
@@ -629,10 +419,6 @@ export const useConnectorDialog = () => {
setIndexingConnector(connector);
setIndexingConnectorConfig(connector.config || {});
setIsOpen(true);
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "configure");
- window.history.pushState({ modal: true }, "", url.toString());
}
}
}
@@ -648,16 +434,7 @@ export const useConnectorDialog = () => {
const handleConnectNonOAuth = useCallback(
(connectorType: string) => {
if (!searchSpaceId) return;
-
- // Set connecting state
setConnectingConnectorType(connectorType);
-
- // Update URL to show connect view
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "connect");
- url.searchParams.set("connectorType", connectorType);
- window.history.pushState({ modal: true }, "", url.toString());
},
[searchSpaceId]
);
@@ -810,27 +587,17 @@ export const useConnectorDialog = () => {
: `${connectorTitle} connected and syncing started!`;
toast.success(successMessage);
- // Close dialog and clean up URL
- setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("view");
- url.searchParams.delete("connectorType");
- router.replace(url.pathname + url.search, { scroll: false });
+ setIsOpen(false);
- // Clear indexing config state since we're not showing the view
- setIndexingConfig(null);
- setIndexingConnector(null);
- setIndexingConnectorConfig(null);
+ setIndexingConfig(null);
+ setIndexingConnector(null);
+ setIndexingConnectorConfig(null);
- // Invalidate queries to refresh data
- queryClient.invalidateQueries({
- queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
- });
+ queryClient.invalidateQueries({
+ queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
+ });
- // Refresh connectors list
- await refetchAllConnectors();
+ await refetchAllConnectors();
} else {
// Non-indexable connector
// For Circleback, transition to edit view to show webhook URL
@@ -852,20 +619,11 @@ export const useConnectorDialog = () => {
setStartDate(undefined);
setEndDate(undefined);
- toast.success(`${connectorTitle} connected successfully!`, {
- description: "Configure the webhook URL in your Circleback settings.",
- });
+ toast.success(`${connectorTitle} connected successfully!`, {
+ description: "Configure the webhook URL in your Circleback settings.",
+ });
- // Transition to edit view
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "edit");
- url.searchParams.set("connectorId", connector.id.toString());
- url.searchParams.delete("connectorType");
- router.replace(url.pathname + url.search, { scroll: false });
-
- // Refresh connectors list
- await refetchAllConnectors();
+ await refetchAllConnectors();
} else {
// Other non-indexable connectors - just show success message and close
const successMessage =
@@ -874,22 +632,13 @@ export const useConnectorDialog = () => {
: `${connectorTitle} connected successfully!`;
toast.success(successMessage);
- // Refresh connectors list before closing modal
- await refetchAllConnectors();
+ await refetchAllConnectors();
- // Close dialog and clean up URL
- setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("view");
- url.searchParams.delete("connectorType");
- router.replace(url.pathname + url.search, { scroll: false });
+ setIsOpen(false);
- // Clear indexing config state
- setIndexingConfig(null);
- setIndexingConnector(null);
- setIndexingConnectorConfig(null);
+ setIndexingConfig(null);
+ setIndexingConnector(null);
+ setIndexingConnectorConfig(null);
}
}
}
@@ -911,96 +660,64 @@ export const useConnectorDialog = () => {
refetchAllConnectors,
updateConnector,
indexConnector,
- router,
setIsOpen,
]
);
// Handle going back from connect view
const handleBackFromConnect = useCallback(() => {
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
-
- // If we're connecting an MCP and came from list view, go back to list
- if (connectingConnectorType === "MCP_CONNECTOR" && viewingMCPList) {
- url.searchParams.set("view", "mcp-list");
- } else {
- url.searchParams.set("tab", "all");
- url.searchParams.delete("view");
+ if (connectCameFromMCPList) {
+ setViewingMCPList(true);
+ setConnectCameFromMCPList(false);
}
-
- url.searchParams.delete("connectorType");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router, connectingConnectorType, viewingMCPList]);
+ setConnectingConnectorType(null);
+ }, [connectCameFromMCPList]);
// Handle going back from YouTube view
const handleBackFromYouTube = useCallback(() => {
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("tab", "all");
- url.searchParams.delete("view");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router]);
+ setIsYouTubeView(false);
+ }, []);
// Handle viewing accounts list for OAuth connector type
const handleViewAccountsList = useCallback(
(connectorType: string, _connectorTitle?: string) => {
if (!searchSpaceId) return;
- // Update URL to show accounts view, preserving current tab
- // The useEffect will handle setting viewingAccountsType based on URL params
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "accounts");
- url.searchParams.set("connectorType", connectorType);
- // Keep the current tab in URL so we can go back to it
- router.replace(url.pathname + url.search, { scroll: false });
+ const oauthConnector =
+ OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) ||
+ COMPOSIO_CONNECTORS.find((c) => c.connectorType === connectorType);
+ if (oauthConnector) {
+ setViewingAccountsType({
+ connectorType: oauthConnector.connectorType,
+ connectorTitle: oauthConnector.title,
+ });
+ }
},
- [searchSpaceId, router]
+ [searchSpaceId]
);
// Handle going back from accounts list view
const handleBackFromAccountsList = useCallback(() => {
setViewingAccountsType(null);
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- // Keep the current tab (don't change it) - just remove view-specific params
- url.searchParams.delete("view");
- url.searchParams.delete("connectorType");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router]);
+ }, []);
// Handle viewing MCP list
const handleViewMCPList = useCallback(() => {
if (!searchSpaceId) return;
-
setViewingMCPList(true);
-
- // Update URL to show MCP list view
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "mcp-list");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [searchSpaceId, router]);
+ }, [searchSpaceId]);
// Handle going back from MCP list view
const handleBackFromMCPList = useCallback(() => {
setViewingMCPList(false);
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.delete("view");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router]);
+ }, []);
// Handle adding new MCP from list view
const handleAddNewMCPFromList = useCallback(() => {
+ setConnectCameFromMCPList(true);
+ setViewingMCPList(false);
setConnectingConnectorType("MCP_CONNECTOR");
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "connect");
- url.searchParams.set("connectorType", "MCP_CONNECTOR");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router]);
+ }, []);
// Handle starting indexing
const handleStartIndexing = useCallback(
@@ -1141,19 +858,12 @@ export const useConnectorDialog = () => {
);
}
- toast.success(`${indexingConfig.connectorTitle} indexing started`);
+ toast.success(`${indexingConfig.connectorTitle} indexing started`);
- // Close dialog and clean up URL
- setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("success");
- url.searchParams.delete("connector");
- url.searchParams.delete("view");
- router.replace(url.pathname + url.search, { scroll: false });
+ setIsOpen(false);
+ setIsFromOAuth(false);
- refreshConnectors();
+ refreshConnectors();
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
});
@@ -1174,7 +884,6 @@ export const useConnectorDialog = () => {
periodicEnabled,
frequencyMinutes,
enableSummary,
- router,
indexingConnectorConfig,
setIsOpen,
]
@@ -1182,16 +891,9 @@ export const useConnectorDialog = () => {
// Handle skipping indexing
const handleSkipIndexing = useCallback(() => {
- // Close dialog and clean up URL
setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("success");
- url.searchParams.delete("connector");
- url.searchParams.delete("view");
- router.replace(url.pathname + url.search, { scroll: false });
- }, [router, setIsOpen]);
+ setIsFromOAuth(false);
+ }, [setIsOpen]);
// Handle starting edit mode
const handleStartEdit = useCallback(
@@ -1213,20 +915,21 @@ export const useConnectorDialog = () => {
return;
}
- // Track if we came from accounts list view
- // If viewingAccountsType matches this connector type, preserve it
- if (viewingAccountsType && viewingAccountsType.connectorType === connector.connector_type) {
- setCameFromAccountsList(viewingAccountsType);
- } else {
- setCameFromAccountsList(null);
- }
+ // Track if we came from accounts list view so handleBackFromEdit can restore it
+ if (viewingAccountsType && viewingAccountsType.connectorType === connector.connector_type) {
+ setCameFromAccountsList(viewingAccountsType);
+ } else {
+ setCameFromAccountsList(null);
+ }
+ setViewingAccountsType(null);
- // Track if we came from MCP list view
- if (viewingMCPList && connector.connector_type === "MCP_CONNECTOR") {
- setCameFromMCPList(true);
- } else {
- setCameFromMCPList(false);
- }
+ // Track if we came from MCP list view so handleBackFromEdit can restore it
+ if (viewingMCPList && connector.connector_type === "MCP_CONNECTOR") {
+ setCameFromMCPList(true);
+ } else {
+ setCameFromMCPList(false);
+ }
+ setViewingMCPList(false);
// Track index with date range opened event
if (connector.is_indexable) {
@@ -1237,24 +940,15 @@ export const useConnectorDialog = () => {
);
}
- setEditingConnector(connector);
- setConnectorName(connector.name);
- // Load existing periodic sync settings (disabled for non-indexable connectors)
- setPeriodicEnabled(!connector.is_indexable ? false : connector.periodic_indexing_enabled);
- setFrequencyMinutes(connector.indexing_frequency_minutes?.toString() || "1440");
- setEnableSummary(connector.enable_summary ?? false);
- // Reset dates - user can set new ones for re-indexing
- setStartDate(undefined);
- setEndDate(undefined);
-
- // Update URL
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "edit");
- url.searchParams.set("connectorId", connector.id.toString());
- window.history.pushState({ modal: true }, "", url.toString());
- },
- [searchSpaceId, viewingAccountsType, viewingMCPList, handleViewMCPList, activeTab]
+ setEditingConnector(connector);
+ setConnectorName(connector.name);
+ setPeriodicEnabled(!connector.is_indexable ? false : connector.periodic_indexing_enabled);
+ setFrequencyMinutes(connector.indexing_frequency_minutes?.toString() || "1440");
+ setEnableSummary(connector.enable_summary ?? false);
+ setStartDate(undefined);
+ setEndDate(undefined);
+ },
+ [searchSpaceId, viewingAccountsType, viewingMCPList, handleViewMCPList, activeTab]
);
// Handle saving connector changes
@@ -1433,43 +1127,35 @@ export const useConnectorDialog = () => {
: indexingDescription,
});
- // Close dialog and clean up URL
- setIsOpen(false);
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("view");
- url.searchParams.delete("connectorId");
- router.replace(url.pathname + url.search, { scroll: false });
+ setIsOpen(false);
- refreshConnectors();
- queryClient.invalidateQueries({
- queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
- });
- } catch (error) {
- console.error("Error saving connector:", error);
- toast.error("Failed to save connector changes");
- } finally {
- setIsSaving(false);
- }
- },
- [
- editingConnector,
- searchSpaceId,
- isSaving,
- startDate,
- endDate,
- indexConnector,
- updateConnector,
- periodicEnabled,
- frequencyMinutes,
- enableSummary,
- getFrequencyLabel,
- router,
- connectorConfig,
- connectorName,
- setIsOpen,
- ]
+ refreshConnectors();
+ queryClient.invalidateQueries({
+ queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
+ });
+ } catch (error) {
+ console.error("Error saving connector:", error);
+ toast.error("Failed to save connector changes");
+ } finally {
+ setIsSaving(false);
+ }
+ },
+ [
+ editingConnector,
+ searchSpaceId,
+ isSaving,
+ startDate,
+ endDate,
+ indexConnector,
+ updateConnector,
+ periodicEnabled,
+ frequencyMinutes,
+ enableSummary,
+ getFrequencyLabel,
+ connectorConfig,
+ connectorName,
+ setIsOpen,
+ ]
);
// Handle disconnecting connector
@@ -1496,25 +1182,16 @@ export const useConnectorDialog = () => {
: `${editingConnector.name} disconnected successfully`
);
- // Update URL - for MCP from list view, go back to list; otherwise close modal
- const url = new URL(window.location.href);
- if (editingConnector.connector_type === "MCP_CONNECTOR" && cameFromMCPList) {
- // Go back to MCP list view only if we came from there
- setViewingMCPList(true);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "mcp-list");
- url.searchParams.delete("connectorId");
- } else {
- // Close dialog for all other cases
- setIsOpen(false);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("view");
- url.searchParams.delete("connectorId");
- }
- router.replace(url.pathname + url.search, { scroll: false });
+ if (editingConnector.connector_type === "MCP_CONNECTOR" && cameFromMCPList) {
+ setViewingMCPList(true);
+ setEditingConnector(null);
+ setConnectorName(null);
+ setConnectorConfig(null);
+ } else {
+ setIsOpen(false);
+ }
- refreshConnectors();
+ refreshConnectors();
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
});
@@ -1525,7 +1202,7 @@ export const useConnectorDialog = () => {
setIsDisconnecting(false);
}
},
- [editingConnector, searchSpaceId, deleteConnector, router, cameFromMCPList, setIsOpen]
+ [editingConnector, searchSpaceId, deleteConnector, cameFromMCPList, setIsOpen]
);
// Handle quick index (index with selected date range, or backend defaults if none selected)
@@ -1584,66 +1261,35 @@ export const useConnectorDialog = () => {
// Handle going back from edit view
const handleBackFromEdit = useCallback(() => {
- // If editing an MCP connector and came from MCP list, go back to MCP list view
if (editingConnector?.connector_type === "MCP_CONNECTOR" && cameFromMCPList) {
setViewingMCPList(true);
setCameFromMCPList(false);
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "mcp-list");
- url.searchParams.delete("connectorId");
- router.replace(url.pathname + url.search, { scroll: false });
setEditingConnector(null);
setConnectorName(null);
setConnectorConfig(null);
return;
}
- // If we came from accounts list view, go back there
if (cameFromAccountsList && editingConnector) {
- // Restore accounts list view
setViewingAccountsType(cameFromAccountsList);
setCameFromAccountsList(null);
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("view", "accounts");
- url.searchParams.set("connectorType", cameFromAccountsList.connectorType);
- url.searchParams.delete("connectorId");
- router.replace(url.pathname + url.search, { scroll: false });
- } else {
- // Otherwise, go back to main connector popup (preserve the tab the user was on)
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("tab", activeTab); // Use current tab instead of always "all"
- url.searchParams.delete("view");
- url.searchParams.delete("connectorId");
- router.replace(url.pathname + url.search, { scroll: false });
}
+
setEditingConnector(null);
setConnectorName(null);
setConnectorConfig(null);
- }, [router, cameFromAccountsList, editingConnector, cameFromMCPList, activeTab]);
+ }, [cameFromAccountsList, editingConnector, cameFromMCPList]);
// Handle dialog open/close
const handleOpenChange = useCallback(
(open: boolean) => {
setIsOpen(open);
- if (open) {
- const url = new URL(window.location.href);
- url.searchParams.set("modal", "connectors");
- url.searchParams.set("tab", activeTab);
- window.history.pushState({ modal: true }, "", url.toString());
- } else {
- const url = new URL(window.location.href);
- url.searchParams.delete("modal");
- url.searchParams.delete("tab");
- url.searchParams.delete("success");
- url.searchParams.delete("connector");
- url.searchParams.delete("view");
- window.history.pushState({ modal: false }, "", url.toString());
+ if (!open) {
setIsScrolled(false);
setSearchQuery("");
+ setIsYouTubeView(false);
+ setIsFromOAuth(false);
if (!isStartingIndexing && !isSaving && !isDisconnecting && !isCreatingConnector) {
setIndexingConfig(null);
setIndexingConnector(null);
@@ -1651,10 +1297,13 @@ export const useConnectorDialog = () => {
setEditingConnector(null);
setConnectorName(null);
setConnectorConfig(null);
- setConnectingConnectorType(null);
- setViewingAccountsType(null);
- setCameFromAccountsList(null);
- setStartDate(undefined);
+ setConnectingConnectorType(null);
+ setViewingAccountsType(null);
+ setViewingMCPList(false);
+ setCameFromAccountsList(null);
+ setCameFromMCPList(false);
+ setConnectCameFromMCPList(false);
+ setStartDate(undefined);
setEndDate(undefined);
setPeriodicEnabled(false);
setFrequencyMinutes("1440");
@@ -1662,14 +1311,9 @@ export const useConnectorDialog = () => {
}
}
},
- [activeTab, isStartingIndexing, isDisconnecting, isSaving, isCreatingConnector, setIsOpen]
+ [isStartingIndexing, isDisconnecting, isSaving, isCreatingConnector, setIsOpen]
);
- // Handle tab change — only update React state.
- // Avoid window.history.replaceState here: Next.js intercepts it, triggers a
- // searchParams update/transition, and the resulting concurrent re-render can
- // cause Radix Dialog's DismissableLayer to detect a transient focus-outside
- // event, which fires onOpenChange(false) and closes the dialog.
const handleTabChange = useCallback((value: string) => {
setActiveTab(value);
}, []);
@@ -1704,6 +1348,8 @@ export const useConnectorDialog = () => {
allConnectors,
viewingAccountsType,
viewingMCPList,
+ isYouTubeView,
+ isFromOAuth,
// Setters
setSearchQuery,
diff --git a/surfsense_web/components/assistant-ui/connector-popup/index.ts b/surfsense_web/components/assistant-ui/connector-popup/index.ts
index adc0e0770..f3209a777 100644
--- a/surfsense_web/components/assistant-ui/connector-popup/index.ts
+++ b/surfsense_web/components/assistant-ui/connector-popup/index.ts
@@ -12,19 +12,16 @@ export type { IndexingConfigState } from "./constants/connector-constants";
// Constants and types
export { CRAWLERS, OAUTH_CONNECTORS, OTHER_CONNECTORS } from "./constants/connector-constants";
export type {
- ConnectorPopupQueryParams,
DateRange,
FrequencyMinutes,
OAuthAuthResponse,
} from "./constants/connector-popup.schemas";
// Schemas and validation
export {
- connectorPopupQueryParamsSchema,
dateRangeSchema,
frequencyMinutesSchema,
indexingConfigStateSchema,
oauthAuthResponseSchema,
- parseConnectorPopupQueryParams,
parseOAuthAuthResponse,
validateIndexingConfigState,
} from "./constants/connector-popup.schemas";