"use client"; import { useState, useEffect, useRef, useCallback } from "react"; import { X } from "lucide-react"; import { useMessengerContext } from "@/contexts/MessengerContext"; import { useRooms, useUnreadCount } from "@/hooks/useMessenger"; import { useMessengerSocket } from "@/hooks/useMessengerSocket"; import { RoomList } from "./RoomList"; import { ChatPanel } from "./ChatPanel"; import { NewRoomModal } from "./NewRoomModal"; const MIN_W = 400, MIN_H = 320; const MAX_W = 1000, MAX_H = 800; const INIT_W = 720, INIT_H = 500; type ResizeDir = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw"; const CURSOR: Record = { n: "ns-resize", s: "ns-resize", e: "ew-resize", w: "ew-resize", ne: "nesw-resize", sw: "nesw-resize", nw: "nwse-resize", se: "nwse-resize", }; export function MessengerModal() { const { isOpen, closeMessenger, selectedRoomId, setUnreadCount } = useMessengerContext(); const { data: rooms = [] } = useRooms(); const { data: serverUnread } = useUnreadCount(); const { userStatuses, typingUsers, emitTypingStart, emitTypingStop } = useMessengerSocket(); useEffect(() => { const count = (serverUnread as any)?.unread_count ?? serverUnread ?? 0; setUnreadCount(Number(count)); }, [serverUnread, setUnreadCount]); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) closeMessenger(); }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [isOpen, closeMessenger]); const selectedRoom = rooms.find((r) => r.id === selectedRoomId) || null; const [newRoomOpen, setNewRoomOpen] = useState(false); // Position & size state const [pos, setPos] = useState({ x: 0, y: 0 }); const [size, setSize] = useState({ w: INIT_W, h: INIT_H }); const initialized = useRef(false); // Initialize position to bottom-right on first open useEffect(() => { if (isOpen && !initialized.current) { setPos({ x: window.innerWidth - INIT_W - 24, y: window.innerHeight - INIT_H - 24, }); initialized.current = true; } }, [isOpen]); // Clamp position when window resizes useEffect(() => { const onResize = () => { setPos((p) => ({ x: Math.min(p.x, window.innerWidth - size.w), y: Math.min(p.y, window.innerHeight - size.h), })); }; window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, [size]); // --- Drag to move --- const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null); const onHeaderMouseDown = useCallback((e: React.MouseEvent) => { if ((e.target as HTMLElement).closest("button")) return; e.preventDefault(); dragRef.current = { startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y }; const onMove = (ev: MouseEvent) => { if (!dragRef.current) return; const dx = ev.clientX - dragRef.current.startX; const dy = ev.clientY - dragRef.current.startY; setPos({ x: Math.max(0, Math.min(dragRef.current.origX + dx, window.innerWidth - size.w)), y: Math.max(0, Math.min(dragRef.current.origY + dy, window.innerHeight - size.h)), }); }; const onUp = () => { dragRef.current = null; document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); }, [pos, size]); // --- Resize --- const resizeRef = useRef<{ dir: ResizeDir; startX: number; startY: number; origX: number; origY: number; origW: number; origH: number; } | null>(null); const onResizeMouseDown = useCallback((e: React.MouseEvent, dir: ResizeDir) => { e.preventDefault(); e.stopPropagation(); resizeRef.current = { dir, startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y, origW: size.w, origH: size.h, }; const onMove = (ev: MouseEvent) => { const r = resizeRef.current; if (!r) return; const dx = ev.clientX - r.startX; const dy = ev.clientY - r.startY; let newX = r.origX, newY = r.origY, newW = r.origW, newH = r.origH; if (r.dir.includes("e")) newW = Math.min(MAX_W, Math.max(MIN_W, r.origW + dx)); if (r.dir.includes("s")) newH = Math.min(MAX_H, Math.max(MIN_H, r.origH + dy)); if (r.dir.includes("w")) { const w = Math.min(MAX_W, Math.max(MIN_W, r.origW - dx)); newX = r.origX + (r.origW - w); newW = w; } if (r.dir.includes("n")) { const h = Math.min(MAX_H, Math.max(MIN_H, r.origH - dy)); newY = r.origY + (r.origH - h); newH = h; } // Clamp to viewport newX = Math.max(0, Math.min(newX, window.innerWidth - newW)); newY = Math.max(0, Math.min(newY, window.innerHeight - newH)); setPos({ x: newX, y: newY }); setSize({ w: newW, h: newH }); }; const onUp = () => { resizeRef.current = null; document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); }, [pos, size]); const handle = (dir: ResizeDir, className: string) => (
onResizeMouseDown(e, dir)} /> ); return ( <>
{/* Resize handles */} {handle("n", "top-0 left-2 right-2 h-1.5")} {handle("s", "bottom-0 left-2 right-2 h-1.5")} {handle("e", "right-0 top-2 bottom-2 w-1.5")} {handle("w", "left-0 top-2 bottom-2 w-1.5")} {handle("ne", "top-0 right-0 w-3 h-3")} {handle("nw", "top-0 left-0 w-3 h-3")} {handle("se", "bottom-0 right-0 w-3 h-3")} {handle("sw", "bottom-0 left-0 w-3 h-3")} {/* Header */}

메신저

{/* Body */}
setNewRoomOpen(true)} />
); }