Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ import { useSession } from '@/lib/auth/auth-client'
import type { OAuthReturnContext } from '@/lib/credentials/client-state'
import { ADD_CONNECTOR_SEARCH_PARAM, writeOAuthReturnContext } from '@/lib/credentials/client-state'
import { defaultCredentialDisplayName } from '@/lib/credentials/display-name'
import { resolveIntegrationBlockTypeForOAuth } from '@/lib/integrations'
import {
getProviderIdFromServiceId,
OAUTH_PROVIDERS,
type OAuthProvider,
parseProvider,
} from '@/lib/oauth'
import { getScopeDescription, getServiceConfigByProviderId } from '@/lib/oauth/utils'
import { BlockTile } from '@/blocks/block-tile'
import { useCreateCredentialDraft, useWorkspaceCredentials } from '@/hooks/queries/credentials'
import { useConnectOAuthService } from '@/hooks/queries/oauth/oauth-connections'

Expand Down Expand Up @@ -175,20 +173,6 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
return resolveService(provider, props.serviceId ?? providerId)
}, [props.serviceName, props.serviceIcon, props.provider, props.serviceId, providerId])

/**
* The block behind this OAuth identity, so the dialog wears the same brand
* tile the canvas and the integrations catalog do. Falls back to the bare
* `OAUTH_PROVIDERS` mark for an id no catalog integration claims.
*/
const headerIcon = useMemo(() => {
const blockType = resolveIntegrationBlockTypeForOAuth(
props.serviceId,
props.provider,
providerId
)
return blockType ? <BlockTile blockType={blockType} size='md' /> : ProviderIcon
}, [props.serviceId, props.provider, providerId, ProviderIcon])

const workspaceId = isConnect ? props.workspaceId : ''
const { data: credentials = [], isPending: credentialsLoading } = useWorkspaceCredentials({
workspaceId,
Expand Down Expand Up @@ -359,7 +343,7 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {

return (
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={title}>
<ChipModalHeader icon={headerIcon} onClose={handleClose}>
<ChipModalHeader icon={ProviderIcon} onClose={handleClose}>
{title}
</ChipModalHeader>
<ChipModalBody>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export function useAvailableResources(
id: integration.blockType,
name: integration.name,
iconComponent: integration.icon,
bgColor: integration.bgColor,
})),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { randomFloat } from '@sim/utils/random'
import { stripVersionSuffix } from '@sim/utils/string'
import { useParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { GmailIcon, SlackIcon } from '@/components/icons'
import {
INTEGRATIONS,
type OAuthServiceMatch,
Expand All @@ -15,7 +16,6 @@ import {
} from '@/lib/integrations'
import { captureEvent } from '@/lib/posthog/client'
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
import { getBlockTileIcon } from '@/blocks/accent'
import { getBareIconStyle } from '@/blocks/brand-icon-style'
import { getAllBlockMeta } from '@/blocks/registry'
import type { ModuleTag } from '@/blocks/types'
Expand Down Expand Up @@ -224,37 +224,27 @@ function computeActions(services: readonly ServiceInfo[], signals: Signals): Act
return [...integrations, ...prompts]
}

/**
* Integrations pinned to the first paint. Named by block type so the mark comes
* from the same registry every other surface reads, rather than a second copy
* imported here that could drift from the block's own icon.
*/
const INITIAL_INTEGRATIONS = [
{ blockType: 'slack', slug: 'slack', name: 'Slack' },
{ blockType: 'gmail', slug: 'gmail', name: 'Gmail' },
] as const

/**
* Initial actions rendered on first paint, before OAuth/credentials queries
* resolve. For users with no connections this is also the final result, so the
* section never flashes. Users with existing connections briefly see this
* before the personalized recompute replaces it.
*/
const INITIAL_ACTIONS: Action[] = [
...INITIAL_INTEGRATIONS.flatMap<Action>(({ blockType, slug, name }) => {
const icon = getBlockTileIcon(blockType)
return icon
? [
{
kind: 'integration',
id: `integrate-${slug}`,
label: `Integrate with ${name}`,
icon,
slug,
},
]
: []
}),
{
kind: 'integration',
id: 'integrate-slack',
label: 'Integrate with Slack',
icon: SlackIcon,
slug: 'slack',
},
{
kind: 'integration',
id: 'integrate-gmail',
label: 'Integrate with Gmail',
icon: GmailIcon,
slug: 'gmail',
},
toPromptAction(TABLE_STARTERS[0]),
...CANDIDATES.filter((c) => c.blockType === 'github' && c.featured)
.slice(0, 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { useQueryState } from 'nuqs'
import { HEADER_ACTION_CLUSTER, PAGE_HEADER_BAR } from '@/components/page-header-bar'
import { isChatEnabled } from '@/lib/core/config/env-flags'
import {
blockTypeToIconMap,
type Integration,
resolveCredentialDisplay,
resolveOAuthServiceForIntegration,
} from '@/lib/integrations'
import { credentialProviderMatchesService } from '@/lib/oauth'
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
import { RESOURCE_TILE_BASE } from '@/app/workspace/[workspaceId]/components/resource-tile'
import { IntegrationSkillsSection } from '@/app/workspace/[workspaceId]/integrations/[block]/integration-skills-section'
import { connectParam } from '@/app/workspace/[workspaceId]/integrations/[block]/search-params'
import {
Expand All @@ -32,7 +34,7 @@ import {
SettingsResourceRow,
} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
import { getBlockTileIcon } from '@/blocks/accent'
import { getTileIconColorClass } from '@/blocks/icon-color'
import { storeCuratedPrompt } from '@/blocks/integration-matcher'
import {
getSuggestedSkillsForBlock,
Expand Down Expand Up @@ -62,6 +64,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
useOAuthReturnRouter()
const router = useRouter()
const [connectMode, setConnectMode] = useQueryState(connectParam.key, connectParam.parser)
const Icon = blockTypeToIconMap[integration.type]
const matchingTemplates = getTemplatesForBlock(integration.type)
const suggestedSkills = getSuggestedSkillsForBlock(integration.type)
const oauthService = resolveOAuthServiceForIntegration(integration)
Expand Down Expand Up @@ -230,10 +233,16 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
>
<div className='mx-auto flex max-w-[48rem] flex-col gap-7 pb-3'>
<div className='flex flex-col gap-3'>
<IntegrationTile
blockType={integration.type}
fallbackLabel={integration.name.charAt(0)}
/>
{Icon ? (
<IntegrationTile blockType={integration.type} icon={Icon} />
) : (
<div
className={cn(RESOURCE_TILE_BASE, getTileIconColorClass(integration.bgColor))}
style={{ background: integration.bgColor }}
>
{integration.name.charAt(0)}
</div>
)}
<div className='flex flex-col gap-1'>
<h1 className='text-[var(--text-body)] text-lg'>{integration.name}</h1>
<p className='text-[var(--text-muted)] text-md'>{integration.description}</p>
Expand All @@ -246,7 +255,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
<SettingsResourceRow
key={credential.id}
iconVariant='custom'
icon={<IntegrationTile blockType={integration.type} />}
icon={Icon && <IntegrationTile blockType={integration.type} icon={Icon} />}
title={credential.displayName}
description={
credential.description || resolveCredentialDisplay(credential).subtitle
Expand Down Expand Up @@ -365,7 +374,8 @@ function TemplateIcons({ blockTypes }: TemplateIconsProps) {
return (
<span aria-hidden className='flex items-center'>
{blockTypes.map((bt, idx) => {
if (!getBlockTileIcon(bt)) return null
const ToolIcon = blockTypeToIconMap[bt]
if (!ToolIcon) return null
const z = TEMPLATE_TILE_Z[idx]
if (!z) return null
const isTrailing = idx > 0
Expand All @@ -379,7 +389,7 @@ function TemplateIcons({ blockTypes }: TemplateIconsProps) {
'outline outline-2 outline-[var(--bg)] transition-[outline-color] duration-150 group-hover:outline-[var(--surface-active)]'
)}
>
<IntegrationTile blockType={bt} />
<IntegrationTile blockType={bt} icon={ToolIcon} />
</span>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
RESOURCE_TILE_PLAIN,
} from '@/app/workspace/[workspaceId]/components/resource-tile'
import { getBlock } from '@/blocks'
import { getBlockTileIcon } from '@/blocks/accent'
import { getTileIconColorClass } from '@/blocks/icon-color'

/**
Expand Down Expand Up @@ -60,15 +59,7 @@ function resolveBrandTileBg(blockType: string): string | null {

interface IntegrationTileProps {
blockType: string
/**
* Overrides the block's registered mark. Only for a tile whose identity is
* not the block itself — a credential issued by a family service account
* wears the family's corporate mark. Everything else takes the registry's,
* so the tile cannot end up with its fill and its icon from two sources.
*/
icon?: ComponentType<{ className?: string }>
/** Drawn when neither the override nor the registry supplies a mark. */
fallbackLabel?: string
icon: ComponentType<{ className?: string }>
framed?: boolean
}

Expand All @@ -77,37 +68,27 @@ interface IntegrationTileProps {
* is a 36px tile used in list rows and headers; the framed variant adds an
* outer 44px halo used inside the showcase grid.
*/
export function IntegrationTile({
blockType,
icon,
fallbackLabel,
framed = false,
}: IntegrationTileProps) {
export function IntegrationTile({ blockType, icon: Icon, framed = false }: IntegrationTileProps) {
const brandBg = resolveBrandTileBg(blockType)
const Icon = icon ?? getBlockTileIcon(blockType)
const contentClass = getTileIconColorClass(brandBg)

if (!framed) {
return (
<div

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Brand tiles use inline styles

The restored integration and connector tiles apply dynamic backgrounds through JSX style props, bypassing the repository's required Tailwind styling convention. The same pattern occurs in the framed tile and the restored integration-detail and connector-card surfaces.

Context Used: Tailwind CSS and styling conventions (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

className={cn(RESOURCE_TILE_BASE, RESOURCE_TILE_PLAIN, !Icon && contentClass)}
className={cn(RESOURCE_TILE_BASE, RESOURCE_TILE_PLAIN)}
style={brandBg ? { background: brandBg } : undefined}
>
{Icon ? <Icon className={contentClass} /> : fallbackLabel}
<Icon className={getTileIconColorClass(brandBg)} />
</div>
)
}

return (
<div className='size-11 flex-shrink-0 rounded-xl border border-[var(--border-muted)] bg-[var(--surface-4)] p-[3px] shadow-sm dark:bg-[var(--surface-5)]'>
<div
className={cn(
'flex size-full items-center justify-center rounded-[9px] border border-[var(--border-1)] bg-[var(--bg)]',
!Icon && contentClass
)}
className='flex size-full items-center justify-center rounded-[9px] border border-[var(--border-1)] bg-[var(--bg)]'
style={brandBg ? { background: brandBg } : undefined}
>
{Icon ? <Icon className={cn('size-6', contentClass)} /> : fallbackLabel}
<Icon className={cn('size-6', getTileIconColorClass(brandBg))} />
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ChipInput,
ChipLink,
ChipTextarea,
cn,
Send,
toast,
} from '@sim/emcn'
Expand All @@ -27,6 +28,10 @@ import {
UnsavedChangesModal,
useCredentialDetailForm,
} from '@/app/workspace/[workspaceId]/components/credential-detail'
import {
RESOURCE_TILE_BASE,
RESOURCE_TILE_PLAIN,
} from '@/app/workspace/[workspaceId]/components/resource-tile'
import {
ConnectServiceAccountModal,
type ServiceAccountProviderId,
Expand Down Expand Up @@ -239,11 +244,15 @@ export function ConnectedCredentialDetail({
<CredentialDetailLayout back={back} actions={actions}>
<CredentialDetailHeading
leading={
<IntegrationTile
blockType={integrationBlockType}
icon={display?.icon ?? undefined}
fallbackLabel={resolveProviderLabel(credential.providerId).slice(0, 1) || '?'}
/>
display?.icon ? (
<IntegrationTile blockType={integrationBlockType} icon={display.icon} />
) : (
<div className={cn(RESOURCE_TILE_BASE, RESOURCE_TILE_PLAIN)}>
<span className='text-[var(--text-tertiary)] text-small'>
{resolveProviderLabel(credential.providerId).slice(0, 1) || '?'}
</span>
</div>
)
}
title={headingTitle}
subtitle={display?.detailSubtitle ?? 'Connected service'}
Expand Down
Loading
Loading