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.9 kB 159 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 const includeKnownBots = searchParams.get('includeKnownBots') === 'true'; 32 33 // Parse activityTypes from URL params (lowercase) and convert to enum values 34 const activityTypesParam = searchParams.getAll('activityTypes'); 35 const selectedActivityTypes = 36 activityTypesParam.length > 0 37 ? activityTypesParam 38 .map((param) => 39 Object.values(ActivityType).find( 40 (t) => t.toLowerCase() === param.toLowerCase(), 41 ), 42 ) 43 .filter((t): t is ActivityType => t !== undefined) 44 : undefined; 45 46 const activityTypesFilter = selectedActivityTypes; 47 48 const globalFeed = useGlobalFeed({ 49 urlType: selectedUrlType, 50 source: selectedSource, 51 activityTypes: activityTypesFilter, 52 includeKnownBots, 53 }); 54 const followingFeed = useFollowingFeed({ 55 urlType: selectedUrlType, 56 source: selectedSource, 57 activityTypes: activityTypesFilter, 58 includeKnownBots, 59 enabled: selectedFeed === 'following', 60 }); 61 62 // Use the appropriate feed based on selection 63 const activeFeed = selectedFeed === 'following' ? followingFeed : globalFeed; 64 65 const { 66 data, 67 error, 68 isPending, 69 fetchNextPage, 70 hasNextPage, 71 isFetchingNextPage, 72 isRefetching, 73 refetch, 74 } = activeFeed; 75 76 // Ensure animation is visible even for fast refetches 77 const [showRefetchLoader, setShowRefetchLoader] = useState(false); 78 const MIN_DISPLAY_TIME = 400; // milliseconds 79 80 useEffect(() => { 81 if (isRefetching) { 82 setShowRefetchLoader(true); 83 } else if (showRefetchLoader) { 84 // Keep showing the loader for minimum time to ensure animation completes 85 const timer = setTimeout(() => { 86 setShowRefetchLoader(false); 87 }, MIN_DISPLAY_TIME); 88 return () => clearTimeout(timer); 89 } 90 }, [isRefetching, showRefetchLoader]); 91 92 const allActivities = 93 data?.pages.flatMap((page) => page.activities ?? []) ?? []; 94 95 if (isPending) { 96 return <MyFeedContainerSkeleton />; 97 } 98 99 if (error) { 100 return <MyFeedContainerError />; 101 } 102 103 return ( 104 <Container p="xs" size="xl"> 105 <Collapse expanded={showRefetchLoader} transitionDuration={350}> 106 <Stack align="center" gap={'xs'}> 107 <Loader size={'sm'} color={'gray'} /> 108 <Text fw={600} c={'gray'} mb={'sm'}> 109 Fetching the latest activities... 110 </Text> 111 </Stack> 112 </Collapse> 113 {allActivities.length === 0 ? ( 114 <Center> 115 <Text fz="h3" fw={600} c="gray"> 116 No activity to show yet 117 </Text> 118 </Center> 119 ) : ( 120 <InfiniteScroll 121 dataLength={allActivities.length} 122 hasMore={!!hasNextPage} 123 isInitialLoading={isPending} 124 isLoading={isFetchingNextPage} 125 loadMore={fetchNextPage} 126 > 127 <Stack gap={'xl'} mx={'auto'} maw={600} w={'100%'}> 128 <Stack gap={60}> 129 {allActivities.map((item) => ( 130 <FeedItem 131 key={item.id} 132 item={item} 133 analyticsContext={{ 134 saveSource: CardSaveSource.FEED, 135 activeFilters: { 136 urlType: selectedUrlType, 137 }, 138 pagePath: pathname, 139 }} 140 /> 141 ))} 142 </Stack> 143 </Stack> 144 </InfiniteScroll> 145 )} 146 147 <Box 148 pos={'fixed'} 149 bottom={0} 150 mt={'md'} 151 mx={{ base: 10, sm: 2.5 }} 152 mb={{ base: 100, sm: 'md' }} 153 style={{ zIndex: 2 }} 154 > 155 <RefetchButton onRefetch={() => refetch()} /> 156 </Box> 157 </Container> 158 ); 159}