Spaces:
Running
Running
| // User management | |
| let currentUser = null; | |
| const users = JSON.parse(localStorage.getItem('users')) || []; | |
| const posts = JSON.parse(localStorage.getItem('posts')) || []; | |
| // Initialize auth forms | |
| function initAuthForms() { | |
| const loginForm = document.getElementById('login-form'); | |
| const registerForm = document.getElementById('register-form'); | |
| const showRegister = document.getElementById('show-register'); | |
| const showLogin = document.getElementById('show-login'); | |
| if (loginForm && registerForm && showRegister && showLogin) { | |
| showRegister.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| loginForm.classList.add('hidden'); | |
| registerForm.classList.remove('hidden'); | |
| } | |
| function showCommentModal(post) { | |
| const modal = document.createElement('div'); | |
| modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; | |
| modal.innerHTML = ` | |
| <div class="bg-white rounded-lg w-full max-w-lg max-h-[90vh] flex flex-col"> | |
| <div class="p-4 border-b flex items-center justify-between"> | |
| <h3 class="text-lg font-semibold">Comments</h3> | |
| <button class="text-gray-500 hover:text-gray-700" id="close-comment-modal"> | |
| <i data-feather="x"></i> | |
| </button> | |
| </div> | |
| <div class="p-4 border-b"> | |
| <div class="flex items-start"> | |
| <img src="${post.user.avatar}" class="w-8 h-8 rounded-full"> | |
| <div class="ml-3"> | |
| <div class="flex items-center"> | |
| <span class="font-medium">${post.user.name}</span> | |
| <span class="mx-1 text-gray-500">•</span> | |
| <span class="text-gray-500 text-sm">${post.timestamp}</span> | |
| </div> | |
| <p class="mt-1">${post.content}</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex-1 overflow-y-auto p-4 space-y-4" id="comments-container"> | |
| ${post.comments > 0 ? '' : ` | |
| <div class="text-center py-8"> | |
| <i data-feather="message-circle" class="w-12 h-12 mx-auto text-gray-300"></i> | |
| <p class="mt-2 text-gray-500">No comments yet</p> | |
| <p class="text-sm text-gray-400">Be the first to share your thoughts!</p> | |
| </div> | |
| `} | |
| </div> | |
| <div class="p-4 border-t"> | |
| <div class="flex items-center"> | |
| <img src="http://static.photos/people/200x200/1" class="w-8 h-8 rounded-full"> | |
| <div class="ml-2 flex-1 flex items-center bg-gray-100 rounded-full"> | |
| <input type="text" placeholder="Add a comment..." class="flex-1 bg-transparent px-4 py-2 focus:outline-none" id="comment-input"> | |
| <button class="text-blue-500 px-3" id="post-comment"> | |
| <i data-feather="send" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| feather.replace(); | |
| document.getElementById('close-comment-modal').addEventListener('click', () => { | |
| modal.remove(); | |
| }); | |
| document.getElementById('post-comment').addEventListener('click', () => { | |
| const commentInput = document.getElementById('comment-input'); | |
| const comment = commentInput.value.trim(); | |
| if (comment) { | |
| // In a real app, you would save the comment | |
| alert(`Comment posted: ${comment}`); | |
| commentInput.value = ''; | |
| } | |
| }); | |
| } | |
| function showShareOptions(post) { | |
| const modal = document.createElement('div'); | |
| modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; | |
| modal.innerHTML = ` | |
| <div class="bg-white rounded-lg w-full max-w-sm"> | |
| <div class="p-4 border-b"> | |
| <h3 class="text-lg font-semibold">Share Post</h3> | |
| </div> | |
| <div class="p-4 space-y-2"> | |
| <button class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100"> | |
| <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-500"> | |
| <i data-feather="user-plus" class="w-5 h-5"></i> | |
| </div> | |
| <div class="ml-4 text-left"> | |
| <p class="font-medium">Send in a message</p> | |
| <p class="text-sm text-gray-500">Share privately with friends</p> | |
| </div> | |
| </button> | |
| <button class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100"> | |
| <div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center text-green-500"> | |
| <i data-feather="twitter" class="w-5 h-5"></i> | |
| </div> | |
| <div class="ml-4 text-left"> | |
| <p class="font-medium">Share on Twitter</p> | |
| <p class="text-sm text-gray-500">Post to Twitter</p> | |
| </div> | |
| </button> | |
| <button class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100"> | |
| <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-500"> | |
| <i data-feather="facebook" class="w-5 h-5"></i> | |
| </div> | |
| <div class="ml-4 text-left"> | |
| <p class="font-medium">Share on Facebook</p> | |
| <p class="text-sm text-gray-500">Post to Facebook</p> | |
| </div> | |
| </button> | |
| <button class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100"> | |
| <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center text-purple-500"> | |
| <i data-feather="link" class="w-5 h-5"></i> | |
| </div> | |
| <div class="ml-4 text-left"> | |
| <p class="font-medium">Copy link</p> | |
| <p class="text-sm text-gray-500">Copy post link</p> | |
| </div> | |
| </button> | |
| </div> | |
| <div class="p-4 border-t"> | |
| <button id="close-share-modal" class="w-full py-2 text-gray-700 rounded-lg hover:bg-gray-100"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| feather.replace(); | |
| document.getElementById('close-share-modal').addEventListener('click', () => { | |
| modal.remove(); | |
| }); | |
| modal.querySelectorAll('button').forEach(btn => { | |
| if (btn.id !== 'close-share-modal') { | |
| btn.addEventListener('click', () => { | |
| alert(`Post shared via ${btn.textContent.trim()}`); | |
| modal.remove(); | |
| }); | |
| } | |
| }); | |
| } | |
| ); | |
| showLogin.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| registerForm.classList.add('hidden'); | |
| loginForm.classList.remove('hidden'); | |
| }); | |
| loginForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const email = document.getElementById('login-email').value; | |
| const password = document.getElementById('login-password').value; | |
| loginUser(email, password); | |
| }); | |
| registerForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const name = document.getElementById('register-name').value; | |
| const username = document.getElementById('register-username').value; | |
| const email = document.getElementById('register-email').value; | |
| const password = document.getElementById('register-password').value; | |
| registerUser(name, username, email, password); | |
| }); | |
| } | |
| } | |
| function loginUser(email, password) { | |
| const user = users.find(u => u.email === email && u.password === password); | |
| if (user) { | |
| currentUser = user; | |
| localStorage.setItem('currentUser', JSON.stringify(user)); | |
| window.location.href = '/'; | |
| } else { | |
| alert('Invalid credentials'); | |
| } | |
| } | |
| function registerUser(name, username, email, password) { | |
| if (users.some(u => u.email === email)) { | |
| alert('Email already registered'); | |
| return; | |
| } | |
| if (users.some(u => u.username === username)) { | |
| alert('Username already taken'); | |
| return; | |
| } | |
| const newUser = { | |
| id: Date.now(), | |
| name, | |
| username, | |
| email, | |
| password, | |
| avatar: `http://static.photos/people/200x200/${Math.floor(Math.random() * 100)}`, | |
| followers: [], | |
| following: [] | |
| }; | |
| users.push(newUser); | |
| localStorage.setItem('users', JSON.stringify(users)); | |
| currentUser = newUser; | |
| localStorage.setItem('currentUser', JSON.stringify(newUser)); | |
| window.location.href = '/'; | |
| } | |
| function logoutUser() { | |
| currentUser = null; | |
| localStorage.removeItem('currentUser'); | |
| window.location.href = '/auth.html'; | |
| } | |
| // Post management | |
| function createPost(content, image = null) { | |
| const newPost = { | |
| id: Date.now(), | |
| user: { | |
| name: currentUser.name, | |
| username: currentUser.username, | |
| avatar: currentUser.avatar | |
| }, | |
| content, | |
| image, | |
| timestamp: 'Just now', | |
| likes: 0, | |
| comments: 0, | |
| shares: 0, | |
| isLiked: false, | |
| isSaved: false | |
| }; | |
| posts.unshift(newPost); | |
| localStorage.setItem('posts', JSON.stringify(posts)); | |
| return newPost; | |
| } | |
| // Sample data for posts if none exists | |
| if (posts.length === 0) { | |
| const samplePosts = [ | |
| { | |
| id: 1, | |
| user: { | |
| name: "Sarah Johnson", | |
| username: "@sarahj", | |
| avatar: "http://static.photos/people/200x200/4" | |
| }, | |
| content: "Just launched my new portfolio website! Built with React and TailwindCSS. Check it out and let me know what you think! 🚀", | |
| image: "http://static.photos/technology/640x360/5", | |
| timestamp: "2 hours ago", | |
| likes: 245, | |
| comments: 32, | |
| shares: 12, | |
| isLiked: false, | |
| isSaved: false | |
| }, | |
| { | |
| id: 2, | |
| user: { | |
| name: "Tech Insights", | |
| username: "@techinsights", | |
| avatar: "http://static.photos/technology/200x200/6" | |
| }, | |
| content: "Breaking: New AI model achieves human-level performance on complex reasoning tasks. This could revolutionize how we approach problem-solving in tech. #AI #MachineLearning", | |
| timestamp: "4 hours ago", | |
| likes: 1240, | |
| comments: 87, | |
| shares: 210, | |
| isLiked: true, | |
| isSaved: false | |
| }, | |
| { | |
| id: 3, | |
| user: { | |
| name: "Design Daily", | |
| username: "@designdaily", | |
| avatar: "http://static.photos/abstract/200x200/7" | |
| }, | |
| content: "Color theory tip: Use the 60-30-10 rule for balanced designs. 60% dominant color, 30% secondary, 10% accent. This creates visual harmony without overwhelming the viewer.", | |
| image: "http://static.photos/design/640x360/8", | |
| timestamp: "6 hours ago", | |
| likes: 562, | |
| comments: 24, | |
| shares: 42, | |
| isLiked: false, | |
| isSaved: true | |
| }, | |
| { | |
| id: 4, | |
| user: { | |
| name: "Alex Morgan", | |
| username: "@alexmorgan", | |
| avatar: "http://static.photos/people/200x200/1" | |
| }, | |
| content: "Working on a new project that combines blockchain with social media. Excited to share more details soon! #Web3 #Blockchain", | |
| timestamp: "1 day ago", | |
| likes: 87, | |
| comments: 15, | |
| shares: 9, | |
| isLiked: true, | |
| isSaved: false | |
| } | |
| ]; | |
| // Track viewed posts to avoid reshowing | |
| let viewedPosts = new Set(); | |
| let currentPosts = []; | |
| // Initialize the feed | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize auth forms if on auth page | |
| initAuthForms(); | |
| // Check if user is logged in | |
| const storedUser = localStorage.getItem('currentUser'); | |
| if (storedUser) { | |
| currentUser = JSON.parse(storedUser); | |
| } else if (window.location.pathname !== '/auth.html') { | |
| window.location.href = '/auth.html'; | |
| return; | |
| } | |
| // Initialize UI if on main page | |
| if (document.getElementById('feed-container')) { | |
| loadPosts(); | |
| // Load more button event | |
| document.getElementById('load-more').addEventListener('click', loadPosts); | |
| // Post creation form | |
| const postForm = document.getElementById('post-form'); | |
| if (postForm) { | |
| postForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const content = document.getElementById('post-content').value; | |
| const image = document.getElementById('post-image').files[0]; | |
| if (content.trim() === '') return; | |
| if (image) { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const newPost = createPost(content, e.target.result); | |
| prependPost(newPost); | |
| }; | |
| reader.readAsDataURL(image); | |
| } else { | |
| const newPost = createPost(content); | |
| prependPost(newPost); | |
| } | |
| postForm.reset(); | |
| }); | |
| } | |
| // Add logout functionality | |
| const logoutBtn = document.getElementById('logout-btn'); | |
| if (logoutBtn) { | |
| logoutBtn.addEventListener('click', logoutUser); | |
| } | |
| } | |
| }); | |
| // Load posts into the feed | |
| function loadPosts() { | |
| const feedContainer = document.getElementById('feed-container'); | |
| // Get posts from local storage | |
| const storedPosts = JSON.parse(localStorage.getItem('posts')) || []; | |
| // Filter out already viewed posts | |
| const newPosts = storedPosts.filter(post => !viewedPosts.has(post.id)); | |
| // If no new posts, show message | |
| if (newPosts.length === 0) { | |
| if (feedContainer.children.length === 0) { | |
| feedContainer.innerHTML = ` | |
| <div class="p-8 text-center"> | |
| <i data-feather="coffee" class="w-12 h-12 mx-auto text-gray-400"></i> | |
| <h3 class="mt-4 text-lg font-medium">No new posts</h3> | |
| <p class="mt-1 text-gray-500">You've seen all recent posts. Check back later for more!</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } else { | |
| document.getElementById('load-more').textContent = "No more posts"; | |
| document.getElementById('load-more').disabled = true; | |
| } | |
| return; | |
| } | |
| // Take first 2 new posts | |
| const postsToShow = newPosts.slice(0, 2); | |
| postsToShow.forEach(post => { | |
| // Mark as viewed | |
| viewedPosts.add(post.id); | |
| currentPosts.push(post); | |
| // Create post element | |
| const postElement = document.createElement('div'); | |
| postElement.className = 'post-card bg-white border-b last:border-b-0 fade-in'; | |
| postElement.innerHTML = ` | |
| <div class="p-6"> | |
| <div class="flex items-start"> | |
| <img src="${post.user.avatar}" alt="${post.user.name}" class="w-12 h-12 rounded-full user-avatar"> | |
| <div class="ml-4 flex-1"> | |
| <div class="flex items-center"> | |
| <h3 class="font-bold">${post.user.name}</h3> | |
| <span class="ml-2 text-gray-500 text-sm">${post.user.username}</span> | |
| <span class="mx-2 text-gray-300">•</span> | |
| <span class="text-gray-500 text-sm">${post.timestamp}</span> | |
| </div> | |
| <p class="mt-2 post-content">${post.content}</p> | |
| ${post.image ? `<img src="${post.image}" alt="Post image" class="mt-4 rounded-xl w-full">` : ''} | |
| <div class="mt-4 flex space-x-6"> | |
| <button class="interaction-btn ${post.isLiked ? 'active' : ''}" data-action="like" data-id="${post.id}"> | |
| <i data-feather="heart" class="w-4 h-4"></i> | |
| <span>${post.likes}</span> | |
| </button> | |
| <button class="interaction-btn" data-action="comment" data-id="${post.id}"> | |
| <i data-feather="message-circle" class="w-4 h-4"></i> | |
| <span>${post.comments}</span> | |
| </button> | |
| <button class="interaction-btn" data-action="share" data-id="${post.id}"> | |
| <i data-feather="share-2" class="w-4 h-4"></i> | |
| <span>${post.shares}</span> | |
| </button> | |
| <button class="interaction-btn ml-auto ${post.isSaved ? 'active' : ''}" data-action="save" data-id="${post.id}"> | |
| <i data-feather="bookmark" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| feedContainer.appendChild(postElement); | |
| }); | |
| // Reinitialize Feather icons | |
| feather.replace(); | |
| // Add event listeners to new buttons | |
| document.querySelectorAll('.interaction-btn').forEach(button => { | |
| button.addEventListener('click', handleInteraction); | |
| }); | |
| } | |
| // Handle post interactions | |
| function prependPost(post) { | |
| const feedContainer = document.getElementById('feed-container'); | |
| const firstChild = feedContainer.firstChild; | |
| const postElement = createPostElement(post); | |
| if (firstChild) { | |
| feedContainer.insertBefore(postElement, firstChild); | |
| } else { | |
| feedContainer.appendChild(postElement); | |
| } | |
| viewedPosts.add(post.id); | |
| currentPosts.unshift(post); | |
| feather.replace(); | |
| } | |
| function createPostElement(post) { | |
| const postElement = document.createElement('div'); | |
| postElement.className = 'post-card fade-in p-4'; | |
| postElement.innerHTML = ` | |
| <div class="flex items-start"> | |
| <img src="${post.user.avatar}" alt="${post.user.name}" class="w-10 h-10 rounded-full user-avatar"> | |
| <div class="ml-3 flex-1"> | |
| <div class="flex items-center"> | |
| <h3 class="font-bold">${post.user.name}</h3> | |
| <span class="mx-2 text-gray-300">•</span> | |
| <span class="text-gray-500 text-sm">${post.timestamp}</span> | |
| <button class="ml-auto text-gray-400 hover:text-gray-600"> | |
| <i data-feather="more-horizontal"></i> | |
| </button> | |
| </div> | |
| <p class="mt-2 post-content">${post.content}</p> | |
| ${post.image ? ` | |
| <div class="mt-3 rounded-xl overflow-hidden border"> | |
| <img src="${post.image}" alt="Post image" class="w-full"> | |
| </div> | |
| ` : ''} | |
| <div class="flex justify-between mt-3 pt-3 border-t"> | |
| <button class="interaction-btn ${post.isLiked ? 'active' : ''}" data-action="like" data-id="${post.id}"> | |
| <i data-feather="heart" class="w-5 h-5"></i> | |
| <span>${post.likes}</span> | |
| </button> | |
| <button class="interaction-btn" data-action="comment" data-id="${post.id}"> | |
| <i data-feather="message-circle" class="w-5 h-5"></i> | |
| <span>${post.comments}</span> | |
| </button> | |
| <button class="interaction-btn" data-action="share" data-id="${post.id}"> | |
| <i data-feather="share-2" class="w-5 h-5"></i> | |
| </button> | |
| <button class="interaction-btn ml-auto ${post.isSaved ? 'active' : ''}" data-action="save" data-id="${post.id}"> | |
| <i data-feather="bookmark" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| return postElement; | |
| } | |
| function handleInteraction(e) { | |
| const button = e.currentTarget; | |
| const action = button.getAttribute('data-action'); | |
| const postId = parseInt(button.getAttribute('data-id')); | |
| // Find the post in storage | |
| const storedPosts = JSON.parse(localStorage.getItem('posts')) || []; | |
| const postIndex = storedPosts.findIndex(p => p.id === postId); | |
| if (!post) return; | |
| // Update UI based on action | |
| switch(action) { | |
| case 'like': | |
| if (button.classList.contains('active')) { | |
| button.classList.remove('active'); | |
| post.likes--; | |
| post.isLiked = false; | |
| } else { | |
| button.classList.add('active'); | |
| post.likes++; | |
| post.isLiked = true; | |
| } | |
| button.querySelector('span').textContent = post.likes; | |
| break; | |
| case 'save': | |
| if (button.classList.contains('active')) { | |
| button.classList.remove('active'); | |
| post.isSaved = false; | |
| } else { | |
| button.classList.add('active'); | |
| post.isSaved = true; | |
| } | |
| break; | |
| case 'comment': | |
| // In a real app, this would open comment section | |
| // Update storage | |
| storedPosts[postIndex] = post; | |
| localStorage.setItem('posts', JSON.stringify(storedPosts)); | |
| // Update current posts | |
| const currentPostIndex = currentPosts.findIndex(p => p.id === postId); | |
| if (currentPostIndex !== -1) { | |
| currentPosts[currentPostIndex] = post; | |
| } | |
| // Show comment modal | |
| showCommentModal(post); | |
| break; | |
| case 'share': | |
| // In a real app, this would open share options | |
| // Update storage | |
| storedPosts[postIndex] = post; | |
| localStorage.setItem('posts', JSON.stringify(storedPosts)); | |
| // Show share options | |
| showShareOptions(post); | |
| break; | |
| } | |
| } |