mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-02 19:55:18 +02:00
Merge pull request #681 from AnishSarkar22/feat/config-connectors
feat: Config for connectors
This commit is contained in:
commit
9655db1995
9 changed files with 373 additions and 16 deletions
|
|
@ -8,6 +8,8 @@ import { Button } from "@/components/ui/button";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { LogActiveTask } from "@/contracts/types/log.types";
|
import type { LogActiveTask } from "@/contracts/types/log.types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||||
|
import { ConnectorStatusBadge } from "./connector-status-badge";
|
||||||
|
|
||||||
interface ConnectorCardProps {
|
interface ConnectorCardProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -104,6 +106,15 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
onConnect,
|
onConnect,
|
||||||
onManage,
|
onManage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Get connector status
|
||||||
|
const { getConnectorStatus, isConnectorEnabled, getConnectorStatusMessage, shouldShowWarnings } =
|
||||||
|
useConnectorStatus();
|
||||||
|
|
||||||
|
const status = getConnectorStatus(connectorType);
|
||||||
|
const isEnabled = isConnectorEnabled(connectorType);
|
||||||
|
const statusMessage = getConnectorStatusMessage(connectorType);
|
||||||
|
const showWarnings = shouldShowWarnings();
|
||||||
|
|
||||||
// Extract count from active task message during indexing
|
// Extract count from active task message during indexing
|
||||||
const indexingCount = extractIndexedCount(activeTask?.message);
|
const indexingCount = extractIndexedCount(activeTask?.message);
|
||||||
|
|
||||||
|
|
@ -139,9 +150,23 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
return description;
|
return description;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const cardContent = (
|
||||||
<div className="group relative flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-200 w-full border border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10">
|
<div
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-lg transition-colors shrink-0 bg-slate-400/5 dark:bg-white/5 border border-slate-400/5 dark:border-white/5">
|
className={cn(
|
||||||
|
"group relative flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-200 w-full border",
|
||||||
|
status.status === "warning"
|
||||||
|
? "border-yellow-500/30 bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
|
||||||
|
: "border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-12 w-12 items-center justify-center rounded-lg transition-colors shrink-0 border",
|
||||||
|
status.status === "warning"
|
||||||
|
? "bg-yellow-500/10 border-yellow-500/20 bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||||
|
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{connectorType ? (
|
{connectorType ? (
|
||||||
getConnectorIcon(connectorType, "size-6")
|
getConnectorIcon(connectorType, "size-6")
|
||||||
) : id === "youtube-crawler" ? (
|
) : id === "youtube-crawler" ? (
|
||||||
|
|
@ -151,8 +176,15 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-[14px] font-semibold leading-tight truncate">{title}</span>
|
<span className="text-[14px] font-semibold leading-tight truncate">{title}</span>
|
||||||
|
{showWarnings && status.status !== "active" && (
|
||||||
|
<ConnectorStatusBadge
|
||||||
|
status={status.status}
|
||||||
|
statusMessage={statusMessage}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
|
<div className="text-[10px] text-muted-foreground mt-1">{getStatusContent()}</div>
|
||||||
{isConnected && documentCount !== undefined && (
|
{isConnected && documentCount !== undefined && (
|
||||||
|
|
@ -179,10 +211,12 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
!isConnected && "shadow-xs"
|
!isConnected && "shadow-xs"
|
||||||
)}
|
)}
|
||||||
onClick={isConnected ? onManage : onConnect}
|
onClick={isConnected ? onManage : onConnect}
|
||||||
disabled={isConnecting}
|
disabled={isConnecting || !isEnabled}
|
||||||
>
|
>
|
||||||
{isConnecting ? (
|
{isConnecting ? (
|
||||||
<Loader2 className="size-3 animate-spin" />
|
<Loader2 className="size-3 animate-spin" />
|
||||||
|
) : !isEnabled ? (
|
||||||
|
"Unavailable"
|
||||||
) : isConnected ? (
|
) : isConnected ? (
|
||||||
"Manage"
|
"Manage"
|
||||||
) : id === "youtube-crawler" ? (
|
) : id === "youtube-crawler" ? (
|
||||||
|
|
@ -195,4 +229,6 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return cardContent;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AlertTriangle, Ban, Wrench } from "lucide-react";
|
||||||
|
import type { FC } from "react";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import type { ConnectorStatus } from "../config/connector-status-config";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface ConnectorStatusBadgeProps {
|
||||||
|
status: ConnectorStatus;
|
||||||
|
statusMessage?: string | null;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectorStatusBadge: FC<ConnectorStatusBadgeProps> = ({
|
||||||
|
status,
|
||||||
|
statusMessage,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
if (status === "active") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBadgeConfig = () => {
|
||||||
|
switch (status) {
|
||||||
|
case "warning":
|
||||||
|
return {
|
||||||
|
icon: AlertTriangle,
|
||||||
|
className: "text-yellow-500 dark:text-yellow-400",
|
||||||
|
defaultTitle: "Warning",
|
||||||
|
};
|
||||||
|
case "disabled":
|
||||||
|
return {
|
||||||
|
icon: Ban,
|
||||||
|
className: "text-red-500 dark:text-red-400",
|
||||||
|
defaultTitle: "Disabled",
|
||||||
|
};
|
||||||
|
case "maintenance":
|
||||||
|
return {
|
||||||
|
icon: Wrench,
|
||||||
|
className: "text-orange-500 dark:text-orange-400",
|
||||||
|
defaultTitle: "Maintenance",
|
||||||
|
};
|
||||||
|
case "deprecated":
|
||||||
|
return {
|
||||||
|
icon: AlertTriangle,
|
||||||
|
className: "ext-slate-500 dark:text-slate-400",
|
||||||
|
defaultTitle: "Deprecated",
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = getBadgeConfig();
|
||||||
|
if (!config) return null;
|
||||||
|
|
||||||
|
const Icon = config.icon;
|
||||||
|
// Show statusMessage in tooltip for warning, deprecated, disabled, and maintenance statuses
|
||||||
|
const shouldUseTooltip =
|
||||||
|
(status === "warning" ||
|
||||||
|
status === "deprecated" ||
|
||||||
|
status === "disabled" ||
|
||||||
|
status === "maintenance") &&
|
||||||
|
statusMessage;
|
||||||
|
const tooltipTitle = shouldUseTooltip ? statusMessage : config.defaultTitle;
|
||||||
|
|
||||||
|
// Use Tooltip component for statuses with statusMessage, native title for others
|
||||||
|
if (shouldUseTooltip) {
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className={cn("inline-flex items-center justify-center shrink-0", className)}>
|
||||||
|
<Icon className={cn("size-3.5", config.className)} />
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" className="max-w-xs">
|
||||||
|
{statusMessage}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn("inline-flex items-center justify-center shrink-0", className)}
|
||||||
|
title={tooltipTitle}
|
||||||
|
>
|
||||||
|
<Icon className={cn("size-3.5", config.className)} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AlertTriangle, X } from "lucide-react";
|
||||||
|
import type { FC } from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface ConnectorWarningBannerProps {
|
||||||
|
warning: string;
|
||||||
|
statusMessage?: string | null;
|
||||||
|
onDismiss?: () => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectorWarningBanner: FC<ConnectorWarningBannerProps> = ({
|
||||||
|
warning,
|
||||||
|
statusMessage,
|
||||||
|
onDismiss,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const [isDismissed, setIsDismissed] = useState(false);
|
||||||
|
|
||||||
|
if (isDismissed) return null;
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
setIsDismissed(true);
|
||||||
|
onDismiss?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-start gap-3 p-3 rounded-lg border border-yellow-500/30 bg-yellow-500/10 dark:bg-yellow-500/5 mb-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AlertTriangle className="size-4 text-yellow-600 dark:text-yellow-500 shrink-0 mt-0.5" />
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-[12px] font-medium text-yellow-900 dark:text-yellow-200">{warning}</p>
|
||||||
|
{statusMessage && (
|
||||||
|
<p className="text-[11px] text-yellow-700 dark:text-yellow-300 mt-1">{statusMessage}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{onDismiss && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
className="shrink-0 p-0.5 rounded hover:bg-yellow-500/20 transition-colors"
|
||||||
|
aria-label="Dismiss warning"
|
||||||
|
>
|
||||||
|
<X className="size-3.5 text-yellow-700 dark:text-yellow-300" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"connectorStatuses": {
|
||||||
|
"SLACK_CONNECTOR": {
|
||||||
|
"enabled": false,
|
||||||
|
"status": "disabled",
|
||||||
|
"statusMessage": "Unavailable due to API changes"
|
||||||
|
},
|
||||||
|
"NOTION_CONNECTOR": {
|
||||||
|
"enabled": true,
|
||||||
|
"status": "warning",
|
||||||
|
"statusMessage": "Rate limits may apply"
|
||||||
|
},
|
||||||
|
"TEAMS_CONNECTOR": {
|
||||||
|
"enabled": false,
|
||||||
|
"status": "maintenance",
|
||||||
|
"statusMessage": "Temporarily unavailable for maintenance"
|
||||||
|
},
|
||||||
|
"JIRA_CONNECTOR": {
|
||||||
|
"enabled": false,
|
||||||
|
"status": "deprecated",
|
||||||
|
"statusMessage": "Deprecated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globalSettings": {
|
||||||
|
"showWarnings": true,
|
||||||
|
"allowManualOverride": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"connectorStatuses": {},
|
||||||
|
"globalSettings": {
|
||||||
|
"showWarnings": true,
|
||||||
|
"allowManualOverride": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/**
|
||||||
|
* Connector Status Configuration
|
||||||
|
*
|
||||||
|
* Manages connector statuses (disable/enable, status messages). Edit connector-status-config.json to configure.
|
||||||
|
* Valid status values: "active", "warning", "disabled", "deprecated", "maintenance".
|
||||||
|
* Unlisted connectors default to "active" and enabled. See connector-status-config.example.json for reference.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
import rawConnectorStatusConfigData from "./connector-status-config.json";
|
||||||
|
|
||||||
|
// Zod schemas for runtime validation and type safety
|
||||||
|
export const connectorStatusSchema = z.enum([
|
||||||
|
"active",
|
||||||
|
"warning",
|
||||||
|
"disabled",
|
||||||
|
"deprecated",
|
||||||
|
"maintenance",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const connectorStatusConfigSchema = z.object({
|
||||||
|
enabled: z.boolean(),
|
||||||
|
status: connectorStatusSchema,
|
||||||
|
statusMessage: z.string().nullable().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const connectorStatusMapSchema = z.record(z.string(), connectorStatusConfigSchema);
|
||||||
|
|
||||||
|
export const connectorStatusConfigFileSchema = z.object({
|
||||||
|
connectorStatuses: connectorStatusMapSchema,
|
||||||
|
globalSettings: z.object({
|
||||||
|
showWarnings: z.boolean(),
|
||||||
|
allowManualOverride: z.boolean(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TypeScript types inferred from Zod schemas
|
||||||
|
export type ConnectorStatus = z.infer<typeof connectorStatusSchema>;
|
||||||
|
export type ConnectorStatusConfig = z.infer<typeof connectorStatusConfigSchema>;
|
||||||
|
export type ConnectorStatusMap = z.infer<typeof connectorStatusMapSchema>;
|
||||||
|
export type ConnectorStatusConfigFile = z.infer<typeof connectorStatusConfigFileSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validated at runtime via Zod schema; invalid JSON throws at module load time.
|
||||||
|
*/
|
||||||
|
export const connectorStatusConfig: ConnectorStatusConfigFile =
|
||||||
|
connectorStatusConfigFileSchema.parse(rawConnectorStatusConfigData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default status config for a connector (when not in config file)
|
||||||
|
* Returns a validated default config
|
||||||
|
*/
|
||||||
|
export function getDefaultConnectorStatus(): ConnectorStatusConfig {
|
||||||
|
return connectorStatusConfigSchema.parse({
|
||||||
|
enabled: true,
|
||||||
|
status: "active",
|
||||||
|
statusMessage: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a connector status config object
|
||||||
|
* Useful for validating config loaded from external sources
|
||||||
|
*/
|
||||||
|
export function validateConnectorStatusConfig(config: unknown): ConnectorStatusConfigFile {
|
||||||
|
return connectorStatusConfigFileSchema.parse(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a single connector status config
|
||||||
|
*/
|
||||||
|
export function validateSingleConnectorStatus(config: unknown): ConnectorStatusConfig {
|
||||||
|
return connectorStatusConfigSchema.parse(config);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
type ConnectorStatusConfig,
|
||||||
|
connectorStatusConfig,
|
||||||
|
getDefaultConnectorStatus,
|
||||||
|
} from "../config/connector-status-config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get connector status information
|
||||||
|
*/
|
||||||
|
export function useConnectorStatus() {
|
||||||
|
/**
|
||||||
|
* Get status configuration for a specific connector type
|
||||||
|
*/
|
||||||
|
const getConnectorStatus = (connectorType: string | undefined): ConnectorStatusConfig => {
|
||||||
|
if (!connectorType) {
|
||||||
|
return getDefaultConnectorStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectorStatusConfig.connectorStatuses[connectorType] || getDefaultConnectorStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a connector is enabled
|
||||||
|
*/
|
||||||
|
const isConnectorEnabled = (connectorType: string | undefined): boolean => {
|
||||||
|
return getConnectorStatus(connectorType).enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status message for a connector
|
||||||
|
*/
|
||||||
|
const getConnectorStatusMessage = (connectorType: string | undefined): string | null => {
|
||||||
|
return getConnectorStatus(connectorType).statusMessage || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if warnings should be shown globally
|
||||||
|
*/
|
||||||
|
const shouldShowWarnings = (): boolean => {
|
||||||
|
return connectorStatusConfig.globalSettings.showWarnings;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
getConnectorStatus,
|
||||||
|
isConnectorEnabled,
|
||||||
|
getConnectorStatusMessage,
|
||||||
|
shouldShowWarnings,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||||
|
import { useConnectorStatus } from "../hooks/use-connector-status";
|
||||||
|
|
||||||
interface ConnectorAccountsListViewProps {
|
interface ConnectorAccountsListViewProps {
|
||||||
connectorType: string;
|
connectorType: string;
|
||||||
|
|
@ -65,13 +66,19 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
onAddAccount,
|
onAddAccount,
|
||||||
isConnecting = false,
|
isConnecting = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Get connector status
|
||||||
|
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||||
|
|
||||||
|
const isEnabled = isConnectorEnabled(connectorType);
|
||||||
|
const statusMessage = getConnectorStatusMessage(connectorType);
|
||||||
|
|
||||||
// Filter connectors to only show those of this type
|
// Filter connectors to only show those of this type
|
||||||
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
const typeConnectors = connectors.filter((c) => c.connector_type === connectorType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-4 border-b border-border/50 bg-muted">
|
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-1 sm:pb-4 border-b border-border/50 bg-muted">
|
||||||
{/* Back button */}
|
{/* Back button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -93,7 +100,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
{connectorTitle}
|
{connectorTitle}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||||
Manage your connector settings and sync configuration
|
{statusMessage || "Manage your connector settings and sync configuration"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,21 +108,23 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onAddAccount}
|
onClick={onAddAccount}
|
||||||
disabled={isConnecting}
|
disabled={isConnecting || !isEnabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-border/70 text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
|
"flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg border-2 border-dashed text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
|
||||||
"border-primary/50 hover:bg-primary/5",
|
!isEnabled
|
||||||
|
? "border-border/30 opacity-50 cursor-not-allowed"
|
||||||
|
: "border-primary/50 hover:bg-primary/5",
|
||||||
isConnecting && "opacity-50 cursor-not-allowed"
|
isConnecting && "opacity-50 cursor-not-allowed"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary/10 shrink-0">
|
<div className="flex h-5 w-5 sm:h-6 sm:w-6 items-center justify-center rounded-md bg-primary/10 shrink-0">
|
||||||
{isConnecting ? (
|
{isConnecting ? (
|
||||||
<Loader2 className="size-3.5 animate-spin text-primary" />
|
<Loader2 className="size-3 sm:size-3.5 animate-spin text-primary" />
|
||||||
) : (
|
) : (
|
||||||
<Plus className="size-3.5 text-primary" />
|
<Plus className="size-3 sm:size-3.5 text-primary" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[12px] font-medium">
|
<span className="text-[11px] sm:text-[12px] font-medium">
|
||||||
{isConnecting ? "Connecting..." : "Add Account"}
|
{isConnecting ? "Connecting..." : "Add Account"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -123,7 +132,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto px-6 sm:px-12 py-6 sm:py-8">
|
<div className="flex-1 overflow-y-auto px-6 sm:px-12 pt-0 sm:pt-6 pb-6 sm:pb-8">
|
||||||
{/* Connected Accounts Grid */}
|
{/* Connected Accounts Grid */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
{typeConnectors.map((connector) => {
|
{typeConnectors.map((connector) => {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export function Header({
|
||||||
{/* Left side - Mobile menu trigger + Breadcrumb */}
|
{/* Left side - Mobile menu trigger + Breadcrumb */}
|
||||||
<div className="flex flex-1 items-center gap-2 min-w-0">
|
<div className="flex flex-1 items-center gap-2 min-w-0">
|
||||||
{mobileMenuTrigger}
|
{mobileMenuTrigger}
|
||||||
{breadcrumb}
|
<div className="hidden md:block">{breadcrumb}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side - Actions */}
|
{/* Right side - Actions */}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue