diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 89c73420f5..29f13af99b 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -16,7 +16,7 @@ import { RotateCcw, } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' -import { Tooltip } from '@/components/emcn' +import { Button, Tooltip } from '@/components/emcn' import { Trash } from '@/components/emcn/icons/trash' import { AlertDialog, @@ -28,7 +28,6 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' -import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { SearchHighlight } from '@/components/ui/search-highlight' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' @@ -1006,7 +1005,6 @@ export function KnowledgeBase({ @@ -1097,7 +1093,6 @@ export function KnowledgeBase({
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/controls.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/controls.tsx index da0c186971..485d4cb750 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/controls.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/controls.tsx @@ -1,7 +1,6 @@ import type { ReactNode } from 'react' import { Loader2, RefreshCw, Search } from 'lucide-react' -import { Tooltip } from '@/components/emcn' -import { Button } from '@/components/ui/button' +import { Button, Tooltip } from '@/components/emcn' import { Input } from '@/components/ui/input' import { cn } from '@/lib/utils' import { soehne } from '@/app/fonts/soehne/soehne' @@ -49,7 +48,7 @@ export function Controls({ placeholder='Search workflows...' value={searchQuery} onChange={(e) => setSearchQuery?.(e.target.value)} - className='h-9 w-full rounded-[11px] border-[#E5E5E5] bg-[var(--white)] pr-10 pl-9 dark:border-[#414141] dark:bg-[var(--surface-elevated)]' + className='h-9 w-full border-[#E5E5E5] bg-[var(--white)] pr-10 pl-9 dark:border-[#414141] dark:bg-[var(--surface-elevated)]' /> {searchQuery && (
-
+
Executions {overview.total}
-
+
Success {overview.rate.toFixed(1)}%
-
+
Failures {overview.failures}
@@ -172,7 +178,7 @@ export function WorkflowDetails({ }) : 'Selected segment' return ( -
+
@@ -264,8 +270,15 @@ export function WorkflowDetails({
-
-
+
+
Time
@@ -287,9 +300,11 @@ export function WorkflowDetails({
Duration
-
- Resume -
+ {hasPendingExecutions && ( +
+ Resume +
+ )}
@@ -333,14 +348,21 @@ export function WorkflowDetails({
setExpandedRowId((prev) => (prev === log.id ? null : log.id)) } > -
+
@@ -356,34 +378,40 @@ export function WorkflowDetails({
-
- {statusLabel} -
+ {isError || !isPending ? ( +
+
+ + {statusLabel} + +
+ ) : ( +
+ {statusLabel} +
+ )}
{log.trigger ? (
{log.trigger}
@@ -403,7 +431,7 @@ export function WorkflowDetails({ {log.workflowName ? (
-
- {isPending && log.executionId ? ( - - - - ) : ( - - )} -
+ {hasPendingExecutions && ( +
+ {isPending && log.executionId ? ( + + + + ) : ( + + )} +
+ )}
{isExpanded && (
-
+
                                   {log.level === 'error' && errorStr ? errorStr : outputsStr}
                                 
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/workflows-list.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/workflows-list.tsx index 53d0c1384f..ac12a1db4f 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/workflows-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/dashboard/workflows-list.tsx @@ -59,7 +59,7 @@ export function WorkflowsList({ } return (
@@ -89,7 +89,7 @@ export function WorkflowsList({ return (
onToggleWorkflow(workflow.workflowId)} @@ -97,7 +97,7 @@ export function WorkflowsList({
(null) const { folderIds, toggleFolderId, setFolderIds } = useFilterStore() - const { getFolderTree, getFolderPath, fetchFolders } = useFolderStore() + const { getFolderTree, fetchFolders } = useFolderStore() const params = useParams() const workspaceId = params.workspaceId as string const [folders, setFolders] = useState([]) @@ -111,7 +110,7 @@ export default function FolderFilter() { return ( - diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx index 8658b4c2d8..1216771797 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx @@ -1,5 +1,5 @@ import { Check, ChevronDown } from 'lucide-react' -import { Button } from '@/components/ui/button' +import { Button } from '@/components/emcn' import { DropdownMenu, DropdownMenuContent, @@ -28,8 +28,7 @@ export default function Level() { @@ -58,7 +58,7 @@ export default function Timeline({ variant = 'default' }: TimelineProps = {}) { onSelect={() => { setTimeRange('All time') }} - className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' + className='flex cursor-pointer items-center justify-between px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > All time {timeRange === 'All time' && } @@ -72,7 +72,7 @@ export default function Timeline({ variant = 'default' }: TimelineProps = {}) { onSelect={() => { setTimeRange(range) }} - className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' + className='flex cursor-pointer items-center justify-between px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > {range} {timeRange === range && } diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx index a29f11b7c9..a94b9b0b18 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx @@ -1,6 +1,6 @@ -import { useMemo, useRef, useState } from 'react' +import { useMemo, useState } from 'react' import { Check, ChevronDown } from 'lucide-react' -import { Button } from '@/components/ui/button' +import { Button } from '@/components/emcn' import { Command, CommandEmpty, @@ -26,7 +26,6 @@ import type { TriggerType } from '@/stores/logs/filters/types' export default function Trigger() { const { triggers, toggleTrigger, setTriggers } = useFilterStore() const [search, setSearch] = useState('') - const triggerRef = useRef(null) const triggerOptions: { value: TriggerType; label: string; color?: string }[] = [ { value: 'manual', label: 'Manual', color: 'bg-gray-500' }, { value: 'api', label: 'API', color: 'bg-blue-500' }, @@ -58,7 +57,7 @@ export default function Trigger() { return ( - diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx index a73e8f6c0c..4cbda5d72b 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx @@ -1,7 +1,7 @@ -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Check, ChevronDown } from 'lucide-react' import { useParams } from 'next/navigation' -import { Button } from '@/components/ui/button' +import { Button } from '@/components/emcn' import { Command, CommandEmpty, @@ -33,7 +33,6 @@ interface WorkflowOption { } export default function Workflow() { - const triggerRef = useRef(null) const { workflowIds, toggleWorkflowId, setWorkflowIds, folderIds } = useFilterStore() const params = useParams() const workspaceId = params?.workspaceId as string | undefined @@ -91,7 +90,7 @@ export default function Workflow() { return ( - diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/filters.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/filters.tsx index 7d0fb7eb3a..29254447c3 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/filters.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/filters.tsx @@ -1,7 +1,7 @@ 'use client' import { TimerOff } from 'lucide-react' -import { Button } from '@/components/ui' +import { Button } from '@/components/emcn' import { isProd } from '@/lib/environment' import { FilterSection, @@ -33,7 +33,7 @@ export function Filters() {
{/* Show retention policy for free users in production only */} {!isLoading && !isPaid && isProd && ( -
+
Log Retention Policy @@ -44,9 +44,8 @@ export function Filters() {

- -
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas.tsx index 5e017683b3..312de84b72 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/frozen-canvas/frozen-canvas.tsx @@ -42,7 +42,7 @@ function ExpandableDataSection({ title, data }: { title: string; data: any }) { {isLargeData && (
@@ -75,14 +75,14 @@ function ExpandableDataSection({ title, data }: { title: string; data: any }) { {/* Modal for large data */} {isModalOpen && (
-
+

{title}

@@ -194,7 +194,7 @@ function PinnedLogs({ } return ( - +
@@ -217,7 +217,7 @@ function PinnedLogs({ -
+
This block was not executed because the workflow failed before reaching it.
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/search/search.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/search/search.tsx index 3ed2acc38e..236b6cdee2 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/search/search.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/search/search.tsx @@ -2,8 +2,8 @@ import { useEffect, useMemo, useState } from 'react' import { Loader2, Search, X } from 'lucide-react' +import { Button } from '@/components/emcn' import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { parseQuery } from '@/lib/logs/query-parser' import { SearchSuggestions } from '@/lib/logs/search-suggestions' @@ -131,7 +131,7 @@ export function AutocompleteSearch({ {/* Search Input */}
{ e.preventDefault() @@ -206,7 +205,7 @@ export function AutocompleteSearch({ {state.isOpen && state.suggestions.length > 0 && (
removeFilter(filter)} > @@ -296,7 +294,6 @@ export function AutocompleteSearch({ diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx index 39adb4a2b5..2b9cba09a6 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx @@ -2,8 +2,11 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { ChevronDown, ChevronUp, Eye, Loader2, X } from 'lucide-react' -import { Tooltip } from '@/components/emcn' -import { Button } from '@/components/ui/button' +import { highlight, languages } from 'prismjs' +import 'prismjs/components/prism-javascript' +import 'prismjs/components/prism-python' +import 'prismjs/components/prism-json' +import { Button, Tooltip } from '@/components/emcn' import { CopyButton } from '@/components/ui/copy-button' import { ScrollArea } from '@/components/ui/scroll-area' import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants' @@ -15,6 +18,7 @@ import { TraceSpans } from '@/app/workspace/[workspaceId]/logs/components/trace- import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils' import { formatCost } from '@/providers/utils' import type { WorkflowLog } from '@/stores/logs/filters/types' +import '@/components/emcn/components/code/code.css' interface LogSidebarProps { log: WorkflowLog | null @@ -72,12 +76,17 @@ const formatJsonContent = (content: string, blockInput?: Record): R const { isJson, formatted } = tryPrettifyJson(content) return ( -
+
{isJson ? ( -
-          {formatted}
-        
+
+
+        
) : ( )} @@ -123,7 +132,7 @@ const BlockContentDisplay = ({
{inputExpanded && ( -
+
)} @@ -55,7 +55,7 @@ export function CollapsibleInputOutput({ span, spanId, depth }: CollapsibleInput {span.status === 'error' ? 'Error Details' : 'Output'} {outputExpanded && ( -
+
-
+
{filtered.map((span, index) => { const normalizedSpan = normalizeChildWorkflowSpan(span) const hasSubItems = Boolean( diff --git a/apps/sim/app/workspace/[workspaceId]/logs/dashboard.tsx b/apps/sim/app/workspace/[workspaceId]/logs/dashboard.tsx index 473d79fd01..d1826fad8f 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/dashboard.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/dashboard.tsx @@ -810,17 +810,17 @@ export default function Dashboard() {
Filters: {workflowIds.length > 0 && ( - + {workflowIds.length} workflow{workflowIds.length !== 1 ? 's' : ''} )} {folderIds.length > 0 && ( - + {folderIds.length} folder{folderIds.length !== 1 ? 's' : ''} )} {triggers.length > 0 && ( - + {triggers.length} trigger{triggers.length !== 1 ? 's' : ''} )} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx index f1dd520638..2b65ace21c 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx @@ -809,18 +809,33 @@ export default function Logs() { {/* Status */}
-
- {statusLabel} -
+ {isError || !isPending ? ( +
+
+ + {statusLabel} + +
+ ) : ( +
+ {statusLabel} +
+ )}
{/* Workflow */} @@ -843,17 +858,8 @@ export default function Logs() {
{log.trigger ? (
{log.trigger}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/deploy/deploy.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/deploy/deploy.tsx index 328c5d150a..97efc0aada 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/deploy/deploy.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/deploy/deploy.tsx @@ -2,8 +2,10 @@ import { useCallback, useState } from 'react' import { Loader2 } from 'lucide-react' -import { Button, Rocket } from '@/components/emcn' +import { Button, Rocket, Tooltip } from '@/components/emcn' +import { cn } from '@/lib/utils' import { DeployModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components' +import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow' import type { WorkspaceUserPermissions } from '@/hooks/use-user-permissions' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useChangeDetection, useDeployedState, useDeployment } from './hooks' @@ -21,6 +23,7 @@ interface DeployProps { export function Deploy({ activeWorkflowId, userPermissions, className }: DeployProps) { const [isModalOpen, setIsModalOpen] = useState(false) const { isLoading: isRegistryLoading } = useWorkflowRegistry() + const { hasBlocks } = useCurrentWorkflow() // Get deployment status from registry const deploymentStatus = useWorkflowRegistry((state) => @@ -49,8 +52,9 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP refetchDeployedState, }) + const isEmpty = !hasBlocks() const canDeploy = userPermissions.canAdmin - const isDisabled = isDeploying || !canDeploy + const isDisabled = isDeploying || !canDeploy || isEmpty const isPreviousVersionActive = isDeployed && changeDetected /** @@ -75,21 +79,65 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP } } + /** + * Get tooltip text based on current state + */ + const getTooltipText = () => { + if (isEmpty) { + return 'Cannot deploy an empty workflow' + } + if (!canDeploy) { + return 'Admin permissions required' + } + if (isDeploying) { + return 'Deploying...' + } + if (changeDetected) { + return 'Update deployment' + } + if (isDeployed) { + return 'Active deployment' + } + return 'Deploy workflow' + } + + const buttonContent = ( + <> + {isDeploying ? ( + + ) : ( + + )} + {changeDetected ? 'Update' : isDeployed ? 'Active' : 'Deploy'} + + ) + return ( <> - + + + {isDisabled ? ( +
+ {buttonContent} +
+ ) : ( + + )} +
+ {getTooltipText()} +
{ + if (!open) return + + const styleId = 'custom-tool-modal-z-index' + let styleEl = document.getElementById(styleId) as HTMLStyleElement + + if (!styleEl) { + styleEl = document.createElement('style') + styleEl.id = styleId + styleEl.textContent = ` + [data-radix-portal] [data-radix-dialog-overlay] { + z-index: 99999998 !important; + } + ` + document.head.appendChild(styleEl) + } + + return () => { + const el = document.getElementById(styleId) + if (el) { + el.remove() + } + } + }, [open]) + return ( <> { // Intercept Escape key when dropdowns are open diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx index bd003d494c..05eed70d54 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/editor.tsx @@ -210,8 +210,9 @@ export function Editor() { /> ) : (

{title}

diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/help-modal/help-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/help-modal/help-modal.tsx index acbb9bc58e..296a81ba4f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/help-modal/help-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/help-modal/help-modal.tsx @@ -7,7 +7,7 @@ import { Loader2, X } from 'lucide-react' import Image from 'next/image' import { useForm } from 'react-hook-form' import { z } from 'zod' -import { Button, Input, Modal, ModalContent } from '@/components/emcn' +import { Button, Input, Modal, ModalContent, ModalTitle } from '@/components/emcn' import { Label } from '@/components/ui/label' import { Select, @@ -355,9 +355,9 @@ export function HelpModal({ open, onOpenChange }: HelpModalProps) { {/* Modal Header */}
-

+ Help & Support -

+
{/* Modal Body */} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/api-keys/api-keys.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/api-keys/api-keys.tsx index 8fc37575f4..47d0d70e28 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/api-keys/api-keys.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/api-keys/api-keys.tsx @@ -3,22 +3,17 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Check, Copy, Info, Plus, Search } from 'lucide-react' import { useParams } from 'next/navigation' -import { Tooltip } from '@/components/emcn/components/tooltip/tooltip' import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Skeleton } from '@/components/ui/skeleton' -import { Switch } from '@/components/ui/switch' + Button, + Modal, + ModalContent, + ModalDescription, + ModalFooter, + ModalHeader, + ModalTitle, +} from '@/components/emcn' +import { Tooltip } from '@/components/emcn/components/tooltip/tooltip' +import { Input, Label, Skeleton, Switch } from '@/components/ui' import { useSession } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console/logger' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' @@ -374,7 +369,8 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { if (!allowPersonalApiKeys && keyType === 'personal') { setKeyType('workspace') } - }, [allowPersonalApiKeys, keyType]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allowPersonalApiKeys]) useEffect(() => { if (shouldScrollToBottom && scrollContainerRef.current) { @@ -398,7 +394,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { return (
{/* Fixed Header */} -
+
{/* Search Input */} {isLoading ? ( @@ -417,7 +413,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { {/* Scrollable Content */}
-
+
{isLoading ? (
@@ -432,44 +428,46 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { <> {/* Allow Personal API Keys Toggle */} {!searchTerm.trim() && ( -
-
- - Allow personal API keys - - - - - - - Allow collaborators to create and use their own keys with billing charged to - them. - - + +
+
+ + Allow personal API keys + + + + + + + Allow collaborators to create and use their own keys with billing charged + to them. + + +
+ {workspaceSettingsLoading ? ( + + ) : ( + { + const previous = allowPersonalApiKeys + setAllowPersonalApiKeys(checked) + try { + await updateWorkspaceSettings({ allowPersonalApiKeys: checked }) + } catch (error) { + setAllowPersonalApiKeys(previous) + } + }} + /> + )}
- {workspaceSettingsLoading ? ( - - ) : ( - { - const previous = allowPersonalApiKeys - setAllowPersonalApiKeys(checked) - try { - await updateWorkspaceSettings({ allowPersonalApiKeys: checked }) - } catch (error) { - setAllowPersonalApiKeys(previous) - } - }} - /> - )} -
+ )} {/* Workspace section */} @@ -494,7 +492,6 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
{/* Create API Key Dialog */} - - - - Create new API key - + + + + Create new API key + {keyType === 'workspace' ? "This key will have access to all workflows in this workspace. Make sure to copy it after creation as you won't be able to see it again." : "This key will have access to your personal workflows. Make sure to copy it after creation as you won't be able to see it again."} - - + +
{canManageWorkspaceKeys && ( @@ -645,26 +642,24 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
@@ -685,24 +680,30 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { className='h-9 rounded-[8px]' autoFocus /> - {createError &&
{createError}
} + {createError && ( +
+ {createError} +
+ )}
- - + - -
-
+ + + {/* New API Key Dialog */} - { + onOpenChange={(open: boolean) => { setShowNewKeyDialog(open) if (!open) { setNewKey(null) @@ -726,14 +727,14 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { } }} > - - - Your API key has been created - + + + Your API key has been created + This is the only time you will see your API key.{' '} Copy it now and store it securely. - - + + {newKey && (
@@ -744,7 +745,6 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
)} - - + + {/* Delete Confirmation Dialog */} - - - - Delete API key? - + + + + Delete API key? + Deleting this API key will immediately revoke access for any integrations using it.{' '} This action cannot be undone. - - + + {deleteKey && (
@@ -783,17 +783,18 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
)} - - + + + +
) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/components/creator-profile/creator-profile.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/components/creator-profile/creator-profile.tsx deleted file mode 100644 index 1a3a89afc4..0000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/components/creator-profile/creator-profile.tsx +++ /dev/null @@ -1,528 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { zodResolver } from '@hookform/resolvers/zod' -import { Camera, Check, Globe, Linkedin, Mail, Save, Twitter, User, Users } from 'lucide-react' -import Image from 'next/image' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { AgentIcon } from '@/components/icons' -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, - RadioGroup, - RadioGroupItem, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - Textarea, -} from '@/components/ui' -import { useSession } from '@/lib/auth-client' -import { createLogger } from '@/lib/logs/console/logger' -import { cn } from '@/lib/utils' -import { useProfilePictureUpload } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/account/hooks/use-profile-picture-upload' -import type { CreatorProfileDetails } from '@/types/creator-profile' - -const logger = createLogger('CreatorProfile') - -type SaveStatus = 'idle' | 'saving' | 'saved' | 'error' - -const creatorProfileSchema = z.object({ - referenceType: z.enum(['user', 'organization']), - referenceId: z.string().min(1, 'Reference is required'), - name: z.string().min(1, 'Display Name is required').max(100, 'Max 100 characters'), - profileImageUrl: z.string().min(1, 'Profile Picture is required'), - about: z.string().max(2000, 'Max 2000 characters').optional(), - xUrl: z.string().url().optional().or(z.literal('')), - linkedinUrl: z.string().url().optional().or(z.literal('')), - websiteUrl: z.string().url().optional().or(z.literal('')), - contactEmail: z.string().email().optional().or(z.literal('')), -}) - -type CreatorProfileFormData = z.infer - -interface Organization { - id: string - name: string - role: string -} - -export function CreatorProfile() { - const { data: session } = useSession() - const [loading, setLoading] = useState(false) - const [saveStatus, setSaveStatus] = useState('idle') - const [organizations, setOrganizations] = useState([]) - const [existingProfile, setExistingProfile] = useState(null) - const [uploadError, setUploadError] = useState(null) - - const form = useForm({ - resolver: zodResolver(creatorProfileSchema), - defaultValues: { - referenceType: 'user', - referenceId: session?.user?.id || '', - name: session?.user?.name || session?.user?.email || '', - profileImageUrl: '', - about: '', - xUrl: '', - linkedinUrl: '', - websiteUrl: '', - contactEmail: '', - }, - }) - - const profileImageUrl = form.watch('profileImageUrl') - - const { - previewUrl: profilePictureUrl, - fileInputRef: profilePictureInputRef, - handleThumbnailClick: handleProfilePictureClick, - handleFileChange: handleProfilePictureChange, - isUploading: isUploadingProfilePicture, - } = useProfilePictureUpload({ - currentImage: profileImageUrl, - onUpload: async (url) => { - form.setValue('profileImageUrl', url || '') - setUploadError(null) - }, - onError: (error) => { - setUploadError(error) - setTimeout(() => setUploadError(null), 5000) - }, - }) - - const referenceType = form.watch('referenceType') - - // Fetch organizations - useEffect(() => { - const fetchOrganizations = async () => { - if (!session?.user?.id) return - - try { - const response = await fetch('/api/organizations') - if (response.ok) { - const data = await response.json() - const orgs = (data.organizations || []).filter( - (org: any) => org.role === 'owner' || org.role === 'admin' - ) - setOrganizations(orgs) - } - } catch (error) { - logger.error('Error fetching organizations:', error) - } - } - - fetchOrganizations() - }, [session?.user?.id]) - - // Load existing profile - useEffect(() => { - const loadProfile = async () => { - if (!session?.user?.id) return - - setLoading(true) - try { - const response = await fetch(`/api/creator-profiles?userId=${session.user.id}`) - if (response.ok) { - const data = await response.json() - if (data.profiles && data.profiles.length > 0) { - const profile = data.profiles[0] - const details = profile.details as CreatorProfileDetails | null - setExistingProfile(profile) - form.reset({ - referenceType: profile.referenceType, - referenceId: profile.referenceId, - name: profile.name || '', - profileImageUrl: profile.profileImageUrl || '', - about: details?.about || '', - xUrl: details?.xUrl || '', - linkedinUrl: details?.linkedinUrl || '', - websiteUrl: details?.websiteUrl || '', - contactEmail: details?.contactEmail || '', - }) - } - } - } catch (error) { - logger.error('Error loading profile:', error) - } finally { - setLoading(false) - } - } - - loadProfile() - }, [session?.user?.id, form]) - - const onSubmit = async (data: CreatorProfileFormData) => { - if (!session?.user?.id) return - - setSaveStatus('saving') - try { - const details: CreatorProfileDetails = {} - if (data.about) details.about = data.about - if (data.xUrl) details.xUrl = data.xUrl - if (data.linkedinUrl) details.linkedinUrl = data.linkedinUrl - if (data.websiteUrl) details.websiteUrl = data.websiteUrl - if (data.contactEmail) details.contactEmail = data.contactEmail - - const payload = { - referenceType: data.referenceType, - referenceId: data.referenceId, - name: data.name, - profileImageUrl: data.profileImageUrl, - details: Object.keys(details).length > 0 ? details : undefined, - } - - const url = existingProfile - ? `/api/creator-profiles/${existingProfile.id}` - : '/api/creator-profiles' - const method = existingProfile ? 'PUT' : 'POST' - - const response = await fetch(url, { - method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }) - - if (response.ok) { - const result = await response.json() - setExistingProfile(result.data) - logger.info('Creator profile saved successfully') - setSaveStatus('saved') - - // Dispatch event to notify that a creator profile was saved - window.dispatchEvent(new CustomEvent('creator-profile-saved')) - - setTimeout(() => { - setSaveStatus('idle') - }, 2000) - } else { - logger.error('Failed to save creator profile') - setSaveStatus('error') - setTimeout(() => { - setSaveStatus('idle') - }, 3000) - } - } catch (error) { - logger.error('Error saving creator profile:', error) - setSaveStatus('error') - setTimeout(() => { - setSaveStatus('idle') - }, 3000) - } - } - - if (loading) { - return ( -
-

Loading...

-
- ) - } - - return ( -
-
-
-

- Set up your creator profile for publishing templates -

-
- -
- - {/* Profile Type - only show if user has organizations */} - {organizations.length > 0 && ( - ( - - Profile Type - - -
- - -
-
- - -
-
-
- -
- )} - /> - )} - - {/* Reference Selection */} - {referenceType === 'organization' && organizations.length > 0 && ( - ( - - Organization - - - - )} - /> - )} - - {/* Profile Name */} - ( - - - Display Name * - - - - - - - )} - /> - - {/* Profile Picture Upload */} - ( - - -
- - Profile Picture * -
-
- -
-
-
- {profilePictureUrl ? ( - Profile picture - ) : ( - - )} - - {/* Upload overlay */} -
- {isUploadingProfilePicture ? ( -
- ) : ( - - )} -
-
- - {/* Hidden file input */} - -
- {uploadError &&

{uploadError}

} -

PNG or JPEG (max 5MB)

-
- - - - )} - /> - - {/* About */} - ( - - About - -