Skip to content

Commit aa3be4b

Browse files
waleedlatif1claude
andcommitted
feat(workspace): add workspace color changing, consolidate update hooks, fix popover dismiss
- Add workspace color change via context menu, reusing workflow ColorGrid UI - Consolidate useUpdateWorkspaceName + useUpdateWorkspaceColor into useUpdateWorkspace - Fix popover hover submenu dismiss by using DismissableLayerBranch with pointerEvents - Remove passthrough wrapper for export, reuse Workspace type for capturedWorkspaceRef - Reorder log columns: workflow first, merge date+time into single column Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4509a75 commit aa3be4b

File tree

8 files changed

+68
-66
lines changed

8 files changed

+68
-66
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/logs-list/logs-list.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,6 @@ const LogRow = memo(
8383
onContextMenu={handleContextMenu}
8484
>
8585
<div className='flex flex-1 items-center'>
86-
<span
87-
className={`${LOG_COLUMNS.date.width} ${LOG_COLUMNS.date.minWidth} font-medium text-[12px] text-[var(--text-primary)]`}
88-
>
89-
{formattedDate.compactDate}
90-
</span>
91-
92-
<span
93-
className={`${LOG_COLUMNS.time.width} ${LOG_COLUMNS.time.minWidth} font-medium text-[12px] text-[var(--text-primary)]`}
94-
>
95-
{formattedDate.compactTime}
96-
</span>
97-
98-
<div className={`${LOG_COLUMNS.status.width} ${LOG_COLUMNS.status.minWidth}`}>
99-
<StatusBadge status={getDisplayStatus(log.status)} />
100-
</div>
101-
10286
<div
10387
className={`flex ${LOG_COLUMNS.workflow.width} ${LOG_COLUMNS.workflow.minWidth} items-center gap-[8px] pr-[8px]`}
10488
>
@@ -120,6 +104,16 @@ const LogRow = memo(
120104
</span>
121105
</div>
122106

107+
<span
108+
className={`${LOG_COLUMNS.date.width} ${LOG_COLUMNS.date.minWidth} font-medium text-[12px] text-[var(--text-primary)]`}
109+
>
110+
{formattedDate.compactDate} {formattedDate.compactTime}
111+
</span>
112+
113+
<div className={`${LOG_COLUMNS.status.width} ${LOG_COLUMNS.status.minWidth}`}>
114+
<StatusBadge status={getDisplayStatus(log.status)} />
115+
</div>
116+
123117
<span
124118
className={`${LOG_COLUMNS.cost.width} ${LOG_COLUMNS.cost.minWidth} font-medium text-[12px] text-[var(--text-primary)]`}
125119
>

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,9 @@ const LOGS_PER_PAGE = 50 as const
8080
const REFRESH_SPINNER_DURATION_MS = 1000 as const
8181

8282
const LOG_COLUMNS: ResourceColumn[] = [
83+
{ id: 'workflow', header: 'Workflow' },
8384
{ id: 'date', header: 'Date' },
84-
{ id: 'time', header: 'Time' },
8585
{ id: 'status', header: 'Status' },
86-
{ id: 'workflow', header: 'Workflow' },
8786
{ id: 'cost', header: 'Cost' },
8887
{ id: 'trigger', header: 'Trigger' },
8988
{ id: 'duration', header: 'Duration' },
@@ -662,9 +661,6 @@ export default function Logs() {
662661
return {
663662
id: log.id,
664663
cells: {
665-
date: { label: formattedDate.compactDate },
666-
time: { label: formattedDate.compactTime },
667-
status: { content: <StatusBadge status={displayStatus} /> },
668664
workflow: {
669665
icon: workflowColor ? (
670666
<div
@@ -678,6 +674,8 @@ export default function Logs() {
678674
) : undefined,
679675
label: workflowName,
680676
},
677+
date: { label: `${formattedDate.compactDate} ${formattedDate.compactTime}` },
678+
status: { content: <StatusBadge status={displayStatus} /> },
681679
cost: { label: costText },
682680
trigger: { content: <TriggerBadge trigger={log.trigger || 'manual'} /> },
683681
duration: { label: durationText },

apps/sim/app/workspace/[workspaceId]/logs/utils.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@ import { getBlock } from '@/blocks/registry'
77
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
88

99
export const LOG_COLUMNS = {
10-
date: { width: 'w-[8%]', minWidth: 'min-w-[70px]', label: 'Date' },
11-
time: { width: 'w-[12%]', minWidth: 'min-w-[90px]', label: 'Time' },
12-
status: { width: 'w-[12%]', minWidth: 'min-w-[100px]', label: 'Status' },
1310
workflow: { width: 'w-[22%]', minWidth: 'min-w-[140px]', label: 'Workflow' },
14-
cost: { width: 'w-[12%]', minWidth: 'min-w-[90px]', label: 'Cost' },
11+
date: { width: 'w-[18%]', minWidth: 'min-w-[140px]', label: 'Date' },
12+
status: { width: 'w-[12%]', minWidth: 'min-w-[100px]', label: 'Status' },
13+
cost: { width: 'w-[14%]', minWidth: 'min-w-[90px]', label: 'Cost' },
1514
trigger: { width: 'w-[14%]', minWidth: 'min-w-[110px]', label: 'Trigger' },
1615
duration: { width: 'w-[20%]', minWidth: 'min-w-[100px]', label: 'Duration' },
1716
} as const
1817

1918
export type LogColumnKey = keyof typeof LOG_COLUMNS
2019

2120
export const LOG_COLUMN_ORDER: readonly LogColumnKey[] = [
21+
'workflow',
2222
'date',
23-
'time',
2423
'status',
25-
'workflow',
2624
'cost',
2725
'trigger',
2826
'duration',

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ interface WorkspaceHeaderProps {
6767
onImportWorkspace: () => void
6868
/** Whether workspace import is in progress */
6969
isImportingWorkspace: boolean
70+
/** Callback to change the workspace color */
71+
onColorChange?: (workspaceId: string, color: string) => Promise<void>
7072
/** Callback to leave the workspace */
7173
onLeaveWorkspace?: (workspaceId: string) => Promise<void>
7274
/** Current user's session ID for owner check */
@@ -92,6 +94,7 @@ export function WorkspaceHeader({
9294
onExportWorkspace,
9395
onImportWorkspace,
9496
isImportingWorkspace,
97+
onColorChange,
9598
onLeaveWorkspace,
9699
sessionUserId,
97100
}: WorkspaceHeaderProps) {
@@ -110,11 +113,7 @@ export function WorkspaceHeader({
110113
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 })
111114
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
112115
const contextMenuRef = useRef<HTMLDivElement | null>(null)
113-
const capturedWorkspaceRef = useRef<{
114-
id: string
115-
name: string
116-
permissions?: 'admin' | 'write' | 'read' | null
117-
} | null>(null)
116+
const capturedWorkspaceRef = useRef<Workspace | null>(null)
118117
const isRenamingRef = useRef(false)
119118
const isContextMenuOpeningRef = useRef(false)
120119
const contextMenuClosedRef = useRef(true)
@@ -166,11 +165,7 @@ export function WorkspaceHeader({
166165
isContextMenuOpeningRef.current = true
167166
contextMenuClosedRef.current = false
168167

169-
capturedWorkspaceRef.current = {
170-
id: workspace.id,
171-
name: workspace.name,
172-
permissions: workspace.permissions,
173-
}
168+
capturedWorkspaceRef.current = workspace
174169
setContextMenuPosition({ x, y })
175170
setIsContextMenuOpen(true)
176171
}
@@ -263,6 +258,14 @@ export function WorkspaceHeader({
263258
}
264259
}
265260

261+
/**
262+
* Handles color change action from context menu
263+
*/
264+
const handleColorChangeAction = async (color: string) => {
265+
if (!capturedWorkspaceRef.current || !onColorChange) return
266+
await onColorChange(capturedWorkspaceRef.current.id, color)
267+
}
268+
266269
/**
267270
* Handle leave workspace after confirmation
268271
*/
@@ -546,14 +549,18 @@ export function WorkspaceHeader({
546549
onExport={handleExportAction}
547550
onDelete={handleDeleteAction}
548551
onLeave={handleLeaveAction}
552+
onColorChange={onColorChange ? handleColorChangeAction : undefined}
553+
currentColor={capturedWorkspace?.color}
549554
showRename={true}
550555
showDuplicate={true}
551556
showExport={true}
557+
showColorChange={!!onColorChange}
552558
showLeave={!isOwner && !!onLeaveWorkspace}
553559
disableRename={!contextCanAdmin}
554560
disableDuplicate={!contextCanEdit}
555561
disableExport={!contextCanAdmin}
556562
disableDelete={!contextCanAdmin}
563+
disableColorChange={!contextCanAdmin}
557564
/>
558565
)
559566
})()}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-workspace-management.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useLeaveWorkspace } from '@/hooks/queries/invitations'
66
import {
77
useCreateWorkspace,
88
useDeleteWorkspace,
9-
useUpdateWorkspaceName,
9+
useUpdateWorkspace,
1010
useWorkspacesQuery,
1111
type Workspace,
1212
workspaceKeys,
@@ -46,7 +46,7 @@ export function useWorkspaceManagement({
4646
const leaveWorkspaceMutation = useLeaveWorkspace()
4747
const createWorkspaceMutation = useCreateWorkspace()
4848
const deleteWorkspaceMutation = useDeleteWorkspace()
49-
const updateWorkspaceNameMutation = useUpdateWorkspaceName()
49+
const updateWorkspaceMutation = useUpdateWorkspace()
5050

5151
const workspaceIdRef = useRef<string>(workspaceId)
5252
const routerRef = useRef<ReturnType<typeof useRouter>>(router)
@@ -92,18 +92,18 @@ export function useWorkspaceManagement({
9292
await refetchWorkspaces()
9393
}, [refetchWorkspaces])
9494

95-
const updateWorkspaceName = useCallback(
96-
async (workspaceId: string, newName: string): Promise<boolean> => {
95+
const updateWorkspace = useCallback(
96+
async (workspaceId: string, updates: { name?: string; color?: string }): Promise<boolean> => {
9797
try {
98-
await updateWorkspaceNameMutation.mutateAsync({ workspaceId, name: newName })
99-
logger.info('Successfully updated workspace name to:', newName.trim())
98+
await updateWorkspaceMutation.mutateAsync({ workspaceId, ...updates })
99+
logger.info('Successfully updated workspace:', updates)
100100
return true
101101
} catch (error) {
102-
logger.error('Error updating workspace name:', error)
102+
logger.error('Error updating workspace:', error)
103103
return false
104104
}
105105
},
106-
[updateWorkspaceNameMutation]
106+
[updateWorkspaceMutation]
107107
)
108108

109109
const switchWorkspace = useCallback(
@@ -251,7 +251,7 @@ export function useWorkspaceManagement({
251251
isLeaving: leaveWorkspaceMutation.isPending,
252252
fetchWorkspaces,
253253
refreshWorkspaceList,
254-
updateWorkspaceName,
254+
updateWorkspace,
255255
switchWorkspace,
256256
handleCreateWorkspace,
257257
confirmDeleteWorkspace,

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export const Sidebar = memo(function Sidebar() {
290290
switchWorkspace,
291291
handleCreateWorkspace,
292292
isCreatingWorkspace,
293-
updateWorkspaceName,
293+
updateWorkspace,
294294
confirmDeleteWorkspace,
295295
handleLeaveWorkspace,
296296
} = useWorkspaceManagement({
@@ -702,9 +702,16 @@ export const Sidebar = memo(function Sidebar() {
702702

703703
const handleRenameWorkspace = useCallback(
704704
async (workspaceIdToRename: string, newName: string) => {
705-
await updateWorkspaceName(workspaceIdToRename, newName)
705+
await updateWorkspace(workspaceIdToRename, { name: newName })
706706
},
707-
[updateWorkspaceName]
707+
[updateWorkspace]
708+
)
709+
710+
const handleColorChangeWorkspace = useCallback(
711+
async (workspaceIdToUpdate: string, color: string) => {
712+
await updateWorkspace(workspaceIdToUpdate, { color })
713+
},
714+
[updateWorkspace]
708715
)
709716

710717
const handleDeleteWorkspace = useCallback(
@@ -734,13 +741,6 @@ export const Sidebar = memo(function Sidebar() {
734741
[duplicateWorkspace]
735742
)
736743

737-
const handleExportWorkspace = useCallback(
738-
async (workspaceIdToExport: string, workspaceName: string) => {
739-
await exportWorkspace(workspaceIdToExport, workspaceName)
740-
},
741-
[exportWorkspace]
742-
)
743-
744744
const handleImportWorkspace = useCallback(() => {
745745
workspaceFileInputRef.current?.click()
746746
}, [])
@@ -907,9 +907,10 @@ export const Sidebar = memo(function Sidebar() {
907907
onRenameWorkspace={handleRenameWorkspace}
908908
onDeleteWorkspace={handleDeleteWorkspace}
909909
onDuplicateWorkspace={handleDuplicateWorkspace}
910-
onExportWorkspace={handleExportWorkspace}
910+
onExportWorkspace={exportWorkspace}
911911
onImportWorkspace={handleImportWorkspace}
912912
isImportingWorkspace={isImportingWorkspace}
913+
onColorChange={handleColorChangeWorkspace}
913914
onLeaveWorkspace={handleLeaveWorkspaceWrapper}
914915
sessionUserId={sessionData?.user?.id}
915916
/>

apps/sim/components/emcn/components/popover/popover.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
'use client'
5151

5252
import * as React from 'react'
53+
import { DismissableLayerBranch } from '@radix-ui/react-dismissable-layer'
5354
import * as PopoverPrimitive from '@radix-ui/react-popover'
5455
import { Check, ChevronLeft, ChevronRight, Search } from 'lucide-react'
5556
import { createPortal } from 'react-dom'
@@ -948,7 +949,7 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
948949
{isHoverOpen &&
949950
typeof document !== 'undefined' &&
950951
createPortal(
951-
<div
952+
<DismissableLayerBranch
952953
className={cn(
953954
'fixed z-[10000201] min-w-[120px]',
954955
STYLES.content,
@@ -958,10 +959,11 @@ const PopoverFolder = React.forwardRef<HTMLDivElement, PopoverFolderProps>(
958959
style={{
959960
top: submenuPosition.top,
960961
left: submenuPosition.left,
962+
pointerEvents: 'auto',
961963
}}
962964
>
963965
{children}
964-
</div>,
966+
</DismissableLayerBranch>,
965967
document.body
966968
)}
967969
</>

apps/sim/hooks/queries/workspace.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,29 +119,31 @@ export function useDeleteWorkspace() {
119119
})
120120
}
121121

122-
interface UpdateWorkspaceNameParams {
122+
interface UpdateWorkspaceParams {
123123
workspaceId: string
124-
name: string
124+
name?: string
125+
color?: string
125126
}
126127

127128
/**
128-
* Updates a workspace's name.
129+
* Updates a workspace's properties (name, color, etc.).
129130
* Invalidates both the workspace list and the specific workspace detail cache.
130131
*/
131-
export function useUpdateWorkspaceName() {
132+
export function useUpdateWorkspace() {
132133
const queryClient = useQueryClient()
133134

134135
return useMutation({
135-
mutationFn: async ({ workspaceId, name }: UpdateWorkspaceNameParams) => {
136+
mutationFn: async ({ workspaceId, ...updates }: UpdateWorkspaceParams) => {
137+
const body = updates.name !== undefined ? { ...updates, name: updates.name.trim() } : updates
136138
const response = await fetch(`/api/workspaces/${workspaceId}`, {
137139
method: 'PATCH',
138140
headers: { 'Content-Type': 'application/json' },
139-
body: JSON.stringify({ name: name.trim() }),
141+
body: JSON.stringify(body),
140142
})
141143

142144
if (!response.ok) {
143145
const error = await response.json()
144-
throw new Error(error.error || 'Failed to update workspace name')
146+
throw new Error(error.error || 'Failed to update workspace')
145147
}
146148

147149
return response.json()

0 commit comments

Comments
 (0)