Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add top contributors panel to feeds page #1347

Merged
merged 11 commits into from
Jul 17, 2023
79 changes: 79 additions & 0 deletions components/atoms/TopContributorCard/top-contributor-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from "react";

import Link from "next/link";
import { useRouter } from "next/router";
import { getAvatarByUsername } from "lib/utils/github";
import useFollowUser from "lib/hooks/useFollowUser";
import useSupabaseAuth from "lib/hooks/useSupabaseAuth";

import Avatar from "../Avatar/avatar";
import Button from "../Button/button";

export interface TopContributorCardProps {
login: string;
}

const TopContributorCard = ({ login }: TopContributorCardProps) => {
const router = useRouter();
const currentPath = router.asPath;

const { isError: notFollowing, follow, unFollow } = useFollowUser(login);
const [host, setHost] = useState("");
const { sessionToken, signIn } = useSupabaseAuth();

const handleFollowUser = async () => {
OgDev-01 marked this conversation as resolved.
Show resolved Hide resolved
try {
if (notFollowing) {
await follow();
return;
}
await unFollow();
} catch (error) {
console.log(error);
}
};

useEffect(() => {
if (typeof window !== "undefined") {
setHost(window.location.origin as string);
}
}, []);

return (
<div className="flex items-center justify-between w-full gap-4 bg-light-slate-1">
<Link href={`/user/${login}`}>
<div className="flex items-center gap-2">
<Avatar isCircle size={35} avatarURL={getAvatarByUsername(login)} />
<p className="font-semibold text-light-slate-12">{login}</p>
</div>
</Link>
{sessionToken && !notFollowing ? (
<Button
onClick={() =>
sessionToken
? handleFollowUser()
: signIn({ provider: "github", options: { redirectTo: `${host}/${currentPath}` } })
}
className="!px-2 !py-1"
variant="primary"
>
following
</Button>
) : (
<Button
onClick={() =>
sessionToken
? handleFollowUser()
: signIn({ provider: "github", options: { redirectTo: `${host}/${currentPath}` } })
}
className="!px-2 !py-1 border-light-orange-7 text-light-orange-10"
variant="text"
>
follow
</Button>
)}
</div>
);
};

export default TopContributorCard;
36 changes: 0 additions & 36 deletions components/atoms/TopUserCard/top-user-card.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { useSWRConfig } from "swr";
import TopContributorCard from "components/atoms/TopContributorCard/top-contributor-card";
import { useFetchTopUsers } from "lib/hooks/useFetchTopUsers";
import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper";

interface TopContributorsPanelProps {
loggedInUserLogin: string;
}
const TopContributorsPanel = ({ loggedInUserLogin }: TopContributorsPanelProps) => {
const { data, isLoading } = useFetchTopUsers();
const { mutate } = useSWRConfig();

const topUsersWithoutLoggedInUser = data ? data.filter((user) => user.login !== loggedInUserLogin) : [];
OgDev-01 marked this conversation as resolved.
Show resolved Hide resolved
const top3Users = topUsersWithoutLoggedInUser.slice(0, 3).map((user) => user.login);
OgDev-01 marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className="flex flex-col max-w-xs gap-6 p-6 bg-white border rounded-xl">
<h2 className="pb-2 text-2xl border-b">Top Contributors</h2>

{isLoading &&
Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="flex items-center justify-between gap-4 ">
<SkeletonWrapper radius={100} height={40} width={40} /> <SkeletonWrapper height={40} classNames="w-full" />
</div>
))}
{top3Users.map((login, i) => (
<TopContributorCard key={i} login={login} />
))}
</div>
);
};

export default TopContributorsPanel;
18 changes: 0 additions & 18 deletions components/molecules/TopUsersPanel/top-user-panel.tsx

This file was deleted.

20 changes: 20 additions & 0 deletions lib/hooks/useFetchTopUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import useSWR, { Fetcher } from "swr";
import publicApiFetcher from "lib/utils/public-api-fetcher";

type TopUsersResponse = { login: string }[];

const useFetchTopUsers = () => {
const { data, error, mutate } = useSWR<TopUsersResponse, Error>(
OgDev-01 marked this conversation as resolved.
Show resolved Hide resolved
"users/top",
publicApiFetcher as Fetcher<TopUsersResponse, Error>
);

return {
data: data ?? [],
isLoading: !error && !data,
isError: !!error,
mutate,
};
};

export { useFetchTopUsers };
7 changes: 5 additions & 2 deletions lib/hooks/useFollowUser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import useSWR, { Fetcher } from "swr";
import useSWR, { Fetcher, useSWRConfig } from "swr";
import publicApiFetcher from "lib/utils/public-api-fetcher";
import useSupabaseAuth from "./useSupabaseAuth";

interface FollowUserResponse {
data: DbFollowUser;
}
const useFollowUser = (username: string) => {
const { sessionToken } = useSupabaseAuth();
const { sessionToken, user } = useSupabaseAuth();
const { mutate: GlobalMutate } = useSWRConfig(); // adding this to mutate the global user data to update the users card status on follow/unfollow
OgDev-01 marked this conversation as resolved.
Show resolved Hide resolved

const { data, error, mutate } = useSWR<FollowUserResponse, Error>(
username ? `users/${username}/follow` : null,
Expand All @@ -23,6 +24,7 @@ const useFollowUser = (username: string) => {

if (req && req.ok) {
mutate();
GlobalMutate(`user/${user?.user_metadata?.username}`);
}
};

Expand All @@ -36,6 +38,7 @@ const useFollowUser = (username: string) => {

if (req && req.ok) {
mutate();
GlobalMutate(`user/${user?.user_metadata?.username}`);
}
};

Expand Down
5 changes: 4 additions & 1 deletion pages/feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Link from "next/link";
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
import clsx from "clsx";

import TopContributorsPanel from "components/molecules/TopContributorsPanel/top-contributors-panel";
import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
import { useFetchAllHighlights } from "lib/hooks/useFetchAllHighlights";
import { useFetchHighlightRepos } from "lib/hooks/useFetchHiglightRepos";
Expand Down Expand Up @@ -68,6 +69,7 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {

const { data, mutate, setPage, isLoading, meta } = useFetchAllHighlights(selectedRepo);
const { data: emojis } = useFetchAllEmojis();

const { data: loggedInUser, isLoading: loggedInUserLoading } = useFetchUser(user?.user_metadata.user_name as string);

const { followers_count, following_count, highlights_count } = loggedInUser || {};
Expand Down Expand Up @@ -121,7 +123,7 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
twitterCard="summary_large_image"
/>
<div className="container flex flex-col gap-16 px-2 pt-12 mx-auto md:px-16 lg:justify-end md:flex-row">
<div className="flex-col flex-1 hidden gap-8 mt-12 md:flex">
<div className="flex-col flex-1 hidden gap-6 mt-12 md:flex">
{user && (
<div>
<UserCard
Expand All @@ -132,6 +134,7 @@ const Feeds: WithPageLayout<HighlightSSRProps> = (props: HighlightSSRProps) => {
/>
</div>
)}
<TopContributorsPanel loggedInUserLogin={loggedInUser?.login ?? ""} />
</div>
{singleHighlight && (
<Dialog
Expand Down
18 changes: 8 additions & 10 deletions stories/atoms/top-user-card.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { ComponentStory } from "@storybook/react";
import TopUserCard from "components/atoms/TopUserCard/top-user-card";
import { StoryFn } from "@storybook/react";
import TopContributorCard from "components/atoms/TopContributorCard/top-contributor-card";

const storyConfig = {
title: "Design System/Atoms/TopUserCard",
title: "Design System/Atoms/TopContributorCard",
};
export default storyConfig;

const TopUserCardTemplate: ComponentStory<typeof TopUserCard> = (args) => <TopUserCard {...args} />;
const TopContributorCardTemplate: StoryFn<typeof TopContributorCard> = (args) => <TopContributorCard {...args} />;

export const Following = TopUserCardTemplate.bind({});
export const NotFollowing = TopUserCardTemplate.bind({});
export const Following = TopContributorCardTemplate.bind({});
export const NotFollowing = TopContributorCardTemplate.bind({});

Following.args = {
username: "diivi",
following: true,
login: "diivi",
};

NotFollowing.args = {
username: "ogdev-01",
following: false,
login: "ogdev-01",
};
16 changes: 16 additions & 0 deletions stories/molecules/top-contributors-panel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Meta, StoryFn } from "@storybook/react";
import TopContributorsPanel from "components/molecules/TopContributorsPanel/top-contributors-panel";

const storyConfig = {
title: "Design System/Molecules/TopContributorsPanel",
} as Meta<typeof TopContributorsPanel>;

export default storyConfig;

const TopContributorsPanelTemplate: StoryFn<typeof TopContributorsPanel> = (args) => <TopContributorsPanel {...args} />;

export const Default = TopContributorsPanelTemplate.bind({});

Default.args = {
loggedInUserLogin: "ogdev-01",
};
23 changes: 0 additions & 23 deletions stories/molecules/top-users-panel.stories.tsx

This file was deleted.