chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { ChipTag } from '@sim/emcn'
|
||||
import { ArrowRight, Plus } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
|
||||
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
|
||||
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row'
|
||||
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
|
||||
import { getCustomBlockIcon } from '@/blocks/custom/custom-block-icon'
|
||||
import { CustomBlockDetail } from '@/ee/custom-blocks/components/custom-block-detail'
|
||||
import { useWhitelabelSettings } from '@/ee/whitelabeling/hooks/whitelabel'
|
||||
import { useCanPublishCustomBlock, useCustomBlocks } from '@/hooks/queries/custom-blocks'
|
||||
import { useWorkspacesQuery } from '@/hooks/queries/workspace'
|
||||
|
||||
export function CustomBlocks() {
|
||||
const params = useParams()
|
||||
const workspaceId = typeof params?.workspaceId === 'string' ? params.workspaceId : undefined
|
||||
|
||||
const { data: canManage = false, isLoading } = useCanPublishCustomBlock(workspaceId)
|
||||
const { data: blocks = [] } = useCustomBlocks(workspaceId)
|
||||
const { data: workspaces = [] } = useWorkspacesQuery()
|
||||
|
||||
// Any org member can view the org's blocks, but publishing requires admin on a
|
||||
// source workspace — the publish route enforces admin on the picked workspace.
|
||||
const currentOrgId = useMemo(
|
||||
() => workspaces.find((w) => w.id === workspaceId)?.organizationId ?? null,
|
||||
[workspaces, workspaceId]
|
||||
)
|
||||
const canCreate = useMemo(
|
||||
() =>
|
||||
!!currentOrgId &&
|
||||
workspaces.some((w) => w.organizationId === currentOrgId && w.permissions === 'admin'),
|
||||
[workspaces, currentOrgId]
|
||||
)
|
||||
const { data: whitelabel } = useWhitelabelSettings(blocks[0]?.organizationId)
|
||||
const fallbackIconUrl = whitelabel?.logoUrl ?? null
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [selected, setSelected] = useState<string | 'new' | null>(null)
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!searchTerm.trim()) return blocks
|
||||
const q = searchTerm.toLowerCase()
|
||||
return blocks.filter((b) => b.name.toLowerCase().includes(q))
|
||||
}, [blocks, searchTerm])
|
||||
|
||||
if (isLoading) return null
|
||||
|
||||
if (!canManage) {
|
||||
return (
|
||||
<SettingsEmptyState>
|
||||
Custom blocks require an Enterprise plan. Contact your admin to enable them.
|
||||
</SettingsEmptyState>
|
||||
)
|
||||
}
|
||||
|
||||
if (selected !== null && workspaceId) {
|
||||
return (
|
||||
<CustomBlockDetail
|
||||
key={selected}
|
||||
blockId={selected === 'new' ? null : selected}
|
||||
workspaceId={workspaceId}
|
||||
onBack={() => setSelected(null)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsPanel
|
||||
search={{
|
||||
value: searchTerm,
|
||||
onChange: setSearchTerm,
|
||||
placeholder: 'Search custom blocks...',
|
||||
}}
|
||||
actions={
|
||||
canCreate
|
||||
? [
|
||||
{
|
||||
text: 'Create block',
|
||||
icon: Plus,
|
||||
variant: 'primary',
|
||||
onSelect: () => setSelected('new'),
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<SettingsSection label={`Blocks (${blocks.length})`}>
|
||||
{blocks.length === 0 ? (
|
||||
<SettingsEmptyState variant='inline'>
|
||||
{canCreate
|
||||
? 'No custom blocks yet. Click "Create block" to publish a workflow as a block.'
|
||||
: 'No custom blocks yet.'}
|
||||
</SettingsEmptyState>
|
||||
) : filtered.length === 0 ? (
|
||||
<SettingsEmptyState variant='inline'>
|
||||
No blocks found matching "{searchTerm}"
|
||||
</SettingsEmptyState>
|
||||
) : (
|
||||
<div className='-mx-2 flex flex-col gap-y-0.5'>
|
||||
{filtered.map((cb) => {
|
||||
const Icon = getCustomBlockIcon(cb.iconUrl, fallbackIconUrl)
|
||||
return (
|
||||
<button
|
||||
key={cb.id}
|
||||
type='button'
|
||||
onClick={() => setSelected(cb.id)}
|
||||
className='w-full rounded-lg p-2 text-left transition-colors hover-hover:bg-[var(--surface-active)]'
|
||||
>
|
||||
<SettingsResourceRow
|
||||
icon={<Icon />}
|
||||
iconFill
|
||||
title={cb.name}
|
||||
description={cb.description || undefined}
|
||||
trailing={
|
||||
<div className='flex flex-shrink-0 items-center gap-2'>
|
||||
{!cb.enabled && <ChipTag variant='gray'>Disabled</ChipTag>}
|
||||
<ArrowRight className='size-4 text-[var(--text-icon)]' />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</SettingsSection>
|
||||
</SettingsPanel>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user