chatmobile-expresso / index.html
Tittoap's picture
const functions = require("firebase-functions");
e76c115 verified
Raw
History Blame Contribute Delete
27.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FireChat Flames</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-storage.js"></script>
<style>
.chat-message::after {
content: "";
position: absolute;
bottom: 0;
width: 20px;
height: 20px;
background: inherit;
clip-path: polygon(0 0, 100% 100%, 0 100%);
}
.message-left::after {
left: -10px;
transform: rotate(90deg);
}
.message-right::after {
right: -10px;
transform: rotate(-90deg);
}
#messages-container {
scroll-behavior: smooth;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body class="bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900 min-h-screen text-white">
<div class="container mx-auto max-w-6xl h-screen flex flex-col">
<!-- Header -->
<header class="bg-black bg-opacity-50 backdrop-blur-md p-4 flex items-center justify-between border-b border-purple-500">
<div class="flex items-center space-x-3">
<i data-feather="message-square" class="text-purple-400"></i>
<h1 class="text-2xl font-bold bg-gradient-to-r from-purple-400 via-pink-500 to-red-500 bg-clip-text text-transparent">FireChat Flames</h1>
</div>
<div class="flex items-center space-x-4">
<button id="logout-btn" class="p-2 rounded-full hover:bg-purple-900 hover:bg-opacity-50">
<i data-feather="log-out"></i>
</button>
<button id="profile-btn" class="flex items-center space-x-2 group">
<div class="w-10 h-10 rounded-full bg-purple-600 flex items-center justify-center overflow-hidden">
<img id="profile-img" src="" alt="Profile" class="w-full h-full object-cover">
</div>
<span class="hidden md:block group-hover:text-purple-300 transition">Profile</span>
</button>
</div>
</header>
<!-- Main Content -->
<div class="flex flex-1 overflow-hidden">
<!-- Sidebar -->
<div class="w-1/4 bg-black bg-opacity-30 backdrop-blur-md border-r border-purple-900 hidden md:block">
<div class="p-4 border-b border-purple-900">
<h2 class="text-lg font-semibold mb-3 text-purple-300">Friends</h2>
<div class="space-y-2 max-h-96 overflow-y-auto" id="friends-list">
</div>
</div>
<div class="p-4">
<h2 class="text-lg font-semibold mb-3 text-purple-300">Friend Requests</h2>
<div class="space-y-2 max-h-96 overflow-y-auto" id="requests-list">
</div>
</div>
</div>
<!-- Chat Area -->
<div class="flex-1 flex flex-col">
<!-- Chat Header -->
<div class="p-3 border-b border-purple-900 bg-black bg-opacity-30 backdrop-blur-md flex items-center">
<div class="w-10 h-10 rounded-full bg-purple-600 mr-3 overflow-hidden">
<img src="http://static.photos/people/200x200/3" class="w-full h-full object-cover">
</div>
<div>
<h2 id="chat-title" class="font-semibold">Global Chat</h2>
<p class="text-xs text-purple-300">Online</p>
</div>
</div>
<!-- Messages -->
<div id="messages-container" class="flex-1 overflow-y-auto p-4 space-y-4 bg-gradient-to-b from-gray-900 to-gray-800">
</div>
<!-- Message Input -->
<div class="p-3 border-t border-purple-900 bg-black bg-opacity-30 backdrop-blur-md">
<form id="message-form" class="flex items-center space-x-2">
<button type="button" id="attach-btn" class="p-2 rounded-full hover:bg-purple-900 hover:bg-opacity-50">
<i data-feather="paperclip"></i>
</button>
<input type="file" id="file-input" class="hidden" accept="image/*">
<input type="text" id="message-input" placeholder="Type a message..."
class="flex-1 bg-gray-800 border border-purple-900 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500">
<button type="submit" class="p-2 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700">
<i data-feather="send"></i>
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Profile Modal -->
<div id="profile-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 hidden">
<div class="bg-gray-900 rounded-xl max-w-md w-full p-6 border border-purple-900">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-purple-400">Edit Profile</h3>
<button id="close-profile-modal" class="text-gray-400 hover:text-white">
<i data-feather="x"></i>
</button>
</div>
<div class="flex flex-col items-center mb-6">
<div class="relative mb-4">
<div class="w-32 h-32 rounded-full bg-purple-900 overflow-hidden border-2 border-purple-600">
<img id="modal-profile-img" src="" class="w-full h-full object-cover">
</div>
<button id="change-photo-btn" class="absolute bottom-0 right-0 bg-purple-600 rounded-full p-2 hover:bg-purple-700">
<i data-feather="camera" class="w-4 h-4"></i>
</button>
</div>
<input type="file" id="profile-photo-input" class="hidden" accept="image/*">
<input type="text" id="profile-name-input" placeholder="Your Name"
class="bg-gray-800 border border-purple-900 rounded-full px-4 py-2 text-center focus:outline-none focus:ring-2 focus:ring-purple-500 w-full max-w-xs">
</div>
<div class="flex justify-end space-x-3">
<button id="cancel-profile-btn" class="px-4 py-2 rounded-full bg-gray-800 hover:bg-gray-700">Cancel</button>
<button id="save-profile-btn" class="px-4 py-2 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700">Save Changes</button>
</div>
</div>
</div>
<script>
// Firebase configuration for direct API calls
const API_BASE_URL = "https://us-central1-app-teste-x.cloudfunctions.net/api";
let currentUser = null;
let authToken = null;
// Initialize Firebase
const firebaseConfig = {
apiKey: "AIzaSyA4MtyRVloOv6WWR_KyF4bM0UqD91gwykY",
authDomain: "app-teste-x.firebaseapp.com",
projectId: "app-teste-x",
databaseURL: "https://app-teste-x-default-rtdb.firebaseio.com",
storageBucket: "app-teste-x.appspot.com"
};
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const database = firebase.database();
// Initialize Feather Icons
feather.replace();
// DOM Elements
const messageForm = document.getElementById('message-form');
const messageInput = document.getElementById('message-input');
const messagesContainer = document.getElementById('messages-container');
const fileInput = document.getElementById('file-input');
const attachBtn = document.getElementById('attach-btn');
const profileBtn = document.getElementById('profile-btn');
const profileModal = document.getElementById('profile-modal');
const closeProfileModal = document.getElementById('close-profile-modal');
const cancelProfileBtn = document.getElementById('cancel-profile-btn');
const saveProfileBtn = document.getElementById('save-profile-btn');
const changePhotoBtn = document.getElementById('change-photo-btn');
const profilePhotoInput = document.getElementById('profile-photo-input');
const modalProfileImg = document.getElementById('modal-profile-img');
const profileImg = document.getElementById('profile-img');
// Event Listeners
messageForm.addEventListener('submit', sendMessage);
attachBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileUpload);
profileBtn.addEventListener('click', openProfileModal);
closeProfileModal.addEventListener('click', closeModal);
cancelProfileBtn.addEventListener('click', closeModal);
saveProfileBtn.addEventListener('click', saveProfile);
changePhotoBtn.addEventListener('click', () => profilePhotoInput.click());
profilePhotoInput.addEventListener('change', handleProfilePhotoChange);
document.getElementById('logout-btn').addEventListener('click', logoutUser);
// Initialize auth state
auth.onAuthStateChanged(async user => {
currentUser = user;
if (user) {
const token = await user.getIdToken();
authToken = token;
// Fetch user profile
const profileResponse = await fetch(`${API_BASE_URL}/users/${user.uid}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (profileResponse.ok) {
const profileData = await profileResponse.json();
profileImg.src = profileData.photoUrl || '';
modalProfileImg.src = profileData.photoUrl || '';
document.getElementById('profile-name-input').value = profileData.name || '';
}
initChat();
loadFriends();
loadFriendRequests();
} else {
window.location.href = '/login.html';
}
});
async function initChat() {
try {
const messagesRef = db.ref('messages').limitToLast(100);
const snapshot = await messagesRef.once('value');
messagesContainer.innerHTML = '';
snapshot.forEach((childSnapshot) => {
const msg = childSnapshot.val();
const isCurrentUser = msg.senderId === currentUser.uid;
if (msg.contentType === 'text') {
appendMessage(msg.content, isCurrentUser, msg.senderName, msg.timestamp);
} else if (msg.contentType === 'image') {
appendImageMessage(msg.content, isCurrentUser, msg.senderName, msg.timestamp);
}
});
scrollToBottom();
setupMessageListener();
} catch (error) {
console.error('Error initializing chat:', error);
}
}
// Functions
async function sendMessage(e) {
e.preventDefault();
const message = messageInput.value.trim();
if (message && currentUser) {
try {
const newMessageRef = db.ref('messages').push();
await newMessageRef.set({
senderId: currentUser.uid,
senderName: currentUser.displayName,
content: message,
contentType: 'text',
timestamp: firebase.database.ServerValue.TIMESTAMP
});
appendMessage(message, true, currentUser.displayName, Date.now());
messageInput.value = '';
scrollToBottom();
} catch (error) {
console.error('Error sending message:', error);
}
}
}
// Listen for new messages
function setupMessageListener() {
db.ref('messages').limitToLast(100).on('child_added', (snapshot) => {
const message = snapshot.val();
const isCurrentUser = message.senderId === currentUser.uid;
if (message.contentType === 'text') {
appendMessage(message.content, isCurrentUser, message.senderName, message.timestamp);
} else if (message.contentType === 'image') {
appendImageMessage(message.content, isCurrentUser, message.senderName, message.timestamp);
}
scrollToBottom();
});
}
async function handleFileUpload(e) {
const file = e.target.files[0];
if (file && currentUser) {
if (file.type.startsWith('image/')) {
try {
const formData = new FormData();
formData.append('photo', file);
formData.append('uid', currentUser.uid);
const response = await fetch(`${API_BASE_URL}/upload-profile`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (response.ok) {
const result = await response.json();
appendImageMessage(result.photoUrl, true, currentUser.displayName, Date.now());
scrollToBottom();
}
} catch (error) {
console.error('Error uploading image:', error);
}
} else {
alert('Please select an image file.');
}
}
fileInput.value = '';
}
function appendMessage(text, isCurrentUser, senderName, timestamp) {
const messageDiv = document.createElement('div');
messageDiv.className = `flex ${isCurrentUser ? 'justify-end' : 'justify-start'}`;
const messageContent = document.createElement('div');
messageContent.className = `chat-message message-${isCurrentUser ? 'right' : 'left'} relative max-w-xs md:max-w-md ${isCurrentUser ? 'bg-indigo-600' : 'bg-purple-900'} rounded-2xl p-3 ${isCurrentUser ? 'ml-10' : 'mr-10'}`;
if (!isCurrentUser) {
const senderName = document.createElement('div');
senderName.className = 'font-semibold text-purple-300';
senderName.textContent = 'Friend';
messageContent.appendChild(senderName);
}
const messageText = document.createElement('p');
messageText.textContent = text;
messageContent.appendChild(messageText);
const timeStamp = document.createElement('span');
timeStamp.className = `text-xs block mt-1 ${isCurrentUser ? 'text-indigo-200 text-right' : 'text-purple-200'}`;
const date = new Date(timestamp);
timeStamp.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageContent.appendChild(timeStamp);
messageDiv.appendChild(messageContent);
messagesContainer.appendChild(messageDiv);
}
function appendImageMessage(imageUrl, isCurrentUser, senderName, timestamp) {
const messageDiv = document.createElement('div');
messageDiv.className = `flex ${isCurrentUser ? 'justify-end' : 'justify-start'}`;
const messageContent = document.createElement('div');
messageContent.className = `chat-message message-${isCurrentUser ? 'right' : 'left'} relative max-w-xs md:max-w-md ${isCurrentUser ? 'bg-indigo-600' : 'bg-purple-900'} rounded-2xl p-3 ${isCurrentUser ? 'ml-10' : 'mr-10'}`;
const imageContainer = document.createElement('div');
imageContainer.className = 'mb-2 rounded-lg overflow-hidden';
const image = document.createElement('img');
image.src = imageUrl;
image.className = 'w-full h-auto';
imageContainer.appendChild(image);
messageContent.appendChild(imageContainer);
const timeStamp = document.createElement('span');
timeStamp.className = `text-xs block mt-1 ${isCurrentUser ? 'text-indigo-200 text-right' : 'text-purple-200'}`;
const date = new Date(timestamp);
timeStamp.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageContent.appendChild(timeStamp);
messageDiv.appendChild(messageContent);
messagesContainer.appendChild(messageDiv);
}
function scrollToBottom() {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function openProfileModal() {
profileModal.classList.remove('hidden');
}
function closeModal() {
profileModal.classList.add('hidden');
}
function handleProfilePhotoChange(e) {
const file = e.target.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (event) => {
modalProfileImg.src = event.target.result;
};
reader.readAsDataURL(file);
}
}
async function saveProfile() {
if (!currentUser) return;
try {
// Upload new profile photo if changed
if (profilePhotoInput.files[0]) {
const formData = new FormData();
formData.append('photo', profilePhotoInput.files[0]);
formData.append('uid', currentUser.uid);
const uploadResponse = await fetch(`${API_BASE_URL}/upload-profile`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`
},
body: formData
});
if (uploadResponse.ok) {
const result = await uploadResponse.json();
profileImg.src = result.photoUrl;
modalProfileImg.src = result.photoUrl;
// Update auth profile
await auth.currentUser.updateProfile({
photoURL: result.photoUrl
});
}
}
// Update profile name if changed
const newName = document.getElementById('profile-name-input').value.trim();
if (newName && newName !== currentUser.displayName) {
await db.ref(`users/${currentUser.uid}/name`).set(newName);
await auth.currentUser.updateProfile({
displayName: newName
});
}
closeModal();
} catch (error) {
console.error('Error saving profile:', error);
}
}
async function loadFriends() {
if (!currentUser) return;
try {
const friendsRef = db.ref(`friends/${currentUser.uid}`);
const snapshot = await friendsRef.once('value');
const friends = snapshot.val() || {};
const friendsList = document.getElementById('friends-list');
friendsList.innerHTML = '';
for (const friendId in friends) {
const userSnapshot = await db.ref(`users/${friendId}`).once('value');
const userData = userSnapshot.val();
const friendElement = document.createElement('div');
friendElement.className = 'flex items-center p-2 rounded hover:bg-purple-900 hover:bg-opacity-30 cursor-pointer';
friendElement.innerHTML = `
<div class="w-8 h-8 rounded-full bg-purple-600 mr-2 overflow-hidden">
<img src="${userData?.photoUrl || 'http://static.photos/people/200x200/1'}" class="w-full h-full object-cover">
</div>
<span>${friends[friendId].name || 'Friend'}</span>
`;
friendsList.appendChild(friendElement);
}
} catch (error) {
console.error('Error loading friends:', error);
}
}
async function loadFriendRequests() {
if (!currentUser) return;
try {
const requestsRef = db.ref(`friendRequests/${currentUser.uid}`);
const snapshot = await requestsRef.once('value');
const requests = snapshot.val() || {};
const requestsList = document.getElementById('requests-list');
requestsList.innerHTML = '';
for (const requesterId in requests) {
if (requests[requesterId].status === 'pending') {
const userSnapshot = await db.ref(`users/${requesterId}`).once('value');
const userData = userSnapshot.val();
const requestElement = document.createElement('div');
requestElement.className = 'flex items-center justify-between p-2 rounded hover:bg-purple-900 hover:bg-opacity-30';
requestElement.innerHTML = `
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-purple-600 mr-2 overflow-hidden">
<img src="${userData?.photoUrl || 'http://static.photos/people/200x200/2'}" class="w-full h-full object-cover">
</div>
<span>${requests[requesterId].name || 'User'}</span>
</div>
<div class="flex space-x-1">
<button onclick="handleFriendRequest('accept', '${requesterId}')" class="p-1 rounded-full bg-green-600 hover:bg-green-700">
<i data-feather="check" class="w-3 h-3"></i>
</button>
<button onclick="handleFriendRequest('reject', '${requesterId}')" class="p-1 rounded-full bg-red-600 hover:bg-red-700">
<i data-feather="x" class="w-3 h-3"></i>
</button>
</div>
`;
requestsList.appendChild(requestElement);
}
}
feather.replace();
} catch (error) {
console.error('Error loading friend requests:', error);
}
}
async function handleFriendRequest(action, requesterId) {
try {
if (action === 'accept') {
// Get requester's name from friend requests
const requestSnapshot = await db.ref(`friendRequests/${currentUser.uid}/${requesterId}`).once('value');
const requesterData = requestSnapshot.val();
// Add to each other's friends lists
await db.ref(`friends/${currentUser.uid}/${requesterId}`).set({
name: requesterData.name
});
// Get current user's name
const userSnapshot = await db.ref(`users/${currentUser.uid}/name`).once('value');
const userName = userSnapshot.val() || 'Friend';
await db.ref(`friends/${requesterId}/${currentUser.uid}`).set({
name: userName
});
}
// Remove from friend requests in both cases (accept or reject)
await db.ref(`friendRequests/${currentUser.uid}/${requesterId}`).remove();
loadFriendRequests();
loadFriends();
} catch (error) {
console.error(`Error ${action}ing friend request:`, error);
}
}
async function logoutUser() {
try {
await auth.signOut();
window.location.href = '/login.html';
} catch (error) {
console.error('Error signing out:', error);
}
}
// Initial setup
scrollToBottom();
feather.replace();
// Initialize Firebase listeners
auth.onAuthStateChanged(async user => {
if (user) {
currentUser = user;
authToken = await user.getIdToken();
// Load initial data
const userSnapshot = await db.ref(`users/${user.uid}`).once('value');
const userData = userSnapshot.val();
if (userData) {
profileImg.src = userData.photoUrl || '';
modalProfileImg.src = userData.photoUrl || '';
document.getElementById('profile-name-input').value = userData.name || '';
}
initChat();
loadFriends();
loadFriendRequests();
} else {
window.location.href = '/login.html';
}
});
</script>
</body>
</html>