${post.username} ${post.caption}
document.addEventListener('DOMContentLoaded', () => {
// --- API CONFIG ---
const API = {
async getUsers() {
const res = await fetch('/api/users');
if (!res.ok) throw new Error(`Server Error: ${res.status}`);
return await res.json();
},
async addUser(user) {
await fetch('/api/users/add', { method: 'POST', body: JSON.stringify(user) });
},
async getPosts() {
const res = await fetch('/api/posts');
if (!res.ok) throw new Error(`Server Error: ${res.status}`);
return await res.json();
},
async addPost(post) {
await fetch('/api/posts/add', { method: 'POST', body: JSON.stringify(post) });
},
async updatePost(post) {
await fetch('/api/posts/update', { method: 'POST', body: JSON.stringify(post) });
},
async deletePost(id) {
await fetch('/api/posts/delete', { method: 'POST', body: JSON.stringify({ id }) });
}
};
// --- INDEXED DB HELPERS (Multi-Media Support) ---
const DB_NAME = 'SocialAppDB';
const DB_VERSION = 1;
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('images')) {
db.createObjectStore('images', { keyPath: 'id' });
}
};
});
}
// Save Array of Blobs/Strings
async function saveMediaToDB(id, mediaArray) {
try {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction('images', 'readwrite');
const store = tx.objectStore('images');
store.put({ id, media: mediaArray });
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} catch (e) {
console.error('IndexedDB Save Error:', e);
throw e;
}
}
// Get Media Array
async function getMediaFromDB(id) {
try {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction('images', 'readonly');
const store = tx.objectStore('images');
const request = store.get(id);
request.onsuccess = () => resolve(request.result ? request.result.media : null);
request.onerror = () => reject(request.error);
});
} catch (e) {
console.error('IndexedDB Read Error:', e);
return null;
}
}
async function deleteImageFromDB(id) {
try {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction('images', 'readwrite');
const store = tx.objectStore('images');
store.delete(id);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} catch (e) {
console.log('IndexedDB Delete Error or ID not found:', e);
}
}
// --- QUERY SELECTORS ---
const loginForm = document.querySelector('.login-form');
const loginView = document.getElementById('login-view');
const feedView = document.getElementById('feed-view');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm-password');
const confirmPasswordGroup = document.getElementById('confirm-password-group');
const submitBtn = document.querySelector('.btn-primary');
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');
// Onboarding Elements
const verificationView = document.getElementById('verification-view');
const onboardingView = document.getElementById('onboarding-view');
const otpInput = document.getElementById('otp-input');
const verifyBtn = document.getElementById('verify-btn');
const displayNameInput = document.getElementById('display-name');
const bioInput = document.getElementById('bio-input');
const avatarInput = document.getElementById('avatar-input');
const avatarPreview = document.getElementById('avatar-preview');
const avatarTrigger = document.getElementById('avatar-upload-trigger');
const completeSetupBtn = document.getElementById('complete-setup-btn');
// 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 mediaPreviewContainer = document.getElementById('media-preview-container');
const captionInput = document.getElementById('post-caption-input');
const navPostBtn = document.querySelector('.nav-btn:nth-child(2)');
const navHomeBtn = document.querySelector('.nav-btn:first-child');
// Lightbox Elements
const lightboxView = document.getElementById('lightbox-view');
const lightboxClose = document.getElementById('lightbox-close');
const lightboxCarousel = lightboxView?.querySelector('.lightbox-carousel');
// State
let isLoginMode = true;
let pendingUser = null;
let generatedOTP = null;
let newAvatarBase64 = null;
// --- CHECK SESSION & LOGOUT ---
async function checkSession() {
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
if (currentUser.email) {
try {
// Verify user actually exists in DB (fixes "Ghost User" after DB wipe)
const users = await API.getUsers();
const validUser = users.find(u => u.email === currentUser.email);
if (!validUser) {
console.log('Session invalid: User not found on server.');
localStorage.removeItem('currentUser');
if (loginView && !loginView.classList.contains('hidden')) return; // Already on login
location.reload();
return;
}
if (loginView) {
loginView.classList.add('hidden');
loginView.style.display = 'none';
}
if (feedView) {
feedView.classList.remove('hidden');
initFeed();
}
} catch (err) {
console.warn('Session check failed:', err);
alert('Connection to Server Failed.\n\nReason: ' + err.message + '\n\nLogging out for security.');
localStorage.removeItem('currentUser');
location.reload();
}
}
}
checkSession();
const logoutBtn = document.getElementById('logout-btn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => {
if (confirm('Log out?')) {
localStorage.removeItem('currentUser');
location.reload();
}
});
}
// --- ONBOARDING LOGIC ---
if (avatarTrigger && avatarInput) {
avatarTrigger.addEventListener('click', () => avatarInput.click());
avatarInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
newAvatarBase64 = e.target.result;
avatarPreview.src = newAvatarBase64;
};
reader.readAsDataURL(file);
}
});
}
// Verify OTP Handler
if (verifyBtn) {
verifyBtn.addEventListener('click', () => {
if (otpInput.value === generatedOTP) {
verificationView.classList.add('hidden');
onboardingView.classList.remove('hidden');
onboardingView.classList.add('fade-in');
} else {
alert('Invalid Code');
}
});
}
// Complete Setup Handler
if (completeSetupBtn) {
completeSetupBtn.addEventListener('click', async () => {
try {
// Safety Check: If pendingUser is lost (e.g. reload), force restart
if (!pendingUser) {
alert('Session expired. Please sign up again.');
window.location.reload();
return;
}
const name = displayNameInput.value.trim();
if (!name) { alert('Name is required'); return; }
const bio = bioInput.value.trim();
// Finalize User
const finalUser = {
...pendingUser,
username: name,
bio: bio,
avatar: newAvatarBase64 || 'https://i.pravatar.cc/150?img=12'
};
// --- ASYNC SAVE ---
await API.addUser(finalUser);
localStorage.setItem('currentUser', JSON.stringify(finalUser)); // Session persistence
// Transition
onboardingView.classList.add('hidden');
feedView.classList.remove('hidden');
feedView.classList.add('fade-in');
initFeed();
} catch (err) {
console.error('Setup Error:', err);
alert('Connection Error: ' + err.message);
}
});
}
// --- LIGHTBOX LOGIC ---
function openLightbox(media, startIndex) {
if (!lightboxView || !lightboxCarousel) return;
lightboxCarousel.innerHTML = '';
media.forEach((item) => {
const slide = document.createElement('div');
slide.className = 'lightbox-slide';
if (item.type === 'video') {
slide.innerHTML = ``;
} else {
slide.innerHTML = ``;
}
lightboxCarousel.appendChild(slide);
});
lightboxView.classList.remove('hidden');
lightboxView.classList.add('fade-in');
// Scroll to index
const width = window.innerWidth;
setTimeout(() => {
lightboxCarousel.scrollLeft = width * startIndex;
}, 10);
const lightboxCounter = lightboxView.querySelector('.media-counter');
if (lightboxCounter) {
lightboxCounter.innerText = `${startIndex + 1}/${media.length}`;
lightboxCounter.style.display = media.length > 1 ? 'block' : 'none';
}
// Attach Interactions
enableDragScroll(lightboxCarousel);
lightboxCarousel.onscroll = () => {
const index = Math.round(lightboxCarousel.scrollLeft / window.innerWidth);
if (lightboxCounter) lightboxCounter.innerText = `${index + 1}/${media.length}`;
};
document.body.style.overflow = 'hidden';
}
if (lightboxClose) {
lightboxClose.addEventListener('click', () => {
if (lightboxView) lightboxView.classList.add('hidden');
if (lightboxCarousel) lightboxCarousel.innerHTML = '';
document.body.style.overflow = '';
});
}
// --- AUTH LOGIC ---
if (toggleLink) {
toggleLink.addEventListener('click', (e) => {
e.preventDefault();
isLoginMode = !isLoginMode;
updateUI();
});
}
function updateUI() {
if (!title || !subtitle || !btnText || !confirmPasswordGroup) return;
if (isLoginMode) {
title.innerText = 'Welcome Back!';
subtitle.innerText = 'Enter your details below';
btnText.innerText = 'Sign In';
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';
const helper = document.querySelector('.signup-link');
if (helper) helper.innerHTML = 'Already have an account? Sign In';
confirmPasswordGroup.classList.remove('hidden');
confirmPasswordInput.required = true;
}
const newLink = document.querySelector('.signup-link a');
if (newLink) {
newLink.addEventListener('click', (e) => {
e.preventDefault();
isLoginMode = !isLoginMode;
updateUI();
});
}
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%)';
} else {
submitBtn.style.background = 'linear-gradient(135deg, #EF4444 0%, #B91C1C 100%)';
}
setTimeout(() => { if (!message.includes('Success')) resetButton(); }, 2000);
}
function navigateToFeed() {
if (loginView && feedView) {
loginView.style.opacity = '0';
setTimeout(() => {
loginView.classList.add('hidden');
loginView.style.display = 'none';
feedView.classList.remove('hidden');
void feedView.offsetWidth;
feedView.classList.add('fade-in');
initFeed();
setTimeout(() => feedView.classList.remove('fade-in'), 600);
}, 600);
}
}
if (loginForm) {
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const email = emailInput.value.trim().toLowerCase();
const password = passwordInput.value.trim();
const confirmPassword = confirmPasswordInput.value.trim();
const timestamp = new Date().toISOString();
if (!email || !password) { showFeedback(false, 'Missing fields'); return; }
try {
const users = await API.getUsers();
if (isLoginMode) {
const user = users.find(u => u.email === email && u.password === password);
if (user) {
localStorage.setItem('currentUser', JSON.stringify(user));
showFeedback(true, 'Success!');
setTimeout(navigateToFeed, 1000);
} else {
showFeedback(false, 'Incorrect details');
}
} else {
if (users.find(u => u.email === email)) {
showFeedback(false, 'User exists');
return;
}
if (password !== confirmPassword) {
showFeedback(false, 'Passwords mismatch');
return;
}
// Start Verification Flow
pendingUser = { email, password, joinedAt: timestamp };
generatedOTP = Math.floor(100000 + Math.random() * 900000).toString();
alert(`[SERVER SYNC] Verification code: ${generatedOTP}`);
loginView.classList.add('hidden');
verificationView.classList.remove('hidden');
verificationView.classList.add('fade-in');
}
} catch (err) {
console.error(err);
alert('Login Failed: ' + err.message);
showFeedback(false, 'Server Error');
}
});
}
// --- 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); }
// --- ASYNC LOAD FEED ---
// --- ASYNC LOAD FEED ---
async function initFeed() {
const container = document.querySelector('.posts-container');
if (!container) return;
container.innerHTML = '';
try {
const posts = await API.getPosts();
if (posts.length === 0) {
container.innerHTML = '
${post.username} ${post.caption}