From 605305071818f52b5c6ca4a56d71e62aa0d71330 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Sat, 14 Feb 2026 11:44:28 -0800 Subject: [PATCH] remove unused code --- .../components/api-keys/api-keys.tsx | 9 +- .../components/credentials/credentials.tsx | 2 - .../components/environment/environment.tsx | 864 ------------------ .../settings-modal/components/index.ts | 2 - .../components/integrations/integrations.tsx | 417 --------- .../settings-modal/settings-modal.tsx | 61 +- apps/sim/stores/modals/settings/types.ts | 2 - 7 files changed, 5 insertions(+), 1352 deletions(-) delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/environment/environment.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/integrations/integrations.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx index 8e3862ecc..cc7e92549 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx @@ -31,10 +31,9 @@ const logger = createLogger('ApiKeys') interface ApiKeysProps { onOpenChange?: (open: boolean) => void - registerCloseHandler?: (handler: (open: boolean) => void) => void } -export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { +export function ApiKeys({ onOpenChange }: ApiKeysProps) { const { data: session } = useSession() const userId = session?.user?.id const params = useParams() @@ -118,12 +117,6 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { onOpenChange?.(open) } - useEffect(() => { - if (registerCloseHandler) { - registerCloseHandler(handleModalClose) - } - }, [registerCloseHandler]) - useEffect(() => { if (shouldScrollToBottom && scrollContainerRef.current) { scrollContainerRef.current.scrollTo({ diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx index da5076821..14c83662a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/credentials/credentials.tsx @@ -4,8 +4,6 @@ import { CredentialsManager } from '@/app/workspace/[workspaceId]/w/components/s interface CredentialsProps { onOpenChange?: (open: boolean) => void - registerCloseHandler?: (handler: (open: boolean) => void) => void - registerBeforeLeaveHandler?: (handler: (onProceed: () => void) => void) => void } export function Credentials(_props: CredentialsProps) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/environment/environment.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/environment/environment.tsx deleted file mode 100644 index eb9081500..000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/environment/environment.tsx +++ /dev/null @@ -1,864 +0,0 @@ -'use client' - -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { createLogger } from '@sim/logger' -import { Plus, Search, Share2, Undo2 } from 'lucide-react' -import { useParams } from 'next/navigation' -import { - Button, - Input as EmcnInput, - Modal, - ModalBody, - ModalContent, - ModalFooter, - ModalHeader, - Tooltip, -} from '@/components/emcn' -import { Trash } from '@/components/emcn/icons/trash' -import { Input, Skeleton } from '@/components/ui' -import { isValidEnvVarName } from '@/executor/constants' -import { - usePersonalEnvironment, - useRemoveWorkspaceEnvironment, - useSavePersonalEnvironment, - useUpsertWorkspaceEnvironment, - useWorkspaceEnvironment, - type WorkspaceEnvironmentData, -} from '@/hooks/queries/environment' - -const logger = createLogger('EnvironmentVariables') - -const GRID_COLS = 'grid grid-cols-[minmax(0,1fr)_8px_minmax(0,1fr)_auto] items-center' - -const generateRowId = (() => { - let counter = 0 - return () => { - counter += 1 - return Date.now() + counter - } -})() - -const createEmptyEnvVar = (): UIEnvironmentVariable => ({ - key: '', - value: '', - id: generateRowId(), -}) - -interface UIEnvironmentVariable { - key: string - value: string - id?: number -} - -/** - * Validates an environment variable key. - * Returns an error message if invalid, undefined if valid. - */ -function validateEnvVarKey(key: string): string | undefined { - if (!key) return undefined - if (key.includes(' ')) return 'Spaces are not allowed' - if (!isValidEnvVarName(key)) return 'Only letters, numbers, and underscores allowed' - return undefined -} - -interface EnvironmentVariablesProps { - registerBeforeLeaveHandler?: (handler: (onProceed: () => void) => void) => void -} - -interface WorkspaceVariableRowProps { - envKey: string - value: string - renamingKey: string | null - pendingKeyValue: string - isNewlyPromoted: boolean - onRenameStart: (key: string) => void - onPendingKeyChange: (value: string) => void - onRenameEnd: (key: string, value: string) => void - onDelete: (key: string) => void - onDemote: (key: string, value: string) => void -} - -function WorkspaceVariableRow({ - envKey, - value, - renamingKey, - pendingKeyValue, - isNewlyPromoted, - onRenameStart, - onPendingKeyChange, - onRenameEnd, - onDelete, - onDemote, -}: WorkspaceVariableRowProps) { - return ( -
- { - if (renamingKey !== envKey) onRenameStart(envKey) - onPendingKeyChange(e.target.value) - }} - onBlur={() => onRenameEnd(envKey, value)} - name={`workspace_env_key_${envKey}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly - onFocus={(e) => e.target.removeAttribute('readOnly')} - className='h-9' - /> -
- -
- {isNewlyPromoted && ( - - - - - Change to personal scope - - )} - - - - - Delete secret - -
-
- ) -} - -export function EnvironmentVariables({ registerBeforeLeaveHandler }: EnvironmentVariablesProps) { - const params = useParams() - const workspaceId = (params?.workspaceId as string) || '' - - const { data: personalEnvData, isLoading: isPersonalLoading } = usePersonalEnvironment() - const { data: workspaceEnvData, isLoading: isWorkspaceLoading } = useWorkspaceEnvironment( - workspaceId, - { - select: useCallback( - (data: WorkspaceEnvironmentData): WorkspaceEnvironmentData => ({ - workspace: data.workspace || {}, - personal: data.personal || {}, - conflicts: data.conflicts || [], - }), - [] - ), - } - ) - const savePersonalMutation = useSavePersonalEnvironment() - const upsertWorkspaceMutation = useUpsertWorkspaceEnvironment() - const removeWorkspaceMutation = useRemoveWorkspaceEnvironment() - - const isLoading = isPersonalLoading || isWorkspaceLoading - const variables = useMemo(() => personalEnvData || {}, [personalEnvData]) - - const [envVars, setEnvVars] = useState([]) - const [searchTerm, setSearchTerm] = useState('') - const [focusedValueIndex, setFocusedValueIndex] = useState(null) - const [showUnsavedChanges, setShowUnsavedChanges] = useState(false) - const [shouldScrollToBottom, setShouldScrollToBottom] = useState(false) - const [workspaceVars, setWorkspaceVars] = useState>({}) - const [conflicts, setConflicts] = useState([]) - const [renamingKey, setRenamingKey] = useState(null) - const [pendingKeyValue, setPendingKeyValue] = useState('') - const [changeToken, setChangeToken] = useState(0) - - const initialWorkspaceVarsRef = useRef>({}) - const scrollContainerRef = useRef(null) - const pendingProceedCallback = useRef<(() => void) | null>(null) - const initialVarsRef = useRef([]) - const hasChangesRef = useRef(false) - const hasSavedRef = useRef(false) - - const filteredEnvVars = useMemo(() => { - const mapped = envVars.map((envVar, index) => ({ envVar, originalIndex: index })) - if (!searchTerm.trim()) return mapped - const term = searchTerm.toLowerCase() - return mapped.filter(({ envVar }) => envVar.key.toLowerCase().includes(term)) - }, [envVars, searchTerm]) - - const filteredWorkspaceEntries = useMemo(() => { - const entries = Object.entries(workspaceVars) - if (!searchTerm.trim()) return entries - const term = searchTerm.toLowerCase() - return entries.filter(([key]) => key.toLowerCase().includes(term)) - }, [workspaceVars, searchTerm]) - - const hasChanges = useMemo(() => { - const initialVars = initialVarsRef.current.filter((v) => v.key || v.value) - const currentVars = envVars.filter((v) => v.key || v.value) - const initialMap = new Map(initialVars.map((v) => [v.key, v.value])) - const currentMap = new Map(currentVars.map((v) => [v.key, v.value])) - - if (initialMap.size !== currentMap.size) return true - - for (const [key, value] of currentMap) { - if (initialMap.get(key) !== value) return true - } - - for (const key of initialMap.keys()) { - if (!currentMap.has(key)) return true - } - - const before = initialWorkspaceVarsRef.current - const after = workspaceVars - const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]) - - if (Object.keys(before).length !== Object.keys(after).length) return true - - for (const key of allKeys) { - if (before[key] !== after[key]) return true - } - - return false - }, [envVars, workspaceVars, changeToken]) - - const hasConflicts = useMemo(() => { - return envVars.some((envVar) => !!envVar.key && Object.hasOwn(workspaceVars, envVar.key)) - }, [envVars, workspaceVars]) - - const hasInvalidKeys = useMemo(() => { - return envVars.some((envVar) => !!envVar.key && validateEnvVarKey(envVar.key)) - }, [envVars]) - - useEffect(() => { - hasChangesRef.current = hasChanges - }, [hasChanges]) - - const handleBeforeLeave = useCallback((onProceed: () => void) => { - if (hasChangesRef.current) { - setShowUnsavedChanges(true) - pendingProceedCallback.current = onProceed - } else { - onProceed() - } - }, []) - - useEffect(() => { - if (hasSavedRef.current) return - - const existingVars = Object.values(variables) - const initialVars = existingVars.length - ? existingVars.map((envVar) => ({ - ...envVar, - id: generateRowId(), - })) - : [createEmptyEnvVar()] - initialVarsRef.current = JSON.parse(JSON.stringify(initialVars)) - setEnvVars(JSON.parse(JSON.stringify(initialVars))) - pendingProceedCallback.current = null - }, [variables]) - - useEffect(() => { - if (workspaceEnvData) { - if (hasSavedRef.current) { - setConflicts(workspaceEnvData?.conflicts || []) - hasSavedRef.current = false - } else { - setWorkspaceVars(workspaceEnvData?.workspace || {}) - initialWorkspaceVarsRef.current = workspaceEnvData?.workspace || {} - setConflicts(workspaceEnvData?.conflicts || []) - } - } - }, [workspaceEnvData]) - - useEffect(() => { - if (registerBeforeLeaveHandler) { - registerBeforeLeaveHandler(handleBeforeLeave) - } - }, [registerBeforeLeaveHandler, handleBeforeLeave]) - - useEffect(() => { - if (shouldScrollToBottom && scrollContainerRef.current) { - scrollContainerRef.current.scrollTo({ - top: scrollContainerRef.current.scrollHeight, - behavior: 'smooth', - }) - setShouldScrollToBottom(false) - } - }, [shouldScrollToBottom]) - - useEffect(() => { - const personalKeys = envVars.map((envVar) => envVar.key.trim()).filter((key) => key.length > 0) - - const uniquePersonalKeys = Array.from(new Set(personalKeys)) - - const computedConflicts = uniquePersonalKeys.filter((key) => Object.hasOwn(workspaceVars, key)) - - setConflicts((prev) => { - if (prev.length === computedConflicts.length) { - const sameKeys = prev.every((key) => computedConflicts.includes(key)) - if (sameKeys) return prev - } - return computedConflicts - }) - }, [envVars, workspaceVars]) - - const handleWorkspaceKeyRename = useCallback( - (currentKey: string, currentValue: string) => { - const newKey = pendingKeyValue.trim() - if (!renamingKey || renamingKey !== currentKey) return - setRenamingKey(null) - if (!newKey || newKey === currentKey) return - - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[currentKey] - next[newKey] = currentValue - return next - }) - }, - [pendingKeyValue, renamingKey] - ) - - const handleDeleteWorkspaceVar = useCallback((key: string) => { - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[key] - return next - }) - }, []) - - const addEnvVar = useCallback(() => { - setEnvVars((prev) => [...prev, createEmptyEnvVar()]) - setSearchTerm('') - setShouldScrollToBottom(true) - }, []) - - const updateEnvVar = useCallback((index: number, field: 'key' | 'value', value: string) => { - setEnvVars((prev) => { - const newEnvVars = [...prev] - newEnvVars[index][field] = value - return newEnvVars - }) - }, []) - - const removeEnvVar = useCallback((index: number) => { - setEnvVars((prev) => { - const newEnvVars = prev.filter((_, i) => i !== index) - return newEnvVars.length ? newEnvVars : [createEmptyEnvVar()] - }) - }, []) - - const handleValueFocus = useCallback((index: number, e: React.FocusEvent) => { - setFocusedValueIndex(index) - e.target.scrollLeft = 0 - }, []) - - const handleValueClick = useCallback((e: React.MouseEvent) => { - e.preventDefault() - e.currentTarget.scrollLeft = 0 - }, []) - - const parseEnvVarLine = useCallback((line: string): UIEnvironmentVariable | null => { - const trimmed = line.trim() - - if (!trimmed || trimmed.startsWith('#')) return null - - const withoutExport = trimmed.replace(/^export\s+/, '') - - const equalIndex = withoutExport.indexOf('=') - if (equalIndex === -1 || equalIndex === 0) return null - - const potentialKey = withoutExport.substring(0, equalIndex).trim() - if (!isValidEnvVarName(potentialKey)) return null - - let value = withoutExport.substring(equalIndex + 1) - - const looksLikeBase64Key = /^[A-Za-z0-9+/]+$/.test(potentialKey) && !potentialKey.includes('_') - const valueIsJustPadding = /^=+$/.test(value.trim()) - if (looksLikeBase64Key && valueIsJustPadding && potentialKey.length > 20) { - return null - } - - const trimmedValue = value.trim() - if ( - !trimmedValue.startsWith('"') && - !trimmedValue.startsWith("'") && - !trimmedValue.startsWith('`') - ) { - const commentIndex = value.search(/\s#/) - if (commentIndex !== -1) { - value = value.substring(0, commentIndex) - } - } - - value = value.trim() - - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) || - (value.startsWith('`') && value.endsWith('`')) - ) { - value = value.slice(1, -1) - } - - return { key: potentialKey, value, id: generateRowId() } - }, []) - - const handleSingleValuePaste = useCallback( - (text: string, index: number, inputType: 'key' | 'value') => { - setEnvVars((prev) => { - const newEnvVars = [...prev] - newEnvVars[index][inputType] = text - return newEnvVars - }) - }, - [] - ) - - const handleKeyValuePaste = useCallback( - (lines: string[]) => { - const parsedVars = lines - .map(parseEnvVarLine) - .filter((parsed): parsed is UIEnvironmentVariable => parsed !== null) - .filter(({ key, value }) => key && value) - - if (parsedVars.length > 0) { - setEnvVars((prev) => { - const existingVars = prev.filter((v) => v.key || v.value) - return [...existingVars, ...parsedVars] - }) - setShouldScrollToBottom(true) - } - }, - [parseEnvVarLine] - ) - - const handlePaste = useCallback( - (e: React.ClipboardEvent, index: number) => { - const text = e.clipboardData.getData('text').trim() - if (!text) return - - const lines = text.split('\n').filter((line) => line.trim()) - if (lines.length === 0) return - - e.preventDefault() - - const inputType = (e.target as HTMLInputElement).getAttribute('data-input-type') as - | 'key' - | 'value' - - if (inputType) { - const hasValidEnvVarPattern = lines.some((line) => parseEnvVarLine(line) !== null) - if (!hasValidEnvVarPattern) { - handleSingleValuePaste(text, index, inputType) - return - } - } - - handleKeyValuePaste(lines) - }, - [parseEnvVarLine, handleSingleValuePaste, handleKeyValuePaste] - ) - - const handleCancel = useCallback(() => { - setEnvVars(JSON.parse(JSON.stringify(initialVarsRef.current))) - setWorkspaceVars({ ...initialWorkspaceVarsRef.current }) - setShowUnsavedChanges(false) - - pendingProceedCallback.current?.() - pendingProceedCallback.current = null - }, []) - - const handleSave = useCallback(async () => { - const onProceed = pendingProceedCallback.current - - const prevInitialVars = [...initialVarsRef.current] - const prevInitialWorkspaceVars = { ...initialWorkspaceVarsRef.current } - - try { - setShowUnsavedChanges(false) - hasSavedRef.current = true - - initialWorkspaceVarsRef.current = { ...workspaceVars } - initialVarsRef.current = JSON.parse(JSON.stringify(envVars.filter((v) => v.key && v.value))) - - setChangeToken((prev) => prev + 1) - - const validVariables = envVars - .filter((v) => v.key && v.value) - .reduce>((acc, { key, value }) => ({ ...acc, [key]: value }), {}) - - await savePersonalMutation.mutateAsync({ variables: validVariables }) - - const before = prevInitialWorkspaceVars - const after = workspaceVars - const toUpsert: Record = {} - const toDelete: string[] = [] - - for (const [k, v] of Object.entries(after)) { - if (!(k in before) || before[k] !== v) { - toUpsert[k] = v - } - } - - for (const k of Object.keys(before)) { - if (!(k in after)) toDelete.push(k) - } - - if (workspaceId) { - if (Object.keys(toUpsert).length) { - await upsertWorkspaceMutation.mutateAsync({ workspaceId, variables: toUpsert }) - } - if (toDelete.length) { - await removeWorkspaceMutation.mutateAsync({ workspaceId, keys: toDelete }) - } - } - - onProceed?.() - pendingProceedCallback.current = null - } catch (error) { - hasSavedRef.current = false - initialVarsRef.current = prevInitialVars - initialWorkspaceVarsRef.current = prevInitialWorkspaceVars - logger.error('Failed to save environment variables:', error) - } - }, [ - envVars, - workspaceVars, - workspaceId, - savePersonalMutation, - upsertWorkspaceMutation, - removeWorkspaceMutation, - ]) - - const promoteToWorkspace = useCallback( - (envVar: UIEnvironmentVariable) => { - if (!envVar.key || !envVar.value || !workspaceId) return - setWorkspaceVars((prev) => ({ ...prev, [envVar.key]: envVar.value })) - setEnvVars((prev) => { - const filtered = prev.filter((entry) => entry !== envVar) - return filtered.length ? filtered : [createEmptyEnvVar()] - }) - }, - [workspaceId] - ) - - const demoteToPersonal = useCallback((key: string, value: string) => { - if (!key) return - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[key] - return next - }) - setEnvVars((prev) => [...prev, { key, value, id: generateRowId() }]) - }, []) - - const conflictClassName = 'border-[var(--text-error)] bg-[#F6D2D2] dark:bg-[#442929]' - - const renderEnvVarRow = useCallback( - (envVar: UIEnvironmentVariable, originalIndex: number) => { - const isConflict = !!envVar.key && Object.hasOwn(workspaceVars, envVar.key) - const keyError = validateEnvVarKey(envVar.key) - const maskedValueStyle = - focusedValueIndex !== originalIndex && !isConflict - ? ({ WebkitTextSecurity: 'disc' } as React.CSSProperties) - : undefined - - return ( - <> -
- updateEnvVar(originalIndex, 'key', e.target.value)} - onPaste={(e) => handlePaste(e, originalIndex)} - placeholder='API_KEY' - name={`env_variable_name_${envVar.id || originalIndex}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly - onFocus={(e) => e.target.removeAttribute('readOnly')} - className={`h-9 ${isConflict ? conflictClassName : ''} ${keyError ? 'border-[var(--text-error)]' : ''}`} - /> -
- updateEnvVar(originalIndex, 'value', e.target.value)} - type='text' - onFocus={(e) => { - if (!isConflict) { - e.target.removeAttribute('readOnly') - handleValueFocus(originalIndex, e) - } - }} - onClick={handleValueClick} - onBlur={() => setFocusedValueIndex(null)} - onPaste={(e) => handlePaste(e, originalIndex)} - placeholder={isConflict ? 'Workspace override active' : 'Enter value'} - disabled={isConflict} - aria-disabled={isConflict} - name={`env_variable_value_${envVar.id || originalIndex}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly={isConflict} - style={maskedValueStyle} - className={`h-9 ${isConflict ? `cursor-not-allowed ${conflictClassName}` : ''}`} - /> -
- - - - - Change to workspace scope - - - - - - Delete secret - -
-
- {keyError && ( -
- {keyError} -
- )} - {isConflict && !keyError && ( -
- Workspace variable with the same name overrides this. Rename your personal key to use - it. -
- )} - - ) - }, - [ - workspaceVars, - workspaceId, - focusedValueIndex, - updateEnvVar, - handlePaste, - handleValueFocus, - handleValueClick, - promoteToWorkspace, - removeEnvVar, - ] - ) - - return ( - <> -
-
- - - -
-
-
- - setSearchTerm(e.target.value)} - name='env_search_field' - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly - onFocus={(e) => e.target.removeAttribute('readOnly')} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' - /> -
- - - - - - {hasConflicts && Resolve all conflicts before saving} - {hasInvalidKeys && !hasConflicts && ( - Fix invalid variable names before saving - )} - -
- -
-
- {isLoading ? ( - <> -
- -
- -
-
-
- - {Array.from({ length: 2 }, (_, i) => ( -
- -
- -
- - -
-
- ))} -
- - ) : ( - <> - {(!searchTerm.trim() || filteredWorkspaceEntries.length > 0) && ( -
-
- Workspace -
- {!searchTerm.trim() && Object.keys(workspaceVars).length === 0 ? ( -
- No workspace variables yet -
- ) : ( - (searchTerm.trim() - ? filteredWorkspaceEntries - : Object.entries(workspaceVars) - ).map(([key, value]) => ( - - )) - )} -
- )} - - {(!searchTerm.trim() || filteredEnvVars.length > 0) && ( -
-
- Personal -
- {filteredEnvVars.map(({ envVar, originalIndex }) => ( -
- {renderEnvVarRow(envVar, originalIndex)} -
- ))} -
- )} - {searchTerm.trim() && - filteredEnvVars.length === 0 && - filteredWorkspaceEntries.length === 0 && - (envVars.length > 0 || Object.keys(workspaceVars).length > 0) && ( -
- No secrets found matching "{searchTerm}" -
- )} - - )} -
-
-
- - - - Unsaved Changes - -

- {hasConflicts || hasInvalidKeys - ? `You have unsaved changes, but ${hasConflicts ? 'conflicts must be resolved' : 'invalid variable names must be fixed'} before saving. You can discard your changes to close the modal.` - : 'You have unsaved changes. Do you want to save them before closing?'} -

-
- - - {hasConflicts || hasInvalidKeys ? ( - - - - - - {hasConflicts - ? 'Resolve all conflicts before saving' - : 'Fix invalid variable names before saving'} - - - ) : ( - - )} - -
-
- - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts index 0621308ac..c19f269fe 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/index.ts @@ -5,10 +5,8 @@ export { CredentialSets } from './credential-sets/credential-sets' export { Credentials } from './credentials/credentials' export { CustomTools } from './custom-tools/custom-tools' export { Debug } from './debug/debug' -export { EnvironmentVariables } from './environment/environment' export { Files as FileUploads } from './files/files' export { General } from './general/general' -export { Integrations } from './integrations/integrations' export { MCP } from './mcp/mcp' export { Skills } from './skills/skills' export { Subscription } from './subscription/subscription' diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/integrations/integrations.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/integrations/integrations.tsx deleted file mode 100644 index dabdfc03f..000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/integrations/integrations.tsx +++ /dev/null @@ -1,417 +0,0 @@ -'use client' - -import { createElement, useEffect, useRef, useState } from 'react' -import { createLogger } from '@sim/logger' -import { Check, ChevronDown, ExternalLink, Search } from 'lucide-react' -import { useRouter, useSearchParams } from 'next/navigation' -import { - Button, - Label, - Modal, - ModalBody, - ModalContent, - ModalFooter, - ModalHeader, -} from '@/components/emcn' -import { Input, Skeleton } from '@/components/ui' -import { cn } from '@/lib/core/utils/cn' -import { OAUTH_PROVIDERS } from '@/lib/oauth' -import { - type ServiceInfo, - useConnectOAuthService, - useDisconnectOAuthService, - useOAuthConnections, -} from '@/hooks/queries/oauth-connections' -import { usePermissionConfig } from '@/hooks/use-permission-config' - -const logger = createLogger('Integrations') - -/** - * Static skeleton structure matching OAUTH_PROVIDERS layout - * Each entry: [providerName, serviceCount] - */ -const SKELETON_STRUCTURE: [string, number][] = [ - ['Google', 7], - ['Microsoft', 6], - ['GitHub', 1], - ['X', 1], - ['Confluence', 1], - ['Jira', 1], - ['Airtable', 1], - ['Notion', 1], - ['Linear', 1], - ['Slack', 1], - ['Reddit', 1], - ['Wealthbox', 1], - ['Webflow', 1], - ['Trello', 1], - ['Asana', 1], - ['Pipedrive', 1], - ['HubSpot', 1], - ['Salesforce', 1], -] - -function IntegrationsSkeleton() { - return ( -
-
- - -
- -
-
- {SKELETON_STRUCTURE.map(([providerName, serviceCount]) => ( -
- - {Array.from({ length: serviceCount }).map((_, index) => ( -
-
- -
- - -
-
- -
- ))} -
- ))} -
-
-
- ) -} - -interface IntegrationsProps { - onOpenChange?: (open: boolean) => void - registerCloseHandler?: (handler: (open: boolean) => void) => void -} - -export function Integrations({ onOpenChange, registerCloseHandler }: IntegrationsProps) { - const router = useRouter() - const searchParams = useSearchParams() - const pendingServiceRef = useRef(null) - - const { data: services = [], isPending } = useOAuthConnections() - const connectService = useConnectOAuthService() - const disconnectService = useDisconnectOAuthService() - const { config: permissionConfig } = usePermissionConfig() - - const [searchTerm, setSearchTerm] = useState('') - const [pendingService, setPendingService] = useState(null) - const [authSuccess, setAuthSuccess] = useState(false) - const [showActionRequired, setShowActionRequired] = useState(false) - const prevConnectedIdsRef = useRef>(new Set()) - const connectionAddedRef = useRef(false) - - // Disconnect confirmation dialog state - const [showDisconnectDialog, setShowDisconnectDialog] = useState(false) - const [serviceToDisconnect, setServiceToDisconnect] = useState<{ - service: ServiceInfo - accountId: string - } | null>(null) - - // Check for OAuth callback - just show success message - useEffect(() => { - const code = searchParams.get('code') - const state = searchParams.get('state') - const error = searchParams.get('error') - - if (code && state) { - logger.info('OAuth callback successful') - setAuthSuccess(true) - - // Clear URL parameters without changing the page - const url = new URL(window.location.href) - url.searchParams.delete('code') - url.searchParams.delete('state') - router.replace(url.pathname + url.search) - } else if (error) { - logger.error('OAuth error:', { error }) - } - }, [searchParams, router]) - - // Track when a new connection is added compared to previous render - useEffect(() => { - try { - const currentConnected = new Set() - services.forEach((svc) => { - if (svc.isConnected) currentConnected.add(svc.id) - }) - // Detect new connections by comparing to previous connected set - for (const id of currentConnected) { - if (!prevConnectedIdsRef.current.has(id)) { - connectionAddedRef.current = true - break - } - } - prevConnectedIdsRef.current = currentConnected - } catch {} - }, [services]) - - // On mount, register a close handler so the parent modal can delegate close events here - useEffect(() => { - if (!registerCloseHandler) return - const handle = (open: boolean) => { - if (open) return - try { - if (typeof window !== 'undefined') { - window.dispatchEvent( - new CustomEvent('oauth-integration-closed', { - detail: { success: connectionAddedRef.current === true }, - }) - ) - } - } catch {} - onOpenChange?.(open) - } - registerCloseHandler(handle) - }, [registerCloseHandler, onOpenChange]) - - // Handle connect button click - const handleConnect = async (service: ServiceInfo) => { - try { - logger.info('Connecting service:', { - serviceId: service.id, - providerId: service.providerId, - scopes: service.scopes, - }) - - // better-auth will automatically redirect back to this URL after OAuth - await connectService.mutateAsync({ - providerId: service.providerId, - callbackURL: window.location.href, - }) - } catch (error) { - logger.error('OAuth connection error:', { error }) - } - } - - /** - * Opens the disconnect confirmation dialog for a service. - */ - const handleDisconnect = (service: ServiceInfo, accountId: string) => { - setServiceToDisconnect({ service, accountId }) - setShowDisconnectDialog(true) - } - - /** - * Confirms and executes the service disconnection. - */ - const confirmDisconnect = async () => { - if (!serviceToDisconnect) return - - setShowDisconnectDialog(false) - const { service, accountId } = serviceToDisconnect - setServiceToDisconnect(null) - - try { - await disconnectService.mutateAsync({ - provider: service.providerId.split('-')[0], - providerId: service.providerId, - serviceId: service.id, - accountId, - }) - } catch (error) { - logger.error('Error disconnecting service:', { error }) - } - } - - // Group services by provider, filtering by permission config - const groupedServices = services.reduce( - (acc, service) => { - // Filter based on allowedIntegrations - if ( - permissionConfig.allowedIntegrations !== null && - !permissionConfig.allowedIntegrations.includes(service.id) - ) { - return acc - } - - // Find the provider for this service - const providerKey = - Object.keys(OAUTH_PROVIDERS).find((key) => - Object.keys(OAUTH_PROVIDERS[key].services).includes(service.id) - ) || 'other' - - if (!acc[providerKey]) { - acc[providerKey] = [] - } - - acc[providerKey].push(service) - return acc - }, - {} as Record - ) - - // Filter services based on search term - const filteredGroupedServices = Object.entries(groupedServices).reduce( - (acc, [providerKey, providerServices]) => { - const filteredServices = providerServices.filter( - (service) => - service.name.toLowerCase().includes(searchTerm.toLowerCase()) || - service.description.toLowerCase().includes(searchTerm.toLowerCase()) - ) - - if (filteredServices.length > 0) { - acc[providerKey] = filteredServices - } - - return acc - }, - {} as Record - ) - - const scrollToHighlightedService = () => { - if (pendingServiceRef.current) { - pendingServiceRef.current.scrollIntoView({ - behavior: 'smooth', - block: 'center', - }) - } - } - - if (isPending) { - return - } - - return ( - <> -
-
- - setSearchTerm(e.target.value)} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' - /> -
- -
-
- {authSuccess && ( -
- -

- Account connected successfully! -

-
- )} - - {pendingService && showActionRequired && ( -
- -
-

- Action Required:{' '} - Please connect your account to enable the requested features. -

- -
-
- )} - -
- {Object.entries(filteredGroupedServices).map(([providerKey, providerServices]) => ( -
- - {providerServices.map((service) => ( -
-
-
- {createElement(service.icon, { className: 'h-4 w-4' })} -
-
- {service.name} - {service.accounts && service.accounts.length > 0 ? ( -

- {service.accounts.map((a) => a.name).join(', ')} -

- ) : ( -

- {service.description} -

- )} -
-
- - {service.accounts && service.accounts.length > 0 ? ( - - ) : ( - - )} -
- ))} -
- ))} - - {searchTerm.trim() && Object.keys(filteredGroupedServices).length === 0 && ( -
- No services found matching "{searchTerm}" -
- )} -
-
-
-
- - - - Disconnect Service - -

- Are you sure you want to disconnect{' '} - - {serviceToDisconnect?.service.name} - - ?{' '} - - This will revoke access and you will need to reconnect to use this service. - -

-
- - - - -
-
- - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx index ff8781c17..2222b2690 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' import * as VisuallyHidden from '@radix-ui/react-visually-hidden' import { useQueryClient } from '@tanstack/react-query' @@ -79,9 +79,7 @@ interface SettingsModalProps { type SettingsSection = | 'general' | 'credentials' - | 'environment' | 'template-profile' - | 'integrations' | 'credential-sets' | 'access-control' | 'apikeys' @@ -216,8 +214,6 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { const activeOrganization = organizationsData?.activeOrganization const { config: permissionConfig } = usePermissionConfig() - const environmentBeforeLeaveHandler = useRef<((onProceed: () => void) => void) | null>(null) - const integrationsCloseHandler = useRef<((open: boolean) => void) | null>(null) const userEmail = session?.user?.email const userId = session?.user?.id @@ -319,32 +315,12 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { if (!isBillingEnabled && (activeSection === 'subscription' || activeSection === 'team')) { return 'general' } - if (activeSection === 'environment' || activeSection === 'integrations') { - return 'credentials' - } return activeSection }, [activeSection]) - const registerEnvironmentBeforeLeaveHandler = useCallback( - (handler: (onProceed: () => void) => void) => { - environmentBeforeLeaveHandler.current = handler - }, - [] - ) - - const registerIntegrationsCloseHandler = useCallback((handler: (open: boolean) => void) => { - integrationsCloseHandler.current = handler - }, []) - const handleSectionChange = useCallback( (sectionId: SettingsSection) => { if (sectionId === effectiveActiveSection) return - - if (effectiveActiveSection === 'credentials' && environmentBeforeLeaveHandler.current) { - environmentBeforeLeaveHandler.current(() => setActiveSection(sectionId)) - return - } - setActiveSection(sectionId) }, [effectiveActiveSection] @@ -368,11 +344,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { useEffect(() => { const handleOpenSettings = (event: CustomEvent<{ tab: SettingsSection }>) => { - if (event.detail.tab === 'environment' || event.detail.tab === 'integrations') { - setActiveSection('credentials') - } else { - setActiveSection(event.detail.tab) - } + setActiveSection(event.detail.tab) onOpenChange(true) } @@ -477,29 +449,8 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { } } - // Handle dialog close - delegate to environment component if it's active const handleDialogOpenChange = (newOpen: boolean) => { - if ( - !newOpen && - effectiveActiveSection === 'credentials' && - environmentBeforeLeaveHandler.current - ) { - environmentBeforeLeaveHandler.current(() => { - if (integrationsCloseHandler.current) { - integrationsCloseHandler.current(newOpen) - } else { - onOpenChange(false) - } - }) - } else if ( - !newOpen && - effectiveActiveSection === 'credentials' && - integrationsCloseHandler.current - ) { - integrationsCloseHandler.current(newOpen) - } else { - onOpenChange(newOpen) - } + onOpenChange(newOpen) } return ( @@ -548,11 +499,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { {effectiveActiveSection === 'general' && } {effectiveActiveSection === 'credentials' && ( - + )} {effectiveActiveSection === 'template-profile' && } {effectiveActiveSection === 'credential-sets' && } diff --git a/apps/sim/stores/modals/settings/types.ts b/apps/sim/stores/modals/settings/types.ts index e010c6fa3..247e2c099 100644 --- a/apps/sim/stores/modals/settings/types.ts +++ b/apps/sim/stores/modals/settings/types.ts @@ -1,9 +1,7 @@ export type SettingsSection = | 'general' | 'credentials' - | 'environment' | 'template-profile' - | 'integrations' | 'apikeys' | 'files' | 'subscription'