This repository has no description
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}