Skip to content

Commit 1a4da7f

Browse files
committed
fix(autofill): add dummy inputs to prevent browser autofill for various fields, prevent having 0 workflows in workspace
1 parent 094f87f commit 1a4da7f

File tree

12 files changed

+383
-20
lines changed

12 files changed

+383
-20
lines changed

apps/sim/app/api/folders/[id]/route.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,23 @@ export async function DELETE(
141141
)
142142
}
143143

144+
// Check if deleting this folder would delete the last workflow(s) in the workspace
145+
const workflowsInFolder = await countWorkflowsInFolderRecursively(
146+
id,
147+
existingFolder.workspaceId
148+
)
149+
const totalWorkflowsInWorkspace = await db
150+
.select({ id: workflow.id })
151+
.from(workflow)
152+
.where(eq(workflow.workspaceId, existingFolder.workspaceId))
153+
154+
if (workflowsInFolder > 0 && workflowsInFolder >= totalWorkflowsInWorkspace.length) {
155+
return NextResponse.json(
156+
{ error: 'Cannot delete folder containing the only workflow(s) in the workspace' },
157+
{ status: 400 }
158+
)
159+
}
160+
144161
// Recursively delete folder and all its contents
145162
const deletionStats = await deleteFolderRecursively(id, existingFolder.workspaceId)
146163

@@ -202,6 +219,37 @@ async function deleteFolderRecursively(
202219
return stats
203220
}
204221

222+
/**
223+
* Counts the number of workflows in a folder and all its subfolders recursively.
224+
*/
225+
async function countWorkflowsInFolderRecursively(
226+
folderId: string,
227+
workspaceId: string
228+
): Promise<number> {
229+
let count = 0
230+
231+
// Count workflows directly in this folder
232+
const workflowsInFolder = await db
233+
.select({ id: workflow.id })
234+
.from(workflow)
235+
.where(and(eq(workflow.folderId, folderId), eq(workflow.workspaceId, workspaceId)))
236+
237+
count += workflowsInFolder.length
238+
239+
// Get all child folders
240+
const childFolders = await db
241+
.select({ id: workflowFolder.id })
242+
.from(workflowFolder)
243+
.where(and(eq(workflowFolder.parentId, folderId), eq(workflowFolder.workspaceId, workspaceId)))
244+
245+
// Recursively count workflows in child folders
246+
for (const childFolder of childFolders) {
247+
count += await countWorkflowsInFolderRecursively(childFolder.id, workspaceId)
248+
}
249+
250+
return count
251+
}
252+
205253
// Helper function to check for circular references
206254
async function checkForCircularReference(folderId: string, parentId: string): Promise<boolean> {
207255
let currentParentId: string | null = parentId

apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,31 @@ export function CreateBaseModal({
339339
<div ref={scrollContainerRef} className='min-h-0 flex-1 overflow-y-auto'>
340340
<div className='space-y-[12px]'>
341341
<div className='flex flex-col gap-[8px]'>
342-
<Label htmlFor='name'>Name</Label>
342+
<Label htmlFor='kb-name'>Name</Label>
343+
{/* Hidden decoy fields to prevent browser autofill */}
344+
<input
345+
type='text'
346+
name='fakeusernameremembered'
347+
autoComplete='username'
348+
style={{
349+
position: 'absolute',
350+
left: '-9999px',
351+
opacity: 0,
352+
pointerEvents: 'none',
353+
}}
354+
tabIndex={-1}
355+
readOnly
356+
/>
343357
<Input
344-
id='name'
358+
id='kb-name'
345359
placeholder='Enter knowledge base name'
346360
{...register('name')}
347361
className={cn(errors.name && 'border-[var(--text-error)]')}
362+
autoComplete='off'
363+
autoCorrect='off'
364+
autoCapitalize='off'
365+
data-lpignore='true'
366+
data-form-type='other'
348367
/>
349368
</div>
350369

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,20 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
490490
<p className='font-medium text-[13px] text-[var(--text-secondary)]'>
491491
Enter a name for your API key to help you identify it later.
492492
</p>
493+
{/* Hidden decoy fields to prevent browser autofill */}
494+
<input
495+
type='text'
496+
name='fakeusernameremembered'
497+
autoComplete='username'
498+
style={{
499+
position: 'absolute',
500+
left: '-9999px',
501+
opacity: 0,
502+
pointerEvents: 'none',
503+
}}
504+
tabIndex={-1}
505+
readOnly
506+
/>
493507
<EmcnInput
494508
value={newKeyName}
495509
onChange={(e) => {
@@ -499,6 +513,12 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
499513
placeholder='e.g., Development, Production'
500514
className='h-9'
501515
autoFocus
516+
name='api_key_label'
517+
autoComplete='off'
518+
autoCorrect='off'
519+
autoCapitalize='off'
520+
data-lpignore='true'
521+
data-form-type='other'
502522
/>
503523
{createError && (
504524
<p className='text-[11px] text-[var(--text-error)] leading-tight'>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/member-invitation-card/member-invitation-card.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,37 @@ export function MemberInvitationCard({
141141
{/* Main invitation input */}
142142
<div className='flex items-start gap-2'>
143143
<div className='flex-1'>
144+
{/* Hidden decoy fields to prevent browser autofill */}
145+
<input
146+
type='text'
147+
name='fakeusernameremembered'
148+
autoComplete='username'
149+
style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
150+
tabIndex={-1}
151+
readOnly
152+
/>
153+
<input
154+
type='email'
155+
name='fakeemailremembered'
156+
autoComplete='email'
157+
style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
158+
tabIndex={-1}
159+
readOnly
160+
/>
144161
<Input
145162
placeholder='Enter email address'
146163
value={inviteEmail}
147164
onChange={handleEmailChange}
148165
disabled={isInviting || !hasAvailableSeats}
149166
className={cn(emailError && 'border-red-500 focus-visible:ring-red-500')}
167+
name='member_invite_field'
168+
autoComplete='off'
169+
autoCorrect='off'
170+
autoCapitalize='off'
171+
spellCheck={false}
172+
data-lpignore='true'
173+
data-form-type='other'
174+
aria-autocomplete='none'
150175
/>
151176
{emailError && (
152177
<p className='mt-1 text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/no-organization-view/no-organization-view.tsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,31 @@ export function NoOrganizationView({
5555

5656
{/* Form fields - clean layout without card */}
5757
<div className='space-y-4'>
58+
{/* Hidden decoy field to prevent browser autofill */}
59+
<input
60+
type='text'
61+
name='fakeusernameremembered'
62+
autoComplete='username'
63+
style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
64+
tabIndex={-1}
65+
readOnly
66+
/>
5867
<div>
59-
<Label htmlFor='orgName' className='font-medium text-[13px]'>
68+
<Label htmlFor='team-name-field' className='font-medium text-[13px]'>
6069
Team Name
6170
</Label>
6271
<Input
63-
id='orgName'
72+
id='team-name-field'
6473
value={orgName}
6574
onChange={onOrgNameChange}
6675
placeholder='My Team'
6776
className='mt-1'
77+
name='team_name_field'
78+
autoComplete='off'
79+
autoCorrect='off'
80+
autoCapitalize='off'
81+
data-lpignore='true'
82+
data-form-type='other'
6883
/>
6984
</div>
7085

@@ -116,31 +131,52 @@ export function NoOrganizationView({
116131
</ModalHeader>
117132

118133
<div className='space-y-4'>
134+
{/* Hidden decoy field to prevent browser autofill */}
135+
<input
136+
type='text'
137+
name='fakeusernameremembered'
138+
autoComplete='username'
139+
style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
140+
tabIndex={-1}
141+
readOnly
142+
/>
119143
<div>
120-
<Label htmlFor='org-name' className='font-medium text-[13px]'>
144+
<Label htmlFor='org-name-field' className='font-medium text-[13px]'>
121145
Organization Name
122146
</Label>
123147
<Input
124-
id='org-name'
148+
id='org-name-field'
125149
placeholder='Enter organization name'
126150
value={orgName}
127151
onChange={onOrgNameChange}
128152
disabled={isCreatingOrg}
129153
className='mt-1'
154+
name='org_name_field'
155+
autoComplete='off'
156+
autoCorrect='off'
157+
autoCapitalize='off'
158+
data-lpignore='true'
159+
data-form-type='other'
130160
/>
131161
</div>
132162

133163
<div>
134-
<Label htmlFor='org-slug' className='font-medium text-[13px]'>
164+
<Label htmlFor='org-slug-field' className='font-medium text-[13px]'>
135165
Organization Slug
136166
</Label>
137167
<Input
138-
id='org-slug'
168+
id='org-slug-field'
139169
placeholder='organization-slug'
140170
value={orgSlug}
141171
onChange={(e) => setOrgSlug(e.target.value)}
142172
disabled={isCreatingOrg}
143173
className='mt-1'
174+
name='org_slug_field'
175+
autoComplete='off'
176+
autoCorrect='off'
177+
autoCapitalize='off'
178+
data-lpignore='true'
179+
data-form-type='other'
144180
/>
145181
</div>
146182
</div>

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/template-profile/template-profile.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,11 +390,26 @@ export function TemplateProfile() {
390390
disabled={isUploadingProfilePicture}
391391
/>
392392
</div>
393+
{/* Hidden decoy field to prevent browser autofill */}
394+
<input
395+
type='text'
396+
name='fakeusernameremembered'
397+
autoComplete='username'
398+
style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
399+
tabIndex={-1}
400+
readOnly
401+
/>
393402
<Input
394403
placeholder='Name'
395404
value={formData.name}
396405
onChange={(e) => updateField('name', e.target.value)}
397406
className='h-9 flex-1'
407+
name='profile_display_name'
408+
autoComplete='off'
409+
autoCorrect='off'
410+
autoCapitalize='off'
411+
data-lpignore='true'
412+
data-form-type='other'
398413
/>
399414
</div>
400415
{uploadError && <p className='text-[12px] text-[var(--text-error)]'>{uploadError}</p>}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx

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

3-
import { useCallback, useState } from 'react'
3+
import { useCallback, useMemo, useState } from 'react'
44
import clsx from 'clsx'
55
import { ChevronRight, Folder, FolderOpen } from 'lucide-react'
66
import { useParams, useRouter } from 'next/navigation'
@@ -15,7 +15,11 @@ import {
1515
useItemRename,
1616
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
1717
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
18-
import { useDeleteFolder, useDuplicateFolder } from '@/app/workspace/[workspaceId]/w/hooks'
18+
import {
19+
useCanDelete,
20+
useDeleteFolder,
21+
useDuplicateFolder,
22+
} from '@/app/workspace/[workspaceId]/w/hooks'
1923
import { useCreateFolder, useUpdateFolder } from '@/hooks/queries/folders'
2024
import { useCreateWorkflow } from '@/hooks/queries/workflows'
2125
import type { FolderTreeNode } from '@/stores/folders/store'
@@ -52,6 +56,10 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
5256
const createFolderMutation = useCreateFolder()
5357
const userPermissions = useUserPermissionsContext()
5458

59+
// Can delete check hook - memoized to avoid recalculating on every render
60+
const { canDeleteFolder } = useCanDelete({ workspaceId })
61+
const canDelete = useMemo(() => canDeleteFolder(folder.id), [canDeleteFolder, folder.id])
62+
5563
// Delete modal state
5664
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
5765

@@ -316,7 +324,7 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
316324
disableCreate={!userPermissions.canEdit || createWorkflowMutation.isPending}
317325
disableCreateFolder={!userPermissions.canEdit || createFolderMutation.isPending}
318326
disableDuplicate={!userPermissions.canEdit}
319-
disableDelete={!userPermissions.canEdit}
327+
disableDelete={!userPermissions.canEdit || !canDelete}
320328
/>
321329

322330
{/* Delete Modal */}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
useItemRename,
1515
} from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
1616
import {
17+
useCanDelete,
1718
useDeleteWorkflow,
1819
useDuplicateWorkflow,
1920
useExportWorkflow,
@@ -44,10 +45,14 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
4445
const userPermissions = useUserPermissionsContext()
4546
const isSelected = selectedWorkflows.has(workflow.id)
4647

48+
// Can delete check hook
49+
const { canDeleteWorkflows } = useCanDelete({ workspaceId })
50+
4751
// Delete modal state
4852
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
4953
const [workflowIdsToDelete, setWorkflowIdsToDelete] = useState<string[]>([])
5054
const [deleteModalNames, setDeleteModalNames] = useState<string | string[]>('')
55+
const [canDeleteCaptured, setCanDeleteCaptured] = useState(true)
5156

5257
// Presence avatars state
5358
const [hasAvatars, setHasAvatars] = useState(false)
@@ -172,10 +177,13 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
172177
workflowNames: workflowNames.length > 1 ? workflowNames : workflowNames[0],
173178
}
174179

180+
// Check if the captured selection can be deleted
181+
setCanDeleteCaptured(canDeleteWorkflows(workflowIds))
182+
175183
// If already selected with multiple selections, keep all selections
176184
handleContextMenuBase(e)
177185
},
178-
[workflow.id, workflows, handleContextMenuBase]
186+
[workflow.id, workflows, handleContextMenuBase, canDeleteWorkflows]
179187
)
180188

181189
// Rename hook
@@ -319,7 +327,7 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf
319327
disableRename={!userPermissions.canEdit}
320328
disableDuplicate={!userPermissions.canEdit}
321329
disableExport={!userPermissions.canEdit}
322-
disableDelete={!userPermissions.canEdit}
330+
disableDelete={!userPermissions.canEdit || !canDeleteCaptured}
323331
/>
324332

325333
{/* Delete Confirmation Modal */}

0 commit comments

Comments
 (0)