|
|
| |
| class FlowFlexApp { |
| constructor() { |
| this.currentFeed = 'trending'; |
| this.posts = []; |
| this.seenPosts = new Set(); |
| this.userPreferences = this.getUserPreferences(); |
| this.isLoading = false; |
| this.page = 1; |
| |
| this.init(); |
| } |
|
|
| init() { |
| this.bindEvents(); |
| this.loadInitialFeed(); |
| this.setupIntersectionObserver(); |
| this.checkAuthStatus(); |
| } |
| bindEvents() { |
| |
| document.querySelectorAll('.feed-filter-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| this.switchFeed(e.target.closest('.feed-filter-btn').dataset.filter); |
| }); |
|
|
| |
| document.addEventListener('click', (e) => { |
| if (e.target.closest('.like-btn')) { |
| this.handleLike(e.target.closest('.like-btn')); |
| }); |
|
|
| |
| document.addEventListener('click', (e) => { |
| if (e.target.closest('.save-btn')) { |
| this.handleSave(e.target.closest('.save-btn')); |
| } |
|
|
| |
| document.addEventListener('click', (e) => { |
| const postCard = e.target.closest('.post-card'); |
| if (postCard) { |
| this.trackPostInteraction(postCard.dataset.postId); |
| } |
|
|
| |
| document.addEventListener('click', (e) => { |
| if (e.target.closest('[data-auth-trigger]')) { |
| this.showAuthModal(); |
| } |
| }); |
| } |
| switchFeed(feedType) { |
| |
| document.querySelectorAll('.feed-filter-btn').forEach(btn => { |
| btn.classList.remove('active', 'bg-primary-500', 'text-white'); |
| btn.classList.add('bg-gray-200', 'text-gray-700'); |
| }); |
|
|
| const activeBtn = document.querySelector(`[data-filter="${feedType}"]`); |
| activeBtn.classList.remove('bg-gray-200', 'text-gray-700'); |
| activeBtn.classList.add('active', 'bg-primary-500', 'text-white'); |
|
|
| this.currentFeed = feedType; |
| this.page = 1; |
| this.posts = []; |
| this.loadFeed(); |
| } |
|
|
| async loadInitialFeed() { |
| await this.loadFeed(); |
| } |
|
|
| async loadFeed() { |
| if (this.isLoading) return; |
|
|
| this.isLoading = true; |
| this.showLoading(); |
|
|
| try { |
| |
| await new Promise(resolve => setTimeout(resolve, 1000)); |
| |
| const newPosts = await this.fetchPosts(this.currentFeed, this.page); |
| const filteredPosts = this.filterDuplicatePosts(newPosts); |
| |
| if (filteredPosts.length > 0) { |
| this.posts = [...this.posts, ...filteredPosts]; |
| this.renderPosts(filteredPosts); |
| this.page++; |
| } else { |
| this.showEmptyState(); |
| } |
| } catch (error) { |
| console.error('Error loading feed:', error); |
| this.showError('Failed to load posts. Please try again.'); |
| } finally { |
| this.isLoading = false; |
| this.hideLoading(); |
| } |
| } |
|
|
| async fetchPosts(feedType, page = 1) { |
| |
| const accessKey = 'YOUR_UNSPLASH_ACCESS_KEY'; |
| const endpoints = { |
| trending: `https://api.unsplash.com/photos?page=${page}&per_page=9&order_by=popular`, |
| following: `https://api.unsplash.com/photos?page=${page}&per_page=9&order_by=latest`, |
| discover: `https://api.unsplash.com/photos/random?count=9`, |
| personalized: `https://api.unsplash.com/photos?page=${page}&per_page=9` |
| }; |
|
|
| |
| return this.generateMockPosts(9); |
| } |
|
|
| generateMockPosts(count) { |
| const categories = ['nature', 'technology', 'travel', 'food', 'architecture', 'people']; |
| const users = [ |
| { name: 'Alex Johnson', username: 'alexj', followers: 1243 }, |
| { name: 'Sarah Miller', username: 'sarahm', followers: 856 }, |
| { name: 'Mike Chen', username: 'mikec', followers: 2107 }, |
| { name: 'Emma Davis', username: 'emmad', followers: 932 }, |
| { name: 'James Wilson', username: 'jamesw', followers: 1541 } |
| ]; |
|
|
| return Array.from({ length: count }, (_, i) => { |
| const user = users[Math.floor(Math.random() * users.length)]; |
| const category = categories[Math.floor(Math.random() * categories.length)]; |
| const postId = `post_${Date.now()}_${i}`; |
| |
| return { |
| id: postId, |
| user: user, |
| image: `http://static.photos/${category}/640x360/${i + 1}`, |
| caption: this.generateMockCaption(category), |
| likes: Math.floor(Math.random() * 1000), |
| comments: Math.floor(Math.random() * 50), |
| shares: Math.floor(Math.random() * 20), |
| timestamp: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000), |
| category: category, |
| engagement: Math.random() * 100 |
| }; |
| }); |
| } |
|
|
| generateMockCaption(category) { |
| const captions = { |
| nature: ['Beautiful sunset at the beach! 🌅', 'Morning hike in the mountains 🏔️', 'Peaceful forest walk 🌲'], |
| technology: ['Working on some exciting new projects! 💻', 'Tech conference was amazing! 🚀', 'Latest gadget unboxing 📱'], |
| travel: ['Exploring new places! ✈️', 'Cultural experience of a lifetime 🌍', 'Travel dreams coming true 🗺️'], |
| food: ['Delicious homemade meal! 🍽️', 'Food photography session 📸', 'Trying out new recipes 👨🍳'], |
| architecture: ['Modern architecture never fails to impress! 🏛️', 'Historical building tour 🏰', 'Architectural marvels 🏗️'], |
| people: ['Great time with friends! 👥', 'Community event was fantastic! 🎉', 'Networking and making connections 🤝'] |
| }; |
|
|
| const categoryCaptions = captions[category] || ['Great day! 😊']; |
| return categoryCaptions[Math.floor(Math.random() * categoryCaptions.length)]; |
| } |
|
|
| filterDuplicatePosts(newPosts) { |
| return newPosts.filter(post => !this.seenPosts.has(post.id)); |
| } |
|
|
| renderPosts(posts) { |
| const feedContainer = document.getElementById('feed-container'); |
| const emptyState = document.getElementById('empty-state'); |
| |
| if (posts.length === 0 && this.posts.length === 0) { |
| this.showEmptyState(); |
| return; |
| } |
|
|
| emptyState.classList.add('hidden'); |
| |
| posts.forEach(post => { |
| this.seenPosts.add(post.id); |
| const postElement = this.createPostElement(post); |
| feedContainer.appendChild(postElement); |
| }); |
| } |
|
|
| createPostElement(post) { |
| const postDiv = document.createElement('div'); |
| postDiv.className = 'post-card bg-white rounded-xl shadow-sm overflow-hidden fade-in'; |
| postDiv.dataset.postId = post.id; |
| |
| const timeAgo = this.getTimeAgo(post.timestamp); |
| |
| postDiv.innerHTML = ` |
| <div class="relative"> |
| <img src="${post.image}" alt="Post image" class="w-full h-48 object-cover"> |
| <button class="save-btn absolute top-3 right-3 p-2 bg-white/80 backdrop-blur-sm rounded-full hover:bg-white transition-colors duration-200"> |
| <i data-feather="bookmark" class="w-4 h-4 text-gray-600"></i> |
| </button> |
| </div> |
| <div class="p-4"> |
| <div class="flex items-center justify-between mb-3"> |
| <div class="flex items-center space-x-3"> |
| <img src="http://static.photos/people/40x40/${Math.floor(Math.random() * 100)}" alt="${post.user.name}" class="w-8 h-8 rounded-full"> |
| <div> |
| <h4 class="font-semibold text-gray-900 text-sm">${post.user.name}</h4> |
| <p class="text-gray-500 text-xs">@${post.user.username}</p> |
| </div> |
| </div> |
| <span class="text-xs text-gray-400">${timeAgo}</span> |
| </div> |
| |
| <p class="text-gray-700 mb-4 text-sm">${post.caption}</p> |
| |
| <div class="flex items-center justify-between text-gray-500"> |
| <div class="flex items-center space-x-4"> |
| <button class="like-btn flex items-center space-x-1 text-sm hover:text-red-500 transition-colors duration-200"> |
| <i data-feather="heart" class="w-4 h-4"></i> |
| <span>${post.likes}</span> |
| </button> |
| <button class="flex items-center space-x-1 text-sm hover:text-blue-500 transition-colors duration-200"> |
| <i data-feather="message-circle" class="w-4 h-4"></i> |
| <span>${post.comments}</span> |
| </button> |
| <button class="flex items-center space-x-1 text-sm hover:text-green-500 transition-colors duration-200"> |
| <i data-feather="share-2" class="w-4 h-4"></i> |
| <span>${post.shares}</span> |
| </button> |
| </div> |
| </div> |
| `; |
| |
| return postDiv; |
| } |
|
|
| handleLike(likeBtn) { |
| const heartIcon = likeBtn.querySelector('i'); |
| const likesCount = likeBtn.querySelector('span'); |
| |
| likeBtn.classList.add('like-animation'); |
| setTimeout(() => likeBtn.classList.remove('like-animation'), 600); |
| |
| const isLiked = heartIcon.style.fill === 'currentColor'; |
| const currentLikes = parseInt(likesCount.textContent); |
| |
| if (isLiked) { |
| heartIcon.style.fill = 'none'; |
| likesCount.textContent = currentLikes - 1; |
| likesCount.style.color = ''; |
| } else { |
| heartIcon.style.fill = 'currentColor'; |
| likesCount.textContent = currentLikes + 1; |
| likesCount.style.color = '#ef4444'; |
| } |
| } |
|
|
| handleSave(saveBtn) { |
| const bookmarkIcon = saveBtn.querySelector('i'); |
| const isSaved = bookmarkIcon.style.fill === 'currentColor'; |
| |
| if (isSaved) { |
| bookmarkIcon.style.fill = 'none'; |
| } else { |
| bookmarkIcon.style.fill = 'currentColor'; |
| } |
| } |
|
|
| trackPostInteraction(postId) { |
| |
| const post = this.posts.find(p => p.id === postId); |
| if (post) { |
| this.updateUserPreferences(post); |
| } |
| } |
|
|
| updateUserPreferences(post) { |
| if (!this.userPreferences.engagedCategories) { |
| this.userPreferences.engagedCategories = {}; |
| } |
| |
| this.userPreferences.engagedCategories[post.category] = |
| (this.userPreferences.engagedCategories[post.category] || 0) + 1; |
| |
| localStorage.setItem('flowflex_preferences', JSON.stringify(this.userPreferences)); |
| } |
|
|
| getUserPreferences() { |
| const stored = localStorage.getItem('flowflex_preferences'); |
| return stored ? JSON.parse(stored) : { |
| engagedCategories: {}, |
| preferredFeed: 'trending', |
| blockedUsers: [] |
| }; |
| } |
|
|
| getTimeAgo(timestamp) { |
| const now = new Date(); |
| const diff = now - new Date(timestamp); |
| const minutes = Math.floor(diff / 60000); |
| const hours = Math.floor(diff / 3600000); |
| const days = Math.floor(diff / 86400000); |
| |
| if (days > 0) return `${days}d ago`; |
| if (hours > 0) return `${hours}h ago`; |
| return `${minutes}m ago`; |
| } |
|
|
| showLoading() { |
| document.getElementById('loading').classList.remove('hidden'); |
| } |
|
|
| hideLoading() { |
| document.getElementById('loading').classList.add('hidden'); |
| } |
|
|
| showEmptyState() { |
| document.getElementById('feed-container').innerHTML = ''; |
| document.getElementById('empty-state').classList.remove('hidden'); |
| } |
| showError(message) { |
| |
| const errorDiv = document.createElement('div'); |
| errorDiv.className = 'fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg z-50'; |
| errorDiv.textContent = message; |
| document.body.appendChild(errorDiv); |
| |
| setTimeout(() => { |
| errorDiv.remove(); |
| }, 3000); |
| } |
|
|
| setupIntersectionObserver() { |
| const observer = new IntersectionObserver((entries) => { |
| entries.forEach(entry => { |
| if (entry.isIntersecting && !this.isLoading) { |
| this.loadFeed(); |
| } |
| }, { |
| rootMargin: '100px' |
| }); |
|
|
| observer.observe(document.getElementById('loading')); |
| } |
|
|
| checkAuthStatus() { |
| const isLoggedIn = localStorage.getItem('flowflex_user'); |
| if (!isLoggedIn) { |
| |
| console.log('User not logged in'); |
| } |
| } |
|
|
| showAuthModal() { |
| const authModal = document.querySelector('auth-modal'); |
| if (authModal) { |
| authModal.show(); |
| } |
| } |
|
|
| |
| loadProfilePosts() { |
| |
| const posts = this.generateMockPosts(12); |
| this.renderProfilePosts(posts); |
| } |
|
|
| renderProfilePosts(posts) { |
| const profileContent = document.getElementById('profile-content'); |
| if (!profileContent) return; |
|
|
| posts.forEach(post => { |
| const postElement = this.createPostElement(post); |
| profileContent.appendChild(postElement); |
| }); |
| } |
| } |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| new FlowFlexApp(); |
| }); |
|
|
| |
| async function apiCall(url, options = {}) { |
| try { |
| const response = await fetch(url, { |
| headers: { |
| 'Authorization': 'Client-ID YOUR_UNSPLASH_ACCESS_KEY', |
| 'Accept-Version': 'v1' |
| }, |
| ...options |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`API call failed: ${response.status}`); |
| } |
| |
| return await response.json(); |
| } catch (error) { |
| console.error('API call error:', error); |
| throw error; |
| } |
| } |