test / index.html
ALDI WAHYUDIN
bagian chat list di kiri bisa di sembnyikan dan di rampilkan dengan tombol icon berger di kiri atas - Initial Deployment
dd20ee6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatsApp Web Clone</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
.chat-container {
height: calc(100vh - 120px);
}
.message-input {
resize: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.typing-indicator::after {
content: '...';
animation: typing 1.5s infinite;
display: inline-block;
width: 0;
}
@keyframes typing {
0% { width: 0; }
50% { width: 0.5em; }
100% { width: 1em; }
}
</style>
</head>
<body class="bg-gray-100">
<div class="flex flex-col h-screen">
<!-- Header -->
<div class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white p-4 shadow-md">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<button id="toggle-sidebar" class="p-1 rounded-full hover:bg-indigo-700">
<i data-feather="menu" class="w-6 h-6"></i>
</button>
<i data-feather="message-circle" class="w-6 h-6"></i>
<h1 class="text-xl font-semibold">WhatsApp Web</h1>
</div>
<div class="flex items-center space-x-4">
<button id="refresh-btn" class="p-2 rounded-full hover:bg-emerald-600">
<i data-feather="refresh-cw" class="w-5 h-5"></i>
</button>
<button id="settings-btn" class="p-2 rounded-full hover:bg-emerald-600">
<i data-feather="settings" class="w-5 h-5"></i>
</button>
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex flex-1 overflow-hidden">
<!-- Sidebar -->
<div id="sidebar" class="w-1/3 border-r border-gray-300 bg-white flex flex-col transition-all duration-300 ease-in-out">
<!-- Search -->
<div class="p-3 bg-gray-50">
<div class="relative">
<input type="text" placeholder="Search or start new chat"
class="w-full py-2 pl-10 pr-4 bg-white rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500">
<i data-feather="search" class="absolute left-3 top-2.5 text-gray-400"></i>
</div>
</div>
<!-- Chat List -->
<div class="flex-1 overflow-y-auto scrollbar-hide">
<div id="chat-list" class="divide-y divide-gray-200">
<!-- Chat items will be added here dynamically -->
</div>
</div>
</div>
<!-- Chat Area -->
<div class="flex-1 flex flex-col bg-gray-100">
<!-- Chat Header -->
<div id="chat-header" class="p-3 bg-white border-b border-gray-300 flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-full bg-gray-300 flex items-center justify-center">
<i data-feather="user" class="text-gray-500"></i>
</div>
<div>
<h3 class="font-semibold">Select a chat</h3>
<p class="text-xs text-gray-500">Last seen</p>
</div>
</div>
<div class="flex items-center space-x-4">
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="search" class="w-5 h-5"></i>
</button>
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="more-vertical" class="w-5 h-5"></i>
</button>
</div>
</div>
<!-- Messages -->
<div id="messages-container" class="flex-1 overflow-y-auto p-4 space-y-3 scrollbar-hide">
<div class="text-center py-10 text-gray-500">
<i data-feather="message-circle" class="w-12 h-12 mx-auto mb-2"></i>
<p>Select a chat to start messaging</p>
</div>
</div>
<!-- Message Input -->
<div id="message-input-container" class="p-3 bg-white border-t border-gray-300 hidden">
<div class="flex items-center space-x-2">
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="smile" class="w-5 h-5"></i>
</button>
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="paperclip" class="w-5 h-5"></i>
</button>
<div class="flex-1">
<textarea id="message-input" rows="1" placeholder="Type a message"
class="w-full py-2 px-4 bg-gray-100 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 message-input"></textarea>
</div>
<button id="send-btn" class="p-2 rounded-full bg-gradient-to-r from-indigo-500 to-purple-500 text-white hover:from-indigo-600 hover:to-purple-600 shadow-lg">
<i data-feather="send" class="w-5 h-5"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Settings Modal -->
<div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white rounded-lg w-full max-w-md">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="text-lg font-semibold">Settings</h3>
<button id="close-settings" class="p-1 rounded-full hover:bg-gray-200">
<i data-feather="x" class="w-5 h-5"></i>
</button>
</div>
<div class="p-4 space-y-4">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 rounded-full bg-gray-300 flex items-center justify-center">
<i data-feather="user" class="text-gray-500"></i>
</div>
<div>
<h4 class="font-medium">User Profile</h4>
<p class="text-sm text-gray-500">Update your profile information</p>
</div>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between p-3 hover:bg-gray-100 rounded-lg cursor-pointer">
<div class="flex items-center space-x-3">
<i data-feather="user" class="w-5 h-5 text-gray-500"></i>
<span>Profile</span>
</div>
<i data-feather="chevron-right" class="w-5 h-5 text-gray-400"></i>
</div>
<div class="flex items-center justify-between p-3 hover:bg-gray-100 rounded-lg cursor-pointer">
<div class="flex items-center space-x-3">
<i data-feather="lock" class="w-5 h-5 text-purple-500"></i>
<span class="text-purple-600">Privacy</span>
</div>
<i data-feather="chevron-right" class="w-5 h-5 text-purple-400"></i>
</div>
<div class="flex items-center justify-between p-3 hover:bg-gray-100 rounded-lg cursor-pointer">
<div class="flex items-center space-x-3">
<i data-feather="bell" class="w-5 h-5 text-indigo-500"></i>
<span class="text-indigo-600">Notifications</span>
</div>
<i data-feather="chevron-right" class="w-5 h-5 text-indigo-400"></i>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-200">
<button id="logout-btn" class="w-full py-2 px-4 bg-red-600 text-white rounded-lg hover:bg-red-700">
Logout
</button>
</div>
</div>
</div>
<script>
// Initialize AOS and Feather Icons
AOS.init();
feather.replace();
// Sample data for demonstration
let chats = [];
let currentChat = null;
let messages = {};
// DOM Elements
const chatList = document.getElementById('chat-list');
const sidebar = document.getElementById('sidebar');
const toggleSidebarBtn = document.getElementById('toggle-sidebar');
const messagesContainer = document.getElementById('messages-container');
const messageInputContainer = document.getElementById('message-input-container');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const chatHeader = document.getElementById('chat-header');
const settingsBtn = document.getElementById('settings-btn');
const settingsModal = document.getElementById('settings-modal');
const closeSettings = document.getElementById('close-settings');
const refreshBtn = document.getElementById('refresh-btn');
const logoutBtn = document.getElementById('logout-btn');
// API base URL
const API_BASE = 'http://localhost:3000';
// Fetch chats from API
async function fetchChats() {
try {
const response = await fetch(`${API_BASE}/chats`);
const data = await response.json();
if (data.code === 'SUCCESS') {
chats = data.results.data;
renderChatList();
}
} catch (error) {
console.error('Error fetching chats:', error);
}
}
// Render chat list
function renderChatList() {
chatList.innerHTML = '';
chats.forEach(chat => {
const chatItem = document.createElement('div');
chatItem.className = `p-3 flex items-center space-x-3 hover:bg-gray-100 cursor-pointer ${chat.unread > 0 ? 'bg-gray-50' : ''}`;
chatItem.dataset.chatId = chat.id;
chatItem.innerHTML = `
<div class="relative">
<div class="w-12 h-12 rounded-full bg-gray-300 flex items-center justify-center">
<i data-feather="user" class="text-gray-500"></i>
</div>
${chat.isOnline ? `<div class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 rounded-full border-2 border-white"></div>` : ''}
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-center">
<h4 class="font-medium truncate">${chat.name}</h4>
<span class="text-xs text-gray-500 whitespace-nowrap">${chat.time}</span>
</div>
<div class="flex justify-between items-center">
<p class="text-sm text-gray-500 truncate">${chat.lastMessage}</p>
${chat.unread > 0 ? `<span class="bg-purple-600 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">${chat.unread}</span>` : ''}
</div>
</div>
`;
chatItem.addEventListener('click', () => loadChat(chat.id));
chatList.appendChild(chatItem);
});
feather.replace();
}
// Load chat messages
async function loadChat(chatJid) {
currentChat = chatJid;
try {
// Fetch chat info
const infoResponse = await fetch(`${API_BASE}/user/info?phone=${encodeURIComponent(chatJid)}`);
const infoData = await infoResponse.json();
// Fetch messages
const messagesResponse = await fetch(`${API_BASE}/chat/${encodeURIComponent(chatJid)}/messages`);
const messagesData = await messagesResponse.json();
if (infoData.code === 'SUCCESS' && messagesData.code === 'SUCCESS') {
const chatInfo = infoData.results;
messages[chatJid] = messagesData.results.data;
// Update chat header
chatHeader.innerHTML = `
<div class="flex items-center space-x-3">
<div class="relative">
<div class="w-10 h-10 rounded-full bg-gray-300 flex items-center justify-center">
<i data-feather="user" class="text-gray-500"></i>
</div>
${chatInfo.isOnline ? `<div class="absolute bottom-0 right-0 w-2 h-2 bg-green-500 rounded-full border border-white"></div>` : ''}
</div>
<div>
<h3 class="font-semibold">${chatInfo.verified_name || chatJid}</h3>
<p class="text-xs text-gray-500">${chatInfo.isOnline ? 'online' : 'last seen recently'}</p>
</div>
</div>
<div class="flex items-center space-x-4">
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="search" class="w-5 h-5"></i>
</button>
<button class="p-2 rounded-full hover:bg-gray-200">
<i data-feather="more-vertical" class="w-5 h-5"></i>
</button>
</div>
`;
// Render messages
messagesContainer.innerHTML = '';
messages.forEach(msg => {
const messageDiv = document.createElement('div');
messageDiv.className = `flex ${msg.isMe ? 'justify-end' : 'justify-start'}`;
messageDiv.innerHTML = `
<div class="max-w-xs md:max-w-md lg:max-w-lg rounded-lg p-3 ${msg.isMe ? 'bg-gradient-to-r from-indigo-100 to-purple-100' : 'bg-white'} shadow">
<p>${msg.text}</p>
<p class="text-xs text-gray-500 text-right mt-1">${msg.time}</p>
</div>
`;
messagesContainer.appendChild(messageDiv);
});
// Scroll to bottom
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Show message input
messageInputContainer.classList.remove('hidden');
messageInput.focus();
feather.replace();
}
// Send message
async function sendMessage() {
const text = messageInput.value.trim();
if (!text || !currentChat) return;
try {
// Show typing indicator
await fetch(`${API_BASE}/send/chat-presence`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: currentChat,
action: 'start'
})
});
// Send message
const response = await fetch(`${API_BASE}/send/message`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: currentChat,
message: text
})
});
const data = await response.json();
if (data.code === 'SUCCESS') {
// Add message to UI
const messageDiv = document.createElement('div');
messageDiv.className = 'flex justify-end';
messageDiv.innerHTML = `
<div class="max-w-xs md:max-w-md lg:max-w-lg rounded-lg p-3 bg-gradient-to-r from-emerald-100 to-teal-100 shadow">
<p>${text}</p>
<p class="text-xs text-gray-500 text-right mt-1">Just now</p>
</div>
`;
messagesContainer.appendChild(messageDiv);
messageInput.value = '';
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Stop typing indicator
await fetch(`${API_BASE}/send/chat-presence`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: currentChat,
action: 'stop'
})
});
// Refresh messages
loadChat(currentChat);
}
} catch (error) {
console.error('Error sending message:', error);
}
}
// Event Listeners
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
settingsBtn.addEventListener('click', () => {
settingsModal.classList.remove('hidden');
});
closeSettings.addEventListener('click', () => {
settingsModal.classList.add('hidden');
});
refreshBtn.addEventListener('click', () => {
fetchChats();
if (currentChat) {
loadChat(currentChat);
}
});
logoutBtn.addEventListener('click', async () => {
try {
const response = await fetch(`${API_BASE}/app/logout`);
const data = await response.json();
if (data.code === 'SUCCESS') {
window.location.reload();
}
} catch (error) {
console.error('Error logging out:', error);
}
settingsModal.classList.add('hidden');
});
// Toggle sidebar
toggleSidebarBtn.addEventListener('click', () => {
sidebar.classList.toggle('hidden');
feather.replace();
});
// Initialize
fetchChats();
</script>
</body>
</html>