feat: add connectorId support for multi-account OAuth connectors

Backend:
- Add connectorId to OAuth redirect URLs in all 10 connector routes
- Enables frontend to identify the specific connector created

Frontend:
- Update OAuth success handler to use connectorId for finding new connector
- Set connectorId in URL when transitioning to configure view
- Add connectorId support in URL sync effect for page refresh
- Consolidate handleAddAccountOAuth into handleConnectOAuth
- Update indexing config view to show connector type and display name
This commit is contained in:
CREDO23 2026-01-07 10:54:49 +02:00
parent 2508b37f4e
commit 9ad1348d6b
13 changed files with 48 additions and 62 deletions

View file

@ -86,7 +86,6 @@ export const ConnectorIndicator: FC = () => {
handleBackFromYouTube,
handleViewAccountsList,
handleBackFromAccountsList,
handleAddAccountOAuth,
handleQuickIndexConnector,
connectorConfig,
setConnectorConfig,
@ -214,7 +213,7 @@ export const ConnectorIndicator: FC = () => {
(c) => c.connectorType === viewingAccountsType.connectorType
);
if (oauthConnector) {
handleAddAccountOAuth(oauthConnector);
handleConnectOAuth(oauthConnector);
}
}}
isConnecting={connectingId !== null}

View file

@ -8,8 +8,10 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import { cn } from "@/lib/utils";
import { DateRangeSelector } from "../../components/date-range-selector";
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
import type { IndexingConfigState } from "../../constants/connector-constants";
import { OAUTH_CONNECTORS, type IndexingConfigState } from "../../constants/connector-constants";
import { getConnectorConfigComponent } from "../index";
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
import { getConnectorDisplayName } from "../../tabs/all-connectors-tab";
interface IndexingConfigurationViewProps {
config: IndexingConfigState;
@ -89,12 +91,14 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
};
}, [checkScrollState]);
const authConnector = OAUTH_CONNECTORS.find((c) => c.connectorType === connector?.connector_type);
return (
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Fixed Header */}
<div
className={cn(
"flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
"shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-sm"
)}
>
@ -111,14 +115,14 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
)}
{/* Success header */}
<div className="flex items-center gap-4 mb-6">
<div className="flex gap-4 mb-6">
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-green-500/10 border border-green-500/20">
<Check className="size-7 text-green-500" />
</div>
<div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
{config.connectorTitle} Connected!
</h2>
<div className="flex flex-col">
<span className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word">{getConnectorTypeDisplay(connector?.connector_type || "")} Connected !</span> <span className="text-xl sm:text-xl font-semibold text-muted-foreground tracking-tight text-wrap whitespace-normal wrap-break-word">{getConnectorDisplayName(connector?.name || "")}</span>
</div>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Configure when to start syncing your data
</p>

View file

@ -148,14 +148,22 @@ export const useConnectorDialog = () => {
// YouTube view is active - no additional state needed
}
if (params.view === "configure" && params.connector && !indexingConfig) {
// Handle configure view (for page refresh support)
if (params.view === "configure" && params.connector && !indexingConfig && allConnectors) {
const oauthConnector = OAUTH_CONNECTORS.find((c) => c.id === params.connector);
if (oauthConnector && allConnectors) {
const existingConnector = allConnectors.find(
(c: SearchSourceConnector) => c.connector_type === oauthConnector.connectorType
);
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) {
// Validate connector data before setting state
const connectorValidation = searchSourceConnector.safeParse(existingConnector);
if (connectorValidation.success) {
const config = validateIndexingConfigState({
@ -253,11 +261,19 @@ export const useConnectorDialog = () => {
refetchAllConnectors().then((result) => {
if (!result.data) return;
const newConnector = result.data.find(
(c: SearchSourceConnector) => c.connector_type === oauthConnector.connectorType
);
let newConnector: SearchSourceConnector | undefined;
if (params.connectorId) {
const connectorId = parseInt(params.connectorId, 10);
newConnector = result.data.find(
(c: SearchSourceConnector) => c.id === connectorId
);
} else {
newConnector = result.data.find(
(c: SearchSourceConnector) => c.connector_type === oauthConnector.connectorType
);
}
if (newConnector) {
// Validate connector data before setting state
const connectorValidation = searchSourceConnector.safeParse(newConnector);
if (connectorValidation.success) {
const config = validateIndexingConfigState({
@ -271,6 +287,7 @@ export const useConnectorDialog = () => {
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 {
@ -691,39 +708,6 @@ export const useConnectorDialog = () => {
router.replace(url.pathname + url.search, { scroll: false });
}, [router]);
// Handle adding a new account for OAuth connector (from accounts list view)
const handleAddAccountOAuth = useCallback(
async (connector: (typeof OAUTH_CONNECTORS)[number]) => {
if (!searchSpaceId || !connector.authEndpoint) return;
// Set connecting state
setConnectingId(connector.id);
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${connector.authEndpoint}?space_id=${searchSpaceId}`,
{ method: "GET" }
);
if (!response.ok) {
throw new Error(`Failed to initiate ${connector.title} OAuth`);
}
const data = await response.json();
const validatedData = parseOAuthAuthResponse(data);
window.location.href = validatedData.auth_url;
} catch (error) {
console.error(`Error connecting to ${connector.title}:`, error);
if (error instanceof Error && error.message.includes("Invalid auth URL")) {
toast.error(`Invalid response from ${connector.title} OAuth endpoint`);
} else {
toast.error(`Failed to connect to ${connector.title}`);
}
setConnectingId(null);
}
},
[searchSpaceId]
);
// Handle starting indexing
const handleStartIndexing = useCallback(
@ -1249,7 +1233,6 @@ export const useConnectorDialog = () => {
handleBackFromYouTube,
handleViewAccountsList,
handleBackFromAccountsList,
handleAddAccountOAuth,
handleQuickIndexConnector,
connectorConfig,
setConnectorConfig,