refactor(integrations): drop unused logos, brand icons, and card wrapper

Keep only the Settings Integrations page wiring: three plugins, i18n,
search/metadata, and the plugins-store registry boolean needed for
failure status. Remove ProviderLogo fallbacks, SVG assets, custom sprite
icons, and the separate IntegrationCard layer.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
Serhii Dziupin
2026-08-14 15:19:25 +00:00
committed by Cursor Agent
co-authored by Serhii Dziupin
parent 0aba428e95
commit 53ab67ea35
11 changed files with 93 additions and 251 deletions
@@ -1,54 +0,0 @@
import { describe, expect, test } from 'bun:test';
import React from 'react';
const integrationCards = await import('./Integration' + 'Card').catch(() => ({}));
type IntegrationCardComponent = (props: {
open: boolean;
onOpenChange: (open: boolean) => void;
header: React.ReactNode;
headerAction?: React.ReactNode;
children: React.ReactNode;
}) => React.ReactElement;
const IntegrationCard = (
integrationCards as unknown as {
IntegrationCard?: IntegrationCardComponent;
}
).IntegrationCard;
describe('IntegrationCard', () => {
test('keeps header controls outside the expansion button', () => {
expect(typeof IntegrationCard).toBe('function');
if (!IntegrationCard) return;
const onOpenChange = () => undefined;
const toggle = <input aria-label="Integration enabled" type="checkbox" />;
const card = IntegrationCard({
open: false,
onOpenChange,
header: <span>Integration</span>,
headerAction: toggle,
children: <div>Details</div>,
});
const cardProps = card.props as {
open: boolean;
onOpenChange: (open: boolean) => void;
children: React.ReactElement;
};
expect(cardProps.open).toBe(false);
expect(cardProps.onOpenChange).toBe(onOpenChange);
const surface = cardProps.children as React.ReactElement<{ children: React.ReactNode }>;
const headerRow = React.Children.toArray(surface.props.children)[0] as React.ReactElement<{
children: React.ReactNode;
}>;
const headerChildren = React.Children.toArray(headerRow.props.children) as React.ReactElement<{
children?: React.ReactNode;
}>[];
expect(headerChildren[0]?.type).toBe('button');
expect(headerChildren[1]?.props.children).toBe(toggle);
});
});
@@ -1,64 +0,0 @@
import React from 'react';
import { Icon } from '@/components/icon/Icon';
import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible';
import { cn } from '@/lib/utils';
interface IntegrationCardProps {
open: boolean;
onOpenChange: (open: boolean) => void;
header: React.ReactNode;
headerAction?: React.ReactNode;
children: React.ReactNode;
className?: string;
contentClassName?: string;
settingsItem?: string;
}
/**
* Shared chrome for an integration summary and its on-demand configuration.
* Header actions sit beside—not inside—the expansion button so their native
* controls preserve their own keyboard and click behavior.
*/
export const IntegrationCard: React.FC<IntegrationCardProps> = ({
open,
onOpenChange,
header,
headerAction,
children,
className,
contentClassName,
settingsItem,
}) => {
return (
<Collapsible open={open} onOpenChange={onOpenChange}>
<div
data-settings-item={settingsItem}
className={cn(
'overflow-hidden rounded-xl border border-[var(--interactive-border)] bg-[var(--surface-elevated)] shadow-sm',
className,
)}
>
<div className="flex min-w-0 items-center">
<button
type="button"
aria-expanded={open}
onClick={() => onOpenChange(!open)}
className="flex min-w-0 flex-1 items-center gap-3 px-4 py-3 text-left hover:bg-[var(--interactive-hover)]/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--interactive-focus-ring)]"
>
{header}
<Icon
name={open ? 'arrow-up-s' : 'arrow-down-s'}
className="size-4 shrink-0 text-muted-foreground"
/>
</button>
{headerAction ? <div className="shrink-0 pr-4">{headerAction}</div> : null}
</div>
<CollapsibleContent
className={cn('border-t border-[var(--interactive-border)] px-4 py-4', contentClassName)}
>
{children}
</CollapsibleContent>
</div>
</Collapsible>
);
};
@@ -12,6 +12,7 @@ import {
import { toast } from '@/components/ui';
import { Icon } from '@/components/icon/Icon';
import { SettingsSection } from '@/components/sections/shared/SettingsSection';
import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible';
import { useI18n } from '@/lib/i18n';
import { openExternalUrl } from '@/lib/url';
import { cn } from '@/lib/utils';
@@ -27,7 +28,6 @@ import {
THIRD_PARTY_PLUGINS,
type ThirdPartyPluginDefinition,
} from './thirdPartyPlugins';
import { IntegrationCard } from './IntegrationCard';
type PendingAction = 'install' | 'update' | 'setup' | 'remove';
@@ -296,16 +296,26 @@ export const ThirdPartyIntegrationsSection: React.FC<ThirdPartyIntegrationsSecti
manage: t('settings.integrations.thirdParty.actions.managePlugins'),
}[primaryAction];
const open = openPluginIds.has(plugin.id);
return (
<IntegrationCard
<Collapsible
key={plugin.id}
open={openPluginIds.has(plugin.id)}
onOpenChange={(open) => setPluginOpen(plugin.id, open)}
settingsItem={`integrations.third-party.${plugin.id}`}
header={(
<div className="flex min-w-0 flex-1 items-center gap-3">
open={open}
onOpenChange={(nextOpen) => setPluginOpen(plugin.id, nextOpen)}
>
<div
data-settings-item={`integrations.third-party.${plugin.id}`}
className="overflow-hidden rounded-xl border border-[var(--interactive-border)] bg-[var(--surface-elevated)]"
>
<button
type="button"
aria-expanded={open}
onClick={() => setPluginOpen(plugin.id, !open)}
className="flex w-full min-w-0 items-center gap-3 px-4 py-3 text-left hover:bg-[var(--interactive-hover)]/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--interactive-focus-ring)]"
>
<div className="flex size-10 shrink-0 items-center justify-center rounded-[10px] bg-[var(--surface-muted)]">
<Icon name={plugin.icon} className={cn('size-5', plugin.brandClassName)} />
<Icon name={plugin.icon} className="size-5 text-foreground" />
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-semibold text-foreground">{t(plugin.nameKey)}</div>
@@ -322,74 +332,79 @@ export const ThirdPartyIntegrationsSection: React.FC<ThirdPartyIntegrationsSecti
>
{status}
</span>
</div>
)}
>
<div className="space-y-4">
<div
className={cn(
'rounded-lg border border-border/60 bg-background/50 px-3 py-2.5',
hasUpdate && 'border-[var(--status-warning)]/35 bg-[var(--status-warning)]/10',
)}
>
<p aria-live="polite" className="text-sm font-medium text-foreground">{status}</p>
{state.projectEntries.length > 0 ? (
<p className="mt-1 text-xs text-muted-foreground">
{t('settings.integrations.thirdParty.status.projectInstalled')}
</p>
) : null}
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
size="sm"
variant={primaryAction === 'manage' ? 'outline' : 'default'}
onClick={() => void handlePrimaryAction(plugin)}
disabled={actionDisabled}
>
{isPending ? (
<Icon name="loader-4" className="size-3.5 animate-spin" />
) : primaryAction === 'setup' ? (
<Icon name="plug-2" className="size-3.5" />
) : null}
{primaryLabel}
</Button>
{registryUnavailable ? (
<Button
type="button"
size="sm"
variant="outline"
onClick={() => void refresh()}
disabled={isRefreshing || isLoadingRegistry || isPending}
<Icon
name={open ? 'arrow-up-s' : 'arrow-down-s'}
className="size-4 shrink-0 text-muted-foreground"
/>
</button>
<CollapsibleContent className="border-t border-[var(--interactive-border)] px-4 py-4">
<div className="space-y-4">
<div
className={cn(
'rounded-lg border border-border/60 bg-background/50 px-3 py-2.5',
hasUpdate && 'border-[var(--status-warning)]/35 bg-[var(--status-warning)]/10',
)}
>
{isRefreshing || isLoadingRegistry ? <Icon name="loader-4" className="size-3.5 animate-spin" /> : null}
{t('settings.integrations.thirdParty.actions.refresh')}
</Button>
) : null}
<Button
type="button"
size="sm"
variant="secondary"
onClick={() => void openExternalUrl(plugin.homepage)}
>
<Icon name="external-link" className="size-3.5" />
{t('settings.integrations.thirdParty.actions.docs')}
</Button>
{state.userEntry && !state.userEntryIsAmbiguous ? (
<Button
type="button"
size="sm"
variant="destructive"
onClick={() => setRemoveTarget(plugin)}
disabled={isPending}
>
<Icon name="delete-bin" className="size-3.5" />
{t('settings.integrations.thirdParty.actions.remove')}
</Button>
) : null}
</div>
<p aria-live="polite" className="text-sm font-medium text-foreground">{status}</p>
{state.projectEntries.length > 0 ? (
<p className="mt-1 text-xs text-muted-foreground">
{t('settings.integrations.thirdParty.status.projectInstalled')}
</p>
) : null}
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
size="sm"
variant={primaryAction === 'manage' ? 'outline' : 'default'}
onClick={() => void handlePrimaryAction(plugin)}
disabled={actionDisabled}
>
{isPending ? (
<Icon name="loader-4" className="size-3.5 animate-spin" />
) : primaryAction === 'setup' ? (
<Icon name="plug-2" className="size-3.5" />
) : null}
{primaryLabel}
</Button>
{registryUnavailable ? (
<Button
type="button"
size="sm"
variant="outline"
onClick={() => void refresh()}
disabled={isRefreshing || isLoadingRegistry || isPending}
>
{isRefreshing || isLoadingRegistry ? <Icon name="loader-4" className="size-3.5 animate-spin" /> : null}
{t('settings.integrations.thirdParty.actions.refresh')}
</Button>
) : null}
<Button
type="button"
size="sm"
variant="secondary"
onClick={() => void openExternalUrl(plugin.homepage)}
>
<Icon name="external-link" className="size-3.5" />
{t('settings.integrations.thirdParty.actions.docs')}
</Button>
{state.userEntry && !state.userEntryIsAmbiguous ? (
<Button
type="button"
size="sm"
variant="destructive"
onClick={() => setRemoveTarget(plugin)}
disabled={isPending}
>
<Icon name="delete-bin" className="size-3.5" />
{t('settings.integrations.thirdParty.actions.remove')}
</Button>
) : null}
</div>
</div>
</CollapsibleContent>
</div>
</IntegrationCard>
</Collapsible>
);
};
@@ -7,8 +7,6 @@ export interface ThirdPartyPluginDefinition {
packageName: string;
providerId: string;
icon: IconName;
/** Brand mark color class (theme tokens only). */
brandClassName: string;
nameKey: I18nKey;
descriptionKey: I18nKey;
homepage: string;
@@ -19,8 +17,7 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [
id: 'opencode-claude',
packageName: '@otto-assistant/opencode-claude',
providerId: 'claude-code',
icon: 'claude-code',
brandClassName: 'text-foreground',
icon: 'sparkling',
nameKey: 'settings.integrations.thirdParty.opencodeClaude.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeClaude.description',
homepage: 'https://github.com/otto-assistant/opencode-claude',
@@ -29,8 +26,7 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [
id: 'opencode-commandcode',
packageName: '@otto-assistant/opencode-commandcode',
providerId: 'command-code',
icon: 'command-code',
brandClassName: 'text-foreground',
icon: 'terminal-box',
nameKey: 'settings.integrations.thirdParty.opencodeCommandcode.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeCommandcode.description',
homepage: 'https://github.com/otto-assistant/opencode-commandcode',
@@ -40,7 +36,6 @@ export const THIRD_PARTY_PLUGINS: readonly ThirdPartyPluginDefinition[] = [
packageName: '@otto-assistant/opencode-cursor-oauth',
providerId: 'cursor',
icon: 'cursor',
brandClassName: 'text-foreground',
nameKey: 'settings.integrations.thirdParty.opencodeCursorOauth.name',
descriptionKey: 'settings.integrations.thirdParty.opencodeCursorOauth.description',
homepage: 'https://github.com/otto-assistant/opencode-cursor',