| |
|
| | |
| | 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; |
| | } |
| | } |