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).
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useActionState } from "react";
|
|
import Link from "next/link";
|
|
import { ArrowRightIcon, Loader2Icon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { addAccountAction, type AddAccountResult } from "@/actions/accounts";
|
|
|
|
const initialState: AddAccountResult = { ok: true, accountId: "" };
|
|
|
|
export function PairForm() {
|
|
const [state, action, isPending] = useActionState(addAccountAction, initialState);
|
|
|
|
return (
|
|
<form action={action} className="space-y-5">
|
|
<div className="space-y-1.5">
|
|
<Label htmlFor="label" className="text-sm font-medium">
|
|
Account label
|
|
</Label>
|
|
<Input
|
|
id="label"
|
|
name="label"
|
|
type="text"
|
|
placeholder="e.g. Personal, Work, Support"
|
|
maxLength={60}
|
|
required
|
|
aria-invalid={state.ok === false ? true : undefined}
|
|
aria-describedby={state.ok === false ? "label-error" : undefined}
|
|
className="h-9"
|
|
autoFocus
|
|
/>
|
|
{state.ok === false && (
|
|
<p
|
|
id="label-error"
|
|
role="alert"
|
|
className="flex items-center gap-1.5 text-xs text-destructive"
|
|
>
|
|
<span className="inline-block size-1 rounded-full bg-destructive shrink-0" />
|
|
{state.error}
|
|
</p>
|
|
)}
|
|
<p className="text-xs text-muted-foreground">
|
|
A short name to identify this WhatsApp account. You can have multiple accounts.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 pt-1">
|
|
<Button
|
|
type="submit"
|
|
disabled={isPending}
|
|
className="gap-2"
|
|
size="default"
|
|
>
|
|
{isPending ? (
|
|
<>
|
|
<Loader2Icon className="size-3.5 animate-spin" />
|
|
Adding…
|
|
</>
|
|
) : (
|
|
<>
|
|
Add Account
|
|
<ArrowRightIcon className="size-3.5" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
<Button asChild variant="ghost" size="default">
|
|
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
<Link href={"/accounts" as any}>Cancel</Link>
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|