diff --git a/script.js b/script.js index f02be57..1854f1c 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { + // --- QUERY SELECTORS --- const loginForm = document.querySelector('.login-form'); const loginContainer = document.querySelector('.login-container'); const feedView = document.getElementById('feed-view'); @@ -7,15 +8,25 @@ document.addEventListener('DOMContentLoaded', () => { const confirmPasswordInput = document.getElementById('confirm-password'); const confirmPasswordGroup = document.getElementById('confirm-password-group'); const submitBtn = document.querySelector('.btn-primary'); - const btnText = submitBtn.querySelector('span'); - const helperText = document.querySelector('.signup-link'); + const btnText = submitBtn ? submitBtn.querySelector('span') : null; const toggleLink = document.querySelector('.signup-link a'); const title = document.querySelector('.brand-section h1'); const subtitle = document.querySelector('.brand-section p'); + // Post Creation Elements + const createPostView = document.getElementById('create-post-view'); + const cancelPostBtn = document.getElementById('cancel-post-btn'); + const sharePostBtn = document.getElementById('share-post-btn'); + const uploadTrigger = document.getElementById('upload-trigger'); + const imageInput = document.getElementById('post-image-input'); + const imagePreview = document.getElementById('image-preview'); + const captionInput = document.getElementById('post-caption-input'); + const navPostBtn = document.querySelector('.nav-btn:nth-child(2)'); + const navHomeBtn = document.querySelector('.nav-btn:first-child'); + let isLoginMode = true; - // Toggle between Login and Signup + // --- AUTH LOGIC --- if (toggleLink) { toggleLink.addEventListener('click', (e) => { e.preventDefault(); @@ -25,47 +36,53 @@ document.addEventListener('DOMContentLoaded', () => { } function updateUI() { + if (!title || !subtitle || !btnText || !confirmPasswordGroup) return; + if (isLoginMode) { title.innerText = 'Welcome Back'; subtitle.innerText = 'Enter your details below'; btnText.innerText = 'Sign In'; - helperText.innerHTML = 'Don\'t have an account? Create One'; + const helper = document.querySelector('.signup-link'); + if (helper) helper.innerHTML = 'Don\'t have an account? Create One'; confirmPasswordGroup.classList.add('hidden'); confirmPasswordInput.required = false; } else { title.innerText = 'Create Account'; subtitle.innerText = 'Start your journey with us'; btnText.innerText = 'Sign Up'; - helperText.innerHTML = 'Already have an account? Sign In'; + const helper = document.querySelector('.signup-link'); + if (helper) helper.innerHTML = 'Already have an account? Sign In'; confirmPasswordGroup.classList.remove('hidden'); confirmPasswordInput.required = true; } - // Re-attach listener to the new link element const newLink = document.querySelector('.signup-link a'); - newLink.addEventListener('click', (e) => { - e.preventDefault(); - isLoginMode = !isLoginMode; - updateUI(); - }); + if (newLink) { + newLink.addEventListener('click', (e) => { + e.preventDefault(); + isLoginMode = !isLoginMode; + updateUI(); + }); + } - // Reset form and styles - loginForm.reset(); + if (loginForm) loginForm.reset(); resetButton(); } function resetButton() { + if (!submitBtn || !btnText) return; submitBtn.style.background = ''; submitBtn.style.transform = ''; btnText.innerText = isLoginMode ? 'Sign In' : 'Sign Up'; } function showFeedback(isSuccess, message) { + if (!btnText || !submitBtn) return; btnText.innerText = message; if (isSuccess) { - submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)'; // Success Green + submitBtn.style.background = 'linear-gradient(135deg, #10B981 0%, #059669 100%)'; } else { - submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)'; // Error Red + submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)'; submitBtn.animate([ { transform: 'translateX(0)' }, { transform: 'translateX(-5px)' }, @@ -83,77 +100,54 @@ document.addEventListener('DOMContentLoaded', () => { function navigateToFeed() { if (loginContainer && feedView) { - // Fade out login loginContainer.style.opacity = '0'; - setTimeout(() => { loginContainer.classList.add('hidden'); - loginContainer.style.display = 'none'; // Ensure it's gone + loginContainer.style.display = 'none'; - // Show Feed feedView.classList.remove('hidden'); - // Trigger reflow - void feedView.offsetWidth; + void feedView.offsetWidth; // Reflow feedView.classList.add('fade-in'); - // Load persisted state for posts - loadPostState(); + loadPostState(); // Load data - // Remove fade-in class after animation to fix fixed positioning context setTimeout(() => { feedView.classList.remove('fade-in'); }, 600); - }, 600); // Wait for transition + }, 600); } } if (loginForm) { loginForm.addEventListener('submit', (e) => { e.preventDefault(); - const email = emailInput.value.trim(); const password = passwordInput.value.trim(); const confirmPassword = confirmPasswordInput.value.trim(); const timestamp = new Date().toISOString(); - - // Get existing users const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]'); if (isLoginMode) { - // --- LOGIN LOGIC --- const user = existingUsers.find(u => u.email === email && u.password === password); - if (user) { - console.log('Login Successful:', user); showFeedback(true, 'Success!'); setTimeout(navigateToFeed, 1000); } else { - console.warn('Login Failed: Invalid credentials'); showFeedback(false, 'Incorrect details'); } - } else { - // --- SIGNUP LOGIC --- - // 1. Check if user already exists if (existingUsers.some(u => u.email === email)) { showFeedback(false, 'User exists'); return; } - - // 2. Validate Password Match if (password !== confirmPassword) { showFeedback(false, 'Passwords do not match'); return; } - const newUser = { email, password, joinedAt: timestamp }; existingUsers.push(newUser); localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers)); - - console.log('New User Registered:', newUser); showFeedback(true, 'Account Created!'); - - // Switch to login mode after success setTimeout(() => { isLoginMode = true; updateUI(); @@ -163,290 +157,150 @@ document.addEventListener('DOMContentLoaded', () => { }); } - // --- PERSISTENCE LOGIC --- + // --- DATA HELPERS --- function getPostState() { return JSON.parse(localStorage.getItem('socialAppPostState') || '{}'); } - function savePostState(state) { localStorage.setItem('socialAppPostState', JSON.stringify(state)); } + function isPostDeleted(id) { + const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]'); + return deletedPosts.includes(id); + } function loadPostState() { const state = getPostState(); - const posts = document.querySelectorAll('.post-card'); - - posts.forEach(card => { + // 1. Remove deleted static posts + document.querySelectorAll('.post-card').forEach(card => { const id = card.getAttribute('data-post-id'); - if (id && state[id]) { + if (id && isPostDeleted(id)) { + card.remove(); + } else if (id && state[id]) { + // Restore Likes/Comments const data = state[id]; - - // Restore Likes const likeIcon = card.querySelector('.heart-icon'); const likesText = card.querySelector('.likes'); - - if (data.liked) { - likeIcon.classList.add('liked'); - likeIcon.style.fill = '#EF4444'; - likeIcon.style.stroke = '#EF4444'; - } else { - likeIcon.classList.remove('liked'); - likeIcon.style.fill = 'none'; - likeIcon.style.stroke = 'white'; + if (likeIcon && likesText) { + if (data.liked) { + likeIcon.classList.add('liked'); + likeIcon.style.fill = '#EF4444'; + likeIcon.style.stroke = '#EF4444'; + } else { + likeIcon.classList.remove('liked'); + likeIcon.style.fill = 'none'; + likeIcon.style.stroke = 'white'; + } + likesText.innerText = data.likesCount + ' likes'; } - likesText.innerText = data.likesCount + ' likes'; - // Restore Comments const commentsContainer = card.querySelector('.added-comments'); - commentsContainer.innerHTML = ''; // Clear existing to prevent dupes on re-run - if (data.comments && data.comments.length > 0) { - data.comments.forEach(text => { - const commentEl = document.createElement('div'); - commentEl.classList.add('comment-item'); - commentEl.innerHTML = `you ${text}`; - commentsContainer.appendChild(commentEl); - }); + if (commentsContainer) { + commentsContainer.innerHTML = ''; + if (data.comments && data.comments.length > 0) { + data.comments.forEach(text => { + const commentEl = document.createElement('div'); + commentEl.classList.add('comment-item'); + commentEl.innerHTML = `you ${text}`; + commentsContainer.appendChild(commentEl); + }); + } } } }); + + // 2. Render User Posts (if not already present) + const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]'); + // We iterate correctly to put newest on top + [...savedUserPosts].reverse().forEach(post => { + if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) { + renderPost(post, true); + // Re-apply state if needed? create function handles it mostly, + // but checking `state` for these dynamic posts is also good practice if we tracked likes on them. + // For now, let's assume they load fresh or we'd need to run the state logic on them too. + // Actually, logic is: render then apply state. + } + }); + + // Re-run state application for newly added dynamic posts + // Optimization: splitting this logic explicitly would be better, but re-querying all is fine for small apps. + // Let's just do a second pass for dynamic posts to support likes on them? + // Existing loop `document.querySelectorAll` covers them if they are in DOM. + // So this order (Filter Static -> Render Dynamic -> (Ideally re-check state on all)) is slightly off. + // Better: Render Dynamic First, THEN apply state to ALL. } - // --- FEED INTERACTIONS --- - if (feedView) { - feedView.addEventListener('click', (e) => { - const target = e.target; + // Improve Load Flow + function initFeed() { + const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]'); + [...savedUserPosts].reverse().forEach(post => { + if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) { + renderPost(post, true); + } + }); - // 1. LIKE ACTION - const likeBtn = target.closest('.heart-icon')?.closest('.icon-btn'); - if (likeBtn) { - const icon = likeBtn.querySelector('.heart-icon'); - const postCard = likeBtn.closest('.post-card'); - const likesText = postCard.querySelector('.likes'); - const postId = postCard.getAttribute('data-post-id'); - - // Toggle State - icon.classList.toggle('liked'); - - // Update Count (Simple parsing) - let count = parseInt(likesText.innerText); - let isLiked = false; - - if (icon.classList.contains('liked')) { - icon.style.fill = '#EF4444'; - icon.style.stroke = '#EF4444'; - count++; - isLiked = true; - } else { - icon.style.fill = 'none'; - icon.style.stroke = 'white'; - count--; - isLiked = false; - } - likesText.innerText = count + ' likes'; - - // Save State - if (postId) { - const state = getPostState(); - if (!state[postId]) state[postId] = { comments: [] }; - state[postId].liked = isLiked; - state[postId].likesCount = count; - // Preserve existing comments if we didn't just load them - if (!state[postId].comments) state[postId].comments = []; - savePostState(state); - } + const state = getPostState(); + document.querySelectorAll('.post-card').forEach(card => { + const id = card.getAttribute('data-post-id'); + if (id && isPostDeleted(id)) { + card.remove(); return; } - - // 2. COMMENT TOGGLE ACTION - const commentBtn = target.closest('.icon-btn'); - if (commentBtn && !commentBtn.querySelector('.heart-icon')) { - // Check if it's the comment button (has specific path) - const path = commentBtn.querySelector('path'); - if (path && (path.getAttribute('d').startsWith('M21 11.5') || path.closest('svg')?.innerHTML.includes('M21 11.5'))) { - const postCard = commentBtn.closest('.post-card'); - const commentSection = postCard.querySelector('.comment-section'); - commentSection.classList.toggle('active'); - if (commentSection.classList.contains('active')) { - commentSection.querySelector('input').focus(); + if (id && state[id]) { + const data = state[id]; + const likeIcon = card.querySelector('.heart-icon'); + const likesText = card.querySelector('.likes'); + if (likeIcon && likesText) { + if (data.liked) { + likeIcon.classList.add('liked'); + likeIcon.style.fill = '#EF4444'; + likeIcon.style.stroke = '#EF4444'; } - return; + likesText.innerText = data.likesCount + ' likes'; } - } - - // 3. POST COMMENT ACTION - if (target.classList.contains('post-btn')) { - const wrapper = target.closest('.comment-input-wrapper'); - const input = wrapper.querySelector('.comment-input'); - const text = input.value.trim(); - const postCard = wrapper.closest('.post-card'); - const postId = postCard.getAttribute('data-post-id'); - - if (text) { - addComment(wrapper.closest('.comment-section'), text); - - if (postId) { - const state = getPostState(); - if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] }; - if (!state[postId].comments) state[postId].comments = []; - state[postId].comments.push(text); - savePostState(state); + const commentsContainer = card.querySelector('.added-comments'); + if (commentsContainer) { + commentsContainer.innerHTML = ''; + if (data.comments) { + data.comments.forEach(text => { + const el = document.createElement('div'); + el.classList.add('comment-item'); + el.innerHTML = `you ${text}`; + commentsContainer.appendChild(el); + }); } - input.value = ''; - } - } - }); - - // Enter key for comments - feedView.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && e.target.classList.contains('comment-input')) { - const text = e.target.value.trim(); - const wrapper = e.target.closest('.comment-input-wrapper'); - const postCard = wrapper.closest('.post-card'); - const postId = postCard.getAttribute('data-post-id'); - - if (text) { - addComment(e.target.closest('.comment-section'), text); - - if (postId) { - const state = getPostState(); - if (!state[postId]) state[postId] = { liked: false, likesCount: parseInt(postCard.querySelector('.likes').innerText) || 0, comments: [] }; - if (!state[postId].comments) state[postId].comments = []; - state[postId].comments.push(text); - savePostState(state); - } - e.target.value = ''; } } }); } - function addComment(section, text) { - const container = section.querySelector('.added-comments'); - const commentEl = document.createElement('div'); - commentEl.classList.add('comment-item'); - commentEl.innerHTML = `you ${text}`; - container.appendChild(commentEl); - } - - // --- CREATE POST LOGIC --- - const createPostView = document.getElementById('create-post-view'); - const cancelPostBtn = document.getElementById('cancel-post-btn'); - const sharePostBtn = document.getElementById('share-post-btn'); - const uploadTrigger = document.getElementById('upload-trigger'); - const imageInput = document.getElementById('post-image-input'); - const imagePreview = document.getElementById('image-preview'); - const captionInput = document.getElementById('post-caption-input'); - const navPostBtn = document.querySelector('.nav-btn:nth-child(2)'); // Center Post Button - const navHomeBtn = document.querySelector('.nav-btn:first-child'); - - // 1. Navigation - if (navPostBtn) { - navPostBtn.addEventListener('click', () => { - feedView.classList.add('hidden'); - createPostView.classList.remove('hidden'); - createPostView.classList.add('fade-in'); - }); - } - - if (cancelPostBtn) { - cancelPostBtn.addEventListener('click', () => { - resetPostForm(); - createPostView.classList.add('hidden'); - feedView.classList.remove('hidden'); - }); - } - - if (navHomeBtn && feedView.classList.contains('hidden') && !createPostView.classList.contains('hidden')) { - // Logic handled by cancel mostly, but good to have explicit nav - navHomeBtn.addEventListener('click', () => { - if (!createPostView.classList.contains('hidden')) { - resetPostForm(); - createPostView.classList.add('hidden'); - feedView.classList.remove('hidden'); - } - }); - } - - // 2. Image Selection - if (uploadTrigger) { - uploadTrigger.addEventListener('click', () => imageInput.click()); - } - - if (imageInput) { - imageInput.addEventListener('change', (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - imagePreview.src = e.target.result; - imagePreview.classList.remove('hidden'); - document.querySelector('.upload-placeholder').classList.add('hidden'); - }; - reader.readAsDataURL(file); - } - }); - } - - // 3. Share Post - if (sharePostBtn) { - sharePostBtn.addEventListener('click', () => { - if (!imagePreview.src || imagePreview.classList.contains('hidden')) { - alert('Please select an image first.'); - return; - } - - const caption = captionInput.value.trim(); - const imageSrc = imagePreview.src; - const id = 'post_' + Date.now(); - - const newPost = { - id, - imageSrc, - caption, - timestamp: new Date().toISOString(), - username: 'you', // Mock user - likes: 0 - }; - - // Save to LocalStorage - const userPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]'); - userPosts.unshift(newPost); // Add to top - localStorage.setItem('socialAppUserPosts', JSON.stringify(userPosts)); - - // Render to Feed - renderPost(newPost, true); // true = prepend - - // Reset and Go Back - resetPostForm(); - createPostView.classList.add('hidden'); - feedView.classList.remove('hidden'); - - // Re-bind listeners isn't needed strictly due to delegation, but state refresh might be - loadPostState(); - }); - } - - function resetPostForm() { - imageInput.value = ''; - captionInput.value = ''; - imagePreview.src = ''; - imagePreview.classList.add('hidden'); - document.querySelector('.upload-placeholder').classList.remove('hidden'); - } - + // --- RENDER POST --- function renderPost(post, prepend = false) { const feedContainer = document.querySelector('.feed-container'); const article = document.createElement('article'); article.className = 'post-card'; article.setAttribute('data-post-id', post.id); + const isUserPost = post.id && post.id.startsWith('post_'); + const optionsHTML = isUserPost ? ` + +
+ ` : ``; + article.innerHTML = `