Contact Form & Chat Widget

Chat Screenshot

This chat widget was implemented to provide real-time interaction between site visitors and support/admin via Telegram. Visitors can type messages directly in the widget — and every message is delivered to a Telegram bot.

Each user is assigned a unique UID which allows the admin to respond to each message separately. Admin replies (sent via /reply_uid_xxxxxx command) appear directly in the chat interface of the user, creating a smooth support flow.

The widget supports message bubbles, avatars, a typing indicator, automatic scroll, persistent message history using localStorage, and visual feedback for delivery. Messages are removed from the queue on the server once shown.

View Live Block
▶ Show JavaScript code
// Check for UID
let UID = localStorage.getItem('chat_uid');
  if (!UID) {
    UID = 'uid_' + Math.random().toString(36).substring(2, 10);
    localStorage.setItem('chat_uid', UID);
  }

  let greeted = localStorage.getItem('greeted_' + UID);
  let lastReplyShown = localStorage.getItem('last_reply_' + UID) || '';

  const history = JSON.parse(localStorage.getItem('chat_history_' + UID) || '[]');
  history.forEach(({ text, type }) => addMessage(text, type, false));

  function scrollToBottom() {
    messages.scrollTop = messages.scrollHeight;
  }

  function addMessage(text, type = 'user', save = true) {
    const msg = document.createElement('div');
    msg.className = `chat-message ${type}`;

    const bubble = document.createElement('div');
    bubble.className = `chat-bubble ${type}`;
    bubble.textContent = text;

    if (type === 'admin') {
      const avatar = document.createElement('img');
      avatar.src = '/me.jpg';
      avatar.alt = 'Admin';
      avatar.className = 'chat-avatar';
      msg.appendChild(avatar);
    }

    msg.appendChild(bubble);
    messages.appendChild(msg);
    scrollToBottom();

    if (save) {
      history.push({ text, type });
      localStorage.setItem('chat_history_' + UID, JSON.stringify(history));
    }
  }

  function showTyping() {
    if (document.getElementById('typing-indicator')) return;
    const typing = document.createElement('div');
    typing.className = 'chat-message admin';
    typing.id = 'typing-indicator';

    const bubble = document.createElement('div');
    bubble.className = 'chat-bubble admin';
    bubble.textContent = '...typing';

    typing.appendChild(document.createElement('span'));
    typing.appendChild(bubble);
    messages.appendChild(typing);
    scrollToBottom();
  }

  function hideTyping() {
    const el = document.getElementById('typing-indicator');
    if (el) el.remove();
  }

  setInterval(() => {
    const cleanUID = UID.replace('uid_', '');

    fetch('/replies/all.json?ts=' + Date.now())
      .then(res => res.ok ? res.json() : null)
      .then(data => {
        if (!data || !data[cleanUID]) return;
        const reply = data[cleanUID].reply;
        if (reply && reply !== lastReplyShown) {
          showTyping();
          setTimeout(() => {
            hideTyping();
            addMessage(reply, 'admin');
            lastReplyShown = reply;
            localStorage.setItem('last_reply_' + UID, reply);
            // ✅ Удаляем ответ с сервера
            fetch(`/replies/clear.php?uid=${cleanUID}`);
          }, 1500);
        }
      })
      .catch(() => {});
  }, 5000);

  trigger.onclick = () => {
    popup.classList.remove('hidden');
    if (!greeted) {
      addMessage("👋 Hi! Feel free to leave a message — we’ll get back to you soon.", 'admin');
      localStorage.setItem('greeted_' + UID, '1');
      greeted = true;
    }
  };

  close.onclick = () => popup.classList.add('hidden');

  sendBtn.onclick = () => {
    const text = input.value.trim();
    if (!text) return;
    addMessage(text + ' ✅', 'user');
    input.value = '';

    const fullText = `💬 New message from site:\n\n👤 UID: ${UID}\n📝 ${text}\n\nReply:\n/reply_${UID} your message`;
    fetch(API, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chat_id: CHAT_ID, text: fullText })
    });
  };

  window.leaveContact = function (type) {
    const contact = prompt(`Enter your ${type}:`);
    if (contact) {
      addMessage(`${type.toUpperCase()}: ${contact}`, 'user');
      const fullText = `📩 User left ${type}:\n\n👤 UID: ${UID}\n${type}: ${contact}`;
      fetch(API, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ chat_id: CHAT_ID, text: fullText })
      });
    }
  };
};
}