- Fix "Invalid datetime" error: createReminderAction's Zod schema rejected
offset-suffixed ISO strings (luxon's `toISO()` produces +08:00 form).
Switched to `.datetime({ offset: true })`.
- Replace the single datetime-local input with separate native date + time
inputs (proper UI pickers on both desktop and mobile). Default value is
now computed server-side ("now + 1h") and passed in as a prop, so first
render is fully populated and there's no SSR/client hydration mismatch
from `Date.now()` inside the client component. Removed the quick-pick
shortcuts.
- Reorder wizard steps: Account → Compose → When → Groups → Review.
Groups is now the last and optional step (Continue button reads
"Skip groups" when empty); the action accepts an empty array and
inserts no reminder_targets in that case.
- Account list: card is the link target. Removed inline Pair / Open /
Delete quick-action buttons; lifecycle actions stay on the detail page.
- Account detail: removed the "Sync Groups Now" card. The bot already
auto-syncs on `groups.upsert` / `groups.update` events. The Groups card
itself is now a clickable link instead of carrying an inline View
button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
import Link from "next/link";
|
|
import { PlusIcon, SmartphoneIcon, CalendarIcon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { AccountStatusBadge } from "@/components/account-status-badge";
|
|
import { getSeededOperator } from "@/lib/operator";
|
|
import { listAccounts } from "@/lib/queries";
|
|
|
|
export default async function AccountsPage() {
|
|
const op = await getSeededOperator();
|
|
const accounts = await listAccounts(op.id);
|
|
|
|
return (
|
|
<div className="px-4 py-6 sm:px-6 sm:py-8 max-w-5xl mx-auto space-y-6">
|
|
<div className="flex items-center justify-between gap-4">
|
|
<h1 className="text-2xl font-semibold tracking-tight">Accounts</h1>
|
|
<Button asChild size="sm">
|
|
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
<Link href={"/accounts/new" as any}>
|
|
<PlusIcon />
|
|
Add Account
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
|
|
{accounts.length > 0 ? (
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
{accounts.map((account) => (
|
|
<Link
|
|
key={account.id}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
href={`/accounts/${account.id}` as any}
|
|
className="block focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
|
|
>
|
|
<Card className="h-full transition-all hover:shadow-md hover:ring-primary/30 cursor-pointer">
|
|
<CardHeader>
|
|
<div className="flex items-start justify-between gap-2">
|
|
<CardTitle className="text-base leading-snug">{account.label}</CardTitle>
|
|
<AccountStatusBadge status={account.status} />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
{account.phoneNumber ? (
|
|
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
<SmartphoneIcon className="size-3.5 shrink-0" />
|
|
<span>{account.phoneNumber}</span>
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground/60 italic">Not paired yet</p>
|
|
)}
|
|
{account.lastConnectedAt ? (
|
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
<CalendarIcon className="size-3 shrink-0" />
|
|
<span>
|
|
Last connected{" "}
|
|
{account.lastConnectedAt.toLocaleDateString("en-MY", {
|
|
timeZone: "Asia/Kuala_Lumpur",
|
|
year: "numeric",
|
|
month: "short",
|
|
day: "numeric",
|
|
})}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<Card>
|
|
<CardContent className="flex flex-col items-center gap-4 py-12 text-center">
|
|
<SmartphoneIcon className="size-10 text-muted-foreground/40" />
|
|
<div className="space-y-1">
|
|
<p className="text-sm font-medium">No accounts paired yet.</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Pair a WhatsApp account to start scheduling reminders.
|
|
</p>
|
|
</div>
|
|
<Button asChild size="sm">
|
|
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
|
<Link href={"/accounts/new" as any}>
|
|
<PlusIcon />
|
|
Add Account
|
|
</Link>
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|