"use client"; import { useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { useSdk, useChannel } from "@lynkd/messaging-inbox-sdk"; import { useUi } from "@/lib/ui-state"; import { Modal, Field, inputCls, PrimaryBtn, GhostBtn } from "@/components/ui/Modal"; import { Avatar } from "@/components/ui/primitives"; import { EmojiPicker } from "@/components/message/EmojiPicker"; import { Hash, Lock, Smile, Upload, Trash2 } from "lucide-react"; import clsx from "clsx"; const AVATAR_COLORS = ["#FDA913", "#5b9dff", "#3ecf8e", "#bb98ff", "#f2555a", "#ff7a3d", "#54e7ff", "#f5b544", "#8c8c94"]; export function Modals() { const { modal } = useUi(); if (!modal) return null; return ( <> {modal === "create-channel" && } {modal === "new-message" && } {modal === "invite" && } {modal === "create-workspace" && } {modal === "callback" && } {modal === "channel-details" && } {modal === "channel-members" && } {modal === "set-status" && } {modal === "edit-profile" && } ); } function ChannelMembersModal() { const { modalArg, closeModal, toast } = useUi(); const { channelById, users, client, refresh, me } = useSdk(); const channel = useChannel(modalArg ?? null); const [q, setQ] = useState(""); const [busy, setBusy] = useState(false); if (!channel) return

No channel selected.

; const memberIds = channelById(channel.id)?.memberIds ?? channel.memberIds; const members = memberIds.map((id) => users.find((u) => u.id === id)).filter(Boolean) as typeof users; const candidates = users.filter((u) => !memberIds.includes(u.id) && (u.name.toLowerCase().includes(q.toLowerCase()) || !q)); const add = async (userId: string, name: string) => { setBusy(true); await client.addMember(channel.id, userId); refresh(); setBusy(false); toast(`Added ${name} to #${channel.name}`, "success"); }; const remove = async (userId: string, name: string) => { setBusy(true); await client.removeMember(channel.id, userId); refresh(); setBusy(false); toast(`Removed ${name}`, "default"); }; return (
In this channel
{members.map((u) => (
{u.name}{u.id === me?.id ? " (you)" : ""}
{u.title}
{u.id !== me?.id && }
))}
Add people
setQ(e.target.value)} placeholder="Search people to add…" className={inputCls} />
{candidates.map((u) => (
{u.name}
{u.title}
))} {candidates.length === 0 &&
Everyone's already here.
}
); } const CLEAR_OPTIONS: { label: string; ms: number | null }[] = [ { label: "Don't clear", ms: null }, { label: "30 minutes", ms: 30 * 60_000 }, { label: "1 hour", ms: 60 * 60_000 }, { label: "4 hours", ms: 4 * 60 * 60_000 }, { label: "Today", ms: -1 }, // end of today ]; function StatusModal() { const { closeModal, toast } = useUi(); const { me, client, refresh } = useSdk(); const [emoji, setEmoji] = useState(me?.statusEmoji ?? ""); const [text, setText] = useState(me?.statusText ?? ""); const [pickerOpen, setPickerOpen] = useState(false); const [clearIdx, setClearIdx] = useState(0); const [customAt, setCustomAt] = useState(""); const presets = [ { e: "πŸ—οΈ", t: "On site" }, { e: "πŸ“…", t: "In a meeting" }, { e: "🎧", t: "Focusing" }, { e: "🌴", t: "On vacation" }, { e: "πŸ€’", t: "Out sick" }, { e: "🏠", t: "Working remotely" }, { e: "🍽️", t: "Out for lunch" }, { e: "πŸš—", t: "Commuting" }, ]; const computeClearAt = (): number | undefined => { if (customAt) { const t = Date.parse(customAt); return Number.isNaN(t) ? undefined : t; } const opt = CLEAR_OPTIONS[clearIdx]; if (opt.ms === null) return undefined; if (opt.ms === -1) { const d = new Date(); d.setHours(23, 59, 0, 0); return d.getTime(); } return Date.now() + opt.ms; }; const save = async () => { await client.setStatus(text.trim() || undefined, emoji || undefined, computeClearAt()); refresh(); closeModal(); toast("Status updated", "success"); }; const clear = async () => { await client.setStatus(undefined, undefined); refresh(); closeModal(); toast("Status cleared", "default"); }; return ( ClearSave}>
{pickerOpen && { setEmoji(e); setPickerOpen(false); }} onClose={() => setPickerOpen(false)} />} setText(e.target.value)} placeholder="What's your status?" className="w-full bg-transparent py-2 text-[14px] outline-none placeholder:text-dim" onKeyDown={(e) => e.key === "Enter" && save()} />
{presets.map((p) => ( ))}
{CLEAR_OPTIONS.map((o, i) => ( ))}
setCustomAt(e.target.value)} className={clsx(inputCls, "mt-2")} title="Custom clear time" />
); } function EditProfileModal() { const { closeModal, toast } = useUi(); const { me, client, refresh } = useSdk(); const fileRef = useRef(null); const [name, setName] = useState(me?.name ?? ""); const [title, setTitle] = useState(me?.title ?? ""); const [color, setColor] = useState(me?.avatarColor ?? AVATAR_COLORS[0]); const [avatarUrl, setAvatarUrl] = useState(me?.avatarUrl ?? null); const onFile = (f: File | undefined) => { if (!f) return; const reader = new FileReader(); reader.onload = () => setAvatarUrl(String(reader.result)); reader.readAsDataURL(f); }; const save = async () => { await client.updateMe({ name: name.trim() || undefined, title: title.trim() || undefined, avatarColor: color, avatarUrl: avatarUrl }); refresh(); closeModal(); toast("Profile updated", "success"); }; const preview = { avatarText: (name || "?").slice(0, 2).toUpperCase(), avatarColor: color, presence: (me?.presence ?? "active") as any, name, avatarUrl: avatarUrl ?? undefined }; return ( CancelSave changes}>
{avatarUrl && } { onFile(e.target.files?.[0]); e.target.value = ""; }} />
{!avatarUrl && (
{AVATAR_COLORS.map((c) => (
)} setName(e.target.value)} className={inputCls} /> setTitle(e.target.value)} className={inputCls} />
); } function CreateChannelModal() { const { client, refresh, users, me } = useSdk(); const { closeModal, toast } = useUi(); const router = useRouter(); const [name, setName] = useState(""); const [topic, setTopic] = useState(""); const [priv, setPriv] = useState(false); const [busy, setBusy] = useState(false); const [selected, setSelected] = useState([]); const [q, setQ] = useState(""); const others = users.filter((u) => u.id !== me?.id); const candidates = others.filter((u) => u.name.toLowerCase().includes(q.toLowerCase()) || u.handle.toLowerCase().includes(q.toLowerCase())); const toggle = (id: string) => setSelected((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id])); async function create() { const clean = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""); if (!clean) return; setBusy(true); // Public channels include the whole team automatically; private channels // include only the members you explicitly select. const memberIds = priv ? selected : others.map((u) => u.id); const { channel } = await client.createChannel({ name: clean, topic: topic.trim() || undefined, kind: priv ? "private" : "channel", memberIds }); refresh(); closeModal(); toast(`Created #${channel.name}`, "success"); router.push(`/c/${channel.id}`); } return ( CancelCreate channel} >
{priv ? : } setName(e.target.value)} placeholder="e.g. marketing" className="w-full bg-transparent py-2 text-[14px] outline-none placeholder:text-dim" onKeyDown={(e) => e.key === "Enter" && create()} />
setTopic(e.target.value)} placeholder="What's this channel about?" className={inputCls} /> {priv ? (
Add members Β· {selected.length} selected
{selected.length > 0 && (
{selected.map((id) => { const u = users.find((x) => x.id === id); if (!u) return null; return ( {u.name} ); })}
)} setQ(e.target.value)} placeholder="Search people to add…" className={inputCls} />
{candidates.map((u) => ( ))} {candidates.length === 0 &&
No people match β€œ{q}”.
}
) : (

Everyone on the team ({others.length}) will be added automatically.

)}
); } function NewMessageModal() { const { users, me, channels, client, refresh } = useSdk(); const { closeModal, toast } = useUi(); const router = useRouter(); const [q, setQ] = useState(""); const people = users.filter((u) => u.id !== me?.id && u.name.toLowerCase().includes(q.toLowerCase())); async function openDm(userId: string, name: string) { const existing = channels.find((c) => c.kind === "dm" && c.memberIds.includes(userId)); closeModal(); if (existing) { router.push(`/c/${existing.id}`); return; } const { channel } = await client.createChannel({ name, kind: "dm", memberIds: [userId] }); refresh(); toast(`Started a conversation with ${name}`, "success"); router.push(`/c/${channel.id}`); } return ( setQ(e.target.value)} placeholder="Search people…" className={inputCls} />
{people.map((u) => ( ))} {people.length === 0 &&
No people match β€œ{q}”.
}
); } function InviteModal() { const { closeModal, toast } = useUi(); const [chips, setChips] = useState([]); const [draft, setDraft] = useState(""); const isEmail = (v: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); const commit = (raw: string) => { const v = raw.trim().replace(/,$/, "").trim(); if (!v) return; if (!isEmail(v)) { toast(`β€œ${v}” doesn't look like an email`, "default"); return; } setChips((c) => (c.includes(v) ? c : [...c, v])); setDraft(""); }; const onKeyDown = (e: React.KeyboardEvent) => { if (e.key === "," || e.key === "Enter" || e.key === " ") { e.preventDefault(); commit(draft); } else if (e.key === "Backspace" && !draft && chips.length) { setChips((c) => c.slice(0, -1)); } }; const total = chips.length + (isEmail(draft.trim()) ? 1 : 0); return ( Cancel { const all = [...chips, ...(isEmail(draft.trim()) ? [draft.trim()] : [])]; closeModal(); toast(`${all.length} invitation${all.length === 1 ? "" : "s"} sent (demo)`, "success"); }}>Send invites} >
{chips.map((email) => ( {email} ))} { const v = e.target.value; if (v.endsWith(",")) commit(v); else setDraft(v); }} onKeyDown={onKeyDown} onBlur={() => commit(draft)} placeholder={chips.length ? "" : "name@company.com, another@company.com"} className="min-w-[160px] flex-1 bg-transparent py-1 text-[14px] outline-none placeholder:text-dim" />

Type an email and press , or Enter to add it as a chip.

); } function CreateWorkspaceModal() { const { closeModal, toast } = useUi(); const [name, setName] = useState(""); return ( Cancel { closeModal(); toast(`Workspace β€œ${name}” created (demo)`, "success"); }}>Create} > setName(e.target.value)} placeholder="e.g. Acme Field Ops" className={inputCls} /> ); } function CallbackModal() { const { closeModal, toast } = useUi(); const [channel, setChannel] = useState("PHONE"); const [time, setTime] = useState(""); return ( Cancel { closeModal(); toast("Callback requested β€” scheduled follow-up created", "success"); }}>Request callback} >
{["PHONE", "ZOOM", "IN_APP"].map((c) => ( ))}
setTime(e.target.value)} placeholder="e.g. Tomorrow 2–4pm CT" className={inputCls} />
); } function ChannelDetailsModal() { const { modalArg, closeModal, openModal } = useUi(); const { userById } = useSdk(); const channel = useChannel(modalArg ?? null); if (!channel) { return

No channel selected.

; } return (
Topic
{channel.topic || "No topic set"}
Members Β· {channel.memberIds.length}
{channel.memberIds.map((id) => { const u = userById(id); return u ? (
{u.name}
{u.title}
) : null; })}
); }