Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function ActivityLogRow({
>
<button
type='button'
aria-expanded={expandable ? expanded : undefined}
className='flex w-full items-center gap-3 px-3 py-2 text-left'
onClick={() => expandable && setExpanded(!expanded)}
disabled={!expandable}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

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

const allowPersonalApiKeys =
Expand Down Expand Up @@ -264,7 +265,7 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
<SettingsSection label='Personal'>
<div className='flex flex-col gap-2'>
{filteredPersonalKeys.map(({ key }) => {
const isConflict = conflicts.includes(key.name)
const isConflict = conflictNames.has(key.name)
return (
<div key={key.id} className='flex flex-col gap-2'>
<div className='flex items-center justify-between gap-3'>
Expand Down Expand Up @@ -318,11 +319,12 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
<SettingsSection label='Permissions'>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-2'>
<span className='text-[var(--text-body)] text-sm'>Allow personal API keys</span>
<Label htmlFor='allow-personal-api-keys'>Allow personal API keys</Label>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button
type='button'
aria-label='About personal API keys'
className='rounded-full p-1 text-[var(--text-muted)] transition hover-hover:text-[var(--text-primary)]'
>
<Info className='size-[12px]' strokeWidth={2} />
Expand All @@ -337,6 +339,7 @@ export function ApiKeys({ scope = 'workspace' }: ApiKeysProps) {
</div>
{isLoadingSettings ? null : (
<Switch
id='allow-personal-api-keys'
checked={allowPersonalApiKeys}
disabled={!canManageWorkspaceKeys || updateSettingsMutation.isPending}
onCheckedChange={async (checked) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ vi.mock('@sim/emcn', () => ({
<a href={href}>{children}</a>
),
Credit: () => <span />,
Label: ({ children, htmlFor }: { children: ReactNode; htmlFor?: string }) => (
<label htmlFor={htmlFor}>{children}</label>
),
Switch: ({
checked,
disabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Credit,
chipVariants,
cn,
Label,
Switch,
Tooltip,
toast,
Expand Down Expand Up @@ -494,14 +495,17 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
{showOnDemand && (
<SettingsSection label='Enable on-demand usage'>
<div className='flex items-center justify-between'>
<span className='text-[var(--text-body)] text-small'>
Allow usage to go past included usage
</span>
<Label htmlFor='on-demand-usage'>Allow usage to go past included usage</Label>
{onDemandLockedOn ? (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<span className='inline-flex'>
<Switch checked disabled onCheckedChange={handleToggleOnDemand} />
<Switch
id='on-demand-usage'
checked
disabled
onCheckedChange={handleToggleOnDemand}
/>
</span>
</Tooltip.Trigger>
<Tooltip.Content className='max-w-[260px]'>
Expand All @@ -514,6 +518,7 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
</Tooltip.Root>
) : (
<Switch
id='on-demand-usage'
checked={isOnDemandActive}
disabled={isTogglingOnDemand || !canManageBilling}
onCheckedChange={handleToggleOnDemand}
Expand All @@ -526,10 +531,9 @@ export function Billing({ scope, organizationId, creditUsageHref }: BillingProps
{!isOrganizationScope && !subscription.isFree && !subscription.isEnterprise && (
<SettingsSection label='Usage notifications'>
<div className='flex items-center justify-between'>
<span className='text-[var(--text-body)] text-small'>
Email me when I reach 80% usage
</span>
<Label htmlFor='usage-notifications'>Email me when I reach 80% usage</Label>
<Switch
id='usage-notifications'
checked={!!billingUsageNotificationsEnabled}
disabled={updateGeneralSetting.isPending}
onCheckedChange={(value: boolean) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,10 @@ export function General() {
<SettingsSection label='Preferences'>
<div className='flex flex-col gap-4'>
<div className='flex items-center justify-between'>
<Label htmlFor='theme-select'>Theme</Label>
<Label>Theme</Label>
<div className={DROPDOWN_TRIGGER_CLASS}>
<ChipSelect
aria-label='Theme'
align='start'
fullWidth
dropdownWidth='trigger'
Expand Down Expand Up @@ -442,7 +443,13 @@ export function General() {
<Label htmlFor='auto-connect'>Auto-connect on drop</Label>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Info className='size-[14px] cursor-default text-[var(--text-muted)]' />
<button
type='button'
aria-label='About auto-connect on drop'
className='inline-flex cursor-default text-[var(--text-muted)]'
>
<Info className='size-[14px]' />
</button>
</Tooltip.Trigger>
<Tooltip.Content side='bottom' align='start'>
<p>Automatically connect blocks when dropped near each other</p>
Expand All @@ -466,7 +473,13 @@ export function General() {
<Label htmlFor='error-notifications'>Canvas error notifications</Label>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Info className='size-[14px] cursor-default text-[var(--text-muted)]' />
<button
type='button'
aria-label='About canvas error notifications'
className='inline-flex cursor-default text-[var(--text-muted)]'
>
<Info className='size-[14px]' />
</button>
</Tooltip.Trigger>
<Tooltip.Content side='bottom' align='start'>
<p>Show error popups on blocks when a workflow run fails</p>
Expand All @@ -485,9 +498,10 @@ export function General() {
</div>

<div className='flex items-center justify-between'>
<Label htmlFor='snap-to-grid'>Snap to grid</Label>
<Label>Snap to grid</Label>
<div className={DROPDOWN_TRIGGER_CLASS}>
<ChipSelect
aria-label='Snap to grid'
align='start'
fullWidth
dropdownWidth='trigger'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ChipModalFooter,
ChipModalHeader,
Tooltip,
useCopyToClipboard,
} from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { Check, Clipboard, Pencil, Plus, Trash2 } from 'lucide-react'
Expand Down Expand Up @@ -45,15 +46,11 @@ export function InboxSettingsTab() {
const [editAddressError, setEditAddressError] = useState<string | null>(null)

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

const handleCopyAddress = useCallback(() => {
if (config?.address) {
navigator.clipboard.writeText(config.address)
setCopiedAddress(true)
setTimeout(() => setCopiedAddress(false), 2000)
}
}, [config?.address])
if (config?.address) void copy(config.address)
}, [config?.address, copy])

const handleEditAddress = useCallback(async () => {
if (!newUsername.trim()) return
Expand Down Expand Up @@ -172,7 +169,7 @@ export function InboxSettingsTab() {
>
<div className='flex items-center gap-2'>
<span className='text-[var(--text-body)] text-sm'>{member.email}</span>
<Badge variant='gray' className='text-xs'>
<Badge variant='gray' size='sm'>
member
</Badge>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export function InboxTaskList() {
<span className='whitespace-nowrap text-[var(--text-muted)] text-caption'>
{formatRelativeTime(task.createdAt)}
</span>
<Badge variant={statusBadge.variant} className='text-xs'>
<Badge variant={statusBadge.variant} size='sm'>
{task.status === 'processing' && (
<span className='mr-1 inline-block size-[6px] animate-pulse rounded-full bg-[var(--badge-amber-text)]' />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,28 @@ export function Inbox() {
)
}
return (
<div className='flex h-full flex-col bg-[var(--bg)]'>
<div className='min-h-0 flex-1 overflow-y-auto px-6 [scrollbar-gutter:stable_both-edges]'>
<div className='mx-auto flex max-w-[48rem] flex-col gap-4.5 pt-6 pb-6'>
<div className='flex flex-col items-center justify-center gap-4 py-20'>
<div className='text-center'>
<h3 className='font-medium text-[var(--text-primary)] text-md'>
Sim Mailer requires an active Max plan
</h3>
<p className='mt-1.5 text-[var(--text-muted)] text-sm'>
Upgrade to Max and ensure billing is active to receive tasks via email and let Sim
work on your behalf.
</p>
</div>
{canAdmin && (
<Chip
variant='primary'
rightIcon={ArrowRight}
onClick={() => navigateToSettings({ section: 'billing' })}
>
Upgrade to Max
</Chip>
)}
</div>
<SettingsPanel>
<div className='flex flex-col items-center justify-center gap-4 py-20'>
<div className='text-center'>
<h3 className='font-medium text-[var(--text-primary)] text-md'>
Sim Mailer requires an active Max plan
</h3>
<p className='mt-1.5 text-[var(--text-muted)] text-sm'>
Upgrade to Max and ensure billing is active to receive tasks via email and let Sim
work on your behalf.
</p>
</div>
{canAdmin && (
<Chip
variant='primary'
rightIcon={ArrowRight}
onClick={() => navigateToSettings({ section: 'billing' })}
>
Upgrade to Max
</Chip>
)}
</div>
</div>
</SettingsPanel>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
'use client'

import { useCallback, useMemo, useState } from 'react'
import { Badge, Button, ChipInput, ChipModalTabs, ChipSelect, Label, Skeleton } from '@sim/emcn'
import {
Badge,
Button,
ChipCopyInput,
ChipInput,
ChipModalTabs,
ChipSelect,
Label,
Skeleton,
} from '@sim/emcn'
import { formatDateTime } from '@sim/utils/formatting'
import { useQueryStates } from 'nuqs'
import { AnthropicIcon, OpenAIIcon } from '@/components/icons'
Expand Down Expand Up @@ -377,7 +386,7 @@ function OverviewTab({
}

function LicensesTab({ environment }: { environment: MothershipEnv }) {
const { data, isLoading, refetch } = useMothershipLicenses(environment)
const { data, isLoading } = useMothershipLicenses(environment)
const generateLicense = useGenerateLicense(environment)
const [newName, setNewName] = useState('')
const [newExpiry, setNewExpiry] = useState('')
Expand All @@ -395,11 +404,10 @@ function LicensesTab({ environment }: { environment: MothershipEnv }) {
setGeneratedKey(result.license_key)
setNewName('')
setNewExpiry('')
refetch()
},
}
)
}, [newName, newExpiry, generateLicense, refetch])
}, [newName, newExpiry, generateLicense.mutate])

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

{generatedKey && (
<div className='rounded-md border border-[var(--border)] bg-[var(--surface-hover)] p-3'>
<p className='mb-1 text-[var(--text-secondary)] text-caption'>
<div className='flex flex-col gap-1.5'>
<p className='text-[var(--text-secondary)] text-caption'>
License key (only shown once):
</p>
<code className='block break-all font-mono text-[var(--text-primary)] text-caption'>
{generatedKey}
</code>
<ChipCopyInput value={generatedKey} copyLabel='Copy license key' />
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
import { useSettingsSearch } from '@/app/workspace/[workspaceId]/settings/components/use-settings-search'
import { useFolders, useRestoreFolder } from '@/hooks/queries/folders'
import { useKnowledgeBasesQuery, useRestoreKnowledgeBase } from '@/hooks/queries/kb/knowledge'
import { useMothershipChats, useRestoreMothershipChat } from '@/hooks/queries/mothership-chats'
Expand All @@ -31,7 +32,6 @@ import {
useWorkspaceFileFolders,
} from '@/hooks/queries/workspace-file-folders'
import { useRestoreWorkspaceFile, useWorkspaceFiles } from '@/hooks/queries/workspace-files'
import { useDebouncedSearchSetter } from '@/hooks/use-debounced-search-setter'
import { useUrlSort } from '@/hooks/use-url-sort'
import { useFolderStore } from '@/stores/folders/store'
import type { WorkflowFolder } from '@/stores/folders/types'
Expand Down Expand Up @@ -142,7 +142,7 @@ export function RecentlyDeleted() {
const workspaceId = params?.workspaceId as string
const workspacePermissions = useUserPermissionsContext()
const canEdit = canMutateWorkspaceSettingsSection('recently-deleted', workspacePermissions)
const [{ tab: activeTab, search: urlSearchTerm }, setRecentlyDeletedFilters] = useQueryStates(
const [{ tab: activeTab }, setRecentlyDeletedFilters] = useQueryStates(
recentlyDeletedParsers,
recentlyDeletedUrlKeys
)
Expand All @@ -160,9 +160,7 @@ export function RecentlyDeleted() {
* write is debounced. Filtering below is cheap in-memory over a small list, so
* it reads the instant value too.
*/
const setSearchTerm = useDebouncedSearchSetter((value, options) =>
setRecentlyDeletedFilters({ search: value }, options)
)
const [urlSearchTerm, setSearchTerm] = useSettingsSearch()

const [restoringIds, setRestoringIds] = useState<Set<string>>(new Set())
const [restoredItems, setRestoredItems] = useState<Map<string, RestoredResourceEntry>>(new Map())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseAsString, parseAsStringLiteral } from 'nuqs/server'
import { parseAsStringLiteral } from 'nuqs/server'
import { createSortParams } from '@/lib/url-state'

/** Selectable resource-type tabs in the Recently Deleted view. */
Expand Down Expand Up @@ -34,12 +34,12 @@ export const recentlyDeletedSortParams = createSortParams(RECENTLY_DELETED_SORT_
* - `tab` is the active resource-type filter.
* - `sort` / `dir` live in {@link recentlyDeletedSortParams} (shared sort
* convention).
* - `search` is the name filter. The input is controlled directly by the nuqs
* value; only its URL write is debounced via `useDebouncedSearchSetter`.
* - The name filter is the settings-wide `?search=` key, owned by
* `settingsSearchParam` and consumed through `useSettingsSearch` — it is
* deliberately not redeclared here (two definitions of one wire key drift).
*/
export const recentlyDeletedParsers = {
tab: parseAsStringLiteral(RECENTLY_DELETED_TABS).withDefault('all'),
search: parseAsString.withDefault(''),
} as const

/** Tab/filter/sort view-state: clean URLs, no back-stack churn. */
Expand Down
Loading
Loading