From c246c05550ec354d1bc89282c5e238f2df2c096e Mon Sep 17 00:00:00 2001 From: dotmantissa Date: Wed, 24 Jun 2026 06:23:29 +0100 Subject: [PATCH] feat: add invite members modal with copy to clipboard Adds an Invite button to the chat header (next to Audit and Members) that opens a modal for sharing group invite codes. When the modal opens it immediately calls POST /api/groups/[id]/invite to generate a fresh UUID invite code, displays it in a monospace block, and provides a one-click copy button. A success toast confirms the copy. Users can also regenerate a new code without closing the modal. Error states (failed generation, clipboard denied) surface as toast notifications with a retry option so the flow never silently fails. --- app/chat/page.tsx | 5 + components/invite-members-modal.tsx | 167 ++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 components/invite-members-modal.tsx diff --git a/app/chat/page.tsx b/app/chat/page.tsx index b1a227a..6d36d2e 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -19,6 +19,7 @@ import { RoomMembersDialog } from "@/components/room-members-dialog"; import ConnectWallet from "@/components/wallet-connector"; import { RoomActivityPanel } from "@/components/room-activity-panel"; import { MessageSearchBar } from "@/components/message-search-bar"; +import { InviteMembersModal } from "@/components/invite-members-modal"; import { cn } from "@/lib/utils"; import { highlightText } from "@/lib/highlight-text"; import { handleAppError } from "@/lib/error-handler"; // Integrated Error Handler @@ -653,6 +654,10 @@ export default function ChatPage() { Members + diff --git a/components/invite-members-modal.tsx b/components/invite-members-modal.tsx new file mode 100644 index 0000000..43eeef7 --- /dev/null +++ b/components/invite-members-modal.tsx @@ -0,0 +1,167 @@ +"use client"; + +import * as Dialog from "@radix-ui/react-dialog"; +import { Check, Copy, Loader2, RefreshCw, UserPlus, X } from "lucide-react"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import { cn } from "@/lib/utils"; + +type InviteMembersModalProps = { + roomId: string; + roomName: string; +}; + +export function InviteMembersModal({ roomId, roomName }: InviteMembersModalProps) { + const [isOpen, setIsOpen] = useState(false); + const [inviteCode, setInviteCode] = useState(null); + const [isGenerating, setIsGenerating] = useState(false); + const [copied, setCopied] = useState(false); + + const generateCode = async () => { + setIsGenerating(true); + try { + const res = await fetch(`/api/groups/${encodeURIComponent(roomId)}/invite`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({}), + }); + const data = await res.json(); + if (!res.ok) { + toast.error(data.error ?? "Failed to generate invite code"); + setInviteCode(null); + return; + } + setInviteCode(data.invite.code); + setCopied(false); + } catch { + toast.error("Failed to generate invite code"); + setInviteCode(null); + } finally { + setIsGenerating(false); + } + }; + + const handleOpenChange = (open: boolean) => { + setIsOpen(open); + if (open) { + setInviteCode(null); + setCopied(false); + generateCode(); + } + }; + + const handleCopy = async () => { + if (!inviteCode) return; + try { + await navigator.clipboard.writeText(inviteCode); + setCopied(true); + toast.success("Invite code copied to clipboard!"); + setTimeout(() => setCopied(false), 2500); + } catch { + toast.error("Failed to copy — please copy the code manually"); + } + }; + + return ( + + + + + + + + +
+
+ Invite Members + + Share this code so others can join{" "} + {roomName}. + +
+ + + Close + +
+ +
+ {isGenerating && ( +
+ + Generating code… +
+ )} + + {!isGenerating && inviteCode && ( + <> +
+

+ Invite Code +

+

+ {inviteCode} +

+
+ + + + + + )} + + {!isGenerating && !inviteCode && ( +
+

+ Could not generate an invite code. +

+ +
+ )} +
+
+
+
+ ); +}