Reminders
- Reminder list / detail show recurrence summary ("Every Mon, Wed, Fri",
"Every 2 weeks until 2027-01-01", etc).
- Detail page reorganised: each section (Account / Message / When /
Groups) is itself a clickable card that deep-links into the wizard
step in edit mode (editReminderId URL param). No standalone Edit
button. Run history stays read-only.
- New /reminders/[id]/edit shell loads the row, encodes its state into
wizard URL params, and forwards to /reminders/new. The wizard
threads editReminderId through every step.
- updateReminderAction: validates ownership of both the existing
reminder and the (possibly changed) target account, replaces targets
+ messages wholesale, re-arms the pg-boss job (singleton key picks
up the new fire time).
- Wizard submit branches to updateReminderAction when editReminderId
is set; button reads "Save changes" / "Saving…".
- Wizard default first-fire is now the current minute in the operator
zone (not now+1h). Same-minute clicks bump silently to next minute
via a 60 s grace window so the user isn't punished.
- /reminders empty state is filter-aware: "No failed reminders yet."
when ?filter=failed and there are reminders in other states.
Recurrence
- Spec is now a structured object: { kind, interval, weeklyDays,
monthDay, end }. Builder produces RRULEs with INTERVAL, BYDAY,
BYMONTHDAY, COUNT, UNTIL as appropriate. specFromRrule round-trips
for resuming/edit.
- When-step UI: frequency pills, "Every N days/weeks/…" interval,
weekday picker (weekly), day-of-month input (monthly), end picker
(Never / After N occurrences / On date), live human-readable
summary preview.
QR pairing
- Throttle QR refresh to once per 25 s and detach the previous
per-account session listener on Re-pair so listeners can't
accumulate. The UI countdown was flicking every ~5 s because each
Re-pair attached an extra listener — every Baileys QR event then
triggered a fresh DB write + NOTIFY.
Tests (60 green total, +33 in this batch)
- recurrence.test.ts: extended to 25 tests covering interval,
monthday, end conditions (COUNT/UNTIL), and round-trip parsing.
- date-picker.test.ts: 14 tests for splitDateTime / combineDateTime /
validateScheduledAt (incl. the "click-too-fast" same-minute grace)
and defaultFirstFireIso.
- /api/qr/[accountId] route.test.ts: 4 tests — 404 when no QR yet,
404 on missing row, 200 with image/png + no-store + correct PNG
bytes, and verifies the where-clause queries by accountId.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
1.6 KiB
TypeScript
64 lines
1.6 KiB
TypeScript
import Link from "next/link";
|
|
import { redirect } from "next/navigation";
|
|
import { ArrowLeftIcon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ComposeFormClient } from "./compose-form-client";
|
|
|
|
interface StepComposeParams {
|
|
step?: string;
|
|
accountId?: string;
|
|
groupIds?: string;
|
|
text?: string;
|
|
mediaId?: string;
|
|
caption?: string;
|
|
scheduledAt?: string;
|
|
rrule?: string;
|
|
editReminderId?: string;
|
|
}
|
|
|
|
interface StepComposeProps {
|
|
params: StepComposeParams;
|
|
}
|
|
|
|
export function StepCompose({ params }: StepComposeProps) {
|
|
const { accountId, groupIds, text, mediaId, caption } = params;
|
|
|
|
if (!accountId) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
redirect("/reminders/new" as any);
|
|
}
|
|
|
|
const backHref = `/reminders/new?step=1` as const;
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Button asChild variant="ghost" size="sm" className="-ml-2">
|
|
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
<Link href={backHref as any}>
|
|
<ArrowLeftIcon />
|
|
Back
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
Write your reminder message. You can also attach an image or document.
|
|
</p>
|
|
|
|
<ComposeFormClient
|
|
accountId={accountId}
|
|
groupIds={groupIds ?? ""}
|
|
initialText={text ?? ""}
|
|
initialMediaId={mediaId}
|
|
initialCaption={caption}
|
|
passThroughParams={{
|
|
scheduledAt: params.scheduledAt,
|
|
rrule: params.rrule,
|
|
editReminderId: params.editReminderId,
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|