Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
chore: fix conflicts
  • Loading branch information
adithyaakrishna committed Apr 4, 2026
commit a1ee666f4606ae9a301ecb23bc2310655d1a0f2d
4 changes: 4 additions & 0 deletions apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -991,3 +991,7 @@ input[type="search"]::-ms-clear {
.react-flow__node[data-parent-node-id] .react-flow__handle {
z-index: 30;
}

.react-flow__panel {
margin: 0 !important;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { Check, Clipboard, Key, Search } from 'lucide-react'
import { Check, Clipboard, Info, Key, Search, Shield, UserPlus } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import {
Avatar,
Expand Down Expand Up @@ -1188,44 +1188,78 @@ export function CredentialsManager() {
</div>
)}

<div className='flex flex-col gap-1.5 border-[var(--border)] border-t pt-4'>
<Label>Members ({activeMembers.length})</Label>
<div className='flex flex-col gap-0 overflow-hidden rounded-lg border border-[var(--border)]'>
{/* Header */}
<div className='flex items-start gap-3 border-b border-[var(--border)] bg-[var(--surface-1)] px-4 py-3'>
<div className='flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-[var(--surface-4)]'>
<Shield className='h-4 w-4 text-[var(--text-secondary)]' />
</div>
<div className='min-w-0 flex-1'>
<div className='flex items-center gap-2'>
<p className='font-medium text-[var(--text-primary)] text-sm'>
Access Control
</p>
<Badge variant='gray-secondary' size='sm'>
{activeMembers.length} {activeMembers.length === 1 ? 'member' : 'members'}
</Badge>
</div>
<p className='mt-0.5 text-[var(--text-tertiary)] text-caption'>
Only workspace members listed below can view and use this secret in their
workflows. Admins can manage access; members can only use the secret.
</p>
</div>
</div>

{/* Member list */}
{membersLoading ? (
<div className='flex flex-col gap-2'>
<Skeleton className='h-[44px] w-full rounded-lg' />
<Skeleton className='h-[44px] w-full rounded-lg' />
<div className='flex flex-col gap-0 px-4'>
<div className='flex items-center gap-3 py-3'>
<Skeleton className='h-8 w-8 rounded-full' />
<div className='flex-1'>
<Skeleton className='mb-1 h-3.5 w-[120px]' />
<Skeleton className='h-3 w-[180px]' />
</div>
<Skeleton className='h-7 w-[80px] rounded-md' />
</div>
<div className='flex items-center gap-3 border-t border-[var(--border)] py-3'>
<Skeleton className='h-8 w-8 rounded-full' />
<div className='flex-1'>
<Skeleton className='mb-1 h-3.5 w-[100px]' />
<Skeleton className='h-3 w-[160px]' />
</div>
<Skeleton className='h-7 w-[80px] rounded-md' />
</div>
</div>
) : (
<div className='flex flex-col gap-2'>
{activeMembers.map((member) => (
<div className='flex flex-col'>
{activeMembers.map((member, index) => (
<div
key={member.id}
className='grid grid-cols-[1fr_120px_72px] items-center gap-2'
className={`flex items-center gap-3 px-4 py-2.5 ${
index > 0 ? 'border-t border-[var(--border)]' : ''
}`}
>
<div className='flex min-w-0 items-center gap-2.5'>
<Avatar className='h-8 w-8 flex-shrink-0'>
<AvatarFallback
style={{
background: getUserColor(member.userId || member.userEmail || ''),
}}
className='border-0 text-small text-white'
>
{(member.userName || member.userEmail || '?').charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className='min-w-0'>
<p className='truncate font-medium text-[var(--text-primary)] text-sm'>
{member.userName || member.userEmail || member.userId}
</p>
<p className='truncate text-[var(--text-tertiary)] text-caption'>
{member.userEmail || member.userId}
</p>
</div>
<Avatar className='h-8 w-8 flex-shrink-0'>
<AvatarFallback
style={{
background: getUserColor(member.userId || member.userEmail || ''),
}}
className='border-0 text-small text-white'
>
{(member.userName || member.userEmail || '?').charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className='min-w-0 flex-1'>
<p className='truncate font-medium text-[var(--text-primary)] text-sm'>
{member.userName || member.userEmail || member.userId}
</p>
<p className='truncate text-[var(--text-tertiary)] text-caption'>
{member.userEmail || member.userId}
</p>
</div>

{isSelectedAdmin ? (
<>
<div className='flex flex-shrink-0 items-center gap-1.5'>
<Combobox
options={ROLE_OPTIONS.map((option) => ({
value: option.value,
Expand All @@ -1250,55 +1284,85 @@ export function CredentialsManager() {
variant='ghost'
onClick={() => handleRemoveMember(member.userId)}
disabled={member.role === 'admin' && adminMemberCount <= 1}
className='w-full justify-end'
className='h-7 px-2 text-[var(--text-tertiary)] text-caption hover-hover:text-[var(--text-error)]'
>
Remove
</Button>
</>
</div>
) : (
<>
<Badge variant='gray-secondary'>{member.role}</Badge>
<div />
</>
<Badge variant='gray-secondary' size='sm'>
{member.role === 'admin' ? 'Admin' : 'Member'}
</Badge>
)}
</div>
))}

{/* Add member row */}
{isSelectedAdmin && (
<div className='grid grid-cols-[1fr_120px_72px] items-center gap-2 border-[var(--border)] border-t pt-2'>
<Combobox
options={workspaceUserOptions}
value={
workspaceUserOptions.find((option) => option.value === memberUserId)
?.label || ''
}
selectedValue={memberUserId}
onChange={setMemberUserId}
placeholder='Add member...'
searchable
searchPlaceholder='Search members...'
size='sm'
/>
<Combobox
options={ROLE_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
value={
ROLE_OPTIONS.find((option) => option.value === memberRole)?.label || ''
}
selectedValue={memberRole}
onChange={(value) => setMemberRole(value as WorkspaceCredentialRole)}
placeholder='Role'
size='sm'
/>
<Button
variant='ghost'
onClick={handleAddMember}
disabled={!memberUserId || upsertMember.isPending}
className='w-full justify-end'
>
Add
</Button>
<div className='flex flex-col gap-2 border-t border-[var(--border)] bg-[var(--surface-1)] px-4 py-3'>
<div className='flex items-center gap-1.5'>
<UserPlus className='h-3.5 w-3.5 text-[var(--text-tertiary)]' />
<p className='text-[var(--text-secondary)] text-caption font-medium'>
Grant access to a workspace member
</p>
</div>
<div className='flex items-center gap-2'>
<div className='flex-1'>
<Combobox
options={workspaceUserOptions}
value={
workspaceUserOptions.find((option) => option.value === memberUserId)
?.label || ''
}
selectedValue={memberUserId}
onChange={setMemberUserId}
placeholder='Select workspace member...'
searchable
searchPlaceholder='Search workspace members...'
emptyMessage='No workspace members available. Invite members to the workspace first.'
size='sm'
/>
</div>
<div className='w-[110px] flex-shrink-0'>
<Combobox
options={ROLE_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
value={
ROLE_OPTIONS.find((option) => option.value === memberRole)?.label ||
''
}
selectedValue={memberRole}
onChange={(value) => setMemberRole(value as WorkspaceCredentialRole)}
placeholder='Role'
size='sm'
/>
</div>
<Button
variant='primary'
onClick={handleAddMember}
disabled={!memberUserId || upsertMember.isPending}
className='h-7 flex-shrink-0 px-3'
>
{upsertMember.isPending ? 'Adding...' : 'Add'}
</Button>
</div>
<p className='flex items-start gap-1 text-[var(--text-muted)] text-[11px]'>
<Info className='mt-0.5 h-3 w-3 flex-shrink-0' />
Only members of this workspace appear here. To add someone new, invite
them to the workspace first.
</p>
</div>
)}

{/* Non-admin notice */}
{!isSelectedAdmin && (
<div className='flex items-center gap-2 border-t border-[var(--border)] bg-[var(--surface-1)] px-4 py-2.5'>
<Info className='h-3.5 w-3.5 flex-shrink-0 text-[var(--text-muted)]' />
<p className='text-[var(--text-muted)] text-caption'>
Only admins of this secret can manage access control.
</p>
</div>
)}
</div>
Expand All @@ -1307,7 +1371,7 @@ export function CredentialsManager() {
</div>
</div>

<div className='mt-auto flex items-center justify-end border-[var(--border)] border-t pt-2.5'>
<div className='mt-auto flex items-center justify-end pt-2.5'>
<div className='flex items-center gap-2'>
<Button onClick={handleBackAttempt} variant='default'>
Back
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use client'

import { memo, useCallback, useRef, useState } from 'react'
import { ArrowUp, Bot } from 'lucide-react'
import { Button, Tooltip } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { usePanelStore } from '@/stores/panel'

const SEND_BUTTON_BASE = 'h-[28px] w-[28px] rounded-full border-0 p-0 transition-colors'
const SEND_BUTTON_ACTIVE =
'bg-[#383838] hover:bg-[#575757] dark:bg-[#E0E0E0] dark:hover:bg-[#CFCFCF]'
const SEND_BUTTON_DISABLED = 'bg-[#808080] dark:bg-[#808080]'
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 hover: used instead of hover-hover:, and hardcoded hex values

Two issues on these constants:

  1. SEND_BUTTON_ACTIVE uses hover:bg-[#575757] and dark:hover:bg-[#CFCFCF]. Throughout the rest of the codebase (panel.tsx, workflow-toolbar.tsx, workflow-history.tsx, etc.) the project exclusively uses the hover-hover: media-query variant to avoid hover states activating on touch devices. Using hover: here is inconsistent and will fire on touch screens.

  2. The hex colors (#383838, #575757, #E0E0E0, #808080) are hardcoded rather than referencing CSS custom properties, breaking the design-token system used everywhere else.

Consider replacing with hover-hover: variants and mapping to the nearest design tokens (e.g. bg-[var(--surface-5)], hover-hover:bg-[var(--surface-6)]).


/**
* Floating copilot input that appears centered on the canvas when the panel
* is collapsed. Provides a quick entry point to the copilot — on submit,
* the message is forwarded to the panel store and the copilot tab opens.
*/
export const CopilotInput = memo(function CopilotInput() {
const isPanelOpen = usePanelStore((s) => s.isPanelOpen)
const setPendingCopilotMessage = usePanelStore((s) => s.setPendingCopilotMessage)

const [value, setValue] = useState('')
const inputRef = useRef<HTMLInputElement>(null)

const canSubmit = value.trim().length > 0

const handleSubmit = useCallback(() => {
const trimmed = value.trim()
if (!trimmed) return
setPendingCopilotMessage(trimmed)
setValue('')
if (inputRef.current) {
inputRef.current.value = ''
}
}, [value, setPendingCopilotMessage])

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault()
handleSubmit()
}
},
[handleSubmit]
)

const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
}, [])

if (isPanelOpen) return null

return (
<div className='pointer-events-none absolute inset-x-0 bottom-6 z-10 flex justify-center'>
<div className='pointer-events-auto flex h-[44px] w-full max-w-[520px] items-center gap-2 rounded-xl border border-[var(--border-1)] bg-[var(--white)] px-3 shadow-sm dark:bg-[var(--surface-4)]'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<div className='flex h-5 w-5 flex-shrink-0 items-center justify-center'>
<Bot className='h-4 w-4 text-[var(--text-muted)]' />
</div>
</Tooltip.Trigger>
<Tooltip.Content side='top'>Ask the copilot</Tooltip.Content>
</Tooltip.Root>
<input
ref={inputRef}
type='text'
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder='Ask copilot anything...'
className='h-full min-w-0 flex-1 border-0 bg-transparent text-[14px] text-[var(--text-primary)] outline-none placeholder:text-[var(--text-subtle)] focus-visible:ring-0'
autoComplete='off'
autoCorrect='off'
spellCheck={false}
/>
<Button
variant='ghost'
onClick={handleSubmit}
disabled={!canSubmit}
className={cn(
SEND_BUTTON_BASE,
canSubmit ? SEND_BUTTON_ACTIVE : SEND_BUTTON_DISABLED,
'flex-shrink-0'
)}
aria-label='Send message'
>
<ArrowUp
className='block h-[16px] w-[16px] text-white dark:text-black'
strokeWidth={2.25}
/>
</Button>
</div>
</div>
)
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { BlockMenu } from './block-menu'
export { CanvasMenu } from './canvas-menu'
export { CommandList } from './command-list/command-list'
export { CopilotInput } from './copilot-input/copilot-input'
export { Cursors } from './cursors/cursors'
export { DiffControls } from './diff-controls/diff-controls'
export { ErrorBoundary } from './error/index'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useEffect, useRef, useState } from 'react'
import clsx from 'clsx'
import { FileText, MoreVertical, Pencil, RotateCcw, SendToBack } from 'lucide-react'
import { MoreVertical, NotepadText, Pencil, RotateCcw, SendToBack } from 'lucide-react'
import {
Button,
Popover,
Expand Down Expand Up @@ -304,7 +304,7 @@ export function Versions({
)}
onClick={() => handleOpenDescriptionModal(v.version)}
>
<FileText className='h-3.5 w-3.5' />
<NotepadText className='h-3.5 w-3.5' />
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='top' className='max-w-[240px]'>
Expand All @@ -329,10 +329,7 @@ export function Versions({
<Pencil className='h-3 w-3' />
<span>Rename</span>
</PopoverItem>
<PopoverItem onClick={() => handleOpenDescriptionModal(v.version)}>
<FileText className='h-3 w-3' />
<span>{v.description ? 'Edit description' : 'Add description'}</span>
</PopoverItem>

{!v.isActive && (
<PopoverItem onClick={() => handlePromote(v.version)}>
<RotateCcw className='h-3 w-3' />
Expand Down
Loading