Skip to content

Commit

Permalink
Merge pull request #78 from game-node-app/dev
Browse files Browse the repository at this point in the history
Activities work
  • Loading branch information
Lamarcke authored Apr 23, 2024
2 parents 4a3d9bf + 55d8d5e commit ec55738
Show file tree
Hide file tree
Showing 30 changed files with 635 additions and 112 deletions.
90 changes: 74 additions & 16 deletions src/components/activity/ActivityFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,93 @@
import React, { useMemo } from "react";
import React, { useCallback, useEffect, useMemo } from "react";
import { useInfiniteActivities } from "@/components/activity/hooks/useInfiniteActivities";
import { Stack } from "@mantine/core";
import { Skeleton, Stack } from "@mantine/core";
import { Activity } from "@/wrapper/server";
import type = Activity.type;
import ReviewActivityItem from "@/components/activity/ReviewActivityItem";
import ReviewActivityItem from "@/components/activity/item/ReviewActivityItem";
import CenteredLoading from "@/components/general/CenteredLoading";
import CollectionEntryActivityItem from "@/components/activity/item/CollectionEntryActivityItem";
import CenteredErrorMessage from "@/components/general/CenteredErrorMessage";
import UserFollowActivityItem from "@/components/activity/item/UserFollowActivityItem";
import { useIntersection } from "@mantine/hooks";
import ActivityList from "@/components/activity/ActivityList";

interface Props {
criteria: "following" | "all";
}

const ActivityFeed = ({ criteria }: Props) => {
const { ref, entry } = useIntersection({
threshold: 1,
});
const activityQuery = useInfiniteActivities({
criteria,
limit: 10,
});

const items = useMemo(() => {
if (!activityQuery.data) return undefined;
return activityQuery.data.pages.flatMap((page) => {
return page.data.map((activity) => {
if (activity.type === type.REVIEW) {
return (
<ReviewActivityItem
key={activity.id}
activity={activity}
/>
);
}
});
});
return activityQuery.data.pages?.flatMap((page) => page.data);
}, [activityQuery.data]);
return <Stack className={"w-full h-full"}>{items}</Stack>;

const isLoading = activityQuery.isLoading;
const isError = activityQuery.isError;
const isSuccess = activityQuery.isSuccess;
const isFetching = activityQuery.isFetching;

const isEmpty =
activityQuery.data != undefined &&
activityQuery.data?.pages.some((page) => {
return page.pagination.totalItems === 0;
});

const buildSkeletons = useCallback(() => {
return new Array(4).fill(0).map((_, i) => {
return <Skeleton key={i} className={"w-full h-[140px]"} />;
});
}, []);

useEffect(() => {
const lastElement =
activityQuery.data?.pages[activityQuery.data?.pages.length - 1];
const hasNextPage =
lastElement != undefined &&
lastElement.data.length > 0 &&
lastElement.pagination.hasNextPage;

const canFetchNextPage = !isFetching && !isLoading && hasNextPage;

// Minimum amount of time (ms) since document creation for
// intersection to be considered valid
const minimumIntersectionTime = 3000;

if (
canFetchNextPage &&
entry?.isIntersecting &&
entry.time > minimumIntersectionTime
) {
activityQuery.fetchNextPage({ cancelRefetch: false });
}
}, [activityQuery, entry, isFetching, isLoading]);

return (
<Stack className={"w-full h-full"}>
{activityQuery.isLoading && buildSkeletons()}
{!isLoading && isEmpty && (
<CenteredErrorMessage
message={"No activities to show. Try a different filter."}
/>
)}
{isError && (
<CenteredErrorMessage
message={
"Error while fetching activities. Please try again or contact support."
}
/>
)}
<ActivityList items={items} />
<div ref={ref} id={"last-element-tracker"}></div>
</Stack>
);
};

export default ActivityFeed;
2 changes: 1 addition & 1 deletion src/components/activity/ActivityFeedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ActivityFeedLayout = ({ children, currentTab }: Props) => {
</Link>
</Tabs.List>
</Tabs>
<Box className={"mt-4"}>{children}</Box>
<Box>{children}</Box>
</Stack>
);
};
Expand Down
38 changes: 38 additions & 0 deletions src/components/activity/ActivityList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { Activity } from "@/wrapper/server";
import ReviewActivityItem from "@/components/activity/item/ReviewActivityItem";
import CollectionEntryActivityItem from "@/components/activity/item/CollectionEntryActivityItem";
import UserFollowActivityItem from "@/components/activity/item/UserFollowActivityItem";
import type = Activity.type;

interface Props {
items: Activity[] | undefined;
}

const ActivityList = ({ items }: Props) => {
if (!items) return null;
return items.map((activity) => {
switch (activity.type) {
case type.REVIEW:
return (
<ReviewActivityItem key={activity.id} activity={activity} />
);
case type.COLLECTION_ENTRY:
return (
<CollectionEntryActivityItem
key={activity.id}
activity={activity}
/>
);
case type.FOLLOW:
return (
<UserFollowActivityItem
key={activity.id}
activity={activity}
/>
);
}
});
};

export default ActivityList;
58 changes: 58 additions & 0 deletions src/components/activity/RecentActivitiesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useMemo } from "react";
import useUserProfile from "@/components/profile/hooks/useUserProfile";
import { useLatestActivities } from "@/components/activity/hooks/useLatestActivities";
import ReviewActivityItem from "@/components/activity/item/ReviewActivityItem";
import CollectionEntryActivityItem from "@/components/activity/item/CollectionEntryActivityItem";
import UserFollowActivityItem from "@/components/activity/item/UserFollowActivityItem";
import { Stack } from "@mantine/core";
import CenteredLoading from "@/components/general/CenteredLoading";
import { Activity } from "@/wrapper/server";
import type = Activity.type;

interface Props {
userId?: string;
limit?: number;
offset?: number;
}

const RecentActivitiesList = ({ userId, offset = 0, limit = 5 }: Props) => {
const activitiesQuery = useLatestActivities(userId, offset, limit);

const items = useMemo(() => {
if (!activitiesQuery.data) return null;
return activitiesQuery.data.data.map((activity) => {
switch (activity.type) {
case type.REVIEW:
return (
<ReviewActivityItem
key={activity.id}
activity={activity}
/>
);
case type.COLLECTION_ENTRY:
return (
<CollectionEntryActivityItem
key={activity.id}
activity={activity}
/>
);
case type.FOLLOW:
return (
<UserFollowActivityItem
key={activity.id}
activity={activity}
/>
);
}
});
}, [activitiesQuery.data]);

return (
<Stack className={"w-full h-full"}>
{activitiesQuery.isLoading && <CenteredLoading />}
{items}
</Stack>
);
};

export default RecentActivitiesList;
20 changes: 20 additions & 0 deletions src/components/activity/hooks/useLatestActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { ActivitiesService } from "@/wrapper/server";

export function useLatestActivities(
userId: string | undefined,
offset = 0,
limit = 10,
) {
return useQuery({
queryKey: ["activities", "latest", userId, offset, limit],
queryFn: async () => {
return ActivitiesService.activitiesRepositoryControllerFindLatest(
userId,
offset,
limit,
);
},
retry: 1,
});
}
35 changes: 17 additions & 18 deletions src/components/activity/input/ActivityItemLikes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import sourceType = StatisticsActionDto.sourceType;
import { ActionIcon, Group, Text } from "@mantine/core";
import { redirectToAuth } from "supertokens-auth-react";
import { IconThumbUp } from "@tabler/icons-react";
import useOnMobile from "@/components/general/hooks/useOnMobile";

interface Props {
activityId: string;
Expand All @@ -19,24 +20,22 @@ const ActivityItemLikes = ({ activityId }: Props) => {
sourceType: sourceType.ACTIVITY,
});
return (
<Group gap={"0.5rem"}>
<ActionIcon
onClick={async () => {
if (!userId) {
redirectToAuth();
return;
}
toggleLike();
}}
variant={isLiked ? "filled" : "subtle"}
size={"xl"}
color={isLiked ? "brand" : "white"}
data-disabled={!userId}
>
<IconThumbUp />
<Text>{likesCount}</Text>
</ActionIcon>
</Group>
<ActionIcon
onClick={async () => {
if (!userId) {
await redirectToAuth();
return;
}
toggleLike();
}}
variant={isLiked ? "filled" : "transparent"}
size={"lg"}
color={isLiked ? "brand" : "white"}
data-disabled={!userId}
>
<IconThumbUp />
<Text>{likesCount}</Text>
</ActionIcon>
);
};

Expand Down
Loading

0 comments on commit ec55738

Please sign in to comment.