Files
supabase/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

163 lines
6.2 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { partition } from 'lodash'
import { ChevronRightIcon, EditIcon, ExternalLinkIcon, Table2Icon, TrashIcon } from 'lucide-react'
import Link from 'next/link'
import { parseAsString, useQueryState } from 'nuqs'
import { Badge, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { INTEGRATIONS } from '../Landing/Integrations.constants'
import { convertKVStringArrayToJson, formatWrapperTables } from './Wrappers.utils'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import type { FDW } from '@/data/fdw/fdws-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
interface WrapperRowProps {
wrapper: FDW
}
export const WrapperRow = ({ wrapper }: WrapperRowProps) => {
const { ref, id } = useParams()
const { can: canManageWrappers } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'wrappers'
)
const [, setSelectedWrapperToEdit] = useQueryState('edit', parseAsString)
const [, setSelectedWrapperToDelete] = useQueryState('delete', parseAsString)
const integration = INTEGRATIONS.find((i) => i.id === id)
if (!integration || integration.type !== 'wrapper') {
return <p className="text-foreground-lighter text-sm">A wrapper with this ID does not exist</p>
}
const serverOptions = convertKVStringArrayToJson(wrapper.server_options ?? [])
const [encryptedMetadata, visibleMetadata] = partition(
integration?.meta?.server.options.filter((option) => !option.hidden),
'secureEntry'
)
const _tables = formatWrapperTables(wrapper, integration?.meta)
const editTooltip = canManageWrappers
? 'Edit wrapper'
: 'You need additional permissions to edit wrappers'
return (
<TableRow>
<TableCell className="gap-2 align-top py-3! min-w-80">
<p className="text-sm">{wrapper.server_name}</p>
{visibleMetadata.map((metadata) => (
<div
key={metadata.name}
className="flex items-center space-x-2 text-sm text-foreground-light"
>
<span className="text-foreground-lighter text-nowrap">{metadata.label}:</span>
<span className="truncate max-w-72" title={serverOptions[metadata.name]}>
{serverOptions[metadata.name] ?? metadata.defaultValue}
</span>
</div>
))}
</TableCell>
<TableCell className="space-y-2 p-4!">
{_tables?.map((table) => {
const target =
(table.table as string | undefined) ??
(table.object as string | undefined) ??
(table.src_key as string | undefined) ??
table.table_name
const tableId = table.id as string
return (
<div key={tableId} className="flex items-center">
<Badge className="bg-surface-300 gap-2 font-mono text-xs tracking-tight h-6 text-foreground rounded-r-none">
<div className="relative w-3 h-3 flex items-center justify-center">
{integration.icon({ className: 'p-0' })}
</div>
<Tooltip>
<TooltipTrigger className="truncate max-w-28">{target}</TooltipTrigger>
<TooltipContent
side="bottom"
className="max-w-64 whitespace-pre-wrap wrap-break-word"
>
{target}
</TooltipContent>
</Tooltip>
<ChevronRightIcon
size={12}
strokeWidth={1.5}
className="text-foreground-lighter/50"
/>
</Badge>
<Link href={`/project/${ref}/editor/${tableId}`}>
<Badge className="transition hover:bg-surface-300 px-2 rounded-l-none gap-1.5 h-6 font-mono text-xs tracking-tight border-l-0">
<Table2Icon size={12} strokeWidth={1.5} className="text-foreground-lighter/50" />
<Tooltip>
<TooltipTrigger className="truncate max-w-28">
{table.schema}.{table.table_name}
</TooltipTrigger>
<TooltipContent
side="bottom"
className="max-w-64 whitespace-pre-wrap wrap-break-word"
>
{table.schema}.{table.table_name}
</TooltipContent>
</Tooltip>
</Badge>
</Link>
</div>
)
})}
</TableCell>
<TableCell>
{encryptedMetadata.map((metadata) => (
<div key={metadata.name} className="flex items-center space-x-2 text-sm">
<Link
href={`/project/${ref}/settings/vault/secrets?search=${encodeURIComponent(
serverOptions[metadata.name] ?? ''
)}`}
className="transition text-foreground-light hover:text-foreground flex items-center gap-x-2"
>
<span title={`View ${metadata.label} in Vault`}>{metadata.label}</span>
<ExternalLinkIcon size={14} strokeWidth={1.5} />
</Link>
</div>
))}
</TableCell>
<TableCell className="flex-nowrap">
<div className="flex items-center gap-x-2">
<ButtonTooltip
disabled={!canManageWrappers}
icon={<EditIcon strokeWidth={1.5} />}
className="px-1.5"
onClick={() => setSelectedWrapperToEdit(wrapper.id.toString())}
tooltip={{
content: {
side: 'bottom',
text: editTooltip,
},
}}
/>
<ButtonTooltip
disabled={!canManageWrappers}
icon={<TrashIcon strokeWidth={1.5} />}
className="px-1.5"
onClick={() => setSelectedWrapperToDelete(wrapper.id.toString())}
tooltip={{
content: {
side: 'bottom',
text: !canManageWrappers
? 'You need additional permissions to delete wrappers'
: 'Delete connection',
},
}}
/>
</div>
</TableCell>
</TableRow>
)
}