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