Skip to content

Commit 0672041

Browse files
committed
fix(settings): a11y labels, URL-backed member search, and design-system cleanup
1 parent 2272d4c commit 0672041

20 files changed

Lines changed: 178 additions & 133 deletions

File tree

apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function ActivityLogRow({
5252
>
5353
<button
5454
type='button'
55+
aria-expanded={expandable ? expanded : undefined}
5556
className='flex w-full items-center gap-3 px-3 py-2 text-left'
5657
onClick={() => expandable && setExpanded(!expanded)}
5758
disabled={!expandable}

apps/sim/app/workspace/[workspaceId]/settings/components/api-keys/api-keys.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useMemo, useState } from 'react'
4-
import { ChipConfirmModal, Switch, Tooltip, toast } from '@sim/emcn'
4+
import { ChipConfirmModal, Label, Switch, Tooltip, toast } from '@sim/emcn'
55
import { createLogger } from '@sim/logger'
66
import { formatDate } from '@sim/utils/formatting'
77
import { Info, Plus } from 'lucide-react'
@@ -98,6 +98,7 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
9898
const workspaceKeys = apiKeysData?.workspaceKeys ?? EMPTY_KEYS
9999
const personalKeys = apiKeysData?.personalKeys ?? EMPTY_KEYS
100100
const conflicts = apiKeysData?.conflicts ?? EMPTY_KEY_NAMES
101+
const conflictNames = useMemo(() => new Set(conflicts), [conflicts])
101102
const isLoading = isLoadingKeys || (showsWorkspaceKeys && isLoadingSettings)
102103

103104
const allowPersonalApiKeys =
@@ -264,7 +265,7 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
264265
<SettingsSection label='Personal'>
265266
<div className='flex flex-col gap-2'>
266267
{filteredPersonalKeys.map(({ key }) => {
267-
const isConflict = conflicts.includes(key.name)
268+
const isConflict = conflictNames.has(key.name)
268269
return (
269270
<div key={key.id} className='flex flex-col gap-2'>
270271
<div className='flex items-center justify-between gap-3'>
@@ -318,11 +319,12 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
318319
<SettingsSection label='Permissions'>
319320
<div className='flex items-center justify-between'>
320321
<div className='flex items-center gap-2'>
321-
<span className='text-[var(--text-body)] text-sm'>Allow personal API keys</span>
322+
<Label htmlFor='allow-personal-api-keys'>Allow personal API keys</Label>
322323
<Tooltip.Root>
323324
<Tooltip.Trigger asChild>
324325
<button
325326
type='button'
327+
aria-label='About personal API keys'
326328
className='rounded-full p-1 text-[var(--text-muted)] transition hover-hover:text-[var(--text-primary)]'
327329
>
328330
<Info className='size-[12px]' strokeWidth={2} />
@@ -337,6 +339,7 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
337339
</div>
338340
{isLoadingSettings ? null : (
339341
<Switch
342+
id='allow-personal-api-keys'
340343
checked={allowPersonalApiKeys}
341344
disabled={!canManageWorkspaceKeys || updateSettingsMutation.isPending}
342345
onCheckedChange={async (checked) => {

apps/sim/app/workspace/[workspaceId]/settings/components/billing/billing.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ vi.mock('@sim/emcn', () => ({
4343
<a href={href}>{children}</a>
4444
),
4545
Credit: () => <span />,
46+
Label: ({ children, htmlFor }: { children: ReactNode; htmlFor?: string }) => (
47+
<label htmlFor={htmlFor}>{children}</label>
48+
),
4649
Switch: ({
4750
checked,
4851
disabled,

apps/sim/app/workspace/[workspaceId]/settings/components/billing/billing.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Credit,
88
chipVariants,
99
cn,
10+
Label,
1011
Switch,
1112
Tooltip,
1213
toast,
@@ -494,14 +495,17 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
494495
{showOnDemand && (
495496
<SettingsSection label='Enable on-demand usage'>
496497
<div className='flex items-center justify-between'>
497-
<span className='text-[var(--text-body)] text-small'>
498-
Allow usage to go past included usage
499-
</span>
498+
<Label htmlFor='on-demand-usage'>Allow usage to go past included usage</Label>
500499
{onDemandLockedOn ? (
501500
<Tooltip.Root>
502501
<Tooltip.Trigger asChild>
503502
<span className='inline-flex'>
504-
<Switch checked disabled onCheckedChange={handleToggleOnDemand} />
503+
<Switch
504+
id='on-demand-usage'
505+
checked
506+
disabled
507+
onCheckedChange={handleToggleOnDemand}
508+
/>
505509
</span>
506510
</Tooltip.Trigger>
507511
<Tooltip.Content className='max-w-[260px]'>
@@ -514,6 +518,7 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
514518
</Tooltip.Root>
515519
) : (
516520
<Switch
521+
id='on-demand-usage'
517522
checked={isOnDemandActive}
518523
disabled={isTogglingOnDemand || !canManageBilling}
519524
onCheckedChange={handleToggleOnDemand}
@@ -526,10 +531,9 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
526531
{!isOrganizationScope && !subscription.isFree && !subscription.isEnterprise && (
527532
<SettingsSection label='Usage notifications'>
528533
<div className='flex items-center justify-between'>
529-
<span className='text-[var(--text-body)] text-small'>
530-
Email me when I reach 80% usage
531-
</span>
534+
<Label htmlFor='usage-notifications'>Email me when I reach 80% usage</Label>
532535
<Switch
536+
id='usage-notifications'
533537
checked={!!billingUsageNotificationsEnabled}
534538
disabled={updateGeneralSetting.isPending}
535539
onCheckedChange={(value: boolean) => {

apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,10 @@ export function General() {
403403
<SettingsSection label='Preferences'>
404404
<div className='flex flex-col gap-4'>
405405
<div className='flex items-center justify-between'>
406-
<Label htmlFor='theme-select'>Theme</Label>
406+
<Label>Theme</Label>
407407
<div className={DROPDOWN_TRIGGER_CLASS}>
408408
<ChipSelect
409+
aria-label='Theme'
409410
align='start'
410411
fullWidth
411412
dropdownWidth='trigger'
@@ -442,7 +443,13 @@ export function General() {
442443
<Label htmlFor='auto-connect'>Auto-connect on drop</Label>
443444
<Tooltip.Root>
444445
<Tooltip.Trigger asChild>
445-
<Info className='size-[14px] cursor-default text-[var(--text-muted)]' />
446+
<button
447+
type='button'
448+
aria-label='About auto-connect on drop'
449+
className='inline-flex cursor-default text-[var(--text-muted)]'
450+
>
451+
<Info className='size-[14px]' />
452+
</button>
446453
</Tooltip.Trigger>
447454
<Tooltip.Content side='bottom' align='start'>
448455
<p>Automatically connect blocks when dropped near each other</p>
@@ -466,7 +473,13 @@ export function General() {
466473
<Label htmlFor='error-notifications'>Canvas error notifications</Label>
467474
<Tooltip.Root>
468475
<Tooltip.Trigger asChild>
469-
<Info className='size-[14px] cursor-default text-[var(--text-muted)]' />
476+
<button
477+
type='button'
478+
aria-label='About canvas error notifications'
479+
className='inline-flex cursor-default text-[var(--text-muted)]'
480+
>
481+
<Info className='size-[14px]' />
482+
</button>
470483
</Tooltip.Trigger>
471484
<Tooltip.Content side='bottom' align='start'>
472485
<p>Show error popups on blocks when a workflow run fails</p>
@@ -485,9 +498,10 @@ export function General() {
485498
</div>
486499

487500
<div className='flex items-center justify-between'>
488-
<Label htmlFor='snap-to-grid'>Snap to grid</Label>
501+
<Label>Snap to grid</Label>
489502
<div className={DROPDOWN_TRIGGER_CLASS}>
490503
<ChipSelect
504+
aria-label='Snap to grid'
491505
align='start'
492506
fullWidth
493507
dropdownWidth='trigger'

apps/sim/app/workspace/[workspaceId]/settings/components/inbox/components/inbox-settings-tab/inbox-settings-tab.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ChipModalFooter,
1313
ChipModalHeader,
1414
Tooltip,
15+
useCopyToClipboard,
1516
} from '@sim/emcn'
1617
import { getErrorMessage } from '@sim/utils/errors'
1718
import { Check, Clipboard, Pencil, Plus, Trash2 } from 'lucide-react'
@@ -45,15 +46,11 @@ export function InboxSettingsTab() {
4546
const [editAddressError, setEditAddressError] = useState<string | null>(null)
4647

4748
const [removeSenderError, setRemoveSenderError] = useState<string | null>(null)
48-
const [copiedAddress, setCopiedAddress] = useState(false)
49+
const { copied: copiedAddress, copy } = useCopyToClipboard()
4950

5051
const handleCopyAddress = useCallback(() => {
51-
if (config?.address) {
52-
navigator.clipboard.writeText(config.address)
53-
setCopiedAddress(true)
54-
setTimeout(() => setCopiedAddress(false), 2000)
55-
}
56-
}, [config?.address])
52+
if (config?.address) void copy(config.address)
53+
}, [config?.address, copy])
5754

5855
const handleEditAddress = useCallback(async () => {
5956
if (!newUsername.trim()) return
@@ -172,7 +169,7 @@ export function InboxSettingsTab() {
172169
>
173170
<div className='flex items-center gap-2'>
174171
<span className='text-[var(--text-body)] text-sm'>{member.email}</span>
175-
<Badge variant='gray' className='text-xs'>
172+
<Badge variant='gray' size='sm'>
176173
member
177174
</Badge>
178175
</div>

apps/sim/app/workspace/[workspaceId]/settings/components/inbox/components/inbox-task-list/inbox-task-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function InboxTaskList() {
171171
<span className='whitespace-nowrap text-[var(--text-muted)] text-caption'>
172172
{formatRelativeTime(task.createdAt)}
173173
</span>
174-
<Badge variant={statusBadge.variant} className='text-xs'>
174+
<Badge variant={statusBadge.variant} size='sm'>
175175
{task.status === 'processing' && (
176176
<span className='mr-1 inline-block size-[6px] animate-pulse rounded-full bg-[var(--badge-amber-text)]' />
177177
)}

apps/sim/app/workspace/[workspaceId]/settings/components/inbox/inbox.tsx

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,28 @@ export function Inbox() {
3737
)
3838
}
3939
return (
40-
<div className='flex h-full flex-col bg-[var(--bg)]'>
41-
<div className='min-h-0 flex-1 overflow-y-auto px-6 [scrollbar-gutter:stable_both-edges]'>
42-
<div className='mx-auto flex max-w-[48rem] flex-col gap-4.5 pt-6 pb-6'>
43-
<div className='flex flex-col items-center justify-center gap-4 py-20'>
44-
<div className='text-center'>
45-
<h3 className='font-medium text-[var(--text-primary)] text-md'>
46-
Sim Mailer requires an active Max plan
47-
</h3>
48-
<p className='mt-1.5 text-[var(--text-muted)] text-sm'>
49-
Upgrade to Max and ensure billing is active to receive tasks via email and let Sim
50-
work on your behalf.
51-
</p>
52-
</div>
53-
{canAdmin && (
54-
<Chip
55-
variant='primary'
56-
rightIcon={ArrowRight}
57-
onClick={() => navigateToSettings({ section: 'billing' })}
58-
>
59-
Upgrade to Max
60-
</Chip>
61-
)}
62-
</div>
40+
<SettingsPanel>
41+
<div className='flex flex-col items-center justify-center gap-4 py-20'>
42+
<div className='text-center'>
43+
<h3 className='font-medium text-[var(--text-primary)] text-md'>
44+
Sim Mailer requires an active Max plan
45+
</h3>
46+
<p className='mt-1.5 text-[var(--text-muted)] text-sm'>
47+
Upgrade to Max and ensure billing is active to receive tasks via email and let Sim
48+
work on your behalf.
49+
</p>
6350
</div>
51+
{canAdmin && (
52+
<Chip
53+
variant='primary'
54+
rightIcon={ArrowRight}
55+
onClick={() => navigateToSettings({ section: 'billing' })}
56+
>
57+
Upgrade to Max
58+
</Chip>
59+
)}
6460
</div>
65-
</div>
61+
</SettingsPanel>
6662
)
6763
}
6864

apps/sim/app/workspace/[workspaceId]/settings/components/mothership/mothership.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
'use client'
22

33
import { useCallback, useMemo, useState } from 'react'
4-
import { Badge, Button, ChipInput, ChipModalTabs, ChipSelect, Label, Skeleton } from '@sim/emcn'
4+
import {
5+
Badge,
6+
Button,
7+
ChipCopyInput,
8+
ChipInput,
9+
ChipModalTabs,
10+
ChipSelect,
11+
Label,
12+
Skeleton,
13+
} from '@sim/emcn'
514
import { formatDateTime } from '@sim/utils/formatting'
615
import { useQueryStates } from 'nuqs'
716
import { AnthropicIcon, OpenAIIcon } from '@/components/icons'
@@ -377,7 +386,7 @@ function OverviewTab({
377386
}
378387

379388
function LicensesTab({ environment }: { environment: MothershipEnv }) {
380-
const { data, isLoading, refetch } = useMothershipLicenses(environment)
389+
const { data, isLoading } = useMothershipLicenses(environment)
381390
const generateLicense = useGenerateLicense(environment)
382391
const [newName, setNewName] = useState('')
383392
const [newExpiry, setNewExpiry] = useState('')
@@ -395,11 +404,10 @@ function LicensesTab({ environment }: { environment: MothershipEnv }) {
395404
setGeneratedKey(result.license_key)
396405
setNewName('')
397406
setNewExpiry('')
398-
refetch()
399407
},
400408
}
401409
)
402-
}, [newName, newExpiry, generateLicense, refetch])
410+
}, [newName, newExpiry, generateLicense.mutate])
403411

404412
return (
405413
<div className='flex flex-col gap-5'>
@@ -437,13 +445,11 @@ function LicensesTab({ environment }: { environment: MothershipEnv }) {
437445
</div>
438446

439447
{generatedKey && (
440-
<div className='rounded-md border border-[var(--border)] bg-[var(--surface-hover)] p-3'>
441-
<p className='mb-1 text-[var(--text-secondary)] text-caption'>
448+
<div className='flex flex-col gap-1.5'>
449+
<p className='text-[var(--text-secondary)] text-caption'>
442450
License key (only shown once):
443451
</p>
444-
<code className='block break-all font-mono text-[var(--text-primary)] text-caption'>
445-
{generatedKey}
446-
</code>
452+
<ChipCopyInput value={generatedKey} copyLabel='Copy license key' />
447453
</div>
448454
)}
449455

apps/sim/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
2222
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
2323
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
24+
import { useSettingsSearch } from '@/app/workspace/[workspaceId]/settings/components/use-settings-search'
2425
import { useFolders, useRestoreFolder } from '@/hooks/queries/folders'
2526
import { useKnowledgeBasesQuery, useRestoreKnowledgeBase } from '@/hooks/queries/kb/knowledge'
2627
import { useMothershipChats, useRestoreMothershipChat } from '@/hooks/queries/mothership-chats'
@@ -31,7 +32,6 @@ import {
3132
useWorkspaceFileFolders,
3233
} from '@/hooks/queries/workspace-file-folders'
3334
import { useRestoreWorkspaceFile, useWorkspaceFiles } from '@/hooks/queries/workspace-files'
34-
import { useDebouncedSearchSetter } from '@/hooks/use-debounced-search-setter'
3535
import { useUrlSort } from '@/hooks/use-url-sort'
3636
import { useFolderStore } from '@/stores/folders/store'
3737
import type { WorkflowFolder } from '@/stores/folders/types'
@@ -142,7 +142,7 @@ export function RecentlyDeleted() {
142142
const workspaceId = params?.workspaceId as string
143143
const workspacePermissions = useUserPermissionsContext()
144144
const canEdit = canMutateWorkspaceSettingsSection('recently-deleted', workspacePermissions)
145-
const [{ tab: activeTab, search: urlSearchTerm }, setRecentlyDeletedFilters] = useQueryStates(
145+
const [{ tab: activeTab }, setRecentlyDeletedFilters] = useQueryStates(
146146
recentlyDeletedParsers,
147147
recentlyDeletedUrlKeys
148148
)
@@ -160,9 +160,7 @@ export function RecentlyDeleted() {
160160
* write is debounced. Filtering below is cheap in-memory over a small list, so
161161
* it reads the instant value too.
162162
*/
163-
const setSearchTerm = useDebouncedSearchSetter((value, options) =>
164-
setRecentlyDeletedFilters({ search: value }, options)
165-
)
163+
const [urlSearchTerm, setSearchTerm] = useSettingsSearch()
166164

167165
const [restoringIds, setRestoringIds] = useState<Set<string>>(new Set())
168166
const [restoredItems, setRestoredItems] = useState<Map<string, RestoredResourceEntry>>(new Map())

0 commit comments

Comments
 (0)