mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-30 10:56:29 +02:00
Trigger changes (#232)
* new external trigger page is shown if no external triggers are setup yet * make one time trigger default to new trigger * default to new trigger in recurring triggers * remove back button if no triggers are setup yet
This commit is contained in:
parent
d899966107
commit
f772088e90
5 changed files with 137 additions and 29 deletions
|
|
@ -25,7 +25,15 @@ const commonCronExamples = [
|
|||
{ label: "Monthly on the 1st at midnight", value: "0 0 1 * *" },
|
||||
];
|
||||
|
||||
export function CreateRecurringJobRuleForm({ projectId }: { projectId: string }) {
|
||||
export function CreateRecurringJobRuleForm({
|
||||
projectId,
|
||||
onBack,
|
||||
hasExistingTriggers = true
|
||||
}: {
|
||||
projectId: string;
|
||||
onBack?: () => void;
|
||||
hasExistingTriggers?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [messages, setMessages] = useState<FormMessage[]>([
|
||||
|
|
@ -89,7 +97,11 @@ export function CreateRecurringJobRuleForm({ projectId }: { projectId: string })
|
|||
input: { messages: convertedMessages },
|
||||
cron: cronExpression,
|
||||
});
|
||||
router.push(`/projects/${projectId}/manage-triggers?tab=recurring`);
|
||||
if (onBack) {
|
||||
onBack();
|
||||
} else {
|
||||
router.push(`/projects/${projectId}/manage-triggers?tab=recurring`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create recurring job rule:", error);
|
||||
alert("Failed to create recurring job rule");
|
||||
|
|
@ -102,11 +114,23 @@ export function CreateRecurringJobRuleForm({ projectId }: { projectId: string })
|
|||
<Panel
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/projects/${projectId}/manage-triggers?tab=recurring`}>
|
||||
<Button variant="secondary" size="sm" startContent={<ArrowLeftIcon className="w-4 h-4" />} className="whitespace-nowrap">
|
||||
{hasExistingTriggers && onBack ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
startContent={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
className="whitespace-nowrap"
|
||||
onClick={onBack}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Link>
|
||||
) : hasExistingTriggers ? (
|
||||
<Link href={`/projects/${projectId}/manage-triggers?tab=recurring`}>
|
||||
<Button variant="secondary" size="sm" startContent={<ArrowLeftIcon className="w-4 h-4" />} className="whitespace-nowrap">
|
||||
Back
|
||||
</Button>
|
||||
</Link>
|
||||
) : null}
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
CREATE RECURRING JOB RULE
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import { listRecurringJobRules, deleteRecurringJobRule } from "@/app/actions/rec
|
|||
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, Trash2 } from "lucide-react";
|
||||
import { PlusIcon, Trash2, ArrowLeftIcon } from "lucide-react";
|
||||
import { CreateRecurringJobRuleForm } from "./create-recurring-job-rule-form";
|
||||
|
||||
type ListedItem = z.infer<typeof ListedRecurringRuleItem>;
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
|
|||
const [loadingMore, setLoadingMore] = useState<boolean>(false);
|
||||
const [hasMore, setHasMore] = useState<boolean>(false);
|
||||
const [deletingRule, setDeletingRule] = useState<string | null>(null);
|
||||
const [showCreateForm, setShowCreateForm] = useState<boolean>(false);
|
||||
|
||||
const fetchPage = useCallback(async (cursorArg?: string | null) => {
|
||||
const res = await listRecurringJobRules({ projectId, cursor: cursorArg ?? undefined, limit: 20 });
|
||||
|
|
@ -39,6 +41,12 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
|
|||
return () => { ignore = true; };
|
||||
}, [fetchPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && items.length === 0 && !showCreateForm) {
|
||||
setShowCreateForm(true);
|
||||
}
|
||||
}, [loading, items.length, showCreateForm]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (!cursor) return;
|
||||
setLoadingMore(true);
|
||||
|
|
@ -49,6 +57,24 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
|
|||
setLoadingMore(false);
|
||||
}, [cursor, fetchPage]);
|
||||
|
||||
const handleCreateNew = () => {
|
||||
setShowCreateForm(true);
|
||||
};
|
||||
|
||||
const handleBackToList = () => {
|
||||
setShowCreateForm(false);
|
||||
// Reload the list in case new triggers were created
|
||||
const reload = async () => {
|
||||
setLoading(true);
|
||||
const res = await fetchPage(null);
|
||||
setItems(res.items);
|
||||
setCursor(res.nextCursor);
|
||||
setHasMore(Boolean(res.nextCursor));
|
||||
setLoading(false);
|
||||
};
|
||||
reload();
|
||||
};
|
||||
|
||||
const handleDeleteRule = async (ruleId: string) => {
|
||||
if (!window.confirm('Are you sure you want to delete this recurring trigger?')) {
|
||||
return;
|
||||
|
|
@ -125,6 +151,10 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
|
|||
return cron;
|
||||
};
|
||||
|
||||
if (showCreateForm) {
|
||||
return <CreateRecurringJobRuleForm projectId={projectId} onBack={handleBackToList} hasExistingTriggers={items.length > 0} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
|
|
@ -134,11 +164,14 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) {
|
|||
}
|
||||
rightActions={
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/projects/${projectId}/manage-triggers/recurring/new`}>
|
||||
<Button size="sm" className="whitespace-nowrap" startContent={<PlusIcon className="w-4 h-4" />}>
|
||||
New Recurring Trigger
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
size="sm"
|
||||
className="whitespace-nowrap"
|
||||
startContent={<PlusIcon className="w-4 h-4" />}
|
||||
onClick={handleCreateNew}
|
||||
>
|
||||
New Recurring Trigger
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,12 @@ export function TriggersTab({ projectId }: { projectId: string }) {
|
|||
}
|
||||
}, [showCreateFlow, loadTriggers]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !error && triggers.length === 0 && !showCreateFlow) {
|
||||
setShowCreateFlow(true);
|
||||
}
|
||||
}, [loading, error, triggers.length, showCreateFlow]);
|
||||
|
||||
useEffect(() => {
|
||||
// No-op: trigger names are now derived from slug locally
|
||||
}, [triggers]);
|
||||
|
|
@ -457,14 +463,16 @@ export function TriggersTab({ projectId }: { projectId: string }) {
|
|||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
Select a Toolkit to Create Trigger
|
||||
</h3>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBackToList}
|
||||
startContent={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
Back to Triggers
|
||||
</Button>
|
||||
{triggers.length > 0 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBackToList}
|
||||
startContent={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
Back to Triggers
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SelectComposioToolkit
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type FormMessage = {
|
|||
content: string;
|
||||
};
|
||||
|
||||
export function CreateScheduledJobRuleForm({ projectId }: { projectId: string }) {
|
||||
export function CreateScheduledJobRuleForm({ projectId, onBack, hasExistingTriggers = true }: { projectId: string; onBack?: () => void; hasExistingTriggers?: boolean }) {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [messages, setMessages] = useState<FormMessage[]>([
|
||||
|
|
@ -90,7 +90,11 @@ export function CreateScheduledJobRuleForm({ projectId }: { projectId: string })
|
|||
input: { messages: convertedMessages },
|
||||
scheduledTime: scheduledTimeString,
|
||||
});
|
||||
router.push(`/projects/${projectId}/manage-triggers?tab=scheduled`);
|
||||
if (onBack) {
|
||||
onBack();
|
||||
} else {
|
||||
router.push(`/projects/${projectId}/manage-triggers?tab=scheduled`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create scheduled job rule:", error);
|
||||
alert("Failed to create scheduled job rule");
|
||||
|
|
@ -105,11 +109,17 @@ export function CreateScheduledJobRuleForm({ projectId }: { projectId: string })
|
|||
<Panel
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/projects/${projectId}/manage-triggers?tab=scheduled`}>
|
||||
<Button variant="secondary" size="sm" startContent={<ArrowLeftIcon className="w-4 h-4" />} className="whitespace-nowrap">
|
||||
{hasExistingTriggers && onBack ? (
|
||||
<Button variant="secondary" size="sm" startContent={<ArrowLeftIcon className="w-4 h-4" />} className="whitespace-nowrap" onClick={onBack}>
|
||||
Back
|
||||
</Button>
|
||||
</Link>
|
||||
) : hasExistingTriggers ? (
|
||||
<Link href={`/projects/${projectId}/manage-triggers?tab=scheduled`}>
|
||||
<Button variant="secondary" size="sm" startContent={<ArrowLeftIcon className="w-4 h-4" />} className="whitespace-nowrap">
|
||||
Back
|
||||
</Button>
|
||||
</Link>
|
||||
) : null}
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
CREATE SCHEDULED JOB RULE
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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, Trash2 } from "lucide-react";
|
||||
import { CreateScheduledJobRuleForm } from "./create-scheduled-job-rule-form";
|
||||
|
||||
type ListedItem = z.infer<typeof ListedRuleItem>;
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
|
|||
const [loadingMore, setLoadingMore] = useState<boolean>(false);
|
||||
const [hasMore, setHasMore] = useState<boolean>(false);
|
||||
const [deletingRule, setDeletingRule] = useState<string | null>(null);
|
||||
const [showCreateFlow, setShowCreateFlow] = useState(false);
|
||||
|
||||
const fetchPage = useCallback(async (cursorArg?: string | null) => {
|
||||
const res = await listScheduledJobRules({ projectId, cursor: cursorArg ?? undefined, limit: 20 });
|
||||
|
|
@ -39,6 +41,12 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
|
|||
return () => { ignore = true; };
|
||||
}, [fetchPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && items.length === 0 && !showCreateFlow) {
|
||||
setShowCreateFlow(true);
|
||||
}
|
||||
}, [loading, items.length, showCreateFlow]);
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (!cursor) return;
|
||||
setLoadingMore(true);
|
||||
|
|
@ -67,6 +75,29 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
|
|||
}
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
setShowCreateFlow(true);
|
||||
};
|
||||
|
||||
const handleBackToList = () => {
|
||||
setShowCreateFlow(false);
|
||||
// Reload the list to show any newly created triggers
|
||||
const loadTriggers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetchPage(null);
|
||||
setItems(response.items);
|
||||
setCursor(response.nextCursor);
|
||||
setHasMore(Boolean(response.nextCursor));
|
||||
} catch (err: any) {
|
||||
console.error('Error loading triggers:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadTriggers();
|
||||
};
|
||||
|
||||
const sections = useMemo(() => {
|
||||
const groups: Record<string, ListedItem[]> = {
|
||||
Today: [],
|
||||
|
|
@ -103,6 +134,10 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
|
|||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
if (showCreateFlow) {
|
||||
return <CreateScheduledJobRuleForm projectId={projectId} onBack={handleBackToList} hasExistingTriggers={items.length > 0} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
|
|
@ -112,11 +147,9 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) {
|
|||
}
|
||||
rightActions={
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href={`/projects/${projectId}/manage-triggers/scheduled/new`}>
|
||||
<Button size="sm" className="whitespace-nowrap" startContent={<PlusIcon className="w-4 h-4" />}>
|
||||
New One-time Trigger
|
||||
</Button>
|
||||
</Link>
|
||||
<Button size="sm" className="whitespace-nowrap" startContent={<PlusIcon className="w-4 h-4" />} onClick={handleCreateNew}>
|
||||
New One-time Trigger
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue