From 2832d93ccdf8a7a86ff7d5214ef63acb3c89ab2a Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 15 Nov 2025 14:32:49 -0800 Subject: [PATCH 1/3] feat(billing): add notif for first failed payment, added upgrade email from free, updated providers that supported granular tool control to support them, fixed envvar popover, fixed redirect to wrong workspace after oauth connect --- apps/sim/lib/billing/webhooks/invoices.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/sim/lib/billing/webhooks/invoices.ts b/apps/sim/lib/billing/webhooks/invoices.ts index 37c17f7e31..6c8e9a6e81 100644 --- a/apps/sim/lib/billing/webhooks/invoices.ts +++ b/apps/sim/lib/billing/webhooks/invoices.ts @@ -7,7 +7,10 @@ import PaymentFailedEmail from '@/components/emails/billing/payment-failed-email import { calculateSubscriptionOverage } from '@/lib/billing/core/billing' import { requireStripeClient } from '@/lib/billing/stripe-client' import { sendEmail } from '@/lib/email/mailer' +<<<<<<< HEAD import { quickValidateEmail } from '@/lib/email/validation' +======= +>>>>>>> 2eb3df14f (feat(billing): add notif for first failed payment, added upgrade email from free, updated providers that supported granular tool control to support them, fixed envvar popover, fixed redirect to wrong workspace after oauth connect) import { createLogger } from '@/lib/logs/console/logger' import { getBaseUrl } from '@/lib/urls/utils' From 7a025db62266dbeaab871ca7d144f6a0b932a6a1 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 15 Nov 2025 16:12:29 -0800 Subject: [PATCH 2/3] feat(performance): added reactquery hooks for workflow operations, for logs, fixed logs reloading, fix subscription UI --- apps/sim/app/api/workflows/route.ts | 22 +- .../logs/components/sidebar/sidebar.tsx | 11 +- .../app/workspace/[workspaceId]/logs/logs.tsx | 402 +++--------------- .../components/panel-new/panel-new.tsx | 36 +- .../[workspaceId]/w/[workflowId]/workflow.tsx | 3 +- .../cancel-subscription.tsx | 8 +- .../components/plan-card/plan-card.tsx | 7 +- .../components/folder-item/folder-item.tsx | 14 +- .../components/create-menu/create-menu.tsx | 18 +- .../sidebar/hooks/use-folder-operations.ts | 2 +- .../sidebar/hooks/use-workflow-operations.ts | 46 +- .../sidebar/hooks/use-workspace-management.ts | 2 +- .../w/components/sidebar/sidebar.tsx | 87 ++-- .../w/hooks/use-import-workflow.ts | 15 +- .../app/workspace/[workspaceId]/w/page.tsx | 36 +- apps/sim/hooks/queries/folders.ts | 16 +- apps/sim/hooks/queries/logs.ts | 168 ++++++++ apps/sim/hooks/queries/workflows.ts | 169 ++++++++ apps/sim/lib/billing/webhooks/invoices.ts | 3 - apps/sim/lib/{ => workspaces}/naming.ts | 154 +------ apps/sim/stores/logs/filters/store.ts | 90 +--- apps/sim/stores/logs/filters/types.ts | 9 - apps/sim/stores/workflows/registry/store.ts | 121 +----- apps/sim/stores/workflows/registry/types.ts | 9 +- apps/sim/stores/workflows/registry/utils.ts | 251 +++++++++++ 25 files changed, 803 insertions(+), 896 deletions(-) create mode 100644 apps/sim/hooks/queries/logs.ts create mode 100644 apps/sim/hooks/queries/workflows.ts rename apps/sim/lib/{ => workspaces}/naming.ts (55%) diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 5664c2bc22..e7b603c7c3 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -118,18 +118,18 @@ export async function POST(req: NextRequest) { logger.info(`[${requestId}] Creating workflow ${workflowId} for user ${session.user.id}`) - // Track workflow creation - try { - const { trackPlatformEvent } = await import('@/lib/telemetry/tracer') - trackPlatformEvent('platform.workflow.created', { - 'workflow.id': workflowId, - 'workflow.name': name, - 'workflow.has_workspace': !!workspaceId, - 'workflow.has_folder': !!folderId, + import('@/lib/telemetry/tracer') + .then(({ trackPlatformEvent }) => { + trackPlatformEvent('platform.workflow.created', { + 'workflow.id': workflowId, + 'workflow.name': name, + 'workflow.has_workspace': !!workspaceId, + 'workflow.has_folder': !!folderId, + }) + }) + .catch(() => { + // Silently fail }) - } catch (_e) { - // Silently fail - } await db.insert(workflow).values({ id: workflowId, 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 2b9cba09a6..a696842a3a 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/sidebar/sidebar.tsx @@ -23,6 +23,7 @@ import '@/components/emcn/components/code/code.css' interface LogSidebarProps { log: WorkflowLog | null isOpen: boolean + isLoadingDetails?: boolean onClose: () => void onNavigateNext?: () => void onNavigatePrev?: () => void @@ -192,6 +193,7 @@ const BlockContentDisplay = ({ export function Sidebar({ log, isOpen, + isLoadingDetails = false, onClose, onNavigateNext, onNavigatePrev, @@ -219,15 +221,6 @@ export function Sidebar({ } }, [log?.id]) - const isLoadingDetails = useMemo(() => { - if (!log) return false - // Only show while we expect details to arrive (has executionId) - if (!log.executionId) return false - const hasEnhanced = !!log.executionData?.enhanced - const hasAnyDetails = hasEnhanced || !!log.cost || Array.isArray(log.executionData?.traceSpans) - return !hasAnyDetails - }, [log]) - const formattedContent = useMemo(() => { if (!log) return null diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx index add261d653..f108170779 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { AlertCircle, ArrowUpRight, Info, Loader2 } from 'lucide-react' import Link from 'next/link' import { useParams } from 'next/navigation' @@ -13,10 +13,11 @@ import { Sidebar } from '@/app/workspace/[workspaceId]/logs/components/sidebar/s import Dashboard from '@/app/workspace/[workspaceId]/logs/dashboard' import { formatDate } from '@/app/workspace/[workspaceId]/logs/utils' import { useFolders } from '@/hooks/queries/folders' +import { useLogDetail, useLogsList } from '@/hooks/queries/logs' import { useDebounce } from '@/hooks/use-debounce' import { useFolderStore } from '@/stores/folders/store' import { useFilterStore } from '@/stores/logs/filters/store' -import type { LogsResponse, WorkflowLog } from '@/stores/logs/filters/types' +import type { WorkflowLog } from '@/stores/logs/filters/types' const logger = createLogger('Logs') const LOGS_PER_PAGE = 50 @@ -64,16 +65,10 @@ export default function Logs() { const { logs, - loading, - error, - setLogs, - setLoading, - setError, setWorkspaceId, page, setPage, hasMore, - setHasMore, isFetchingMore, setIsFetchingMore, initializeFromURL, @@ -95,10 +90,6 @@ export default function Logs() { const [selectedLog, setSelectedLog] = useState(null) const [selectedLogIndex, setSelectedLogIndex] = useState(-1) const [isSidebarOpen, setIsSidebarOpen] = useState(false) - const [isDetailsLoading, setIsDetailsLoading] = useState(false) - const detailsCacheRef = useRef>(new Map()) - const detailsAbortRef = useRef(null) - const currentDetailsIdRef = useRef(null) const selectedRowRef = useRef(null) const loaderRef = useRef(null) const scrollContainerRef = useRef(null) @@ -112,10 +103,31 @@ export default function Logs() { // Live and refresh state const [isLive, setIsLive] = useState(false) - const [isRefreshing, setIsRefreshing] = useState(false) - const liveIntervalRef = useRef(null) const isSearchOpenRef = useRef(false) + // Build filters object for React Query + const logFilters = useMemo( + () => ({ + timeRange, + level, + workflowIds, + folderIds, + triggers, + searchQuery: debouncedSearchQuery, + page, + limit: LOGS_PER_PAGE, + }), + [timeRange, level, workflowIds, folderIds, triggers, debouncedSearchQuery, page] + ) + + // React Query hooks for logs + const logsQuery = useLogsList(workspaceId, logFilters, { + enabled: Boolean(workspaceId) && isInitialized.current, + refetchInterval: isLive ? 5000 : false, + }) + + const logDetailQuery = useLogDetail(selectedLog?.id) + // Sync local search query with store search query useEffect(() => { setSearchQuery(storeSearchQuery) @@ -182,62 +194,7 @@ export default function Logs() { const index = logs.findIndex((l) => l.id === log.id) setSelectedLogIndex(index) setIsSidebarOpen(true) - setIsDetailsLoading(true) - - const currentId = log.id - const prevId = index > 0 ? logs[index - 1]?.id : undefined - const nextId = index < logs.length - 1 ? logs[index + 1]?.id : undefined - - if (detailsAbortRef.current) { - try { - detailsAbortRef.current.abort() - } catch { - /* no-op */ - } - } - const controller = new AbortController() - detailsAbortRef.current = controller - currentDetailsIdRef.current = currentId - - const idsToFetch: Array<{ id: string; merge: boolean }> = [] - const cachedCurrent = currentId ? detailsCacheRef.current.get(currentId) : undefined - if (currentId && !cachedCurrent) idsToFetch.push({ id: currentId, merge: true }) - if (prevId && !detailsCacheRef.current.has(prevId)) - idsToFetch.push({ id: prevId, merge: false }) - if (nextId && !detailsCacheRef.current.has(nextId)) - idsToFetch.push({ id: nextId, merge: false }) - - if (cachedCurrent) { - setSelectedLog((prev) => - prev && prev.id === currentId - ? ({ ...(prev as any), ...(cachedCurrent as any) } as any) - : prev - ) - setIsDetailsLoading(false) - } - if (idsToFetch.length === 0) return - - Promise.all( - idsToFetch.map(async ({ id, merge }) => { - try { - const res = await fetch(`/api/logs/${id}`, { signal: controller.signal }) - if (!res.ok) return - const body = await res.json() - const detailed = body?.data - if (detailed) { - detailsCacheRef.current.set(id, detailed) - if (merge && id === currentId) { - setSelectedLog((prev) => - prev && prev.id === id ? ({ ...(prev as any), ...(detailed as any) } as any) : prev - ) - if (currentDetailsIdRef.current === id) setIsDetailsLoading(false) - } - } - } catch (e: any) { - if (e?.name === 'AbortError') return - } - }) - ).catch(() => {}) + // React Query will automatically fetch details via logDetailQuery } const handleNavigateNext = useCallback(() => { @@ -246,54 +203,7 @@ export default function Logs() { setSelectedLogIndex(nextIndex) const nextLog = logs[nextIndex] setSelectedLog(nextLog) - if (detailsAbortRef.current) { - try { - detailsAbortRef.current.abort() - } catch { - /* no-op */ - } - } - const controller = new AbortController() - detailsAbortRef.current = controller - - const cached = detailsCacheRef.current.get(nextLog.id) - if (cached) { - setSelectedLog((prev) => - prev && prev.id === nextLog.id ? ({ ...(prev as any), ...(cached as any) } as any) : prev - ) - } else { - const prevId = nextIndex > 0 ? logs[nextIndex - 1]?.id : undefined - const afterId = nextIndex < logs.length - 1 ? logs[nextIndex + 1]?.id : undefined - const idsToFetch: Array<{ id: string; merge: boolean }> = [] - if (nextLog.id && !detailsCacheRef.current.has(nextLog.id)) - idsToFetch.push({ id: nextLog.id, merge: true }) - if (prevId && !detailsCacheRef.current.has(prevId)) - idsToFetch.push({ id: prevId, merge: false }) - if (afterId && !detailsCacheRef.current.has(afterId)) - idsToFetch.push({ id: afterId, merge: false }) - Promise.all( - idsToFetch.map(async ({ id, merge }) => { - try { - const res = await fetch(`/api/logs/${id}`, { signal: controller.signal }) - if (!res.ok) return - const body = await res.json() - const detailed = body?.data - if (detailed) { - detailsCacheRef.current.set(id, detailed) - if (merge && id === nextLog.id) { - setSelectedLog((prev) => - prev && prev.id === id - ? ({ ...(prev as any), ...(detailed as any) } as any) - : prev - ) - } - } - } catch (e: any) { - if (e?.name === 'AbortError') return - } - }) - ).catch(() => {}) - } + // React Query will automatically fetch details via logDetailQuery } }, [selectedLogIndex, logs]) @@ -303,54 +213,7 @@ export default function Logs() { setSelectedLogIndex(prevIndex) const prevLog = logs[prevIndex] setSelectedLog(prevLog) - if (detailsAbortRef.current) { - try { - detailsAbortRef.current.abort() - } catch { - /* no-op */ - } - } - const controller = new AbortController() - detailsAbortRef.current = controller - - const cached = detailsCacheRef.current.get(prevLog.id) - if (cached) { - setSelectedLog((prev) => - prev && prev.id === prevLog.id ? ({ ...(prev as any), ...(cached as any) } as any) : prev - ) - } else { - const beforeId = prevIndex > 0 ? logs[prevIndex - 1]?.id : undefined - const afterId = prevIndex < logs.length - 1 ? logs[prevIndex + 1]?.id : undefined - const idsToFetch: Array<{ id: string; merge: boolean }> = [] - if (prevLog.id && !detailsCacheRef.current.has(prevLog.id)) - idsToFetch.push({ id: prevLog.id, merge: true }) - if (beforeId && !detailsCacheRef.current.has(beforeId)) - idsToFetch.push({ id: beforeId, merge: false }) - if (afterId && !detailsCacheRef.current.has(afterId)) - idsToFetch.push({ id: afterId, merge: false }) - Promise.all( - idsToFetch.map(async ({ id, merge }) => { - try { - const res = await fetch(`/api/logs/${id}`, { signal: controller.signal }) - if (!res.ok) return - const body = await res.json() - const detailed = body?.data - if (detailed) { - detailsCacheRef.current.set(id, detailed) - if (merge && id === prevLog.id) { - setSelectedLog((prev) => - prev && prev.id === id - ? ({ ...(prev as any), ...(detailed as any) } as any) - : prev - ) - } - } - } catch (e: any) { - if (e?.name === 'AbortError') return - } - }) - ).catch(() => {}) - } + // React Query will automatically fetch details via logDetailQuery } }, [selectedLogIndex, logs]) @@ -369,102 +232,13 @@ export default function Logs() { } }, [selectedLogIndex]) - const fetchLogs = useCallback(async (pageNum: number, append = false) => { - try { - // Don't fetch if workspaceId is not set - const { workspaceId: storeWorkspaceId } = useFilterStore.getState() - if (!storeWorkspaceId) { - return - } - - if (pageNum === 1) { - setLoading(true) - } else { - setIsFetchingMore(true) - } - - const { buildQueryParams: getCurrentQueryParams } = useFilterStore.getState() - const queryParams = getCurrentQueryParams(pageNum, LOGS_PER_PAGE) - - const { searchQuery: currentSearchQuery } = useFilterStore.getState() - const parsedQuery = parseQuery(currentSearchQuery) - const enhancedParams = queryToApiParams(parsedQuery) - - const allParams = new URLSearchParams(queryParams) - Object.entries(enhancedParams).forEach(([key, value]) => { - if (key === 'triggers' && allParams.has('triggers')) { - const existingTriggers = allParams.get('triggers')?.split(',') || [] - const searchTriggers = value.split(',') - const combined = [...new Set([...existingTriggers, ...searchTriggers])] - allParams.set('triggers', combined.join(',')) - } else { - allParams.set(key, value) - } - }) - - allParams.set('details', 'basic') - const response = await fetch(`/api/logs?${allParams.toString()}`) - - if (!response.ok) { - throw new Error(`Error fetching logs: ${response.statusText}`) - } - - const data: LogsResponse = await response.json() - - setHasMore(data.data.length === LOGS_PER_PAGE && data.page < data.totalPages) - - setLogs(data.data, append) - - setError(null) - } catch (err) { - logger.error('Failed to fetch logs:', { err }) - setError(err instanceof Error ? err.message : 'An unknown error occurred') - } finally { - if (pageNum === 1) { - setLoading(false) - } else { - setIsFetchingMore(false) - } - } - }, []) - const handleRefresh = async () => { - if (isRefreshing) return - - setIsRefreshing(true) - - try { - await fetchLogs(1) - setError(null) - } catch (err) { - setError(err instanceof Error ? err.message : 'An unknown error occurred') - } finally { - setIsRefreshing(false) + await logsQuery.refetch() + if (selectedLog?.id) { + await logDetailQuery.refetch() } } - // Setup or clear the live refresh interval when isLive changes - useEffect(() => { - if (liveIntervalRef.current) { - clearInterval(liveIntervalRef.current) - liveIntervalRef.current = null - } - - if (isLive) { - handleRefresh() - liveIntervalRef.current = setInterval(() => { - handleRefresh() - }, 5000) - } - - return () => { - if (liveIntervalRef.current) { - clearInterval(liveIntervalRef.current) - liveIntervalRef.current = null - } - } - }, [isLive]) - const toggleLive = () => { setIsLive(!isLive) } @@ -506,101 +280,22 @@ export default function Logs() { return () => window.removeEventListener('popstate', handlePopState) }, [initializeFromURL]) + // Reset to page 1 when filters change useEffect(() => { - if (!isInitialized.current) { - return - } - - // Don't fetch if workspaceId is not set yet - if (!workspaceId) { - return - } - - setPage(1) - setHasMore(true) - - const fetchWithFilters = async () => { - try { - setLoading(true) - - const params = new URLSearchParams() - params.set('details', 'basic') - params.set('limit', LOGS_PER_PAGE.toString()) - params.set('offset', '0') // Always start from page 1 - params.set('workspaceId', workspaceId) - - const parsedQuery = parseQuery(debouncedSearchQuery) - const enhancedParams = queryToApiParams(parsedQuery) - - if (level !== 'all') params.set('level', level) - if (triggers.length > 0) params.set('triggers', triggers.join(',')) - if (workflowIds.length > 0) params.set('workflowIds', workflowIds.join(',')) - if (folderIds.length > 0) params.set('folderIds', folderIds.join(',')) - - Object.entries(enhancedParams).forEach(([key, value]) => { - if (key === 'triggers' && params.has('triggers')) { - const storeTriggers = params.get('triggers')?.split(',') || [] - const searchTriggers = value.split(',') - const combined = [...new Set([...storeTriggers, ...searchTriggers])] - params.set('triggers', combined.join(',')) - } else { - params.set(key, value) - } - }) - - if (timeRange !== 'All time') { - const now = new Date() - let startDate: Date - switch (timeRange) { - case 'Past 30 minutes': - startDate = new Date(now.getTime() - 30 * 60 * 1000) - break - case 'Past hour': - startDate = new Date(now.getTime() - 60 * 60 * 1000) - break - case 'Past 24 hours': - startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000) - break - default: - startDate = new Date(0) - } - params.set('startDate', startDate.toISOString()) - } - - const response = await fetch(`/api/logs?${params.toString()}`) - - if (!response.ok) { - throw new Error(`Error fetching logs: ${response.statusText}`) - } - - const data: LogsResponse = await response.json() - setHasMore(data.data.length === LOGS_PER_PAGE && data.page < data.totalPages) - setLogs(data.data, false) - setError(null) - } catch (err) { - logger.error('Failed to fetch logs:', { err }) - setError(err instanceof Error ? err.message : 'An unknown error occurred') - } finally { - setLoading(false) - } + if (isInitialized.current) { + setPage(1) } - - fetchWithFilters() - }, [workspaceId, timeRange, level, workflowIds, folderIds, debouncedSearchQuery, triggers]) + }, [timeRange, level, workflowIds, folderIds, debouncedSearchQuery, triggers]) const loadMoreLogs = useCallback(() => { - if (!isFetchingMore && hasMore) { + if (!logsQuery.isFetching && hasMore) { const nextPage = page + 1 setPage(nextPage) - setIsFetchingMore(true) - setTimeout(() => { - fetchLogs(nextPage, true) - }, 50) } - }, [fetchLogs, isFetchingMore, hasMore, page]) + }, [logsQuery.isFetching, hasMore, page, setPage]) useEffect(() => { - if (loading || !hasMore) return + if (logsQuery.isLoading || !hasMore) return const scrollContainer = scrollContainerRef.current if (!scrollContainer) return @@ -622,13 +317,13 @@ export default function Logs() { return () => { scrollContainer.removeEventListener('scroll', handleScroll) } - }, [loading, hasMore, isFetchingMore, loadMoreLogs]) + }, [logsQuery.isLoading, hasMore, isFetchingMore, loadMoreLogs]) useEffect(() => { const currentLoaderRef = loaderRef.current const scrollContainer = scrollContainerRef.current - if (!currentLoaderRef || !scrollContainer || loading || !hasMore) return + if (!currentLoaderRef || !scrollContainer || logsQuery.isLoading || !hasMore) return const observer = new IntersectionObserver( (entries) => { @@ -652,7 +347,7 @@ export default function Logs() { return () => { observer.unobserve(currentLoaderRef) } - }, [loading, hasMore, isFetchingMore, loadMoreLogs]) + }, [logsQuery.isLoading, hasMore, isFetchingMore, loadMoreLogs]) useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -701,7 +396,7 @@ export default function Logs() {
setIsLive(fn)} @@ -750,18 +445,20 @@ export default function Logs() { {/* Table body - scrollable */}
- {loading && page === 1 ? ( + {logsQuery.isLoading && page === 1 ? (
Loading logs...
- ) : error ? ( + ) : logsQuery.isError ? (
- Error: {error} + + Error: {logsQuery.error?.message || 'Failed to load logs'} +
) : logs.length === 0 ? ( @@ -932,8 +629,9 @@ export default function Logs() { {/* Log Sidebar */} (null) + const fileInputRef = useRef(null) const { activeTab, setActiveTab, panelWidth, _hasHydrated, setHasHydrated } = usePanelStore() const copilotRef = useRef<{ createNewChat: () => void @@ -77,6 +79,7 @@ export function Panel() { // Hooks const userPermissions = useUserPermissionsContext() + const { isImporting, handleFileChange } = useImportWorkflow({ workspaceId }) const { workflows, activeWorkflowId, @@ -262,6 +265,14 @@ export function Panel() { workspaceId, ]) + /** + * Handles triggering file input for workflow import + */ + const handleImportWorkflow = useCallback(() => { + setIsMenuOpen(false) + fileInputRef.current?.click() + }, []) + // Compute run button state const canRun = userPermissions.canRead // Running only requires read permissions const isLoadingPermissions = userPermissions.isLoading @@ -314,7 +325,7 @@ export function Panel() { { setVariablesOpen(!isVariablesOpen)}> - + Variables } @@ -331,7 +342,14 @@ export function Panel() { disabled={isExporting || !currentWorkflow} > - Export JSON + Export workflow + + + + Import workflow + + {/* Hidden file input for workflow import */} + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index f47e7732dd..71eb76a46c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -116,8 +116,7 @@ const WorkflowContent = React.memo(() => { // Get workspace ID from the params const workspaceId = params.workspaceId as string - const { workflows, activeWorkflowId, isLoading, setActiveWorkflow, createWorkflow } = - useWorkflowRegistry() + const { workflows, activeWorkflowId, isLoading, setActiveWorkflow } = useWorkflowRegistry() // Use the clean abstraction for current workflow state const currentWorkflow = useCurrentWorkflow() diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx index c11b6b5d68..38696c88f6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx @@ -227,12 +227,8 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub onClick={() => setIsDialogOpen(true)} disabled={isLoading} className={cn( - 'h-8 rounded-[8px] font-medium text-xs transition-all duration-200', - error - ? 'border-red-500 text-red-500 dark:border-red-500 dark:text-red-500' - : isCancelAtPeriodEnd - ? 'text-muted-foreground hover:border-green-500 hover:bg-green-500 hover:text-white dark:hover:border-green-500 dark:hover:bg-green-500' - : 'text-muted-foreground hover:border-red-500 hover:bg-red-500 hover:text-white dark:hover:border-red-500 dark:hover:bg-red-500' + 'h-8 rounded-[8px] font-medium text-xs', + error && 'border-red-500 text-red-500 dark:border-red-500 dark:text-red-500' )} > {error ? 'Error' : isCancelAtPeriodEnd ? 'Restore' : 'Manage'} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/plan-card/plan-card.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/plan-card/plan-card.tsx index a8599da299..46009c821a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/plan-card/plan-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/components/plan-card/plan-card.tsx @@ -107,12 +107,11 @@ export function PlanCard({