Renders an advisory ETA badge above the Schedule button: * Green "Fits before deadline" when the projected finish lands before the chosen deadline hour. * Amber "Likely to pause" with a "Push the deadline later or split into smaller runs" hint when it doesn't. Pill is purely informational — the operator can still schedule a run that's likely to pause; the pause/resume flow (Phase 3) covers that case. The pill just removes the surprise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { CalendarCheckIcon, AlertCircleIcon, Loader2Icon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { createReminderAction, updateReminderAction } from "@/actions/reminders";
|
|
import { cn } from "@/lib/utils";
|
|
import type { MessagePart } from "@/lib/reminder-messages";
|
|
import { RunEtaPill } from "./run-eta-pill";
|
|
import { windowEndAt } from "@cmbot/shared";
|
|
|
|
interface ReviewSubmitClientProps {
|
|
accountId: string;
|
|
groupIds?: string;
|
|
name?: string;
|
|
messages: MessagePart[];
|
|
scheduledAt: string;
|
|
rrule?: string;
|
|
editReminderId?: string;
|
|
timezone: string;
|
|
deliveryEndHour?: number;
|
|
}
|
|
|
|
export function ReviewSubmitClient({
|
|
accountId,
|
|
groupIds,
|
|
name,
|
|
messages,
|
|
scheduledAt,
|
|
rrule,
|
|
editReminderId,
|
|
timezone,
|
|
deliveryEndHour,
|
|
}: ReviewSubmitClientProps) {
|
|
const router = useRouter();
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
async function handleSchedule() {
|
|
const trimmedName = name?.trim();
|
|
if (!trimmedName) {
|
|
// The wizard's compose step now blocks Continue when the name is
|
|
// blank, so the only way to land here without one is a stale
|
|
// bookmarked URL. Bounce the operator back to step 2 with a
|
|
// clear error rather than letting the server reject it.
|
|
setError("Give the reminder a name (back on the Message step).");
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const payload = {
|
|
accountId,
|
|
groupIds: groupIds ? groupIds.split(",").filter(Boolean) : [],
|
|
name: trimmedName,
|
|
messages,
|
|
scheduledAtIso: scheduledAt,
|
|
rrule: rrule ?? null,
|
|
timezone,
|
|
deliveryWindowEndHour: deliveryEndHour,
|
|
};
|
|
const result = editReminderId
|
|
? await updateReminderAction({ ...payload, reminderId: editReminderId })
|
|
: await createReminderAction(payload);
|
|
|
|
if (result.ok) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
router.push(`/reminders/${result.reminderId}` as any);
|
|
} else {
|
|
setError(result.error);
|
|
setSubmitting(false);
|
|
}
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "An unexpected error occurred.");
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
const groupCount = groupIds ? groupIds.split(",").filter(Boolean).length : 0;
|
|
const fireAt = new Date(scheduledAt);
|
|
const endHour = deliveryEndHour ?? 18;
|
|
const wEnd = windowEndAt(timezone, endHour, fireAt);
|
|
|
|
return (
|
|
<div className="space-y-3 pt-2">
|
|
<RunEtaPill
|
|
targetCount={groupCount}
|
|
fireAt={fireAt}
|
|
windowEndAt={wEnd}
|
|
timezone={timezone}
|
|
/>
|
|
|
|
{error && (
|
|
<div className="flex items-start gap-2 rounded-lg bg-destructive/10 px-3 py-2.5 text-sm text-destructive">
|
|
<AlertCircleIcon className="size-4 shrink-0 mt-0.5" />
|
|
<span>{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex justify-end">
|
|
<Button
|
|
type="button"
|
|
size="lg"
|
|
onClick={handleSchedule}
|
|
disabled={submitting}
|
|
className={cn("gap-2", submitting && "cursor-wait")}
|
|
>
|
|
{submitting ? (
|
|
<>
|
|
<Loader2Icon className="size-4 animate-spin" />
|
|
{editReminderId ? "Saving…" : "Scheduling…"}
|
|
</>
|
|
) : (
|
|
<>
|
|
<CalendarCheckIcon className="size-4" />
|
|
{editReminderId ? "Save changes" : "Schedule Reminder"}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|