chore: ran frontend linting

This commit is contained in:
Anish Sarkar 2026-01-19 21:23:22 +05:30
parent 5f672a07f2
commit f0997b0a30
11 changed files with 199 additions and 193 deletions

View file

@ -101,7 +101,8 @@ export const ConnectorIndicator: FC = () => {
// 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
const useElectricData = connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError);
const useElectricData =
connectorsFromElectric.length > 0 || (connectorsLoading && !connectorsError);
const connectors = useElectricData ? connectorsFromElectric : allConnectors || [];
// Manual refresh function that works with both Electric and API
@ -129,7 +130,7 @@ export const ConnectorIndicator: FC = () => {
const hasConnectors = connectors.length > 0;
const hasSources = hasConnectors || activeDocumentTypes.length > 0;
const totalSourceCount = connectors.length + activeDocumentTypes.length;
const activeConnectorsCount = connectors.length;
// Check which connectors are already connected
@ -226,7 +227,6 @@ export const ConnectorIndicator: FC = () => {
isDisconnecting={isDisconnecting}
isIndexing={indexingConnectorIds.has(editingConnector.id)}
searchSpaceId={searchSpaceId?.toString()}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
onPeriodicEnabledChange={setPeriodicEnabled}

View file

@ -49,12 +49,12 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
const handleConfigChange = (value: string) => {
setConfigJson(value);
// Clear previous error
if (jsonError) {
setJsonError(null);
}
// Validate immediately to show errors as user types (with debouncing via parseMCPConfig cache)
if (value.trim()) {
const result = parseMCPConfig(value);
@ -120,13 +120,14 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 [&>svg]:top-2 sm:[&>svg]:top-3">
<div className="flex items-center gap-2">
<Server className="h-4 w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs">
Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a separate connector.
</AlertDescription>
</div>
</Alert>
<div className="flex items-center gap-2">
<Server className="h-4 w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs">
Connect to an MCP (Model Context Protocol) server. Each MCP server is added as a
separate connector.
</AlertDescription>
</div>
</Alert>
<form id="mcp-connect-form" onSubmit={handleSubmit} className="space-y-6">
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-4 sm:p-6 space-y-4">
@ -140,11 +141,10 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
rows={16}
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
/>
{jsonError && (
<p className="text-xs text-red-500">{jsonError}</p>
)}
{jsonError && <p className="text-xs text-red-500">{jsonError}</p>}
<p className="text-[10px] sm:text-xs text-muted-foreground">
Paste a single MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional).
Paste a single MCP server configuration. Must include: name, command, args (optional),
env (optional), transport (optional).
</p>
</div>
@ -178,7 +178,9 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
<div className="flex-1">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0">
<AlertTitle className="text-xs sm:text-sm">
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"}
{testResult.status === "success"
? "Connection Successful"
: "Connection Failed"}
</AlertTitle>
{testResult.tools.length > 0 && (
<Button
@ -212,9 +214,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
{testResult.message}
{showDetails && testResult.tools.length > 0 && (
<div className="mt-3 pt-3 border-t border-green-500/20">
<p className="font-semibold mb-2 text-[10px] sm:text-xs">
Available tools:
</p>
<p className="font-semibold mb-2 text-[10px] sm:text-xs">Available tools:</p>
<ul className="list-disc list-inside text-[10px] sm:text-xs space-y-0.5">
{testResult.tools.map((tool) => (
<li key={tool.name}>{tool.name}</li>
@ -230,4 +230,4 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
</form>
</div>
);
};
};

View file

@ -34,7 +34,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
if (connector.name) {
setName(connector.name);
}
const serverConfig = connector.config?.server_config as MCPServerConfig | undefined;
if (serverConfig) {
// Convert server config to JSON string for editing (name is in separate field)
@ -50,17 +50,12 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
// Validate that this is an MCP connector (after hooks)
if (connector.connector_type !== EnumConnectorName.MCP_CONNECTOR) {
console.error(
"MCPConfig received non-MCP connector:",
connector.connector_type
);
console.error("MCPConfig received non-MCP connector:", connector.connector_type);
return (
<Alert className="border-red-500/50 bg-red-500/10">
<XCircle className="h-4 w-4 text-red-600" />
<AlertTitle>Invalid Connector Type</AlertTitle>
<AlertDescription>
This component can only be used with MCP connectors.
</AlertDescription>
<AlertDescription>This component can only be used with MCP connectors.</AlertDescription>
</Alert>
);
}
@ -87,10 +82,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
if (jsonError) {
setJsonError(null);
}
// Use shared utility for validation and parsing (with caching)
const result = parseMCPConfig(value);
if (result.config && onConfigChange) {
// Valid config - update parent immediately
onConfigChange({ server_config: result.config });
@ -127,7 +122,9 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
{/* Server Name */}
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<div className="space-y-2">
<Label htmlFor="name" className="text-xs sm:text-sm">Server Name</Label>
<Label htmlFor="name" className="text-xs sm:text-sm">
Server Name
</Label>
<Input
id="name"
value={name}
@ -159,11 +156,10 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
rows={16}
className={`font-mono text-xs ${jsonError ? "border-red-500" : ""}`}
/>
{jsonError && (
<p className="text-xs text-red-500">JSON Error: {jsonError}</p>
)}
{jsonError && <p className="text-xs text-red-500">JSON Error: {jsonError}</p>}
<p className="text-[10px] sm:text-xs text-muted-foreground">
Edit your MCP server configuration. Must include: name, command, args (optional), env (optional), transport (optional).
Edit your MCP server configuration. Must include: name, command, args (optional), env
(optional), transport (optional).
</p>
</div>
@ -197,7 +193,9 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
<div className="flex-1">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0">
<AlertTitle className="text-sm">
{testResult.status === "success" ? "Connection Successful" : "Connection Failed"}
{testResult.status === "success"
? "Connection Successful"
: "Connection Failed"}
</AlertTitle>
{testResult.tools.length > 0 && (
<Button
@ -231,9 +229,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
{testResult.message}
{showDetails && testResult.tools.length > 0 && (
<div className="mt-3 pt-3 border-t border-green-500/20">
<p className="font-semibold mb-2">
Available tools:
</p>
<p className="font-semibold mb-2">Available tools:</p>
<ul className="list-disc list-inside text-xs space-y-0.5">
{testResult.tools.map((tool) => (
<li key={tool.name}>{tool.name}</li>

View file

@ -99,7 +99,10 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
</div>
<div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
Connect {connectorType === "MCP_CONNECTOR" ? "MCP Server" : getConnectorTypeDisplay(connectorType)}
Connect{" "}
{connectorType === "MCP_CONNECTOR"
? "MCP Server"
: getConnectorTypeDisplay(connectorType)}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Enter your connection details
@ -139,7 +142,11 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
Connecting
</>
) : (
<>{connectorType === "MCP_CONNECTOR" ? "Connect" : `Connect ${getConnectorTypeDisplay(connectorType)}`}</>
<>
{connectorType === "MCP_CONNECTOR"
? "Connect"
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
</>
)}
</Button>
</div>

View file

@ -151,7 +151,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal wrap-break-word">
{connector.name}
{connector.name}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration
@ -200,7 +200,6 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
onConfigChange={onConfigChange}
onNameChange={onNameChange}
searchSpaceId={searchSpaceId}
/>
)}

View file

@ -540,18 +540,18 @@ export const useConnectorDialog = () => {
data: {
...connectorData,
connector_type: connectorData.connector_type as EnumConnectorName,
is_active: true,
next_scheduled_at: connectorData.next_scheduled_at as string | null,
},
queryParams: {
search_space_id: searchSpaceId,
},
});
is_active: true,
next_scheduled_at: connectorData.next_scheduled_at as string | null,
},
queryParams: {
search_space_id: searchSpaceId,
},
});
// Refetch connectors to get the new one
const result = await refetchAllConnectors();
if (result.data) {
const connector = result.data.find(
// Refetch connectors to get the new one
const result = await refetchAllConnectors();
if (result.data) {
const connector = result.data.find(
(c: SearchSourceConnector) => c.id === newConnector.id
);
if (connector) {
@ -644,34 +644,35 @@ export const useConnectorDialog = () => {
},
});
const successMessage = currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} added successfully`
: `${connectorTitle} connected and indexing started!`;
toast.success(successMessage, {
description: periodicEnabledForIndexing
? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.`
: "You can continue working while we sync your data.",
});
const successMessage =
currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} added successfully`
: `${connectorTitle} connected and indexing started!`;
toast.success(successMessage, {
description: periodicEnabledForIndexing
? `Periodic sync enabled every ${getFrequencyLabel(frequencyMinutesForIndexing)}.`
: "You can continue working while we sync your data.",
});
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 });
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 });
// Clear indexing config state since we're not showing the view
setIndexingConfig(null);
setIndexingConnector(null);
setIndexingConnectorConfig(null);
// Clear indexing config state since we're not showing the view
setIndexingConfig(null);
setIndexingConnector(null);
setIndexingConnectorConfig(null);
// Invalidate queries to refresh data
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
});
// Invalidate queries to refresh data
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
});
// Refresh connectors list
await refetchAllConnectors();
// Refresh connectors list
await refetchAllConnectors();
} else {
// Non-indexable connector
// For Circleback, transition to edit view to show webhook URL
@ -708,9 +709,10 @@ export const useConnectorDialog = () => {
await refetchAllConnectors();
} else {
// Other non-indexable connectors - just show success message and close
const successMessage = currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} added successfully`
: `${connectorTitle} connected successfully!`;
const successMessage =
currentConnectorType === "MCP_CONNECTOR"
? `${connector.name} added successfully`
: `${connectorTitle} connected successfully!`;
toast.success(successMessage);
// Refresh connectors list before closing modal
@ -758,7 +760,7 @@ export const useConnectorDialog = () => {
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");
@ -766,7 +768,7 @@ export const useConnectorDialog = () => {
url.searchParams.set("tab", "all");
url.searchParams.delete("view");
}
url.searchParams.delete("connectorType");
router.replace(url.pathname + url.search, { scroll: false });
}, [router, connectingConnectorType, viewingMCPList]);
@ -1252,33 +1254,33 @@ export const useConnectorDialog = () => {
);
}
// Generate toast message based on connector type
const toastTitle = `${editingConnector.name} updated successfully`;
// Generate toast message based on connector type
const toastTitle = `${editingConnector.name} updated successfully`;
toast.success(toastTitle, {
description: periodicEnabled
? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}`
: indexingDescription,
});
toast.success(toastTitle, {
description: periodicEnabled
? `Periodic sync ${frequency ? `enabled every ${getFrequencyLabel(frequencyMinutes)}` : "enabled"}. ${indexingDescription}`
: indexingDescription,
});
// Update URL - the effect will handle closing the modal and clearing state
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 });
// Update URL - the effect will handle closing the modal and clearing state
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 });
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);
}
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,

View file

@ -96,9 +96,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
// Separate OAuth and non-OAuth connectors
const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type));
const nonOauthConnectors = connectors.filter(
(c) => !oauthConnectorTypes.has(c.connector_type)
);
const nonOauthConnectors = connectors.filter((c) => !oauthConnectorTypes.has(c.connector_type));
// Group OAuth connectors by type
const oauthConnectorsByType = oauthConnectors.reduce(
@ -150,8 +148,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
});
const hasActiveConnectors =
filteredOAuthConnectorTypes.length > 0 ||
filteredNonOAuthConnectors.length > 0;
filteredOAuthConnectorTypes.length > 0 || filteredNonOAuthConnectors.length > 0;
return (
<TabsContent value="active" className="m-0">

View file

@ -123,7 +123,6 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
isConnecting={isConnecting}
documentCount={documentCount}
accountCount={accountCount}
isIndexing={isIndexing}
onConnect={() => onConnectOAuth(connector)}
onManage={
@ -165,9 +164,13 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
// For MCP connectors, count total MCP connectors instead of document count
const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR;
const mcpConnectorCount = isMCP && allConnectors
? allConnectors.filter((c: SearchSourceConnector) => c.connector_type === EnumConnectorName.MCP_CONNECTOR).length
: undefined;
const mcpConnectorCount =
isMCP && allConnectors
? allConnectors.filter(
(c: SearchSourceConnector) =>
c.connector_type === EnumConnectorName.MCP_CONNECTOR
).length
: undefined;
const handleConnect = onConnectNonOAuth
? () => onConnectNonOAuth(connector.connectorType)

View file

@ -1,14 +1,14 @@
/**
* MCP Configuration Validator Utility
*
*
* Shared validation and parsing logic for MCP (Model Context Protocol) server configurations.
*
*
* Features:
* - Zod schema validation for runtime type safety
* - Configuration caching to avoid repeated parsing (5-minute TTL)
* - Standardized error messages
* - Connection testing utilities
*
*
* Usage:
* ```typescript
* // Parse and validate config
@ -18,14 +18,14 @@
* } else {
* // Show result.error to user
* }
*
*
* // Test connection
* const testResult = await testMCPConnection(config);
* if (testResult.status === "success") {
* console.log(`Found ${testResult.tools.length} tools`);
* }
* ```
*
*
* @module mcp-config-validator
*/
@ -36,7 +36,7 @@ import { connectorsApiService } from "@/lib/apis/connectors-api.service";
/**
* Zod schema for MCP server configuration
* Provides compile-time and runtime type safety
*
*
* Exported for advanced use cases (e.g., form builders)
*/
export const MCPServerConfigSchema = z.object({
@ -95,11 +95,11 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Check cache first
const cached = configCache.get(configJson);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
console.log('[MCP Validator] ✅ Using cached config');
console.log("[MCP Validator] ✅ Using cached config");
return { config: cached.config, error: null };
}
console.log('[MCP Validator] 🔍 Parsing new config...');
console.log("[MCP Validator] 🔍 Parsing new config...");
// Clean up expired cache entries periodically
if (configCache.size > 100) {
@ -111,7 +111,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Validate that it's an object, not an array
if (Array.isArray(parsed)) {
console.error('[MCP Validator] ❌ Error: Config is an array, expected object');
console.error("[MCP Validator] ❌ Error: Config is an array, expected object");
return {
config: null,
error: "Please provide a single server configuration object, not an array",
@ -125,27 +125,23 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
// Format Zod validation errors for user-friendly display
const firstError = result.error.issues[0];
const fieldPath = firstError.path.join(".");
// Clean up error message - remove technical Zod jargon
let errorMsg = firstError.message;
// Replace technical error messages with user-friendly ones
if (errorMsg.includes("expected string, received undefined")) {
errorMsg = fieldPath
? `The '${fieldPath}' field is required`
: "This field is required";
errorMsg = fieldPath ? `The '${fieldPath}' field is required` : "This field is required";
} else if (errorMsg.includes("Invalid input")) {
errorMsg = fieldPath
? `The '${fieldPath}' field has an invalid value`
: "Invalid value";
errorMsg = fieldPath ? `The '${fieldPath}' field has an invalid value` : "Invalid value";
} else if (fieldPath && !errorMsg.toLowerCase().includes(fieldPath.toLowerCase())) {
// If error message doesn't mention the field name, prepend it
errorMsg = `The '${fieldPath}' field: ${errorMsg}`;
}
console.error('[MCP Validator] ❌ Validation error:', errorMsg);
console.error('[MCP Validator] Full Zod errors:', result.error.issues);
console.error("[MCP Validator] ❌ Validation error:", errorMsg);
console.error("[MCP Validator] Full Zod errors:", result.error.issues);
return {
config: null,
error: errorMsg,
@ -164,8 +160,8 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
config,
timestamp: Date.now(),
});
console.log('[MCP Validator] ✅ Config parsed successfully:', config);
console.log("[MCP Validator] ✅ Config parsed successfully:", config);
return {
config,
@ -173,7 +169,7 @@ export const parseMCPConfig = (configJson: string): MCPConfigValidationResult =>
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : "Invalid JSON";
console.error('[MCP Validator] ❌ JSON parse error:', errorMsg);
console.error("[MCP Validator] ❌ JSON parse error:", errorMsg);
return {
config: null,
error: errorMsg,
@ -222,11 +218,11 @@ export const testMCPConnection = async (
export const extractServerName = (configJson: string): string => {
try {
const parsed = JSON.parse(configJson);
// Use Zod to validate and extract name field safely
const nameSchema = z.object({ name: z.string().optional() });
const result = nameSchema.safeParse(parsed);
if (result.success && result.data.name) {
return result.data.name;
}

View file

@ -82,11 +82,13 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
// Filter connectors to only show those of this type
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
// Determine button text - default to "Add Account" unless specified
const buttonText = addButtonText || (connectorType === EnumConnectorName.MCP_CONNECTOR ? "Add New MCP Server" : "Add Account");
const buttonText =
addButtonText ||
(connectorType === EnumConnectorName.MCP_CONNECTOR ? "Add New MCP Server" : "Add Account");
const isMCP = connectorType === EnumConnectorName.MCP_CONNECTOR;
// Helper to get display name for connector (handles MCP server name extraction)
const getDisplayName = (connector: SearchSourceConnector): string => {
if (isMCP) {
@ -177,58 +179,58 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{typeConnectors.map((connector) => {
const isIndexing = indexingConnectorIds.has(connector.id);
const isIndexing = indexingConnectorIds.has(connector.id);
return (
<div
key={connector.id}
className={cn(
"flex items-center gap-4 p-4 rounded-xl transition-all",
isIndexing
? "bg-primary/5 border-0"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
)}
>
return (
<div
key={connector.id}
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
"flex items-center gap-4 p-4 rounded-xl transition-all",
isIndexing
? "bg-primary/10 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
? "bg-primary/5 border-0"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
)}
>
{getConnectorIcon(connector.connector_type, "size-6")}
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate">
{getDisplayName(connector)}
</p>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
Syncing
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
isIndexing
? "bg-primary/10 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
)}
>
{getConnectorIcon(connector.connector_type, "size-6")}
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate">
{getDisplayName(connector)}
</p>
) : (
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap truncate">
{isIndexableConnector(connector.connector_type)
? connector.last_indexed_at
? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}`
: "Never indexed"
: "Active"}
</p>
)}
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
Syncing
</p>
) : (
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap truncate">
{isIndexableConnector(connector.connector_type)
? connector.last_indexed_at
? `Last indexed: ${formatLastIndexedDate(connector.last_indexed_at)}`
: "Never indexed"
: "Active"}
</p>
)}
</div>
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
onClick={() => onManage(connector)}
>
Manage
</Button>
</div>
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
onClick={() => onManage(connector)}
>
Manage
</Button>
</div>
);
})}
);
})}
</div>
)}
</div>