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
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/integrations/(shell)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
type FAQItem,
INTEGRATIONS,
type Integration,
POPULAR_WORKFLOWS,
toIntegrationSummary,
} from '@/lib/integrations'
import { POPULAR_WORKFLOWS } from '@/lib/integrations/popular-workflows'
import { withFilteredNoindex } from '@/lib/landing/seo'
import { JsonLd } from '@/app/(landing)/components/json-ld'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { cn } from '@sim/emcn'
import type { ReactNodeViewProps } from '@tiptap/react'
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
import { useParams, useRouter } from 'next/navigation'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/icon-color'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/brand-icon-style'
import { mentionIcon } from './mention-icon'
import { MarkdownMention, type MentionAttrs } from './mention-node'
import { simLinkPath } from './sim-link'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { AgentSkillsIcon, McpIcon } from '@/components/icons'
import { getDocumentIcon } from '@/components/icons/document-icons'
import type { ChatContextKind, ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
import { getBareIconStyle } from '@/blocks/icon-color'
import { getBareIconStyle } from '@/blocks/brand-icon-style'
import { getBlockRegistry } from '@/blocks/registry'

interface RenderIconArgs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
import { extractStreamingStringArgument } from '@/lib/copilot/tools/streaming-args'
import { getToolStatusDisplayTitle, getWaitCountdownTitle } from '@/lib/copilot/tools/tool-display'
import { getBareIconStyle } from '@/blocks/icon-color'
import { getBareIconStyle } from '@/blocks/brand-icon-style'
import { getBlockByToolName } from '@/blocks/registry'
import type { ToolCallStatus } from '../../../../types'
import { resolveToolDisplayState } from '../../utils'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
MothershipResource,
MothershipResourceType,
} from '@/app/workspace/[workspaceId]/home/types'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/icon-color'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/brand-icon-style'
import { logKeys } from '@/hooks/queries/logs'
import { mothershipChatKeys } from '@/hooks/queries/mothership-chats'
import { scheduleKeys } from '@/hooks/queries/schedules'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { useParams } from 'next/navigation'
import { usePostHog } from 'posthog-js/react'
import { GmailIcon, SlackIcon } from '@/components/icons'
import {
getAllBlockMeta,
INTEGRATIONS,
type OAuthServiceMatch,
resolveOAuthServiceForIntegration,
resolveOAuthServiceForSlug,
} from '@/lib/integrations'
import { captureEvent } from '@/lib/posthog/client'
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
import { getBareIconStyle } from '@/blocks/icon-color'
import { getBareIconStyle } from '@/blocks/brand-icon-style'
import { getAllBlockMeta } from '@/blocks/registry'
import type { ModuleTag } from '@/blocks/types'
import { useWorkspaceCredentials } from '@/hooks/queries/credentials'
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
WorkflowLogSummary,
} from '@/lib/api/contracts/logs'
import { dollarsToCredits } from '@/lib/billing/credits/conversion'
import { formatDateShort } from '@/lib/core/utils/date-display'
import {
getEndDateFromTimeRange,
getStartDateFromTimeRange,
Expand Down Expand Up @@ -91,7 +92,6 @@ import {
DELETED_WORKFLOW_LABEL,
extractRetryInput,
formatDate,
formatDateShort,
getDisplayStatus,
type LogStatus,
parseDuration,
Expand Down
25 changes: 0 additions & 25 deletions apps/sim/app/workspace/[workspaceId]/logs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,31 +188,6 @@ export function formatLatency(ms: number): string {
return formatDuration(ms, { precision: 2 }) ?? '—'
}

export function formatDateShort(dateStr: string): string {
const hasTime = dateStr.includes('T')
const [datePart, timePart] = dateStr.split('T')
const [, month, day] = datePart.split('-').map(Number)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
]
const dateLabel = `${months[month - 1]} ${day}`
if (hasTime && timePart) {
return `${dateLabel} ${timePart.slice(0, 5)}`
}
return dateLabel
}

export const formatDate = (dateString: string) => {
const date = new Date(dateString)
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { useRouter } from 'next/navigation'
import { useQueryStates } from 'nuqs'
import type { UsageLogEntry, UsageLogPeriod } from '@/lib/api/contracts/user'
import { formatApportionedCreditCost, formatCreditsLabel } from '@/lib/billing/credits/conversion'
import { formatDateShort } from '@/lib/core/utils/date-display'
import { USAGE_LOG_SOURCE_LABELS } from '@/app/api/users/me/usage-logs/source-labels'
import { formatDateShort } from '@/app/workspace/[workspaceId]/logs/utils'
import {
creditUsageParsers,
creditUsageUrlKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/icon-color'
import { getBareIconStyle, type StyleableIcon } from '@/blocks/brand-icon-style'
import type { SubBlockConfig } from '@/blocks/types'
import { useWorkspaceCredential, useWorkspaceCredentials } from '@/hooks/queries/credentials'
import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials'
Expand Down
53 changes: 53 additions & 0 deletions apps/sim/blocks/brand-icon-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Brand-icon styling that needs the block registry, kept apart from the pure
* contrast helpers in `@/blocks/icon-color`.
*
* The split is by dependency, not by topic: this module reads `getAllBlocks()`,
* so importing it pulls `blocks/registry-maps` → all 282 block configs → the
* tool registry. `icon-color.ts` needs none of that, and it is imported by the
* public landing `/integrations` page — which was inheriting the whole registry
* purely because the two lived in one file.
*
* Every consumer here is under `app/workspace/**`, where the registry is
* already legitimately present.
*/

import type { ComponentType, CSSProperties } from 'react'
import { getAllBlocks } from '@/blocks/registry'

/** A brand icon component that accepts standard styling props. */
export type StyleableIcon = ComponentType<{ className?: string; style?: CSSProperties }>

/**
* Lazily-built lookup from a block's icon component to its theme-safe brand
* {@link BlockConfig.iconColor}. Keyed by component reference so callers that
* already hold the icon (suggested actions, credential pickers, …) never need
* to thread a block type or hand-pick a color. Built once on first read since
* the block registry is static for the app's lifetime.
*/
let iconColorByComponent: Map<StyleableIcon, string> | null = null

function getIconColorMap(): Map<StyleableIcon, string> {
if (iconColorByComponent) return iconColorByComponent
const map = new Map<StyleableIcon, string>()
for (const block of getAllBlocks()) {
if (block.iconColor) map.set(block.icon, block.iconColor)
}
iconColorByComponent = map
return map
}

/**
* Inline `style` for rendering a brand icon bare (without its colored tile
* background): the block's theme-safe {@link BlockConfig.iconColor} as `color`,
* or `undefined` when none is defined so the caller keeps its own default
* icon styling.
*
* Single-fill icons drawn with `fill='currentColor'` (e.g. HubSpot) adopt the
* color; multi-color brand icons that hardcode their own fills (Slack, Gmail,
* Jira, Salesforce, Google Calendar) ignore it and keep their own colors.
*/
export function getBareIconStyle(icon: StyleableIcon): CSSProperties | undefined {
const color = getIconColorMap().get(icon)
return color ? { color } : undefined
}
43 changes: 5 additions & 38 deletions apps/sim/blocks/icon-color.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,10 @@
import type { ComponentType, CSSProperties } from 'react'
import { isLightColor } from '@/lib/colors'
import { getAllBlocks } from '@/blocks/registry'

/** A brand icon component that accepts standard styling props. */
export type StyleableIcon = ComponentType<{ className?: string; style?: CSSProperties }>

/**
* Lazily-built lookup from a block's icon component to its theme-safe brand
* {@link BlockConfig.iconColor}. Keyed by component reference so callers that
* already hold the icon (suggested actions, credential pickers, …) never need
* to thread a block type or hand-pick a color. Built once on first read since
* the block registry is static for the app's lifetime.
* Contrast helpers for brand tiles. Pure colour maths — deliberately free of any
* `@/blocks/registry` import so the public landing `/integrations` page can use
* these without pulling 282 block configs and the tool registry into its bundle.
* Registry-backed icon styling lives in `@/blocks/brand-icon-style`.
*/
let iconColorByComponent: Map<StyleableIcon, string> | null = null

function getIconColorMap(): Map<StyleableIcon, string> {
if (iconColorByComponent) return iconColorByComponent
const map = new Map<StyleableIcon, string>()
for (const block of getAllBlocks()) {
if (block.iconColor) map.set(block.icon, block.iconColor)
}
iconColorByComponent = map
return map
}

/**
* Inline `style` for rendering a brand icon bare (without its colored tile
* background): the block's theme-safe {@link BlockConfig.iconColor} as `color`,
* or `undefined` when none is defined so the caller keeps its own default
* icon styling.
*
* Single-fill icons drawn with `fill='currentColor'` (e.g. HubSpot) adopt the
* color; multi-color brand icons that hardcode their own fills (Slack, Gmail,
* Jira, Salesforce, Google Calendar) ignore it and keep their own colors.
*/
export function getBareIconStyle(icon: StyleableIcon): CSSProperties | undefined {
const color = getIconColorMap().get(icon)
return color ? { color } : undefined
}
import { isLightColor } from '@/lib/colors'

/**
* Brightness above which a brand tile is "clearly light" and a white foreground
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/ee/audit-logs/components/audit-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import { createLogger } from '@sim/logger'
import { formatDateTime } from '@sim/utils/formatting'
import { isRecordLike } from '@sim/utils/object'
import { useQueryStates } from 'nuqs'
import { formatDateShort } from '@/lib/core/utils/date-display'
import { getEndDateFromTimeRange, getStartDateFromTimeRange } from '@/lib/logs/filters'
import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state'
import type { EnterpriseAuditLogEntry } from '@/app/api/v1/audit-logs/format'
import { formatDateShort } from '@/app/workspace/[workspaceId]/logs/utils'
import {
ActivityLog,
type ActivityLogEntry,
Expand Down
41 changes: 41 additions & 0 deletions apps/sim/lib/core/utils/date-display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Compact date labels for dense table rows (`Jul 31`, `Jul 31 14:22`).
*
* Lives in `lib/` rather than beside the logs table because three unrelated
* features render it — logs, credit usage, and audit logs — and the previous
* home (`app/workspace/[workspaceId]/logs/utils.ts`) also exports registry-backed
* badge components. Importing a date formatter from there pulled
* `@/blocks/registry` → all 282 block configs → the tool registry into every
* consumer's bundle.
*/

const MONTHS = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
] as const

/**
* Formats an ISO-ish date string as `MMM D`, appending `HH:mm` when the input
* carries a time component. Parsed by string split rather than `Date` so the
* label reflects the timestamp exactly as stored, with no timezone shifting.
*/
export function formatDateShort(dateStr: string): string {
const hasTime = dateStr.includes('T')
const [datePart, timePart] = dateStr.split('T')
const [, month, day] = datePart.split('-').map(Number)
const dateLabel = `${MONTHS[month - 1]} ${day}`
if (hasTime && timePart) {
return `${dateLabel} ${timePart.slice(0, 5)}`
}
return dateLabel
}
52 changes: 6 additions & 46 deletions apps/sim/lib/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
* `INTEGRATIONS` is the serialized projection of `BlockConfig` written by
* `scripts/generate-docs.ts` whenever a block changes.
*
* `POPULAR_WORKFLOWS` is derived from each block's `*BlockMeta` export (see
* `apps/sim/blocks/registry.ts`, which now hosts both the execution
* `BlockConfig` lookups and the presentation `BlockMeta` lookups). Block
* files are the source of truth for both surfaces.
* Deliberately free of `@/blocks` value imports: the landing `/integrations`
* grid is a client component, so anything imported here that reaches
* `blocks/registry-maps` ships all 282 block configs and the tool registry to a
* public marketing page. `POPULAR_WORKFLOWS` lives in
* `@/lib/integrations/popular-workflows` for exactly that reason. Type-only
* imports are fine — they erase.
*/

import { stripVersionSuffix } from '@sim/utils/string'
import integrationsJson from '@/lib/integrations/integrations.json'
import type { Integration, IntegrationSummary } from '@/lib/integrations/types'
import { getAllBlockMeta } from '@/blocks/registry'

/** All integrations surfaced in the catalog, ordered by `scripts/generate-docs.ts`. */
export const INTEGRATIONS: readonly Integration[] =
Expand All @@ -28,45 +28,6 @@ export const INTEGRATIONS: readonly Integration[] =
*/
export const INTEGRATIONS_UPDATED_AT: string = integrationsJson.updatedAt

/** A curated `from → to` block-pair workflow surfaced on the landing page. */
export interface PopularWorkflow {
/** Integration display name (matches `Integration.name`). */
from: string
/** Integration display name. */
to: string
headline: string
description: string
}

const TYPE_TO_NAME = new Map<string, string>()
for (const integration of INTEGRATIONS) {
TYPE_TO_NAME.set(integration.type, integration.name)
TYPE_TO_NAME.set(stripVersionSuffix(integration.type), integration.name)
}

/**
* Curated popular workflow pairs (templates flagged `featured: true` that
* reference at least one other integration). Derived from per-block meta —
* each entry's `from` is the owner block, `to` is the first
* `alsoIntegrations` entry, and `headline`/`description` come from the
* template title and prompt.
*/
export const POPULAR_WORKFLOWS: readonly PopularWorkflow[] = (() => {
const pairs: PopularWorkflow[] = []
for (const [ownerType, meta] of Object.entries(getAllBlockMeta())) {
for (const template of meta.templates ?? []) {
if (!template.featured) continue
const toType = template.alsoIntegrations?.[0]
if (!toType) continue
const from = TYPE_TO_NAME.get(ownerType) ?? TYPE_TO_NAME.get(stripVersionSuffix(ownerType))
const to = TYPE_TO_NAME.get(toType) ?? TYPE_TO_NAME.get(stripVersionSuffix(toType))
if (!from || !to) continue
pairs.push({ from, to, headline: template.title, description: template.prompt })
}
}
return pairs
})()

/**
* Projects a full `Integration` down to the fields the `/integrations`
* catalog grid renders and searches by, replacing the full `operations`/
Expand Down Expand Up @@ -109,6 +70,5 @@ export {
resolveOAuthServiceForSlug,
} from '@/lib/integrations/oauth-service'
export type { AuthType, FAQItem, Integration, IntegrationSummary } from '@/lib/integrations/types'
export { getAllBlockMeta, getBlockMeta, getTemplatesForBlock } from '@/blocks/registry'
export type { BlockMeta, BlockTemplate } from '@/blocks/types'
export { formatIntegrationType } from '@/blocks/types'
Loading
Loading