From e4486010465ad63d60a61320b72a7e110f2a7e2c Mon Sep 17 00:00:00 2001 From: akhisud3195 Date: Tue, 19 Aug 2025 13:03:39 +0530 Subject: [PATCH] Update all triggers to display standard cards for existing triggers with delete buttons --- .../job-rules/components/job-rules-tabs.tsx | 6 +- .../components/recurring-job-rules-list.tsx | 96 +++-- .../job-rules/components/triggers-tab.tsx | 349 +++++++++++++----- .../components/scheduled-job-rules-list.tsx | 83 +++-- 4 files changed, 381 insertions(+), 153 deletions(-) diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/job-rules-tabs.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/job-rules-tabs.tsx index 32d01d2d..7afb463a 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/job-rules-tabs.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/job-rules-tabs.tsx @@ -21,13 +21,13 @@ export function JobRulesTabs({ projectId }: { projectId: string }) { aria-label="Job Rules" fullWidth > - + - + - + diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx index eeb12d12..008ae461 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx @@ -4,11 +4,11 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Link, Spinner } from "@heroui/react"; import { Button } from "@/components/ui/button"; import { Panel } from "@/components/common/panel-common"; -import { listRecurringJobRules } from "@/app/actions/recurring-job-rules.actions"; +import { listRecurringJobRules, deleteRecurringJobRule } from "@/app/actions/recurring-job-rules.actions"; import { z } from "zod"; import { ListedRecurringRuleItem } from "@/src/application/repositories/recurring-job-rules.repository.interface"; import { isToday, isThisWeek, isThisMonth } from "@/lib/utils/date"; -import { PlusIcon } from "lucide-react"; +import { PlusIcon, Trash2 } from "lucide-react"; type ListedItem = z.infer; @@ -18,6 +18,7 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) { const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(false); + const [deletingRule, setDeletingRule] = useState(null); const fetchPage = useCallback(async (cursorArg?: string | null) => { const res = await listRecurringJobRules({ projectId, cursor: cursorArg ?? undefined, limit: 20 }); @@ -48,6 +49,24 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) { setLoadingMore(false); }, [cursor, fetchPage]); + const handleDeleteRule = async (ruleId: string) => { + if (!window.confirm('Are you sure you want to delete this recurring trigger?')) { + return; + } + + try { + setDeletingRule(ruleId); + await deleteRecurringJobRule({ projectId, ruleId }); + // Remove the deleted item from the list + setItems(prev => prev.filter(item => item.id !== ruleId)); + } catch (err: any) { + console.error('Error deleting recurring trigger:', err); + alert('Failed to delete recurring trigger. Please try again.'); + } finally { + setDeletingRule(null); + } + }; + const sections = useMemo(() => { const groups: Record = { Today: [], @@ -109,18 +128,15 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) { return ( -
- RECURRING JOB RULES -
+
+ Run your assistant workflow on an automated repeating schedule (cron jobs).
} rightActions={
-
@@ -145,38 +161,48 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
{sectionItems.map((item) => ( - -
+
-
- - {getStatusText(item.disabled, item.lastError || null)} - - - Next run: {formatNextRunAt(item.nextRunAt)} - -
-
- Schedule: {formatCronExpression(item.cron)} -
-
- Created: {new Date(item.createdAt).toLocaleDateString()} -
- {item.lastError && ( -
- Last error: {item.lastError} + +
+ + {getStatusText(item.disabled, item.lastError || null)} + + + Next run: {formatNextRunAt(item.nextRunAt)} +
- )} -
-
- {new Date(item.createdAt).toLocaleDateString()} +
+ Schedule: {formatCronExpression(item.cron)} +
+
+ Created: {new Date(item.createdAt).toLocaleDateString()} +
+ {item.lastError && ( +
+ Last error: {item.lastError} +
+ )} +
+
- +
))}
@@ -184,7 +210,7 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) { })} {items.length === 0 && !loading && (
- No recurring job rules found. Create your first rule to get started. + No recurring triggers yet. Create your first recurring trigger to get started.
)} {hasMore && ( diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx index 9d7d9afe..ec0f2922 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx @@ -1,12 +1,15 @@ 'use client'; -import React, { useState, useEffect, useCallback } from 'react'; -import { Button, Spinner, Card, CardBody, CardHeader } from '@heroui/react'; -import { Plus, Trash2, ZapIcon } from 'lucide-react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { Spinner } from '@heroui/react'; +import { Button } from '@/components/ui/button'; +import { Panel } from '@/components/common/panel-common'; +import { Plus, Trash2, ZapIcon, ChevronDown, ChevronUp } from 'lucide-react'; import { z } from 'zod'; import { ComposioTriggerDeployment } from '@/src/entities/models/composio-trigger-deployment'; import { ComposioTriggerType } from '@/src/entities/models/composio-trigger-type'; -import { listComposioTriggerDeployments, deleteComposioTriggerDeployment, createComposioTriggerDeployment } from '@/app/actions/composio.actions'; +import { isToday, isThisWeek, isThisMonth } from '@/lib/utils/date'; +import { listComposioTriggerDeployments, deleteComposioTriggerDeployment, createComposioTriggerDeployment, listComposioTriggerTypes } from '@/app/actions/composio.actions'; import { SelectComposioToolkit } from '../../tools/components/SelectComposioToolkit'; import { ComposioTriggerTypesPanel } from '../../workflow/components/ComposioTriggerTypesPanel'; import { TriggerConfigForm } from '../../workflow/components/TriggerConfigForm'; @@ -28,6 +31,11 @@ export function TriggersTab({ projectId }: { projectId: string }) { const [isSubmittingTrigger, setIsSubmittingTrigger] = useState(false); const [deletingTrigger, setDeletingTrigger] = useState(null); const [projectConfig, setProjectConfig] = useState | null>(null); + const [triggerTypeNames, setTriggerTypeNames] = useState>({}); + const [expandedTrigger, setExpandedTrigger] = useState(null); + const [cursor, setCursor] = useState(null); + const [hasMore, setHasMore] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); const loadProjectConfig = useCallback(async () => { try { @@ -38,12 +46,56 @@ export function TriggersTab({ projectId }: { projectId: string }) { } }, [projectId]); + const loadTriggerTypeNames = useCallback(async () => { + try { + const names: Record = {}; + + // Get unique toolkit slugs from existing triggers + const uniqueToolkits = [...new Set(triggers.map(t => t.toolkitSlug))]; + + // Fetch trigger types for each toolkit + for (const toolkitSlug of uniqueToolkits) { + try { + const response = await listComposioTriggerTypes(toolkitSlug); + response.items.forEach(triggerType => { + names[triggerType.slug] = triggerType.name; + }); + } catch (err) { + console.error(`Error fetching trigger types for ${toolkitSlug}:`, err); + } + } + + setTriggerTypeNames(names); + } catch (err: any) { + console.error('Error loading trigger type names:', err); + } + }, [triggers]); + + const sections = useMemo(() => { + const groups: Record = { + Today: [], + 'This week': [], + 'This month': [], + Older: [], + }; + for (const trigger of triggers) { + const d = new Date(trigger.createdAt); + if (isToday(d)) groups['Today'].push(trigger); + else if (isThisWeek(d)) groups['This week'].push(trigger); + else if (isThisMonth(d)) groups['This month'].push(trigger); + else groups['Older'].push(trigger); + } + return groups; + }, [triggers]); + const loadTriggers = useCallback(async () => { try { setLoading(true); setError(null); const response = await listComposioTriggerDeployments({ projectId }); setTriggers(response.items); + setCursor(response.nextCursor); + setHasMore(Boolean(response.nextCursor)); } catch (err: any) { console.error('Error loading triggers:', err); setError('Failed to load triggers. Please try again.'); @@ -52,6 +104,21 @@ export function TriggersTab({ projectId }: { projectId: string }) { } }, [projectId]); + const loadMore = useCallback(async () => { + if (!cursor) return; + setLoadingMore(true); + try { + const response = await listComposioTriggerDeployments({ projectId, cursor }); + setTriggers(prev => [...prev, ...response.items]); + setCursor(response.nextCursor); + setHasMore(Boolean(response.nextCursor)); + } catch (err: any) { + console.error('Error loading more triggers:', err); + } finally { + setLoadingMore(false); + } + }, [cursor, projectId]); + const handleDeleteTrigger = async (deploymentId: string) => { if (!window.confirm('Are you sure you want to delete this trigger?')) { return; @@ -79,6 +146,7 @@ export function TriggersTab({ projectId }: { projectId: string }) { setSelectedTriggerType(null); setShowAuthModal(false); setIsSubmittingTrigger(false); + setExpandedTrigger(null); // Reset expanded state loadTriggers(); // Reload in case any triggers were created }; @@ -157,106 +225,217 @@ export function TriggersTab({ projectId }: { projectId: string }) { } }, [showCreateFlow, loadTriggers]); + useEffect(() => { + if (triggers.length > 0) { + loadTriggerTypeNames(); + } + }, [triggers, loadTriggerTypeNames]); + const renderTriggerList = () => { if (loading) { return ( -
- - Loading triggers... -
+ + Loading your triggers + + } + > +
+
+
+ + Loading triggers... +
+
+
+
); } if (error) { return ( -
-

{error}

- -
+ + Error loading your triggers + + } + rightActions={ + + } + > +
+
+
+

{error}

+
+
+
+
); } if (triggers.length === 0) { return ( -
- -

- No triggers configured -

-

- Set up your first trigger to listen for events from your connected apps. -

- -
+ + Listen for events from connected apps to run your assistant workflow automatically. + + } + rightActions={ + + } + > +
+
+
+ +

+ No external triggers yet +

+

+ Create your first external trigger to listen for events from your connected apps. +

+
+
+
+
); } return ( -
-
-

- Active Triggers ({triggers.length}) -

+ + Listen for events from connected apps to run your assistant workflow automatically. +
+ } + rightActions={ -
- -
- {triggers.map((trigger) => ( - - -
-

- {trigger.triggerTypeSlug} -

-

- Created {new Date(trigger.createdAt).toLocaleDateString()} -

-
- -
- -
-

Trigger ID: {trigger.triggerId}

-

Connected Account: {trigger.connectedAccountId}

- {Object.keys(trigger.triggerConfig).length > 0 && ( -
- Configuration: -
-                        {JSON.stringify(trigger.triggerConfig, null, 2)}
-                      
+ } + > +
+
+
+ {Object.entries(sections).map(([sectionName, sectionTriggers]) => { + if (sectionTriggers.length === 0) return null; + return ( +
+

+ {sectionName} +

+
+ {sectionTriggers.map((trigger) => ( +
+
+
+
+ + Active + + + {triggerTypeNames[trigger.triggerTypeSlug] || trigger.triggerTypeSlug} + +
+
+ Created: {new Date(trigger.createdAt).toLocaleDateString()} +
+ {Object.keys(trigger.triggerConfig).length > 0 && ( +
+ Configuration: {Object.keys(trigger.triggerConfig).length} settings +
+ )} +
+ +
+ + {/* Advanced Details Section - Collapsible */} +
+ + + {expandedTrigger === trigger.id && ( +
+
+ Slug: {trigger.triggerTypeSlug} +
+
+ Trigger ID: {trigger.triggerId} +
+
+ Connected Account: {trigger.connectedAccountId} +
+
+ )} +
+
+ ))}
- )} +
+ ); + })} + + {hasMore && ( +
+
- - - ))} + )} +
+
-
+ ); }; @@ -288,8 +467,8 @@ export function TriggersTab({ projectId }: { projectId: string }) { Select a Toolkit to Create Trigger @@ -320,11 +499,7 @@ export function TriggersTab({ projectId }: { projectId: string }) { return ( <> -
-
- {showCreateFlow ? renderCreateFlow() : renderTriggerList()} -
-
+ {showCreateFlow ? renderCreateFlow() : renderTriggerList()} {/* Auth Modal */} {selectedToolkit && ( diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx index 24ee11a9..da980452 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx @@ -4,11 +4,11 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Link, Spinner } from "@heroui/react"; import { Button } from "@/components/ui/button"; import { Panel } from "@/components/common/panel-common"; -import { listScheduledJobRules } from "@/app/actions/scheduled-job-rules.actions"; +import { listScheduledJobRules, deleteScheduledJobRule } from "@/app/actions/scheduled-job-rules.actions"; import { z } from "zod"; import { ListedRuleItem } from "@/src/application/repositories/scheduled-job-rules.repository.interface"; import { isToday, isThisWeek, isThisMonth } from "@/lib/utils/date"; -import { PlusIcon } from "lucide-react"; +import { PlusIcon, Trash2 } from "lucide-react"; type ListedItem = z.infer; @@ -18,6 +18,7 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(false); + const [deletingRule, setDeletingRule] = useState(null); const fetchPage = useCallback(async (cursorArg?: string | null) => { const res = await listScheduledJobRules({ projectId, cursor: cursorArg ?? undefined, limit: 20 }); @@ -48,6 +49,24 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { setLoadingMore(false); }, [cursor, fetchPage]); + const handleDeleteRule = async (ruleId: string) => { + if (!window.confirm('Are you sure you want to delete this one-time trigger?')) { + return; + } + + try { + setDeletingRule(ruleId); + await deleteScheduledJobRule({ projectId, ruleId }); + // Remove the deleted item from the list + setItems(prev => prev.filter(item => item.id !== ruleId)); + } catch (err: any) { + console.error('Error deleting one-time trigger:', err); + alert('Failed to delete one-time trigger. Please try again.'); + } finally { + setDeletingRule(null); + } + }; + const sections = useMemo(() => { const groups: Record = { Today: [], @@ -87,18 +106,15 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { return ( -
- SCHEDULED JOB RULES -
+
+ Schedule a single job to run your assistant workflow at a specific date and time.
} rightActions={
-
@@ -123,30 +139,40 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
{sectionItems.map((item) => ( - -
+
-
- - {getStatusText(item.status, item.processedAt || null)} - - - Next run: {formatNextRunAt(item.nextRunAt)} - -
-
- Created: {new Date(item.createdAt).toLocaleDateString()} -
-
-
- {new Date(item.createdAt).toLocaleDateString()} + +
+ + {getStatusText(item.status, item.processedAt || null)} + + + Next run: {formatNextRunAt(item.nextRunAt)} + +
+
+ Created: {new Date(item.createdAt).toLocaleDateString()} +
+
+
- +
))}
@@ -154,7 +180,7 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { })} {items.length === 0 && !loading && (
- No scheduled job rules found. Create your first rule to get started. + No one-time triggers yet. Create your first one-time trigger to get started.
)} {hasMore && ( @@ -183,3 +209,4 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { ); } +