diff --git a/apps/web/src/app/accounts/[id]/groups/page.tsx b/apps/web/src/app/accounts/[id]/groups/page.tsx new file mode 100644 index 0000000..4661589 --- /dev/null +++ b/apps/web/src/app/accounts/[id]/groups/page.tsx @@ -0,0 +1,135 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { + ArrowLeftIcon, + SearchIcon, + UsersIcon, + RefreshCwIcon, + Users2Icon, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, +} from "@/components/ui/card"; +import { getSeededOperator } from "@/lib/operator"; +import { listGroupsForAccount } from "@/lib/queries"; + +interface Props { + params: Promise<{ id: string }>; + searchParams: Promise<{ q?: string }>; +} + +export default async function GroupsListPage({ params, searchParams }: Props) { + const { id } = await params; + const { q } = await searchParams; + + const op = await getSeededOperator(); + const data = await listGroupsForAccount(op.id, id, q); + + if (!data) { + notFound(); + } + + const { account, groups } = data; + + return ( +
+ {/* Back link */} + + + {/* Header */} +
+
+

+ Groups in {account.label} +

+ + {groups.length} + +
+ + {/* Refresh button — no-op placeholder, wired in Task 17 */} +
{ "use server"; /* wired in Task 17 */ }}> + +
+
+ + {/* Search */} +
+ + + + + {/* Group list */} + {groups.length > 0 ? ( +
+ {groups.map((group) => ( + +
+
+ +
+ {group.name} +
+ + {group.participantCount} member{group.participantCount !== 1 ? "s" : ""} + + + ))} +
+ ) : ( + + + +
+ {q ? ( + <> +

No groups match “{q}”

+

+ Try a different search term or clear the search to see all groups. +

+ + ) : ( + <> +

No groups synced yet

+

+ Use “Refresh Groups” to pull the latest groups from this WhatsApp account. +

+ + )} +
+ {q && ( + + )} +
+
+ )} +
+ ); +} diff --git a/apps/web/src/app/groups/[id]/page.tsx b/apps/web/src/app/groups/[id]/page.tsx new file mode 100644 index 0000000..2498ca2 --- /dev/null +++ b/apps/web/src/app/groups/[id]/page.tsx @@ -0,0 +1,130 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { + ArrowLeftIcon, + UsersIcon, + SendIcon, + BellPlusIcon, + ClockIcon, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; +import { getSeededOperator } from "@/lib/operator"; +import { getGroup } from "@/lib/queries"; + +interface Props { + params: Promise<{ id: string }>; +} + +async function _sendTestStub(_formData: FormData) { + "use server"; + // wired in Task 18 +} + +export default async function GroupDetailPage({ params }: Props) { + const { id } = await params; + + const op = await getSeededOperator(); + const data = await getGroup(op.id, id); + + if (!data) { + notFound(); + } + + const { group, account } = data; + + const lastSynced = group.lastSyncedAt + ? new Date(group.lastSyncedAt).toLocaleDateString("en-MY", { + timeZone: "Asia/Kuala_Lumpur", + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }) + : "Never"; + + return ( +
+ {/* Back link */} + + + {/* Hero */} +
+

{group.name}

+

+ + Account: {account.label} + {" · "} + {group.participantCount} member{group.participantCount !== 1 ? "s" : ""} +

+
+ + {/* Send Test Message */} + + + Send Test Message + + Send a one-off message to this group to verify the connection. + + + +
+