2025-12-30 15:51:10 +05:30
"use client" ;
2025-12-30 04:03:34 +05:30
import { useAtomValue } from "jotai" ;
2026-02-01 19:56:03 -08:00
import { AlertTriangle , Cable , Settings } from "lucide-react" ;
import Link from "next/link" ;
2025-12-31 18:17:03 +05:30
import { useSearchParams } from "next/navigation" ;
2026-01-16 11:32:06 -08:00
import type { FC } from "react" ;
2026-02-01 19:56:03 -08:00
import {
globalNewLLMConfigsAtom ,
llmPreferencesAtom ,
} from "@/atoms/new-llm-config/new-llm-config-query.atoms" ;
2025-12-30 04:03:34 +05:30
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms" ;
2026-01-23 18:57:10 +05:30
import { currentUserAtom } from "@/atoms/user/user-query.atoms" ;
2026-01-01 22:56:37 -08:00
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button" ;
2026-02-01 19:56:03 -08:00
import { Alert , AlertDescription , AlertTitle } from "@/components/ui/alert" ;
import { Button } from "@/components/ui/button" ;
2026-01-23 20:19:04 +05:30
import { Dialog , DialogContent , DialogTitle } from "@/components/ui/dialog" ;
2026-01-25 15:23:45 +05:30
import { Spinner } from "@/components/ui/spinner" ;
2026-01-01 22:24:42 +05:30
import { Tabs , TabsContent } from "@/components/ui/tabs" ;
2026-01-01 22:56:37 -08:00
import type { SearchSourceConnector } from "@/contracts/types/connector.types" ;
2026-01-16 11:32:06 -08:00
import { useConnectorsElectric } from "@/hooks/use-connectors-electric" ;
2026-02-04 22:25:42 +05:30
import { useDocuments } from "@/hooks/use-documents" ;
2026-01-23 18:57:10 +05:30
import { useInbox } from "@/hooks/use-inbox" ;
2025-12-30 20:07:15 +05:30
import { cn } from "@/lib/utils" ;
2025-12-31 02:00:11 +05:30
import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header" ;
2025-12-31 12:35:43 +05:30
import { ConnectorConnectView } from "./connector-popup/connector-configs/views/connector-connect-view" ;
2026-01-01 22:56:37 -08:00
import { ConnectorEditView } from "./connector-popup/connector-configs/views/connector-edit-view" ;
2025-12-31 02:00:11 +05:30
import { IndexingConfigurationView } from "./connector-popup/connector-configs/views/indexing-configuration-view" ;
2026-01-24 04:36:34 +05:30
import {
COMPOSIO_CONNECTORS ,
OAUTH_CONNECTORS ,
} from "./connector-popup/constants/connector-constants" ;
2025-12-31 02:00:11 +05:30
import { useConnectorDialog } from "./connector-popup/hooks/use-connector-dialog" ;
2026-01-16 11:32:06 -08:00
import { useIndexingConnectors } from "./connector-popup/hooks/use-indexing-connectors" ;
2026-01-01 22:56:37 -08:00
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab" ;
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab" ;
2026-01-07 09:28:07 +02:00
import { ConnectorAccountsListView } from "./connector-popup/views/connector-accounts-list-view" ;
2026-01-01 22:56:37 -08:00
import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view" ;
2025-12-30 15:51:10 +05:30
2026-02-04 12:55:38 +05:30
export const ConnectorIndicator : FC < { hideTrigger? : boolean } > = ( { hideTrigger = false } ) = > {
2025-12-30 04:03:34 +05:30
const searchSpaceId = useAtomValue ( activeSearchSpaceIdAtom ) ;
2025-12-31 18:17:03 +05:30
const searchParams = useSearchParams ( ) ;
2026-01-23 18:57:10 +05:30
const { data : currentUser } = useAtomValue ( currentUserAtom ) ;
2026-02-01 19:56:03 -08:00
const { data : preferences = { } , isFetching : preferencesLoading } =
useAtomValue ( llmPreferencesAtom ) ;
const { data : globalConfigs = [ ] , isFetching : globalConfigsLoading } =
useAtomValue ( globalNewLLMConfigsAtom ) ;
// Check if document summary LLM is properly configured
// - If ID is 0 (Auto mode), we need global configs to be available
// - If ID is positive (user config) or negative (specific global config), it's configured
// - If ID is null/undefined, it's not configured
const docSummaryLlmId = preferences . document_summary_llm_id ;
const isAutoMode = docSummaryLlmId === 0 ;
const hasGlobalConfigs = globalConfigs . length > 0 ;
const hasDocumentSummaryLLM =
docSummaryLlmId !== null &&
docSummaryLlmId !== undefined &&
// If it's Auto mode, we need global configs to actually be available
( ! isAutoMode || hasGlobalConfigs ) ;
const llmConfigLoading = preferencesLoading || globalConfigsLoading ;
2026-01-16 11:32:06 -08:00
// Fetch document type counts using Electric SQL + PGlite for real-time updates
2026-02-04 22:25:42 +05:30
const { typeCounts : documentTypeCounts , loading : documentTypesLoading } = useDocuments (
searchSpaceId ? Number ( searchSpaceId ) : null
) ;
2026-01-01 22:24:42 +05:30
2026-01-23 18:57:10 +05:30
// Fetch notifications to detect indexing failures
const { inboxItems = [ ] } = useInbox (
currentUser ? . id ? ? null ,
searchSpaceId ? Number ( searchSpaceId ) : null ,
"connector_indexing"
) ;
2025-12-31 18:17:03 +05:30
// Check if YouTube view is active
const isYouTubeView = searchParams . get ( "view" ) === "youtube" ;
2025-12-30 19:49:28 +05:30
2025-12-30 20:07:15 +05:30
// Use the custom hook for dialog state management
const {
isOpen ,
activeTab ,
connectingId ,
isScrolled ,
searchQuery ,
indexingConfig ,
2025-12-31 02:00:11 +05:30
indexingConnector ,
indexingConnectorConfig ,
editingConnector ,
2025-12-31 12:35:43 +05:30
connectingConnectorType ,
isCreatingConnector ,
2025-12-30 20:07:15 +05:30
startDate ,
endDate ,
isStartingIndexing ,
2025-12-31 02:00:11 +05:30
isSaving ,
isDisconnecting ,
2025-12-30 20:07:15 +05:30
periodicEnabled ,
frequencyMinutes ,
2026-02-26 18:24:57 -08:00
enableSummary ,
2025-12-30 20:07:15 +05:30
allConnectors ,
2026-01-07 09:28:07 +02:00
viewingAccountsType ,
2026-01-16 14:39:44 -08:00
viewingMCPList ,
2025-12-30 20:07:15 +05:30
setSearchQuery ,
setStartDate ,
setEndDate ,
setPeriodicEnabled ,
setFrequencyMinutes ,
2026-02-26 18:24:57 -08:00
setEnableSummary ,
2025-12-30 20:07:15 +05:30
handleOpenChange ,
handleTabChange ,
handleScroll ,
handleConnectOAuth ,
2025-12-31 12:35:43 +05:30
handleConnectNonOAuth ,
2025-12-31 11:41:57 +05:30
handleCreateWebcrawler ,
2025-12-31 18:17:03 +05:30
handleCreateYouTubeCrawler ,
2025-12-31 12:35:43 +05:30
handleSubmitConnectForm ,
2025-12-30 20:07:15 +05:30
handleStartIndexing ,
handleSkipIndexing ,
2025-12-31 02:00:11 +05:30
handleStartEdit ,
handleSaveConnector ,
handleDisconnectConnector ,
handleBackFromEdit ,
2025-12-31 12:35:43 +05:30
handleBackFromConnect ,
2025-12-31 18:17:03 +05:30
handleBackFromYouTube ,
2026-01-07 09:28:07 +02:00
handleViewAccountsList ,
handleBackFromAccountsList ,
2026-01-16 14:39:44 -08:00
handleBackFromMCPList ,
handleAddNewMCPFromList ,
2026-01-01 21:22:34 +05:30
handleQuickIndexConnector ,
2025-12-31 02:00:11 +05:30
connectorConfig ,
setConnectorConfig ,
setIndexingConnectorConfig ,
2025-12-31 12:35:43 +05:30
setConnectorName ,
2025-12-30 20:07:15 +05:30
} = useConnectorDialog ( ) ;
2025-12-30 15:51:10 +05:30
2026-01-16 11:32:06 -08:00
// Fetch connectors using Electric SQL + PGlite for real-time updates
// This provides instant updates when connectors change, without polling
2025-12-31 12:35:43 +05:30
const {
2026-01-16 11:32:06 -08:00
connectors : connectorsFromElectric = [ ] ,
loading : connectorsLoading ,
error : connectorsError ,
refreshConnectors : refreshConnectorsElectric ,
} = useConnectorsElectric ( searchSpaceId ) ;
2025-12-31 12:35:43 +05:30
2026-01-16 16:13:20 -08:00
// Fallback to API if Electric is not available or fails
// Use Electric data if: 1) we have data, or 2) still loading without error
// Use API data if: Electric failed (has error) or finished loading with no data
2026-01-19 21:23:22 +05:30
const useElectricData =
connectorsFromElectric . length > 0 || ( connectorsLoading && ! connectorsError ) ;
2026-01-16 16:13:20 -08:00
const connectors = useElectricData ? connectorsFromElectric : allConnectors || [ ] ;
2025-12-31 12:35:43 +05:30
2026-01-16 11:32:06 -08:00
// Manual refresh function that works with both Electric and API
const refreshConnectors = async ( ) = > {
2026-01-16 16:13:20 -08:00
if ( useElectricData ) {
2026-01-16 11:32:06 -08:00
await refreshConnectorsElectric ( ) ;
} else {
// Fallback: use allConnectors from useConnectorDialog (which uses connectorsAtom)
// The connectorsAtom will handle refetching if needed
}
} ;
2025-12-31 12:35:43 +05:30
2026-01-16 11:32:06 -08:00
// Track indexing state locally - clears automatically when Electric SQL detects last_indexed_at changed
2026-01-23 18:57:10 +05:30
// Also clears when failed notifications are detected
const { indexingConnectorIds , startIndexing , stopIndexing } = useIndexingConnectors (
connectors as SearchSourceConnector [ ] ,
inboxItems
2026-01-16 11:32:06 -08:00
) ;
2025-12-31 12:35:43 +05:30
const isLoading = connectorsLoading || documentTypesLoading ;
2025-12-30 04:03:34 +05:30
// Get document types that have documents in the search space
const activeDocumentTypes = documentTypeCounts
2025-12-30 19:49:28 +05:30
? Object . entries ( documentTypeCounts ) . filter ( ( [ , count ] ) = > count > 0 )
2025-12-30 04:03:34 +05:30
: [ ] ;
const hasConnectors = connectors . length > 0 ;
const hasSources = hasConnectors || activeDocumentTypes . length > 0 ;
const totalSourceCount = connectors . length + activeDocumentTypes . length ;
2026-01-19 21:23:22 +05:30
2026-01-16 14:39:44 -08:00
const activeConnectorsCount = connectors . length ;
2025-12-30 04:03:34 +05:30
2025-12-30 15:51:10 +05:30
// Check which connectors are already connected
2026-01-16 11:32:06 -08:00
// Using Electric SQL + PGlite for real-time connector updates
2026-01-23 03:57:02 +05:30
const connectedTypes = new Set < string > (
2026-01-16 11:32:06 -08:00
( connectors || [ ] ) . map ( ( c : SearchSourceConnector ) = > c . connector_type )
2025-12-30 15:51:10 +05:30
) ;
2025-12-30 04:03:34 +05:30
if ( ! searchSpaceId ) return null ;
return (
2025-12-30 15:51:10 +05:30
< Dialog open = { isOpen } onOpenChange = { handleOpenChange } >
2026-02-04 12:55:38 +05:30
{ ! hideTrigger && (
< TooltipIconButton
data - joyride = "connector-icon"
2026-02-06 05:35:15 +05:30
tooltip = {
hasConnectors ? ` Manage ${ activeConnectorsCount } connectors ` : "Connect your data"
}
2026-02-04 12:55:38 +05:30
side = "bottom"
className = { cn (
"size-[34px] rounded-full p-1 flex items-center justify-center transition-colors relative" ,
"hover:bg-muted-foreground/15 dark:hover:bg-muted-foreground/30" ,
"outline-none focus:outline-none focus-visible:outline-none font-semibold text-xs" ,
"border-0 ring-0 focus:ring-0 shadow-none focus:shadow-none"
) }
aria - label = {
hasConnectors ? ` View ${ activeConnectorsCount } connectors ` : "Add your first connector"
}
onClick = { ( ) = > handleOpenChange ( true ) }
>
{ isLoading ? (
< Spinner size = "sm" / >
) : (
< >
< Cable className = "size-4 stroke-[1.5px]" / >
{ activeConnectorsCount > 0 && (
< span className = "absolute -top-0.5 right-0 flex items-center justify-center min-w-[16px] h-4 px-1 text-[10px] font-medium rounded-full bg-primary text-primary-foreground shadow-sm" >
{ activeConnectorsCount > 99 ? "99+" : activeConnectorsCount }
< / span >
) }
< / >
) }
< / TooltipIconButton >
) }
2025-12-30 15:51:10 +05:30
2026-02-21 22:30:41 +05:30
< DialogContent className = "max-w-3xl w-[95vw] sm:w-full h-[75vh] sm:h-[85vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 [&>button]:right-4 sm:[&>button]:right-12 [&>button]:top-6 sm:[&>button]:top-10 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button_svg]:size-5 select-none" >
2026-01-23 20:19:04 +05:30
< DialogTitle className = "sr-only" > Manage Connectors < / DialogTitle >
2025-12-31 18:17:03 +05:30
{ /* YouTube Crawler View - shown when adding YouTube videos */ }
{ isYouTubeView && searchSpaceId ? (
2026-01-01 22:24:42 +05:30
< YouTubeCrawlerView searchSpaceId = { searchSpaceId } onBack = { handleBackFromYouTube } / >
2026-01-16 14:39:44 -08:00
) : viewingMCPList ? (
2026-01-19 19:50:07 +05:30
< ConnectorAccountsListView
connectorType = "MCP_CONNECTOR"
connectorTitle = "MCP Connectors"
connectors = { ( allConnectors || [ ] ) as SearchSourceConnector [ ] }
indexingConnectorIds = { indexingConnectorIds }
onBack = { handleBackFromMCPList }
onManage = { handleStartEdit }
onAddAccount = { handleAddNewMCPFromList }
addButtonText = "Add New MCP Server"
/ >
2026-01-07 09:28:07 +02:00
) : viewingAccountsType ? (
< ConnectorAccountsListView
connectorType = { viewingAccountsType . connectorType }
connectorTitle = { viewingAccountsType . connectorTitle }
2026-01-16 11:32:06 -08:00
connectors = { ( connectors || [ ] ) as SearchSourceConnector [ ] } // Using Electric SQL + PGlite for real-time connector updates (all connector types)
2026-01-07 09:28:07 +02:00
indexingConnectorIds = { indexingConnectorIds }
onBack = { handleBackFromAccountsList }
onManage = { handleStartEdit }
onAddAccount = { ( ) = > {
2026-01-23 18:37:09 +05:30
// Check both OAUTH_CONNECTORS and COMPOSIO_CONNECTORS
const oauthConnector =
OAUTH_CONNECTORS . find (
( c ) = > c . connectorType === viewingAccountsType . connectorType
) ||
COMPOSIO_CONNECTORS . find (
( c ) = > c . connectorType === viewingAccountsType . connectorType
) ;
2026-01-07 09:28:07 +02:00
if ( oauthConnector ) {
2026-01-07 10:54:49 +02:00
handleConnectOAuth ( oauthConnector ) ;
2026-01-07 09:28:07 +02:00
}
} }
isConnecting = { connectingId !== null }
/ >
2025-12-31 18:17:03 +05:30
) : connectingConnectorType ? (
2025-12-31 12:35:43 +05:30
< ConnectorConnectView
connectorType = { connectingConnectorType }
2026-01-16 11:32:06 -08:00
onSubmit = { ( formData ) = > handleSubmitConnectForm ( formData , startIndexing ) }
2025-12-31 12:35:43 +05:30
onBack = { handleBackFromConnect }
isSubmitting = { isCreatingConnector }
/ >
) : editingConnector ? (
2025-12-31 02:00:11 +05:30
< ConnectorEditView
connector = { {
. . . editingConnector ,
config : connectorConfig || editingConnector . config ,
2025-12-31 12:35:43 +05:30
name : editingConnector.name ,
2026-02-01 03:52:00 +05:30
// Sync last_indexed_at with live data from Electric SQL for real-time updates
last_indexed_at :
( connectors as SearchSourceConnector [ ] ) . find ( ( c ) = > c . id === editingConnector . id )
? . last_indexed_at ? ? editingConnector . last_indexed_at ,
2025-12-31 02:00:11 +05:30
} }
startDate = { startDate }
endDate = { endDate }
periodicEnabled = { periodicEnabled }
frequencyMinutes = { frequencyMinutes }
2026-02-26 18:24:57 -08:00
enableSummary = { enableSummary }
2025-12-31 02:00:11 +05:30
isSaving = { isSaving }
isDisconnecting = { isDisconnecting }
2026-01-01 21:22:34 +05:30
isIndexing = { indexingConnectorIds . has ( editingConnector . id ) }
2026-01-13 13:46:17 -08:00
searchSpaceId = { searchSpaceId ? . toString ( ) }
2025-12-31 02:00:11 +05:30
onStartDateChange = { setStartDate }
onEndDateChange = { setEndDate }
onPeriodicEnabledChange = { setPeriodicEnabled }
onFrequencyChange = { setFrequencyMinutes }
2026-02-26 18:24:57 -08:00
onEnableSummaryChange = { setEnableSummary }
2026-01-16 11:32:06 -08:00
onSave = { ( ) = > {
startIndexing ( editingConnector . id ) ;
handleSaveConnector ( ( ) = > refreshConnectors ( ) ) ;
} }
2025-12-31 12:35:43 +05:30
onDisconnect = { ( ) = > handleDisconnectConnector ( ( ) = > refreshConnectors ( ) ) }
2025-12-31 02:00:11 +05:30
onBack = { handleBackFromEdit }
2026-01-01 22:24:42 +05:30
onQuickIndex = {
editingConnector . connector_type !== "GOOGLE_DRIVE_CONNECTOR"
2026-01-16 11:32:06 -08:00
? ( ) = > {
startIndexing ( editingConnector . id ) ;
2026-01-23 23:03:29 +05:30
handleQuickIndexConnector (
editingConnector . id ,
editingConnector . connector_type ,
stopIndexing ,
startDate ,
endDate
) ;
2026-01-16 11:32:06 -08:00
}
2026-01-01 22:24:42 +05:30
: undefined
}
2025-12-31 02:00:11 +05:30
onConfigChange = { setConnectorConfig }
2025-12-31 12:35:43 +05:30
onNameChange = { setConnectorName }
2025-12-31 02:00:11 +05:30
/ >
) : indexingConfig ? (
2025-12-30 20:07:15 +05:30
< IndexingConfigurationView
config = { indexingConfig }
2026-01-01 22:24:42 +05:30
connector = {
indexingConnector
? {
. . . indexingConnector ,
config : indexingConnectorConfig || indexingConnector . config ,
}
: undefined
}
2025-12-30 20:07:15 +05:30
startDate = { startDate }
endDate = { endDate }
periodicEnabled = { periodicEnabled }
frequencyMinutes = { frequencyMinutes }
2026-02-26 18:24:57 -08:00
enableSummary = { enableSummary }
2025-12-30 20:07:15 +05:30
isStartingIndexing = { isStartingIndexing }
onStartDateChange = { setStartDate }
onEndDateChange = { setEndDate }
onPeriodicEnabledChange = { setPeriodicEnabled }
onFrequencyChange = { setFrequencyMinutes }
2026-02-26 18:24:57 -08:00
onEnableSummaryChange = { setEnableSummary }
2025-12-31 02:00:11 +05:30
onConfigChange = { setIndexingConnectorConfig }
2026-01-16 11:32:06 -08:00
onStartIndexing = { ( ) = > {
if ( indexingConfig . connectorId ) {
startIndexing ( indexingConfig . connectorId ) ;
}
handleStartIndexing ( ( ) = > refreshConnectors ( ) ) ;
} }
2025-12-30 20:07:15 +05:30
onSkip = { handleSkipIndexing }
/ >
) : (
2026-01-01 22:24:42 +05:30
< Tabs
value = { activeTab }
onValueChange = { handleTabChange }
className = "flex-1 flex flex-col min-h-0"
>
2025-12-30 20:07:15 +05:30
{ /* Header */ }
< ConnectorDialogHeader
activeTab = { activeTab }
2026-01-01 20:38:12 +05:30
totalSourceCount = { activeConnectorsCount }
2025-12-30 20:07:15 +05:30
searchQuery = { searchQuery }
onTabChange = { handleTabChange }
onSearchChange = { setSearchQuery }
isScrolled = { isScrolled }
/ >
{ /* Content */ }
< div className = "flex-1 min-h-0 relative overflow-hidden" >
< div className = "h-full overflow-y-auto" onScroll = { handleScroll } >
2026-01-02 17:17:49 +05:30
< div className = "px-4 sm:px-12 py-4 sm:py-8 pb-12 sm:pb-16" >
2026-02-01 19:56:03 -08:00
{ /* LLM Configuration Warning */ }
{ ! llmConfigLoading && ! hasDocumentSummaryLLM && (
< Alert variant = "destructive" className = "mb-6" >
< AlertTriangle className = "h-4 w-4" / >
< AlertTitle > LLM Configuration Required < / AlertTitle >
< AlertDescription className = "mt-2" >
< p className = "mb-3" >
{ isAutoMode && ! hasGlobalConfigs
? "Auto mode is selected but no global LLM configurations are available. Please configure a custom LLM in Settings to process and summarize documents from your connected sources."
: "You need to configure a Document Summary LLM before adding connectors. This LLM is used to process and summarize documents from your connected sources." }
< / p >
< Button asChild size = "sm" variant = "outline" >
2026-02-21 22:55:54 +05:30
< Link href = { ` /dashboard/ ${ searchSpaceId } /settings?section=models ` } >
2026-02-01 19:56:03 -08:00
< Settings className = "mr-2 h-4 w-4" / >
Go to Settings
< / Link >
< / Button >
< / AlertDescription >
< / Alert >
) }
2026-01-01 22:24:42 +05:30
< TabsContent value = "all" className = "m-0" >
< AllConnectorsTab
searchQuery = { searchQuery }
searchSpaceId = { searchSpaceId }
connectedTypes = { connectedTypes }
connectingId = { connectingId }
2026-01-16 11:32:06 -08:00
allConnectors = { connectors }
2026-01-01 22:24:42 +05:30
documentTypeCounts = { documentTypeCounts }
indexingConnectorIds = { indexingConnectorIds }
2026-02-01 19:56:03 -08:00
onConnectOAuth = { hasDocumentSummaryLLM ? handleConnectOAuth : ( ) = > { } }
onConnectNonOAuth = { hasDocumentSummaryLLM ? handleConnectNonOAuth : ( ) = > { } }
onCreateWebcrawler = { hasDocumentSummaryLLM ? handleCreateWebcrawler : ( ) = > { } }
2026-02-01 21:17:24 -08:00
onCreateYouTubeCrawler = {
hasDocumentSummaryLLM ? handleCreateYouTubeCrawler : ( ) = > { }
}
2026-01-01 22:24:42 +05:30
onManage = { handleStartEdit }
2026-01-07 09:28:07 +02:00
onViewAccountsList = { handleViewAccountsList }
2026-01-01 22:24:42 +05:30
/ >
< / TabsContent >
2025-12-30 20:07:15 +05:30
< ActiveConnectorsTab
2026-01-02 01:23:04 +05:30
searchQuery = { searchQuery }
2025-12-30 20:07:15 +05:30
hasSources = { hasSources }
totalSourceCount = { totalSourceCount }
activeDocumentTypes = { activeDocumentTypes }
connectors = { connectors as SearchSourceConnector [ ] }
indexingConnectorIds = { indexingConnectorIds }
searchSpaceId = { searchSpaceId }
onTabChange = { handleTabChange }
2025-12-31 02:00:11 +05:30
onManage = { handleStartEdit }
2026-01-07 15:20:36 +02:00
onViewAccountsList = { handleViewAccountsList }
2025-12-30 16:05:38 +05:30
/ >
< / div >
2025-12-30 15:51:10 +05:30
< / div >
2025-12-30 20:07:15 +05:30
{ /* Bottom fade shadow */ }
2026-02-20 16:45:50 -08:00
< div className = "absolute bottom-0 left-0 right-0 h-7 bg-linear-to-t from-muted via-muted/80 to-transparent pointer-events-none z-10" / >
2025-12-30 04:03:34 +05:30
< / div >
2025-12-30 20:07:15 +05:30
< / Tabs >
) }
2025-12-30 15:51:10 +05:30
< / DialogContent >
< / Dialog >
2025-12-30 04:03:34 +05:30
) ;
} ;