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
38 changes: 31 additions & 7 deletions apps/sim/app/api/organizations/[id]/invitations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,16 +244,40 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
const emailsToInvite = newEmails.filter((email: string) => !pendingEmails.includes(email))

if (emailsToInvite.length === 0) {
const isSingleEmail = processedEmails.length === 1
const existingMembersEmails = processedEmails.filter((email: string) =>
existingEmails.includes(email)
)
const pendingInvitationEmails = processedEmails.filter((email: string) =>
pendingEmails.includes(email)
)

if (isSingleEmail) {
if (existingMembersEmails.length > 0) {
return NextResponse.json(
{
error: 'Failed to send invitation. User is already a part of the organization.',
},
{ status: 400 }
)
}
if (pendingInvitationEmails.length > 0) {
return NextResponse.json(
{
error:
'Failed to send invitation. A pending invitation already exists for this email.',
},
{ status: 400 }
)
}
}

return NextResponse.json(
{
error: 'All emails are already members or have pending invitations',
error: 'All emails are already members or have pending invitations.',
details: {
existingMembers: processedEmails.filter((email: string) =>
existingEmails.includes(email)
),
pendingInvitations: processedEmails.filter((email: string) =>
pendingEmails.includes(email)
),
existingMembers: existingMembersEmails,
pendingInvitations: pendingInvitationEmails,
},
},
{ status: 400 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ export function Account(_props: AccountProps) {
<div className='flex flex-1 flex-col justify-center'>
<h3 className='font-medium text-base'>{profile?.name || ''}</h3>
<p className='font-normal text-muted-foreground text-sm'>{profile?.email || ''}</p>
{uploadError && <p className='mt-1 text-destructive text-xs'>{uploadError}</p>}
{uploadError && (
<p className='mt-1 text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{uploadError}
</p>
)}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,9 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
autoFocus
/>
{createError && (
<div className='text-[#DC2626] text-[12px] leading-tight dark:text-[#F87171]'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{createError}
</div>
</p>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ export function CreatorProfile() {
/>
</div>
<div className='flex flex-col gap-1'>
{uploadError && <p className='text-destructive text-sm'>{uploadError}</p>}
{uploadError && (
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{uploadError}
</p>
)}
<p className='text-muted-foreground text-xs'>PNG or JPEG (max 5MB)</p>
</div>
</div>
Expand Down Expand Up @@ -411,9 +415,9 @@ export function CreatorProfile() {
{/* Error Message */}
{saveError && (
<div className='px-6 pb-2'>
<div className='text-[#DC2626] text-[12px] leading-tight dark:text-[#F87171]'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{saveError}
</div>
</p>
</div>
)}

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

import { useState } from 'react'
import { AlertCircle, Plus, Search } from 'lucide-react'
import { Plus, Search } from 'lucide-react'
import { useParams } from 'next/navigation'
import {
Button,
Expand All @@ -13,7 +13,7 @@ import {
ModalHeader,
ModalTitle,
} from '@/components/emcn'
import { Alert, AlertDescription, Input, Skeleton } from '@/components/ui'
import { Input, Skeleton } from '@/components/ui'
import { createLogger } from '@/lib/logs/console/logger'
import { CustomToolModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal'
import { useCustomTools, useDeleteCustomTool } from '@/hooks/queries/custom-tools'
Expand Down Expand Up @@ -84,7 +84,6 @@ export function CustomTools() {
setShowDeleteDialog(false)

try {
// Pass null workspaceId for user-scoped tools (legacy tools without workspaceId)
await deleteToolMutation.mutateAsync({
workspaceId: tool.workspaceId ?? null,
toolId: toolToDelete.id,
Expand All @@ -105,24 +104,13 @@ export function CustomTools() {
const handleToolSaved = () => {
setShowAddForm(false)
setEditingTool(null)
// React Query will automatically refetch via cache invalidation
refetchTools()
}

return (
<div className='relative flex h-full flex-col'>
{/* Fixed Header with Search */}
<div className='px-6 pt-4 pb-2'>
{/* Error Alert - only show when modal is not open */}
{error && !showAddForm && !editingTool && (
<Alert variant='destructive' className='mb-4'>
<AlertCircle className='h-4 w-4' />
<AlertDescription>
{error instanceof Error ? error.message : 'An error occurred'}
</AlertDescription>
</Alert>
)}

{/* Search Input */}
{isLoading ? (
<Skeleton className='h-9 w-56 rounded-[8px]' />
Expand All @@ -148,6 +136,12 @@ export function CustomTools() {
<CustomToolSkeleton />
<CustomToolSkeleton />
</div>
) : error ? (
<div className='flex h-full flex-col items-center justify-center gap-2'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{error instanceof Error ? error.message : 'Failed to load tools'}
</p>
</div>
) : tools.length === 0 && !showAddForm && !editingTool ? (
<div className='flex h-full items-center justify-center text-muted-foreground text-sm'>
Click "Create Tool" below to get started
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,9 @@ export function Files() {
{/* Error message */}
{uploadError && (
<div className='px-6 pb-2'>
<div className='text-[#DC2626] text-[12px] leading-tight dark:text-[#F87171]'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{uploadError}
</div>
</p>
</div>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ export function AddServerForm({
<div className='space-y-1.5'>
{/* Error message above buttons */}
{testResult && !testResult.success && (
<div className='text-[#DC2626] text-[12px] leading-tight dark:text-[#F87171]'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{testResult.error || testResult.message}
</div>
</p>
)}

{/* Buttons row */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client'

import { useCallback, useRef, useState } from 'react'
import { AlertCircle, Plus, Search } from 'lucide-react'
import { Plus, Search } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Button } from '@/components/emcn'
import { Alert, AlertDescription, Input, Skeleton } from '@/components/ui'
import { Input, Skeleton } from '@/components/ui'
import { createLogger } from '@/lib/logs/console/logger'
import { createMcpToolId } from '@/lib/mcp/utils'
import { checkEnvVarTrigger } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/env-var-dropdown'
Expand All @@ -24,7 +24,6 @@ export function MCP() {
const params = useParams()
const workspaceId = params.workspaceId as string

// React Query hooks
const {
data: servers = [],
isLoading: serversLoading,
Expand All @@ -42,7 +41,7 @@ export function MCP() {
transport: 'streamable-http',
url: '',
timeout: 30000,
headers: {}, // Start with no headers
headers: {},
})

const [showEnvVars, setShowEnvVars] = useState(false)
Expand Down Expand Up @@ -207,7 +206,6 @@ export function MCP() {

try {
await deleteServerMutation.mutateAsync({ workspaceId, serverId })
// TanStack Query mutations automatically invalidate and refetch tools

logger.info(`Removed MCP server: ${serverId}`)
} catch (error) {
Expand Down Expand Up @@ -264,27 +262,23 @@ export function MCP() {
/>
</div>
)}

{/* Error Alert */}
{(toolsError || serversError) && (
<Alert variant='destructive' className='mt-4'>
<AlertCircle className='h-4 w-4' />
<AlertDescription>
{toolsError instanceof Error
? toolsError.message
: serversError instanceof Error
? serversError.message
: 'An error occurred'}
</AlertDescription>
</Alert>
)}
</div>

{/* Scrollable Content */}
<div className='min-h-0 flex-1 overflow-y-auto px-6'>
<div className='space-y-2 pt-2 pb-6'>
{/* Server List */}
{serversLoading ? (
{toolsError || serversError ? (
<div className='flex h-full flex-col items-center justify-center gap-2'>
<p className='text-[#DC2626] text-[11px] leading-tight dark:text-[#F87171]'>
{toolsError instanceof Error
? toolsError.message
: serversError instanceof Error
? serversError.message
: 'Failed to load MCP servers'}
</p>
</div>
) : serversLoading ? (
<div className='space-y-2'>
<McpServerSkeleton />
<McpServerSkeleton />
Expand Down Expand Up @@ -342,7 +336,6 @@ export function MCP() {
) : (
<div className='space-y-2'>
{filteredServers.map((server: any) => {
// Add defensive checks for server properties
if (!server || !server.id) {
return null
}
Expand Down
Loading