88 type Connector as QueryConnector ,
99 useGetConnectorsQuery ,
1010} from "@/app/api/queries/useGetConnectorsQuery" ;
11+ import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog" ;
1112import { useAuth } from "@/contexts/auth-context" ;
1213import { useBrand } from "@/contexts/brand-context" ;
1314import { isSaasPolicyContext } from "@/lib/brand" ;
@@ -29,6 +30,9 @@ export default function ConnectorCards() {
2930 } ) ;
3031 const router = useRouter ( ) ;
3132 const [ openDialog , setOpenDialog ] = useState < string | null > ( null ) ;
33+ const [ disconnectTarget , setDisconnectTarget ] = useState < Connector | null > (
34+ null ,
35+ ) ;
3236
3337 const { data : queryConnectors = [ ] , isLoading : connectorsLoading } =
3438 useGetConnectorsQuery ( {
@@ -50,10 +54,17 @@ export default function ConnectorCards() {
5054 return < Icon /> ;
5155 } , [ ] ) ;
5256
53- const connectors = queryConnectors . map ( ( c ) => ( {
54- ...c ,
55- icon : getConnectorIcon ( c . type ) ,
56- } ) ) as Connector [ ] ;
57+ const connectors = queryConnectors . reduce < Connector [ ] > ( ( acc , c ) => {
58+ // Keep OAuth connectors regardless of availability
59+ // Only hide credential-based connectors when unavailable
60+ if ( c . requiresOAuth || c . available !== false ) {
61+ acc . push ( {
62+ ...c ,
63+ icon : getConnectorIcon ( c . type ) ,
64+ } as Connector ) ;
65+ }
66+ return acc ;
67+ } , [ ] ) ;
5768
5869 const handleConnect = async ( connector : Connector ) => {
5970 connectMutation . mutate ( {
@@ -62,8 +73,19 @@ export default function ConnectorCards() {
6273 } ) ;
6374 } ;
6475
65- const handleDisconnect = async ( connector : Connector ) => {
66- disconnectMutation . mutate ( connector as unknown as QueryConnector ) ;
76+ const handleDisconnect = ( connector : Connector ) => {
77+ setDisconnectTarget ( connector ) ;
78+ } ;
79+
80+ const confirmDisconnect = async ( ) => {
81+ if ( ! disconnectTarget ) return ;
82+ try {
83+ await disconnectMutation . mutateAsync (
84+ disconnectTarget as unknown as QueryConnector ,
85+ ) ;
86+ } finally {
87+ setDisconnectTarget ( null ) ;
88+ }
6789 } ;
6890
6991 const navigateToKnowledgePage = ( connector : Connector ) => {
@@ -94,36 +116,82 @@ export default function ConnectorCards() {
94116 ( d ) => d . SettingsDialog ,
95117 ) ;
96118
119+ // Split connectors into OAuth and credential-based
120+ const oauthConnectors = connectors . filter ( ( c ) => c . requiresOAuth ) ;
121+ const credentialConnectors = connectors . filter ( ( c ) => ! c . requiresOAuth ) ;
122+
97123 return (
98124 < >
99- < div className = "grid gap-6 md:grid-cols-2 lg:grid-cols-3" >
100- { connectorsLoading ? (
101- < >
102- < ConnectorsSkeleton />
103- < ConnectorsSkeleton />
104- < ConnectorsSkeleton />
105- </ >
106- ) : (
107- connectors . map ( ( connector ) => (
108- < ConnectorCard
109- key = { connector . id }
110- connector = { connector }
111- isConnecting = {
112- connectMutation . isPending &&
113- connectMutation . variables ?. connector . id === connector . id
114- }
115- isDisconnecting = {
116- disconnectMutation . isPending &&
117- ( disconnectMutation . variables as any ) ?. type === connector . type
118- }
119- onConnect = { handleConnect }
120- onDisconnect = { handleDisconnect }
121- onNavigateToKnowledge = { navigateToKnowledgePage }
122- onConfigure = { getConfigureHandler ( connector ) }
123- />
124- ) )
125- ) }
126- </ div >
125+ { /* OAuth Connectors Section */ }
126+ { ( connectorsLoading || oauthConnectors . length > 0 ) && (
127+ < div className = "space-y-4" >
128+ < h3 className = "text-lg font-semibold" > OAuth Connectors</ h3 >
129+ < div className = "grid gap-6 md:grid-cols-2 lg:grid-cols-3" >
130+ { connectorsLoading ? (
131+ < >
132+ < ConnectorsSkeleton />
133+ < ConnectorsSkeleton />
134+ < ConnectorsSkeleton />
135+ </ >
136+ ) : (
137+ oauthConnectors . map ( ( connector ) => (
138+ < ConnectorCard
139+ key = { connector . id }
140+ connector = { connector }
141+ isConnecting = {
142+ connectMutation . isPending &&
143+ connectMutation . variables ?. connector . id === connector . id
144+ }
145+ isDisconnecting = {
146+ disconnectMutation . isPending &&
147+ ( disconnectMutation . variables as any ) ?. type ===
148+ connector . type
149+ }
150+ onConnect = { handleConnect }
151+ onDisconnect = { handleDisconnect }
152+ onNavigateToKnowledge = { navigateToKnowledgePage }
153+ onConfigure = { getConfigureHandler ( connector ) }
154+ />
155+ ) )
156+ ) }
157+ </ div >
158+ </ div >
159+ ) }
160+
161+ { /* Credential-Based Connectors Section */ }
162+ { ( connectorsLoading || credentialConnectors . length > 0 ) && (
163+ < div className = "space-y-4" >
164+ < h3 className = "text-lg font-semibold" > Credential-Based Connectors</ h3 >
165+ < div className = "grid gap-6 md:grid-cols-2 lg:grid-cols-3" >
166+ { connectorsLoading ? (
167+ < >
168+ < ConnectorsSkeleton />
169+ < ConnectorsSkeleton />
170+ </ >
171+ ) : (
172+ credentialConnectors . map ( ( connector ) => (
173+ < ConnectorCard
174+ key = { connector . id }
175+ connector = { connector }
176+ isConnecting = {
177+ connectMutation . isPending &&
178+ connectMutation . variables ?. connector . id === connector . id
179+ }
180+ isDisconnecting = {
181+ disconnectMutation . isPending &&
182+ ( disconnectMutation . variables as any ) ?. type ===
183+ connector . type
184+ }
185+ onConnect = { handleConnect }
186+ onDisconnect = { handleDisconnect }
187+ onNavigateToKnowledge = { navigateToKnowledgePage }
188+ onConfigure = { getConfigureHandler ( connector ) }
189+ />
190+ ) )
191+ ) }
192+ </ div >
193+ </ div >
194+ ) }
127195
128196 { dialogDescriptors . map ( ( descriptor ) => {
129197 // Render only while open so the component unmounts on close, which
@@ -145,6 +213,25 @@ export default function ConnectorCards() {
145213 />
146214 ) ;
147215 } ) }
216+
217+ < DeleteConfirmationDialog
218+ open = { disconnectTarget !== null }
219+ onOpenChange = { ( open ) => {
220+ if ( ! open ) setDisconnectTarget ( null ) ;
221+ } }
222+ title = { `Disconnect ${ disconnectTarget ?. name ?? "connector" } ?` }
223+ description = { `This will remove the ${ disconnectTarget ?. name ?? "connector" } connection from OpenRAG.` }
224+ confirmText = "Disconnect"
225+ onConfirm = { confirmDisconnect }
226+ isLoading = { disconnectMutation . isPending }
227+ >
228+ < p >
229+ Any documents previously ingested from this connector will remain in
230+ your knowledge base, but new syncs will no longer be possible until
231+ you reconnect. Re-authenticating later will allow you to resume
232+ syncing.
233+ </ p >
234+ </ DeleteConfirmationDialog >
148235 </ >
149236 ) ;
150237}
0 commit comments