yiekheng 9437df74ee feat(web): split Add Account from Pair; add Unpair/Re-pair/Delete actions
Reshape the account lifecycle to match how operators actually want to
work the system:

- Add Account → creates a row with status='unpaired'. No QR yet; the
  operator lands on the detail page.
- Pair / Re-pair → transitions an unpaired account to status='pending'
  and opens the live QR flow. Works for first-time pair AND for re-pair
  of an account that was previously unpaired.
- Unpair → asks the bot to stop the live Baileys session and clean
  session files; sets status='unpaired' but KEEPS the row (and its
  reminders) so the operator can re-pair without retyping anything.
- Delete → permanently removes the account and cascades to its groups,
  reminders, run history.

Schema:
- whatsapp_groups.account_id and reminders.account_id now have
  ON DELETE CASCADE so deleting an account fans out cleanly.

UI:
- /accounts list shows everything except the transient 'pending' state.
- /accounts/[id] shows state-aware buttons: Pair (when unpaired/banned/
  disconnected), Sync + Unpair (when connected), Delete (always).
- /accounts/new is now an "Add Account" form (label only).

Other fixes:
- next.config.ts: allowedDevOrigins includes 192.168.0.253 +
  test/rexwa subdomains so Server Actions work across the LAN.
- packages/shared/src/rrule.ts: rrule@2.8.1 has no exports field and
  ships ESM that some bundlers can't resolve via default OR named
  import. Use createRequire to bridge — works under both NodeNext
  (bot runtime) and Turbopack (web SSR).
2026-05-10 00:27:33 +08:00

60 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;
}
interface StepComposeProps {
params: StepComposeParams;
}
export function StepCompose({ params }: StepComposeProps) {
const { accountId, groupIds, text, mediaId, caption } = params;
if (!accountId || !groupIds) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
redirect("/reminders/new" as any);
}
const backHref = `/reminders/new?step=2&accountId=${accountId}&groupIds=${groupIds}` 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,
}}
/>
</div>
);
}