Загрузка данных


import {
  Hash,
  Bell,
  Users,
  Smile,
  PlusCircle,
  Gift,
  Pencil,
  Trash2,
  Image
} from "lucide-react";

import {
  useEffect,
  useRef,
  useState
} from "react";

import socket from "../lib/socket";

import useChatStore from "../store/chatStore";
import useServerStore from "../store/serverStore";
import useAuthStore from "../store/authStore";

export default function ChatPage() {

  const fileInputRef = useRef();

  const user = useAuthStore((s) => s.user);

  const avatar = localStorage.getItem("avatar");

  const [message, setMessage] = useState("");

  const [editingId, setEditingId] =
    useState(null);

  const [editingText, setEditingText] =
    useState("");

  const selectedChannel = useServerStore(
    (s) => s.selectedChannel
  );

  const {
    messages,
    addMessage,
    deleteMessage,
    editMessage,
    typingUsers,
    setTypingUsers
  } = useChatStore();

  useEffect(() => {

    if (!selectedChannel) return;

    socket.emit(
      "join-channel",
      selectedChannel.id
    );

    socket.on("new-message", (data) => {

      if (
        data.channelId === selectedChannel.id
      ) {

        addMessage(data);

      }

    });

    socket.on("user-typing", (data) => {

      if (
        data.channelId === selectedChannel.id
      ) {

        setTypingUsers([data.username]);

      }

    });

    socket.on("user-stop-typing", () => {

      setTypingUsers([]);

    });

    return () => {

      socket.off("new-message");
      socket.off("user-typing");
      socket.off("user-stop-typing");

    };

  }, [selectedChannel]);

  function sendMessage() {

    if (!message.trim()) return;

    const data = {
      id: Date.now(),
      author: user.username,
      avatar,
      content: message,
      channelId: selectedChannel.id,
      createdAt: new Date().toLocaleTimeString()
    };

    socket.emit(
      "send-message",
      data
    );

    socket.emit(
      "stop-typing",
      {
        channelId: selectedChannel.id
      }
    );

    setMessage("");

  }

  function handleTyping(value) {

    setMessage(value);

    socket.emit("typing", {
      username: user.username,
      channelId: selectedChannel.id
    });

    clearTimeout(window.typingTimeout);

    window.typingTimeout = setTimeout(() => {

      socket.emit("stop-typing", {
        channelId: selectedChannel.id
      });

    }, 1200);

  }

  function startEdit(msg) {

    setEditingId(msg.id);

    setEditingText(msg.content);

  }

  function saveEdit(id) {

    editMessage(id, editingText);

    setEditingId(null);

  }

  function uploadImage(e) {

    const file = e.target.files[0];

    if (!file) return;

    const reader = new FileReader();

    reader.onloadend = () => {

      const data = {
        id: Date.now(),
        author: user.username,
        avatar,
        image: reader.result,
        content: "",
        channelId: selectedChannel.id,
        createdAt: new Date().toLocaleTimeString()
      };

      socket.emit(
        "send-message",
        data
      );

    };

    reader.readAsDataURL(file);

  }

  return (
    <div className="flex-1 flex flex-col bg-[#202225]">

      <div className="h-14 border-b border-white/5 bg-[#18191c]/80 backdrop-blur-xl flex items-center justify-between px-5">

        <div className="flex items-center gap-2">

          <Hash
            size={22}
            className="text-zinc-500"
          />

          <span className="font-semibold text-[15px]">

            {selectedChannel?.name || "general"}

          </span>

        </div>

        <div className="flex items-center gap-4 text-zinc-400">

          <Bell
            size={20}
            className="hover:text-white cursor-pointer"
          />

          <Users
            size={20}
            className="hover:text-white cursor-pointer"
          />

        </div>

      </div>

      <div className="flex-1 overflow-y-auto px-6 py-6">

        {messages
          .filter(
            (msg) =>
              msg.channelId === selectedChannel?.id
          )
          .map((msg) => (

          <div
            key={msg.id}
            className="flex gap-4 mb-6 group"
          >

            {msg.avatar ? (

              <img
                src={msg.avatar}
                className="w-11 h-11 rounded-full object-cover"
              />

            ) : (

              <div className="w-11 h-11 rounded-full bg-[#5865f2]" />

            )}

            <div className="flex-1">

              <div className="flex items-center justify-between">

                <div className="flex items-center gap-2">

                  <span className="font-semibold">
                    {msg.author}
                  </span>

                  <span className="text-xs text-zinc-500">
                    {msg.createdAt}
                  </span>

                  {msg.edited && (

                    <span className="text-xs text-zinc-500 italic">
                      edited
                    </span>

                  )}

                </div>

                {msg.author === user.username && (

                  <div className="hidden group-hover:flex items-center gap-2">

                    {!msg.image && (

                      <button
                        onClick={() =>
                          startEdit(msg)
                        }
                        className="text-zinc-400 hover:text-white"
                      >

                        <Pencil size={16} />

                      </button>

                    )}

                    <button
                      onClick={() =>
                        deleteMessage(msg.id)
                      }
                      className="text-zinc-400 hover:text-red-400"
                    >

                      <Trash2 size={16} />

                    </button>

                  </div>

                )}

              </div>

              {editingId === msg.id ? (

                <input
                  value={editingText}
                  onChange={(e) =>
                    setEditingText(
                      e.target.value
                    )
                  }
                  onKeyDown={(e) => {

                    if (e.key === "Enter") {

                      saveEdit(msg.id);

                    }

                  }}
                  className="mt-2 bg-[#2b2d31] px-3 py-2 rounded-lg outline-none w-full"
                />

              ) : (

                <>
                  {msg.content && (

                    <div className="text-zinc-300 mt-1 break-words">

                      {msg.content}

                    </div>

                  )}

                  {msg.image && (

                    <img
                      src={msg.image}
                      className="mt-3 rounded-2xl max-w-[420px] border border-white/5 shadow-xl hover:scale-[1.01] transition-all"
                    />

                  )}
                </>

              )}

            </div>

          </div>

        ))}

      </div>

      {typingUsers.length > 0 && (

        <div className="px-6 pb-2 text-sm text-zinc-400 italic">

          {typingUsers.join(", ")} typing...

        </div>

      )}

      <div className="p-5">

        <div className="bg-[#2b2d31] rounded-2xl flex items-center px-4 py-3 gap-3 border border-white/5 shadow-lg">

          <input
            type="file"
            accept="image/*"
            hidden
            ref={fileInputRef}
            onChange={uploadImage}
          />

          <button
            onClick={() =>
              fileInputRef.current.click()
            }
            className="text-zinc-400 hover:text-white"
          >

            <PlusCircle size={22} />

          </button>

          <input
            value={message}
            onChange={(e) =>
              handleTyping(e.target.value)
            }
            onKeyDown={(e) => {

              if (e.key === "Enter") {

                sendMessage();

              }

            }}
            className="flex-1 bg-transparent outline-none text-white text-[15px]"
            placeholder={`Message #${selectedChannel?.name}`}
          />

          <button className="text-zinc-400 hover:text-white">

            <Gift size={22} />

          </button>

          <button className="text-zinc-400 hover:text-white">

            <Smile size={22} />

          </button>

          <button
            onClick={() =>
              fileInputRef.current.click()
            }
            className="text-zinc-400 hover:text-white"
          >

            <Image size={20} />

          </button>

        </div>

      </div>

    </div>
  );

}