Skip to content

Commit 2ce68ae

Browse files
authored
fix(sockets): force user to refresh on disconnect in order to mkae changes, add read-only offline mode (#641)
* force user to refresh on disconnect in order to mkae changes, add read-only offline mode * remove unused hook * style * update tooltip msg * remove unnecessary useMemo around log
1 parent d904604 commit 2ce68ae

File tree

10 files changed

+247
-151
lines changed

10 files changed

+247
-151
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,57 @@
11
'use client'
22

3-
import { useEffect, useState } from 'react'
3+
import { AlertTriangle, RefreshCw } from 'lucide-react'
4+
import { Button } from '@/components/ui/button'
5+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
6+
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
47

58
interface ConnectionStatusProps {
69
isConnected: boolean
710
}
811

912
export function ConnectionStatus({ isConnected }: ConnectionStatusProps) {
10-
const [showOfflineNotice, setShowOfflineNotice] = useState(false)
13+
const userPermissions = useUserPermissionsContext()
1114

12-
useEffect(() => {
13-
let timeoutId: NodeJS.Timeout
14-
15-
if (!isConnected) {
16-
// Show offline notice after 6 seconds of being disconnected
17-
timeoutId = setTimeout(() => {
18-
setShowOfflineNotice(true)
19-
}, 6000) // 6 seconds
20-
} else {
21-
// Hide notice immediately when reconnected
22-
setShowOfflineNotice(false)
23-
}
24-
25-
return () => {
26-
if (timeoutId) {
27-
clearTimeout(timeoutId)
28-
}
29-
}
30-
}, [isConnected])
15+
const handleRefresh = () => {
16+
window.location.reload()
17+
}
3118

32-
// Don't render anything if connected or if we haven't been disconnected long enough
33-
if (!showOfflineNotice) {
19+
// Don't render anything if not in offline mode
20+
if (!userPermissions.isOfflineMode) {
3421
return null
3522
}
3623

3724
return (
38-
<div className='flex items-center gap-1.5'>
39-
<div className='flex items-center gap-1.5 text-red-600'>
25+
<div className='flex items-center gap-2 rounded-md border border-red-200 bg-red-50 px-3 py-2'>
26+
<div className='flex items-center gap-2 text-red-700'>
4027
<div className='relative flex items-center justify-center'>
41-
<div className='absolute h-3 w-3 animate-ping rounded-full bg-red-500/20' />
42-
<div className='relative h-2 w-2 rounded-full bg-red-500' />
28+
{!isConnected && (
29+
<div className='absolute h-4 w-4 animate-ping rounded-full bg-red-500/20' />
30+
)}
31+
<AlertTriangle className='relative h-4 w-4' />
4332
</div>
4433
<div className='flex flex-col'>
45-
<span className='font-medium text-xs leading-tight'>Connection lost</span>
46-
<span className='text-xs leading-tight opacity-90'>
47-
Changes not saved - please refresh
34+
<span className='font-medium text-xs leading-tight'>
35+
{isConnected ? 'Reconnected' : 'Connection lost - please refresh'}
36+
</span>
37+
<span className='text-red-600 text-xs leading-tight'>
38+
{isConnected ? 'Refresh to continue editing' : 'Read-only mode active'}
4839
</span>
4940
</div>
5041
</div>
42+
<Tooltip>
43+
<TooltipTrigger asChild>
44+
<Button
45+
onClick={handleRefresh}
46+
variant='ghost'
47+
size='sm'
48+
className='h-7 w-7 p-0 text-red-700 hover:bg-red-100 hover:text-red-800'
49+
>
50+
<RefreshCw className='h-4 w-4' />
51+
</Button>
52+
</TooltipTrigger>
53+
<TooltipContent className='z-[9999]'>Refresh page to continue editing</TooltipContent>
54+
</Tooltip>
5155
</div>
5256
)
5357
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/user-avatar-stack/user-avatar-stack.tsx

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,6 @@ export function UserAvatarStack({
4444
}
4545
}, [users, maxVisible])
4646

47-
// Show connection status component regardless of user count
48-
// This will handle the offline notice when disconnected for 15 seconds
49-
const connectionStatusElement = <ConnectionStatus isConnected={isConnected} />
50-
51-
// Only show presence when there are multiple users (>1)
52-
// But always show connection status
53-
if (users.length <= 1) {
54-
return connectionStatusElement
55-
}
56-
5747
// Determine spacing based on size
5848
const spacingClass = {
5949
sm: '-space-x-1',
@@ -62,46 +52,55 @@ export function UserAvatarStack({
6252
}[size]
6353

6454
return (
65-
<div className={`flex items-center ${spacingClass} ${className}`}>
66-
{/* Connection status - always present */}
67-
{connectionStatusElement}
55+
<div className={`flex items-center gap-3 ${className}`}>
56+
{/* Connection status - always check, shows when offline */}
57+
<ConnectionStatus isConnected={isConnected} />
6858

69-
{/* Render visible user avatars */}
70-
{visibleUsers.map((user, index) => (
71-
<UserAvatar
72-
key={user.connectionId}
73-
connectionId={user.connectionId}
74-
name={user.name}
75-
color={user.color}
76-
size={size}
77-
index={index}
78-
tooltipContent={
79-
user.name ? (
80-
<div className='text-center'>
81-
<div className='font-medium'>{user.name}</div>
82-
{user.info && <div className='mt-1 text-muted-foreground text-xs'>{user.info}</div>}
83-
</div>
84-
) : null
85-
}
86-
/>
87-
))}
59+
{/* Only show avatar stack when there are multiple users (>1) */}
60+
{users.length > 1 && (
61+
<div className={`flex items-center ${spacingClass}`}>
62+
{/* Render visible user avatars */}
63+
{visibleUsers.map((user, index) => (
64+
<UserAvatar
65+
key={user.connectionId}
66+
connectionId={user.connectionId}
67+
name={user.name}
68+
color={user.color}
69+
size={size}
70+
index={index}
71+
tooltipContent={
72+
user.name ? (
73+
<div className='text-center'>
74+
<div className='font-medium'>{user.name}</div>
75+
{user.info && (
76+
<div className='mt-1 text-muted-foreground text-xs'>{user.info}</div>
77+
)}
78+
</div>
79+
) : null
80+
}
81+
/>
82+
))}
8883

89-
{/* Render overflow indicator if there are more users */}
90-
{overflowCount > 0 && (
91-
<UserAvatar
92-
connectionId='overflow-indicator' // Use a unique string identifier
93-
name={`+${overflowCount}`}
94-
size={size}
95-
index={visibleUsers.length}
96-
tooltipContent={
97-
<div className='text-center'>
98-
<div className='font-medium'>
99-
{overflowCount} more user{overflowCount > 1 ? 's' : ''}
100-
</div>
101-
<div className='mt-1 text-muted-foreground text-xs'>{users.length} total online</div>
102-
</div>
103-
}
104-
/>
84+
{/* Render overflow indicator if there are more users */}
85+
{overflowCount > 0 && (
86+
<UserAvatar
87+
connectionId='overflow-indicator' // Use a unique string identifier
88+
name={`+${overflowCount}`}
89+
size={size}
90+
index={visibleUsers.length}
91+
tooltipContent={
92+
<div className='text-center'>
93+
<div className='font-medium'>
94+
{overflowCount} more user{overflowCount > 1 ? 's' : ''}
95+
</div>
96+
<div className='mt-1 text-muted-foreground text-xs'>
97+
{users.length} total online
98+
</div>
99+
</div>
100+
}
101+
/>
102+
)}
103+
</div>
105104
)}
106105
</div>
107106
)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/control-bar.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,11 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
670670
</h2>
671671
</TooltipTrigger>
672672
{!canEdit && (
673-
<TooltipContent>Edit permissions required to rename workflows</TooltipContent>
673+
<TooltipContent>
674+
{userPermissions.isOfflineMode
675+
? 'Connection lost - please refresh'
676+
: 'Edit permissions required to rename workflows'}
677+
</TooltipContent>
674678
)}
675679
</Tooltip>
676680
)}
@@ -934,7 +938,11 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
934938
)}
935939
</TooltipTrigger>
936940
<TooltipContent>
937-
{canEdit ? 'Duplicate Workflow' : 'Admin permission required to duplicate workflows'}
941+
{canEdit
942+
? 'Duplicate Workflow'
943+
: userPermissions.isOfflineMode
944+
? 'Connection lost - please refresh'
945+
: 'Admin permission required to duplicate workflows'}
938946
</TooltipContent>
939947
</Tooltip>
940948
)
@@ -975,7 +983,9 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
975983
</TooltipTrigger>
976984
<TooltipContent command='Shift+L'>
977985
{!userPermissions.canEdit
978-
? 'Admin permission required to use auto-layout'
986+
? userPermissions.isOfflineMode
987+
? 'Connection lost - please refresh'
988+
: 'Admin permission required to use auto-layout'
979989
: 'Auto Layout'}
980990
</TooltipContent>
981991
</Tooltip>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/toolbar/components/toolbar-block/toolbar-block.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback } from 'react'
22
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
33
import { cn } from '@/lib/utils'
4+
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
45
import type { BlockConfig } from '@/blocks/types'
56

67
export type ToolbarBlockProps = {
@@ -9,6 +10,8 @@ export type ToolbarBlockProps = {
910
}
1011

1112
export function ToolbarBlock({ config, disabled = false }: ToolbarBlockProps) {
13+
const userPermissions = useUserPermissionsContext()
14+
1215
const handleDragStart = (e: React.DragEvent) => {
1316
if (disabled) {
1417
e.preventDefault()
@@ -66,7 +69,11 @@ export function ToolbarBlock({ config, disabled = false }: ToolbarBlockProps) {
6669
return (
6770
<Tooltip>
6871
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
69-
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
72+
<TooltipContent>
73+
{userPermissions.isOfflineMode
74+
? 'Connection lost - please refresh'
75+
: 'Edit permissions required to add blocks'}
76+
</TooltipContent>
7077
</Tooltip>
7178
)
7279
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/toolbar/components/toolbar-loop-block/toolbar-loop-block.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback } from 'react'
22
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
33
import { cn } from '@/lib/utils'
4+
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
45
import { LoopTool } from '../../../loop-node/loop-config'
56

67
type LoopToolbarItemProps = {
@@ -9,6 +10,8 @@ type LoopToolbarItemProps = {
910

1011
// Custom component for the Loop Tool
1112
export default function LoopToolbarItem({ disabled = false }: LoopToolbarItemProps) {
13+
const userPermissions = useUserPermissionsContext()
14+
1215
const handleDragStart = (e: React.DragEvent) => {
1316
if (disabled) {
1417
e.preventDefault()
@@ -74,7 +77,11 @@ export default function LoopToolbarItem({ disabled = false }: LoopToolbarItemPro
7477
return (
7578
<Tooltip>
7679
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
77-
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
80+
<TooltipContent>
81+
{userPermissions.isOfflineMode
82+
? 'Connection lost - please refresh'
83+
: 'Edit permissions required to add blocks'}
84+
</TooltipContent>
7885
</Tooltip>
7986
)
8087
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/toolbar/components/toolbar-parallel-block/toolbar-parallel-block.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback } from 'react'
22
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
33
import { cn } from '@/lib/utils'
4+
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
45
import { ParallelTool } from '../../../parallel-node/parallel-config'
56

67
type ParallelToolbarItemProps = {
@@ -9,6 +10,7 @@ type ParallelToolbarItemProps = {
910

1011
// Custom component for the Parallel Tool
1112
export default function ParallelToolbarItem({ disabled = false }: ParallelToolbarItemProps) {
13+
const userPermissions = useUserPermissionsContext()
1214
const handleDragStart = (e: React.DragEvent) => {
1315
if (disabled) {
1416
e.preventDefault()
@@ -75,7 +77,11 @@ export default function ParallelToolbarItem({ disabled = false }: ParallelToolba
7577
return (
7678
<Tooltip>
7779
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
78-
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
80+
<TooltipContent>
81+
{userPermissions.isOfflineMode
82+
? 'Connection lost - please refresh'
83+
: 'Edit permissions required to add blocks'}
84+
</TooltipContent>
7985
</Tooltip>
8086
)
8187
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/action-bar/action-bar.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Copy, Trash2 } from 'lu
22
import { Button } from '@/components/ui/button'
33
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
44
import { cn } from '@/lib/utils'
5+
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
56
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
67
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
78

@@ -22,9 +23,17 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
2223
const horizontalHandles = useWorkflowStore(
2324
(state) => state.blocks[blockId]?.horizontalHandles ?? false
2425
)
26+
const userPermissions = useUserPermissionsContext()
2527

2628
const isStarterBlock = blockType === 'starter'
2729

30+
const getTooltipMessage = (defaultMessage: string) => {
31+
if (disabled) {
32+
return userPermissions.isOfflineMode ? 'Connection lost - please refresh' : 'Read-only mode'
33+
}
34+
return defaultMessage
35+
}
36+
2837
return (
2938
<div
3039
className={cn(
@@ -68,7 +77,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
6877
</Button>
6978
</TooltipTrigger>
7079
<TooltipContent side='right'>
71-
{disabled ? 'Read-only mode' : isEnabled ? 'Disable Block' : 'Enable Block'}
80+
{getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')}
7281
</TooltipContent>
7382
</Tooltip>
7483

@@ -89,9 +98,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
8998
<Copy className='h-4 w-4' />
9099
</Button>
91100
</TooltipTrigger>
92-
<TooltipContent side='right'>
93-
{disabled ? 'Read-only mode' : 'Duplicate Block'}
94-
</TooltipContent>
101+
<TooltipContent side='right'>{getTooltipMessage('Duplicate Block')}</TooltipContent>
95102
</Tooltip>
96103
)}
97104

@@ -116,7 +123,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
116123
</Button>
117124
</TooltipTrigger>
118125
<TooltipContent side='right'>
119-
{disabled ? 'Read-only mode' : horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports'}
126+
{getTooltipMessage(horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports')}
120127
</TooltipContent>
121128
</Tooltip>
122129

@@ -140,9 +147,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
140147
<Trash2 className='h-4 w-4' />
141148
</Button>
142149
</TooltipTrigger>
143-
<TooltipContent side='right'>
144-
{disabled ? 'Read-only mode' : 'Delete Block'}
145-
</TooltipContent>
150+
<TooltipContent side='right'>{getTooltipMessage('Delete Block')}</TooltipContent>
146151
</Tooltip>
147152
)}
148153
</div>

0 commit comments

Comments
 (0)