Skip to content

Commit 49d0e07

Browse files
committed
Simplify the Platform shell's links, help and sign-out against what already exists
1 parent c6580ff commit 49d0e07

24 files changed

Lines changed: 180 additions & 266 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/help-modal/help-modal.tsx‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import { useMutation } from '@tanstack/react-query'
1515
import Image from 'next/image'
1616
import { Controller, useForm } from 'react-hook-form'
1717
import { z } from 'zod'
18+
import { HELP_REQUEST_TYPES } from '@/lib/api/contracts/common'
1819
import {
1920
ACCEPTED_HELP_IMAGE_TYPES,
2021
compressHelpImage,
2122
DEFAULT_HELP_REQUEST_TYPE,
2223
HELP_REQUEST_TYPE_OPTIONS,
23-
HELP_REQUEST_TYPES,
24-
MAX_HELP_IMAGE_BYTES,
2524
submitHelpRequest,
25+
validateHelpImage,
2626
} from '@/lib/help/request'
2727

2828
const logger = createLogger('HelpModal')
@@ -151,12 +151,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
151151
let hasError = false
152152

153153
for (const file of Array.from(files)) {
154-
if (file.size > MAX_HELP_IMAGE_BYTES) {
155-
hasError = true
156-
continue
157-
}
158-
159-
if (!ACCEPTED_HELP_IMAGE_TYPES.includes(file.type)) {
154+
if (validateHelpImage(file)) {
160155
hasError = true
161156
continue
162157
}

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import type { ComponentType } from 'react'
44
import { ANONYMOUS_USER_ID } from '@sim/auth/principal'
5-
import type { DesktopUpdateState } from '@sim/desktop-bridge'
65
import {
76
Chip,
87
chipContentLabelClass,
@@ -32,22 +31,13 @@ import {
3231
SIDEBAR_RAIL_CHIP_CLASS,
3332
} from '@/app/workspace/[workspaceId]/w/components/sidebar/constants'
3433
import { useUserProfile } from '@/hooks/queries/user-profile'
35-
import { useDesktopUpdateState } from '@/hooks/use-desktop-update-state'
34+
import {
35+
desktopUpdateActionLabel,
36+
hasAvailableDesktopUpdate,
37+
useDesktopUpdateState,
38+
} from '@/hooks/use-desktop-update-state'
3639
import { useSettingsDirtyStore } from '@/stores/settings/dirty/store'
3740

38-
function hasAvailableDesktopUpdate(state: DesktopUpdateState): boolean {
39-
return state.status === 'available' || state.status === 'downloading' || state.status === 'ready'
40-
}
41-
42-
function desktopUpdateActionLabel(state: DesktopUpdateState): string {
43-
if (state.status === 'downloading') {
44-
return state.percent === undefined
45-
? 'Downloading update…'
46-
: `Downloading update ${state.percent}%`
47-
}
48-
return state.status === 'ready' ? 'Restart to update' : 'Update'
49-
}
50-
5141
/** Compact primary update circle using the same footprint as the surrounding sidebar icons. */
5242
function DesktopUpdateIcon({ className }: { className?: string }) {
5343
return (

‎apps/sim/hooks/use-desktop-update-state.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ import { useEffect, useState } from 'react'
44
import type { DesktopUpdateState } from '@sim/desktop-bridge'
55
import { getDesktopUpdates } from '@/lib/desktop'
66

7+
export function hasAvailableDesktopUpdate(state: DesktopUpdateState): boolean {
8+
return state.status === 'available' || state.status === 'downloading' || state.status === 'ready'
9+
}
10+
11+
export function desktopUpdateActionLabel(state: DesktopUpdateState): string {
12+
if (state.status === 'downloading') {
13+
return state.percent === undefined
14+
? 'Downloading update…'
15+
: `Downloading update ${state.percent}%`
16+
}
17+
return state.status === 'ready' ? 'Restart to update' : 'Update'
18+
}
19+
720
const INITIAL_UPDATE_STATE: DesktopUpdateState = { status: 'idle' }
821

922
export function useDesktopUpdateState(): DesktopUpdateState {

‎apps/sim/lib/api/contracts/common.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
44

55
const NO_EMAIL_HEADER_CONTROL_CHARS_REGEX = /^[^\r\n\u0000-\u001F\u007F]+$/
66

7+
export const HELP_REQUEST_TYPES = ['bug', 'feedback', 'feature_request', 'other'] as const
8+
79
export const helpFormBodySchema = z.object({
810
subject: z
911
.string()
1012
.trim()
1113
.min(1, 'Subject is required')
1214
.regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'),
1315
message: z.string().min(1, 'Message is required'),
14-
type: z.enum(['bug', 'feedback', 'feature_request', 'other']),
16+
type: z.enum(HELP_REQUEST_TYPES),
1517
})
1618
export type HelpFormBody = z.input<typeof helpFormBodySchema>
1719

‎apps/sim/lib/help/request.ts‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { createLogger } from '@sim/logger'
2-
import imageCompression from 'browser-image-compression'
2+
import type { HelpFormBody } from '@/lib/api/contracts/common'
33

44
const logger = createLogger('HelpRequest')
55

6-
export const MAX_HELP_IMAGE_BYTES = 20 * 1024 * 1024
6+
const MAX_HELP_IMAGE_BYTES = 20 * 1024 * 1024
77
const TARGET_SIZE_MB = 2
88
export const ACCEPTED_HELP_IMAGE_TYPES = [
99
'image/jpeg',
@@ -13,12 +13,7 @@ export const ACCEPTED_HELP_IMAGE_TYPES = [
1313
'image/gif',
1414
]
1515

16-
export const HELP_REQUEST_TYPES = ['bug', 'feedback', 'feature_request', 'other'] as const
17-
18-
export const HELP_REQUEST_TYPE_OPTIONS: {
19-
label: string
20-
value: (typeof HELP_REQUEST_TYPES)[number]
21-
}[] = [
16+
export const HELP_REQUEST_TYPE_OPTIONS: { label: string; value: HelpFormBody['type'] }[] = [
2217
{ label: 'Bug report', value: 'bug' },
2318
{ label: 'Feedback', value: 'feedback' },
2419
{ label: 'Feature request', value: 'feature_request' },
@@ -27,12 +22,21 @@ export const HELP_REQUEST_TYPE_OPTIONS: {
2722

2823
export const DEFAULT_HELP_REQUEST_TYPE = 'bug'
2924

25+
/** Why `file` cannot be attached to a help request, or null when it can. */
26+
export function validateHelpImage(file: Pick<File, 'size' | 'type'>): string | null {
27+
if (file.size > MAX_HELP_IMAGE_BYTES) return 'Images must be 20MB or smaller.'
28+
if (!ACCEPTED_HELP_IMAGE_TYPES.includes(file.type))
29+
return 'Attach a JPEG, PNG, WebP or GIF image.'
30+
return null
31+
}
32+
3033
export async function compressHelpImage(file: File): Promise<File> {
3134
if (file.size < TARGET_SIZE_MB * 1024 * 1024 || file.type === 'image/gif') {
3235
return file
3336
}
3437

3538
try {
39+
const { default: imageCompression } = await import('browser-image-compression')
3640
const compressedFile = await imageCompression(file, {
3741
maxSizeMB: TARGET_SIZE_MB,
3842
maxWidthOrHeight: 1920,
@@ -53,7 +57,7 @@ export async function compressHelpImage(file: File): Promise<File> {
5357
}
5458

5559
interface SubmitHelpRequestInput {
56-
data: { subject: string; message: string; type: string }
60+
data: HelpFormBody
5761
images: readonly File[]
5862
workflowId?: string
5963
workspaceId?: string

‎apps/sim/platform/app/(shell)/_components/model.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,14 @@ interface ChatRemoval {
105105
/** The optional members are the ones only a real backend can honour. */
106106
export interface ShellChatActions {
107107
removeLabel: 'Archive' | 'Delete'
108+
/** What happens to removed chats, when there is more to say than the label. */
109+
removeDetail?: (count: number) => string
108110
rename: (id: string, title: string) => Promise<void>
109111
remove: (ids: readonly string[]) => Promise<ChatRemoval>
110112
setPinned?: (id: string, pinned: boolean) => void
111113
setRead?: (id: string, read: boolean) => void
112114
/** Reads a chat ahead of an intended open. Returns how to call it off. */
113-
prefetch?: (id: string) => () => void
115+
prefetch?: (chat: ShellChat) => () => void
114116
retry?: () => void
115117
}
116118

@@ -155,8 +157,6 @@ export interface ShellHelp {
155157
changelogUrl?: string
156158
downloadUrl?: string
157159
desktopUpdate?: { label: string; disabled: boolean; run: () => void }
158-
onOpenDocumentation?: () => void
159-
onOpenSlack?: () => void
160160
}
161161

162162
/**

‎apps/sim/platform/app/(shell)/_components/organization-menu/organization-menu.tsx‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
} from '@/platform/design-system/components'
1616
import { IdentityButton } from '@/platform/design-system/components/identity-button/identity-button'
1717
import { AnchoredPopover } from '@/platform/design-system/components/menu/anchored-popover'
18-
import { toast } from '@/platform/lib/data/toast'
1918

2019
/** Lazy because its form reads real data, which a fixture session never loads. */
2120
const InviteModal = dynamic(
@@ -60,7 +59,6 @@ export function OrganizationMenu({
6059
if (savedName && savedName.replaces !== organization?.name) setSavedName(null)
6160
const name = savedName?.name ?? organization?.name ?? 'Organization'
6261
const logo = organization?.logo ?? undefined
63-
const canEdit = Boolean(actions?.canEditIdentity)
6462

6563
function stopRenaming() {
6664
setDraftName(null)
@@ -96,13 +94,8 @@ export function OrganizationMenu({
9694
async function uploadLogo(file: File | undefined) {
9795
if (!file || uploading || !actions) return
9896
setUploading(true)
99-
try {
100-
await actions.uploadLogo(file)
101-
} catch (error) {
102-
toast.error(getErrorMessage(error, 'Failed to upload logo'))
103-
} finally {
104-
setUploading(false)
105-
}
97+
await actions.uploadLogo(file)
98+
setUploading(false)
10699
}
107100

108101
return (
@@ -190,7 +183,7 @@ export function OrganizationMenu({
190183
</Button>
191184
)}
192185
</div>
193-
{canEdit && actions && (
186+
{actions?.canEditIdentity && (
194187
<>
195188
<input
196189
ref={fileInput}

‎apps/sim/platform/app/(shell)/_components/platform-shell/platform-shell.tsx‎

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import { WindowHistoryButtons } from '@/platform/app/(shell)/_components/platfor
1414
import { useChatChanges } from '@/platform/app/(shell)/_components/session/chat-changes'
1515
import { useSessionShellActions } from '@/platform/app/(shell)/_components/session/shell-actions'
1616
import { SETTINGS_DESTINATIONS } from '@/platform/app/(shell)/_components/settings-navigation/destinations'
17-
import { LEGACY_DESTINATION } from '@/platform/app/(shell)/_components/sidebar/row-links'
1817
import { Sidebar } from '@/platform/app/(shell)/_components/sidebar/sidebar'
1918
import { useSidebarLayout } from '@/platform/app/(shell)/_components/sidebar/sidebar-layout'
2019
import { SidebarToolbar } from '@/platform/app/(shell)/_components/sidebar/sidebar-toolbar'
2120
import { IconButton, OverflowText, ScrollArea, Tooltip } from '@/platform/design-system/components'
2221
import { useCompactViewport } from '@/platform/design-system/components/layout/use-compact-viewport'
2322
import { useScrollEdges } from '@/platform/design-system/components/scroll-area/use-scroll-edges'
24-
import { platformHref } from '@/platform/design-system/navigation'
23+
import { isAbsoluteHref, platformHref } from '@/platform/design-system/navigation'
2524
import { useLiveShellActions } from '@/platform/lib/data/shell-context'
2625

2726
function isEditableTarget(target: EventTarget | null): boolean {
@@ -32,17 +31,6 @@ function isEditableTarget(target: EventTarget | null): boolean {
3231
)
3332
}
3433

35-
/** A focused editor can claim a shortcut through `data-owned-shortcuts`, as on the legacy surface. */
36-
function ownsShortcut(target: EventTarget | null, shortcut: string): boolean {
37-
const owner = target instanceof Element ? target.closest('[data-owned-shortcuts]') : null
38-
return Boolean(
39-
owner
40-
?.getAttribute('data-owned-shortcuts')
41-
?.split(',')
42-
.some((entry) => entry.trim() === shortcut)
43-
)
44-
}
45-
4634
interface PlatformShellProps {
4735
data: ShellData | null
4836
/** A row of its own below any native titlebar, where the window controls stay reachable. */
@@ -127,12 +115,7 @@ export function PlatformShell({ data, banner, children }: PlatformShellProps) {
127115
event.preventDefault()
128116
setCommand((open) => !open && canOpen)
129117
}
130-
if (
131-
key === 'b' &&
132-
canOpen &&
133-
!isEditableTarget(event.target) &&
134-
!ownsShortcut(event.target, 'Mod+B')
135-
) {
118+
if (key === 'b' && canOpen && !isEditableTarget(event.target)) {
136119
event.preventDefault()
137120
toggleOnShortcut()
138121
}
@@ -150,7 +133,7 @@ export function PlatformShell({ data, banner, children }: PlatformShellProps) {
150133
setCommand(false)
151134
setDrawer(false)
152135
actions.requestLeave(() => {
153-
if (LEGACY_DESTINATION.test(path)) window.location.assign(path)
136+
if (isAbsoluteHref(path)) window.location.assign(path)
154137
else router.push(platformHref(path, search))
155138
})
156139
}

‎apps/sim/platform/app/(shell)/_components/sidebar/account-menu.tsx‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface AccountMenuProps {
1111
name: string
1212
image?: string | null
1313
onSettings: () => void
14-
onSignOut?: () => void
14+
onSignOut: () => void
1515
}
1616

1717
export function AccountMenu({ anchor, name, image, onSettings, onSignOut }: AccountMenuProps) {
@@ -42,16 +42,14 @@ export function AccountMenu({ anchor, name, image, onSettings, onSignOut }: Acco
4242
onSettings()
4343
}}
4444
/>
45-
{onSignOut && (
46-
<MenuItem
47-
label='Sign out'
48-
icon='I132'
49-
onSelect={() => {
50-
setOpen(false)
51-
onSignOut()
52-
}}
53-
/>
54-
)}
45+
<MenuItem
46+
label='Sign out'
47+
icon='I132'
48+
onSelect={() => {
49+
setOpen(false)
50+
onSignOut()
51+
}}
52+
/>
5553
</div>
5654
</AnchoredPopover>
5755
)

‎apps/sim/platform/app/(shell)/_components/sidebar/chats-section.tsx‎

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import type {
77
ShellChat,
88
ShellQueryStatus,
99
} from '@/platform/app/(shell)/_components/model'
10-
import {
11-
LEGACY_DESTINATION,
12-
linkMenuActions,
13-
} from '@/platform/app/(shell)/_components/sidebar/row-links'
10+
import { linkMenuActions, readFormText } from '@/platform/app/(shell)/_components/sidebar/row-links'
1411
import { SectionStatus } from '@/platform/app/(shell)/_components/sidebar/section-status'
1512
import styles from '@/platform/app/(shell)/_components/sidebar/sidebar-collections.module.css'
1613
import { useSidebarLayout } from '@/platform/app/(shell)/_components/sidebar/sidebar-layout'
@@ -222,14 +219,15 @@ export function ChatsSection({ chats, status, activeChat, actions, navigate }: C
222219
const href = item.href ?? `/chat/${item.id}`
223220
const rows = rowActions(item)
224221
const isCurrent = activeChat === item.id
222+
const selectOnMenuOpen = (open: boolean) => {
223+
if (open && !selected.has(item.id)) selectOnly(item.id)
224+
}
225225
return (
226226
<ContextActionMenu
227227
key={item.id}
228228
label={`Actions for ${item.title}`}
229229
actions={rows}
230-
onOpenChange={(open) => {
231-
if (open && !selected.has(item.id)) selectOnly(item.id)
232-
}}
230+
onOpenChange={selectOnMenuOpen}
233231
>
234232
<div
235233
className={styles.row}
@@ -240,8 +238,7 @@ export function ChatsSection({ chats, status, activeChat, actions, navigate }: C
240238
selectOnly(item.id)
241239
}}
242240
onPointerEnter={() => {
243-
if (!isCurrent && !LEGACY_DESTINATION.test(href))
244-
cancelPrefetch.current = actions.prefetch?.(item.id) ?? null
241+
if (!isCurrent) cancelPrefetch.current = actions.prefetch?.(item) ?? null
245242
}}
246243
onPointerLeave={() => cancelPrefetch.current?.()}
247244
>
@@ -256,9 +253,7 @@ export function ChatsSection({ chats, status, activeChat, actions, navigate }: C
256253
triggerSize='compact'
257254
align='end'
258255
actions={rows}
259-
onOpenChange={(open) => {
260-
if (open && !selected.has(item.id)) selectOnly(item.id)
261-
}}
256+
onOpenChange={selectOnMenuOpen}
262257
/>
263258
}
264259
>
@@ -278,8 +273,7 @@ export function ChatsSection({ chats, status, activeChat, actions, navigate }: C
278273
title='Rename chat'
279274
confirmLabel='Save'
280275
onConfirm={async (values) => {
281-
const title = values.get('title')
282-
const next = typeof title === 'string' ? title.trim() : ''
276+
const next = readFormText(values, 'title')
283277
if (!next) throw new Error('Enter a chat name.')
284278
if (next !== renamingChat.title) await actions.rename(renamingChat.id, next)
285279
}}
@@ -307,8 +301,7 @@ export function ChatsSection({ chats, status, activeChat, actions, navigate }: C
307301
{removing.length === 1
308302
? `${actions.removeLabel} “${removing[0].title}”?`
309303
: `${actions.removeLabel} ${removing.length} chats?`}
310-
{actions.removeLabel === 'Delete' &&
311-
` You can restore ${removing.length === 1 ? 'it' : 'them'} from Recently deleted in Settings.`}
304+
{actions.removeDetail && ` ${actions.removeDetail(removing.length)}`}
312305
</p>
313306
</Modal>
314307
)}

0 commit comments

Comments
 (0)