"use client"; /** * The group profile's Settings tab: rename, choose who may post, and leave. * * Editing is admin-only; everyone else sees the same facts read-only, so the * tab still answers "why can't I post here?" for a plain member. */ import { useRef, useState } from "react"; import { Camera, Loader2, LogOut } from "lucide-react"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { UserAvatar } from "@/components/ui/user-avatar"; import { compressImage } from "@/lib/compress-image"; import { chat, groupAvatarUrl, isGroupAdmin, uploadGroupAvatar, type ConversationDetail, type SendPolicy, } from "@/lib/chat"; import { cn } from "@/lib/utils"; export function GroupSettingsTab({ conversationId, detail, selfHandle, onChanged, }: { conversationId: string; detail: ConversationDetail; selfHandle: string; onChanged: () => void; }) { const router = useRouter(); const [title, setTitle] = useState(detail.title ?? ""); const [busy, setBusy] = useState(false); const [uploading, setUploading] = useState(false); const fileRef = useRef(null); const iAmAdmin = isGroupAdmin(detail.my_role); const renamed = title.trim() !== (detail.title ?? "").trim(); /** * Upload first, then point the group at the new key. Two steps because the * bytes go straight to storage and never through the BFF — the same path * message attachments take. */ const changePhoto = async (file: File) => { setUploading(true); try { const compressed = await compressImage(file).catch(() => file); const key = await uploadGroupAvatar(conversationId, compressed); await chat.updateGroup(conversationId, { avatar_key: key }); toast.success("Group photo updated"); onChanged(); } catch { toast.error("Could not update the group photo"); } finally { setUploading(false); } }; const rename = async () => { if (!renamed || title.trim() === "") return; setBusy(true); try { await chat.updateGroup(conversationId, { title: title.trim() }); toast.success("Group renamed"); onChanged(); } catch { toast.error("Could not rename the group"); } finally { setBusy(false); } }; const setPolicy = async (policy: SendPolicy) => { if (policy === detail.send_policy) return; setBusy(true); try { await chat.updateGroup(conversationId, { send_policy: policy }); toast.success( policy === "admins" ? "Only admins can send messages now" : "Everyone can send messages now", ); onChanged(); } catch { toast.error("Could not change the setting"); } finally { setBusy(false); } }; const leave = async () => { if ( !window.confirm( detail.my_role === "owner" ? "Leave this group? Another admin will become the owner." : "Leave this group?", ) ) { return; } setBusy(true); try { await chat.removeMember(conversationId, selfHandle); toast.success("You left the group"); router.push("/chats"); } catch { toast.error("Could not leave the group"); setBusy(false); } }; return (

Group photo

{uploading && ( )}
{iAmAdmin ? ( <> { const file = e.target.files?.[0]; if (file) void changePhoto(file); // Reset so re-picking the same file still fires a change. e.target.value = ""; }} /> ) : (

Only group admins can change the photo.

)}

Group name

{iAmAdmin ? (
setTitle(e.target.value)} maxLength={120} className="flex-1 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm outline-none pointer-coarse:text-base focus:ring-1 focus:ring-primary/40" />
) : (

{detail.title}

)}

Who can send messages

setPolicy("everyone")} title="Everyone" description="Any member can send messages." /> setPolicy("admins")} title="Only admins" description="Members can read but not post — an announcement group." />
{!iAmAdmin && (

Only group admins can change these settings.

)}

Leave

{detail.my_role === "owner" && (

You own this group. If you leave, the longest-serving admin becomes the owner.

)}
); } function PolicyOption({ active, disabled, onClick, title, description, }: { active: boolean; disabled: boolean; onClick: () => void; title: string; description: string; }) { return ( ); }