import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { PersonalizedFeed } from '../home/components/PersonalizedFeed'; import { PersonalizedFeedItem } from '@/services/personalizationService'; import { officialFeedService } from '@/services/officialFeedService'; import { useAuthStore } from '@/store/authStore'; import { useErrorHandler } from '@/hooks/useErrorHandler'; import { useDebounce } from '@/hooks/useDebounce'; import { useDesignTokens } from '@/theme/useDesignTokens'; import { Button } from '@/components/ui/design-system/Button'; import { useTranslation } from '@/hooks/useTranslation'; import { FilterType } from '../home/components/QuickFilters'; import { ExploreToolbar } from './components/ExploreToolbar'; import { ExploreFilters, type FilterOptions } from './components/ExploreFilters'; import { BrowseContent } from './components/BrowseContent'; import { BrowseFilterBar } from './components/BrowseFilterBar'; import { PageTemplate } from '@/components/layout/PageTemplate'; import { PinDetailModal } from '@/components/ui/PinDetailModal/PinDetailModal'; import { useUserPinOwnership } from '@/hooks/useUserPinOwnership'; import { exploreService } from '@/services/exploreService'; import { usePullToRefresh } from '@/hooks/usePullToRefresh'; import { PullToRefreshIndicator } from '@/components/ui/PullToRefreshIndicator'; import type { BrowseFilters, BrowsePin } from '@/types/explore'; import { SparklesIcon, ArrowTrendingUpIcon, UserIcon, ExclamationTriangleIcon, } from '@heroicons/react/24/outline'; type FeedMode = 'browse' | 'personalized' | 'trending'; export const ExplorePage: React.FC = () => { const { user } = useAuthStore(); const { handleError } = useErrorHandler(); const { semantic } = useDesignTokens(); const { common } = useTranslation() as any; const navigate = useNavigate(); // Hook para verificar quais pins o usuΓ‘rio jΓ‘ possui const { ownedPinDatabaseIds, refresh: refreshOwnership } = useUserPinOwnership(); // Location for URL sync const location = useLocation(); // Read initial feedMode from URL or default to 'browse' const getInitialFeedMode = (): FeedMode => { const params = new URLSearchParams(location.search); const modeParam = params.get('mode') as FeedMode | null; if (modeParam && ['browse', 'trending', 'personalized'].includes(modeParam)) { return modeParam; } return 'browse'; // Default to browse }; const [personalizedFeed, setPersonalizedFeed] = useState([]); const [selectedPin, setSelectedPin] = useState(null); const [loading, setLoading] = useState(true); const [hasMore, setHasMore] = useState(true); const [page, setPage] = useState(1); const [loadingMore, setLoadingMore] = useState(false); const [feedMode, setFeedMode] = useState(getInitialFeedMode()); const [activeFilter, setActiveFilter] = useState('all'); const [showFilters, setShowFilters] = useState(false); const [sortBy, setSortBy] = useState<'relevance' | 'recent' | 'popular' | 'price'>('relevance'); const [exploreFilters, setExploreFilters] = useState({ rarity: [], series: [], yearRange: { min: 2020, max: 2024 }, sortBy: 'newest', showOnlyTradeable: false, }); const [error, setError] = useState(null); // Browse mode state const [browsePins, setBrowsePins] = useState([]); const [browseFilters, setBrowseFilters] = useState({ listBy: 'collection', sortBy: 'newest', collectionIds: [], originIds: [], releaseYears: [], setIds: [], limit: 20, offset: 0, }); const [browseTotalCount, setBrowseTotalCount] = useState(0); const [browseHasMore, setBrowseHasMore] = useState(true); const [_showBrowseSidebar, setShowBrowseSidebar] = useState(false); const [_isMobile, setIsMobile] = useState(window.innerWidth < 1024); // Refs for cleanup const abortControllerRef = useRef(null); const debouncedFilter = useDebounce(activeFilter, 300); const scrollContainerRef = useRef(null); const tabsContainerRef = useRef(null); const [tabsVisible, setTabsVisible] = useState(true); const [tabsHeight, setTabsHeight] = useState(0); // Measure tabs height and set CSS variable useEffect(() => { const tabsContainer = tabsContainerRef.current; if (tabsContainer) { const updateHeight = () => { const height = tabsContainer.offsetHeight; setTabsHeight(height); // Initialize CSS variable for sticky headers document.documentElement.style.setProperty('--explore-tabs-offset', `${height}px`); }; // Delay initial measurement to ensure layout is stable const timeoutId = setTimeout(updateHeight, 100); // Also update on resize window.addEventListener('resize', updateHeight); return () => { clearTimeout(timeoutId); window.removeEventListener('resize', updateHeight); }; } }, []); // Control tabs visibility - mobile uses MutationObserver, desktop uses scroll listener useEffect(() => { const tabsContainer = tabsContainerRef.current; if (!tabsContainer) return; const isMobileView = () => window.innerWidth < 768; const hideTabs = () => { const header = document.querySelector('.mobile-header-fixed') as HTMLElement | null; const headerHeight = header?.offsetHeight || 0; const currentTabsHeight = tabsContainer.offsetHeight; // On desktop, no header to account for, just hide tabs const hideOffset = isMobileView() ? headerHeight + currentTabsHeight : currentTabsHeight; tabsContainer.style.transform = `translateY(-${hideOffset}px)`; setTabsVisible(false); document.documentElement.style.setProperty('--explore-tabs-offset', '0px'); }; const showTabs = () => { tabsContainer.style.transform = 'translateY(0)'; setTabsVisible(true); document.documentElement.style.setProperty( '--explore-tabs-offset', `${tabsContainer.offsetHeight}px` ); }; // --- MOBILE: Use MutationObserver on data-ui-chrome-hidden --- const observer = new MutationObserver((mutations) => { if (!isMobileView()) return; // Only for mobile for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'data-ui-chrome-hidden') { const isHidden = document.documentElement.getAttribute('data-ui-chrome-hidden') === 'true'; if (isHidden) { hideTabs(); } else { showTabs(); } } } }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-ui-chrome-hidden'], }); // --- DESKTOP: Use scroll listener --- let lastScrollY = 0; let ticking = false; let isHidden = false; const handleDesktopScroll = () => { if (isMobileView()) return; // Only for desktop if (ticking) return; ticking = true; requestAnimationFrame(() => { // Get scroll position from multiple sources const scrollY = Math.max( window.pageYOffset || 0, document.documentElement.scrollTop || 0, document.body.scrollTop || 0 ); // Always show near top if (scrollY < 50) { if (isHidden) { showTabs(); isHidden = false; } lastScrollY = scrollY; ticking = false; return; } const deltaY = scrollY - lastScrollY; // Ignore small variations if (Math.abs(deltaY) < 5) { ticking = false; return; } // Scroll down: hide tabs if (deltaY > 0 && !isHidden) { hideTabs(); isHidden = true; } // Scroll up: show tabs if (deltaY < 0 && isHidden) { showTabs(); isHidden = false; } lastScrollY = scrollY; ticking = false; }); }; // Add scroll listeners to window and body for desktop window.addEventListener('scroll', handleDesktopScroll, { passive: true }); document.body.addEventListener('scroll', handleDesktopScroll, { passive: true }); // Initial state if (isMobileView()) { const isCurrentlyHidden = document.documentElement.getAttribute('data-ui-chrome-hidden') === 'true'; if (isCurrentlyHidden) { hideTabs(); } else { showTabs(); } } else { showTabs(); // Desktop starts visible } return () => { observer.disconnect(); window.removeEventListener('scroll', handleDesktopScroll); document.body.removeEventListener('scroll', handleDesktopScroll); }; }, []); // Check window size for mobile useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth < 1024); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Cleanup function const cleanup = useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } }, []); // Client-side filter application fallback const applyClientSideFilters = useCallback( (items: PersonalizedFeedItem[], filterType: FilterType): PersonalizedFeedItem[] => { if (filterType === 'all') return items; return items.filter((item) => { switch (filterType) { case 'trending': return item.category === 'trending' || item.reasoningTags.includes('Trending'); case 'recent': return ( item.category === 'fresh_content' || item.reasoningTags.includes('Recent') || item.reasoningTags.includes('Fresh') ); case 'liked': return item.pin.isLiked; case 'nearby': return item.reasoningTags.includes('Nearby') || item.reasoningTags.includes('Location'); case 'categories': return true; default: return true; } }); }, [] ); // Load browse pins const loadBrowsePins = useCallback( async (filters: BrowseFilters, append = false) => { try { if (!append) { setLoading(true); setError(null); } else { setLoadingMore(true); } console.log(' Loading browse pins with filters:', filters); const response = await exploreService.getBrowsePins(filters); console.log('πŸ“¦ Response from backend:', { pinsCount: response.pins?.length || 0, total: response.pagination?.total || 0, hasMore: response.pagination?.hasMore, firstPin: response.pins?.[0], fullResponse: response, }); if (append) { setBrowsePins((prev) => [...prev, ...response.pins]); } else { setBrowsePins(response.pins); } setBrowseTotalCount(response.pagination.total); setBrowseHasMore(response.pagination.hasMore); console.log( `βœ… Loaded ${response.pins.length} browse pins (total: ${response.pagination.total})` ); } catch (error) { console.error(' Error loading browse pins:', error); setError('Failed to load pins. Please try again.'); handleError(error as Error, 'Failed to load browse pins'); } finally { setLoading(false); setLoadingMore(false); } }, [handleError] ); // Enhanced feed loading with proper service calls const loadFeedContent = useCallback( async (pageNum = 1, isRefresh = false) => { if (!user?.id) return; // Cancel any previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } // Create new AbortController for this request const abortController = new AbortController(); abortControllerRef.current = abortController; try { if (pageNum === 1) { setLoading(true); setLoadingMore(false); setError(null); } else { setLoading(false); setLoadingMore(true); } console.log( `πŸ” Loading ${feedMode} content - Page: ${pageNum}, Refresh: ${isRefresh}, Filter: ${debouncedFilter}` ); let feedData: PersonalizedFeedItem[] = []; // Check if request was aborted before proceeding if (abortController.signal.aborted) { return; } // Load content based on feed mode using OFFICIAL catalog switch (feedMode) { case 'browse': // Browse β†’ oficial (todos pins aprovados) feedData = await officialFeedService.getNewestApproved({ limit: 20, page: pageNum }); break; case 'personalized': // For You β†’ oficial (novos/aprovados mais recentes) feedData = await officialFeedService.getNewestApproved({ limit: 20, page: pageNum }); break; case 'trending': // Trending β†’ oficial (aprovados ordenados por updated_at como proxy de trending) feedData = await officialFeedService.getTrendingApproved({ limit: 20, page: pageNum }); break; default: feedData = await officialFeedService.getNewestApproved({ limit: 20, page: pageNum }); } // Check if request was aborted after async operation if (abortController.signal.aborted) { return; } // Apply client-side filters if needed const filteredData = applyClientSideFilters(feedData, debouncedFilter); if (isRefresh || pageNum === 1) { setPersonalizedFeed(filteredData); setPage(1); } else { // Deduplicate before adding new items setPersonalizedFeed((prev) => { const existingIds = new Set(prev.map((item) => item.pin.id)); const newItems = filteredData.filter((item) => !existingIds.has(item.pin.id)); return [...prev, ...newItems]; }); setPage(pageNum); } // Set hasMore based on the response length const newHasMore = filteredData.length === 20; setHasMore(newHasMore); console.log(`βœ… ${feedMode} content loaded:`, { page: pageNum, items: filteredData.length, totalItems: isRefresh || pageNum === 1 ? filteredData.length : personalizedFeed.length + filteredData.length, hasMore: newHasMore, isRefresh, }); } catch (error) { console.error(`❌ Error loading ${feedMode} content:`, error); setError(`Failed to load ${feedMode} content. Please try again.`); handleError(error as Error, `Failed to load ${feedMode} content`); } finally { setLoading(false); setLoadingMore(false); } }, [user?.id, feedMode, debouncedFilter, handleError, applyClientSideFilters] ); // Load content on mount and when dependencies change (REMOVED - causing loop with browseFilters) // useEffect(() => { // if (feedMode === 'browse') { // loadBrowsePins(browseFilters, false); // } else if (user?.id) { // loadFeedContent(1, true); // } // }, [user?.id, feedMode, loadFeedContent, loadBrowsePins, browseFilters]); // Cleanup function useEffect(() => { return cleanup; }, [cleanup]); // Initial load and dependency changes useEffect(() => { console.log(' Initial load effect triggered - feedMode:', feedMode, 'user:', user?.id); console.log('πŸ“Š Current state:', { browsePinsLength: browsePins.length, personalizedFeedLength: personalizedFeed.length, loading, error, }); if (feedMode === 'browse') { console.log(' Loading browse mode with initial filters'); // Reset and load browse pins (hydrate from URL if present) setBrowsePins([]); const params = new URLSearchParams(location.search); const listByParam = (params.get('listBy') as BrowseFilters['listBy']) || 'collection'; const parseStringArrayParam = (key: string, legacyKey?: string): string[] => { const csvValue = params.get(key); if (csvValue) { return csvValue .split(',') .map((value) => value.trim()) .filter(Boolean); } if (legacyKey) { const legacyValue = params.get(legacyKey); if (legacyValue) return [legacyValue]; } return []; }; const parseNumberArrayParam = (key: string, legacyKey?: string): number[] => { return parseStringArrayParam(key, legacyKey) .map((value) => Number(value)) .filter((num) => !Number.isNaN(num)); }; const initialFilters: BrowseFilters = { listBy: listByParam, sortBy: (params.get('sortBy') as BrowseFilters['sortBy']) || 'newest', collectionIds: parseStringArrayParam('collectionIds', 'collectionId'), originIds: parseStringArrayParam('originIds', 'origin'), releaseYears: parseNumberArrayParam('releaseYears', 'releaseYear'), setIds: parseStringArrayParam('setIds', 'setId'), search: params.get('search') || undefined, limit: params.get('limit') ? Number(params.get('limit')) : 20, offset: params.get('offset') ? Number(params.get('offset')) : 0, }; console.log(' Initial filters for Browse:', initialFilters); setBrowseFilters(initialFilters); setError(null); loadBrowsePins(initialFilters, false); } else if (user?.id) { console.log(' Loading feed mode for user:', user?.id); setPersonalizedFeed([]); setPage(1); setHasMore(true); setError(null); loadFeedContent(1, true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [feedMode, user?.id]); // Helper: update URL with current browse filters const updateBrowseUrl = useCallback( (next: Partial) => { const params = new URLSearchParams(location.search); const merged: BrowseFilters = { ...browseFilters, ...next } as BrowseFilters; // Write params params.set('listBy', merged.listBy); if (merged.collectionIds?.length) params.set('collectionIds', merged.collectionIds.join(',')); else params.delete('collectionIds'); if (merged.originIds?.length) params.set('originIds', merged.originIds.join(',')); else params.delete('originIds'); if (merged.releaseYears?.length) params.set('releaseYears', merged.releaseYears.map((year) => String(year)).join(',')); else params.delete('releaseYears'); if (merged.setIds?.length) params.set('setIds', merged.setIds.join(',')); else params.delete('setIds'); if (merged.search) params.set('search', merged.search); else params.delete('search'); if (merged.sortBy) params.set('sortBy', merged.sortBy); else params.delete('sortBy'); if (typeof merged.limit === 'number') params.set('limit', String(merged.limit)); else params.delete('limit'); if (typeof merged.offset === 'number') params.set('offset', String(merged.offset)); else params.delete('offset'); navigate({ search: params.toString() }, { replace: true }); }, [browseFilters, location.search, navigate] ); // Handle filter changes const _handleFilterChange = useCallback( (filter: FilterType) => { console.log('πŸ”§ Filter changed from', activeFilter, 'to', filter); setActiveFilter(filter); }, [activeFilter] ); // Handle feed mode changes const handleModeChange = useCallback( (mode: FeedMode) => { console.log('πŸ”„ Feed mode changed from', feedMode, 'to', mode); setFeedMode(mode); // Update URL to persist the selected mode const params = new URLSearchParams(location.search); params.set('mode', mode); navigate({ search: params.toString() }, { replace: true }); }, [feedMode, location.search, navigate] ); // Load more content const handleLoadMore = useCallback(() => { console.log('πŸ”„ handleLoadMore called:', { loadingMore, hasMore, loading, page }); if (!loadingMore && hasMore && !loading) { const nextPage = page + 1; console.log('πŸ”„ Loading more content, next page:', nextPage); loadFeedContent(nextPage, false); } else { console.log('🚫 LoadMore blocked:', { loadingMore, hasMore, loading, page }); } }, [loadingMore, hasMore, loading, page, loadFeedContent]); // Refresh content const handleRefresh = useCallback(async () => { console.log('πŸ”„ Refreshing content'); if (feedMode === 'browse') { setBrowsePins([]); setBrowseFilters((prev) => ({ ...prev, offset: 0 })); setError(null); await loadBrowsePins({ ...browseFilters, offset: 0 }, false); } else { setPersonalizedFeed([]); setPage(1); setHasMore(true); setError(null); await loadFeedContent(1, true); } }, [feedMode, browseFilters, loadBrowsePins, loadFeedContent]); // Pull to refresh hook const { containerRef: pullToRefreshContainerRef, isPulling, isRefreshing, pullDistance, progress, touchHandlers, } = usePullToRefresh({ onRefresh: handleRefresh, threshold: 80, disabled: loading, }); // Browse-specific handlers const handleBrowseFiltersChange = useCallback( (newFilters: Partial) => { setBrowseFilters((prev) => ({ ...prev, ...newFilters, offset: 0, // Reset offset when filters change })); const next = { ...browseFilters, ...newFilters, offset: 0 } as BrowseFilters; updateBrowseUrl(next); loadBrowsePins(next, false); }, [browseFilters, loadBrowsePins, updateBrowseUrl] ); const handleBrowseClearFilters = useCallback(() => { const defaultFilters: BrowseFilters = { listBy: 'collection', sortBy: 'newest', collectionIds: [], originIds: [], releaseYears: [], setIds: [], limit: 20, offset: 0, }; setBrowseFilters(defaultFilters); updateBrowseUrl(defaultFilters); loadBrowsePins(defaultFilters, false); }, [loadBrowsePins, updateBrowseUrl]); const handleBrowseLoadMore = useCallback(() => { if (!loadingMore && browseHasMore && !loading) { const newOffset = (browseFilters.offset || 0) + (browseFilters.limit || 20); const newFilters = { ...browseFilters, offset: newOffset }; setBrowseFilters(newFilters); updateBrowseUrl(newFilters); loadBrowsePins(newFilters, true); } }, [browseFilters, browseHasMore, loading, loadingMore, loadBrowsePins, updateBrowseUrl]); const handleBrowsePinClick = useCallback((pin: BrowsePin) => { // Convert BrowsePin to format expected by PinDetailModal setSelectedPin({ id: pin.pin_id, pinDatabaseId: pin.pin_id, // Map pin_id to pinDatabaseId for ownership check name: pin.title, image: pin.image_url, description: pin.description, origin: pin.origin, releaseYear: pin.release_year, originalPrice: pin.original_price, pinNumber: pin.pin_number, }); }, []); // Handle personalized feed filter changes const handlePersonalizedFeedFilterChange = useCallback((filters: any) => { console.log('πŸŽ›οΈ PersonalizedFeed filter change:', filters); // This could be used for additional filtering within PersonalizedFeed }, []); // Feed mode icon helper const _getFeedModeIcon = (mode: FeedMode) => { switch (mode) { case 'browse': return ; case 'trending': return ; case 'personalized': return ; default: return ; } }; // Feed mode label helper const _getFeedModeLabel = (mode: FeedMode) => { switch (mode) { case 'browse': return common('explore.modes.browse', { defaultValue: 'Browse' }); case 'trending': return common('explore.modes.trending', { defaultValue: 'Trending' }); case 'personalized': return common('explore.modes.forYou', { defaultValue: 'For You' }); default: return common('pages.explore', { defaultValue: 'Explore' }); } }; // Error state const renderErrorState = () => { const getErrorContent = () => { if (!user) { return { title: common('auth.signInRequiredTitle', { defaultValue: 'Sign in required' }), message: common('auth.signInRequiredMessage', { defaultValue: 'Please sign in to access personalized content and explore pins', }), action: common('auth.signIn', { defaultValue: 'Sign In' }), actionHandler: () => navigate('/login'), }; } return { title: common('errors.somethingWentWrong', { defaultValue: 'Something went wrong' }), message: common('errors.exploreLoadFailed', { defaultValue: 'We encountered an error while loading the explore content. Please try again.', }), action: common('actions.retry', { defaultValue: 'Retry' }), actionHandler: () => window.location.reload(), }; }; const { title, message, action, actionHandler } = getErrorContent(); return (

{title}

{message}

); }; // Empty state const renderEmptyState = () => { if (!user) { return (

{common('auth.signInExploreTitle', { defaultValue: 'Sign in to explore content' })}

{common('auth.signInExploreDesc', { defaultValue: 'Create an account or sign in to discover amazing pins and connect with the community', })}

); } const getEmptyContent = () => { switch (feedMode) { case 'browse': return { title: common('explore.empty.browse.title', { defaultValue: 'No content available' }), message: common('explore.empty.browse.message', { defaultValue: 'No pins to browse at the moment. Check back later.', }), action: common('actions.refresh', { defaultValue: 'Refresh' }), actionHandler: () => window.location.reload(), }; case 'personalized': return { title: common('explore.empty.personalized.title', { defaultValue: 'No personalized content yet', }), message: common('explore.empty.personalized.message', { defaultValue: 'Start following users and liking pins to see personalized recommendations here', }), action: common('explore.empty.personalized.action', { defaultValue: 'Explore Trending', }), actionHandler: () => setFeedMode('trending'), }; case 'trending': return { title: common('explore.empty.trending.title', { defaultValue: 'No trending content' }), message: common('explore.empty.trending.message', { defaultValue: 'Check back later for trending pins from the community', }), action: common('actions.refresh', { defaultValue: 'Refresh' }), actionHandler: () => window.location.reload(), }; default: return { title: common('empty.noContentTitle', { defaultValue: 'No content available' }), message: common('empty.noContentMessage', { defaultValue: 'No pins to show at the moment. Check back later.', }), action: common('actions.refresh', { defaultValue: 'Refresh' }), actionHandler: () => window.location.reload(), }; } }; const { title, message, action, actionHandler } = getEmptyContent(); return (

{title}

{message}

); }; // Show error state if there's an error if (error) { return (
{renderErrorState()}
); } // Show empty state if no content and not loading // Check the correct array based on feedMode const hasNoContent = feedMode === 'browse' ? browsePins.length === 0 : personalizedFeed.length === 0; if (hasNoContent && !loading && !error) { return (
{renderEmptyState()}
); } // Browse mode: render com layout sem sidebar lateral, apenas barra horizontal de filtros if (feedMode === 'browse') { return ( <> {/* Pull to refresh indicator - outside container to stay on top */}
{/* Tabs container - moves with header, starts after sidebar on desktop */}
{/* Toolbar with tabs */}
setSortBy(v)} onOpenFilters={() => setShowBrowseSidebar(true)} onRefresh={handleRefresh} isRefreshing={loading} />
{/* Barra horizontal de filtros - aparece apenas no modo Browse */}
{/* Spacer to account for fixed tabs - dynamic height based on tabs visibility */} {/* Note: header spacer is already handled by AppLayout, this is just for tabs */}
{/* Main Content Area - no overflow here, scroll handled by mobile-main-area */}
{/* Conteudo principal */}
{/* Pin Detail Modal */} {selectedPin && ( setSelectedPin(null)} context="feed" onLike={(pinId: string) => console.log('Like pin:', pinId)} onSave={(pinId: string) => console.log('Save pin:', pinId)} onShare={(pinId: string) => console.log('Share pin:', pinId)} /> )}
); } // Trending/Personalized modes: render with toolbar return ( {/* Pull to refresh indicator - outside container to stay on top */}
setSortBy(v)} onOpenFilters={() => setShowFilters(true)} onRefresh={handleRefresh} isRefreshing={loading} /> {/* Quick Filters removidos desta pΓ‘gina para evitar segunda barra e reduzir ruΓ­do visual */} {/* Explore Filters Modal */} {showFilters && ( setShowFilters(false)} onApplyFilters={(f) => setExploreFilters(f)} currentFilters={exploreFilters} /> )} {/* Personalized Feed */}
{ // Find the pin in the feed and open modal const pin = personalizedFeed.find((item) => item.pin.id === pinId)?.pin; if (pin) { setSelectedPin(pin); } }} onLike={(pinId) => console.log('Like pin:', pinId)} onSave={(pinId) => console.log('Save pin:', pinId)} onShare={(pinId) => console.log('Share pin:', pinId)} onFilterChange={handlePersonalizedFeedFilterChange} titleOverride={ feedMode === 'trending' ? common('explore.modes.trending', { defaultValue: 'Trending' }) : common('explore.modes.forYou', { defaultValue: 'For You' }) } hideHeader ownedPinDatabaseIds={ownedPinDatabaseIds} onRefreshOwnership={refreshOwnership} />
{/* Pin Detail Modal */} {selectedPin && ( setSelectedPin(null)} context="feed" onLike={(pinId: string) => console.log('Like pin:', pinId)} onSave={(pinId: string) => console.log('Save pin:', pinId)} onShare={(pinId: string) => console.log('Share pin:', pinId)} /> )}
); };