This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / features / feeds / containers / myFeedContainer / MyFeedContainer.tsx
4.8 kB 156 lines
1'use client'; 2 3import useGlobalFeed from '@/features/feeds/lib/queries/useGlobalFeed'; 4import useFollowingFeed from '@/features/feeds/lib/queries/useFollowingFeed'; 5import FeedItem from '@/features/feeds/components/feedItem/FeedItem'; 6import { 7 Stack, 8 Text, 9 Center, 10 Container, 11 Box, 12 Loader, 13 Collapse, 14} from '@mantine/core'; 15import MyFeedContainerSkeleton from './Skeleton.MyFeedContainer'; 16import MyFeedContainerError from './Error.MyFeedContainer'; 17import InfiniteScroll from '@/components/contentDisplay/infiniteScroll/InfiniteScroll'; 18import RefetchButton from '@/components/navigation/refetchButton/RefetchButton'; 19import { UrlType, ActivitySource, ActivityType } from '@semble/types'; 20import { useSearchParams, useRouter, usePathname } from 'next/navigation'; 21import { CardSaveSource } from '@/features/analytics/types'; 22import { useState, useEffect } from 'react'; 23 24export default function MyFeedContainer() { 25 const pathname = usePathname(); 26 const searchParams = useSearchParams(); 27 const selectedUrlType = searchParams.get('type') as UrlType; 28 const selectedSource = searchParams.get('source') as ActivitySource; 29 const selectedFeed = 30 (searchParams.get('feed') as 'global' | 'following') || 'global'; 31 32 // Parse activityTypes from URL params (lowercase) and convert to enum values 33 const activityTypesParam = searchParams.getAll('activityTypes'); 34 const selectedActivityTypes = 35 activityTypesParam.length > 0 36 ? activityTypesParam 37 .map((param) => 38 Object.values(ActivityType).find( 39 (t) => t.toLowerCase() === param.toLowerCase(), 40 ), 41 ) 42 .filter((t): t is ActivityType => t !== undefined) 43 : undefined; 44 45 const activityTypesFilter = selectedActivityTypes; 46 47 const globalFeed = useGlobalFeed({ 48 urlType: selectedUrlType, 49 source: selectedSource, 50 activityTypes: activityTypesFilter, 51 }); 52 const followingFeed = useFollowingFeed({ 53 urlType: selectedUrlType, 54 source: selectedSource, 55 activityTypes: activityTypesFilter, 56 enabled: selectedFeed === 'following', 57 }); 58 59 // Use the appropriate feed based on selection 60 const activeFeed = selectedFeed === 'following' ? followingFeed : globalFeed; 61 62 const { 63 data, 64 error, 65 isPending, 66 fetchNextPage, 67 hasNextPage, 68 isFetchingNextPage, 69 isRefetching, 70 refetch, 71 } = activeFeed; 72 73 // Ensure animation is visible even for fast refetches 74 const [showRefetchLoader, setShowRefetchLoader] = useState(false); 75 const MIN_DISPLAY_TIME = 400; // milliseconds 76 77 useEffect(() => { 78 if (isRefetching) { 79 setShowRefetchLoader(true); 80 } else if (showRefetchLoader) { 81 // Keep showing the loader for minimum time to ensure animation completes 82 const timer = setTimeout(() => { 83 setShowRefetchLoader(false); 84 }, MIN_DISPLAY_TIME); 85 return () => clearTimeout(timer); 86 } 87 }, [isRefetching, showRefetchLoader]); 88 89 const allActivities = 90 data?.pages.flatMap((page) => page.activities ?? []) ?? []; 91 92 if (isPending) { 93 return <MyFeedContainerSkeleton />; 94 } 95 96 if (error) { 97 return <MyFeedContainerError />; 98 } 99 100 return ( 101 <Container p="xs" size="xl"> 102 <Collapse expanded={showRefetchLoader} transitionDuration={350}> 103 <Stack align="center" gap={'xs'}> 104 <Loader size={'sm'} color={'gray'} /> 105 <Text fw={600} c={'gray'} mb={'sm'}> 106 Fetching the latest activities... 107 </Text> 108 </Stack> 109 </Collapse> 110 {allActivities.length === 0 ? ( 111 <Center> 112 <Text fz="h3" fw={600} c="gray"> 113 No activity to show yet 114 </Text> 115 </Center> 116 ) : ( 117 <InfiniteScroll 118 dataLength={allActivities.length} 119 hasMore={!!hasNextPage} 120 isInitialLoading={isPending} 121 isLoading={isFetchingNextPage} 122 loadMore={fetchNextPage} 123 > 124 <Stack gap={'xl'} mx={'auto'} maw={600} w={'100%'}> 125 <Stack gap={60}> 126 {allActivities.map((item) => ( 127 <FeedItem 128 key={item.id} 129 item={item} 130 analyticsContext={{ 131 saveSource: CardSaveSource.FEED, 132 activeFilters: { 133 urlType: selectedUrlType, 134 }, 135 pagePath: pathname, 136 }} 137 /> 138 ))} 139 </Stack> 140 </Stack> 141 </InfiniteScroll> 142 )} 143 144 <Box 145 pos={'fixed'} 146 bottom={0} 147 mt={'md'} 148 mx={{ base: 10, sm: 2.5 }} 149 mb={{ base: 100, sm: 'md' }} 150 style={{ zIndex: 2 }} 151 > 152 <RefetchButton onRefetch={() => refetch()} /> 153 </Box> 154 </Container> 155 ); 156}