Skip to content

Commit

Permalink
Adds contributors details page and private repo support (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
sudoFerraz authored Feb 29, 2024
1 parent fada939 commit 4f7ee45
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 9 deletions.
100 changes: 100 additions & 0 deletions src/app/(dashboard)/contributors/[contributorUserName]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { ContributorDetailsDto } from "@/app/api/contributors/details/fetchContributorDetails";
import BreadCrumb from "@/components/breadcrumbs";
import { GithubRepoList } from "@/components/githubRepoList";
import { TeamCardList } from "@/components/teamCardList";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Heading } from "@/components/ui/heading";
import { Separator } from "@/components/ui/separator";
import axios from "axios";
import React, { useEffect, useState } from "react";

interface ContributorDetailsPageProps {
params: { contributorUserName: string };
}

export default function ContributorDetailsPage({
params,
}: ContributorDetailsPageProps) {
const contributorUserName = params.contributorUserName;
const breadcrumbItems = [
{ title: "Contributors", link: "/contributors" },
{
title: "Contributor details",
link: `/contributors/${contributorUserName}`,
},
];
const [contributorDetails, setContributorDetails] =
useState<ContributorDetailsDto>();

useEffect(() => {
if (contributorUserName) {
handleFetchContributorDetails();
}
}, []);

const handleFetchContributorDetails = async () => {
const { data } = await axios.get(
"/api/contributors/details?contributor_username=" + contributorUserName,
);
console.log("fetched");
console.log(data);
if (data.success) {
setContributorDetails(data.contributorDetails);
}
};

return (
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
<BreadCrumb items={breadcrumbItems} />
<div className="flex items-start justify-between">
<Heading
title={contributorUserName}
description={`View the details of your contributor`}
/>
</div>
<Separator />

<div className="pt-16 grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4 md:col-span-3">
<CardHeader>
<CardTitle>Teams</CardTitle>
<CardDescription>
This contributor is part of {contributorDetails?.teams.length}{" "}
teams
</CardDescription>
</CardHeader>
<CardContent>
{contributorDetails?.teams && (
<TeamCardList teamArray={contributorDetails?.teams} />
)}
</CardContent>
</Card>

<Card className="col-span-4 md:col-span-3">
<CardHeader>
<CardTitle>Repositories</CardTitle>
<CardDescription>
This contributor is part of{" "}
{contributorDetails?.github_repositories.length} repositories
</CardDescription>
</CardHeader>
<CardContent>
{contributorDetails?.github_repositories && (
<GithubRepoList
githubRepos={contributorDetails?.github_repositories}
/>
)}
</CardContent>
</Card>
</div>
</div>
);
}
13 changes: 11 additions & 2 deletions src/app/(dashboard)/contributors/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { ColumnDef } from "@tanstack/react-table";
import { ContributorDto } from "@/app/api/contributors/fetchUserContributors";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useRouter } from "next/navigation";

export const contributorsColumns: ColumnDef<ContributorDto>[] = [
{
Expand Down Expand Up @@ -83,7 +84,9 @@ export const contributorsColumns: ColumnDef<ContributorDto>[] = [
},
{
id: "actions",
cell: () => {
cell: ({ row }) => {
const contributor = row.original;
const router = useRouter();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand All @@ -95,7 +98,13 @@ export const contributorsColumns: ColumnDef<ContributorDto>[] = [
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>View contributor details</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
router.push(`/contributors/${contributor.userName}`);
}}
>
View contributor details
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
5 changes: 5 additions & 0 deletions src/app/api/auth/[...nextauth]/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export const options: AuthOptions = {
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
authorization: {
params: {
scope: "read:user user:email repo",
},
},
}),
],
pages: {
Expand Down
56 changes: 56 additions & 0 deletions src/app/api/contributors/details/fetchContributorDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { GithubRepo, Team } from "@prisma/client";
import prisma from "db";

export type ContributorDetailsDto = {
userName: string;
teams: Team[];
github_repositories: GithubRepo[];
};

/**
* Fetch contributor details by userName
* Fetches the teams in which the username is a contributor (case insensitive) and the github repositories of those teams
* @param {string} contributorUserName
* @returns {Object} ContributorDetailsDto
*/
export async function fetchContributorDetails(
contributorUserName: string,
): Promise<ContributorDetailsDto | null> {
try {
const foundContributorTeams = await prisma.team.findMany({
where: {
ContributionCalculation: {
some: {
UserScore: {
some: {
username: {
equals: contributorUserName,
mode: "insensitive",
},
},
},
},
},
},
});
const contributorTeams = foundContributorTeams.map((team) => team.name);
const foundContributorRepositories = await prisma.githubRepo.findMany({
where: {
Team: {
name: {
in: contributorTeams,
},
},
},
});
const contributorDetailsDto: ContributorDetailsDto = {
userName: contributorUserName,
teams: foundContributorTeams,
github_repositories: foundContributorRepositories,
};
return contributorDetailsDto;
} catch (error) {
console.log(error);
}
return null;
}
17 changes: 17 additions & 0 deletions src/app/api/contributors/details/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import { fetchContributorDetails } from "./fetchContributorDetails";

export async function GET(req: NextRequest) {
const contributorUserName = req.nextUrl.searchParams.get(
"contributor_username",
);
if (contributorUserName) {
const contributorDetails =
await fetchContributorDetails(contributorUserName);
return NextResponse.json({
success: true,
contributorDetails: contributorDetails,
});
}
return NextResponse.json({ success: false, contributorDetails: null });
}
6 changes: 2 additions & 4 deletions src/app/api/github/repo/fetchRepositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { GithubRepoDto } from "./types/githubRepo.dto";

export async function fetchPaginatedGithubRepoResult(
githubAccessToken: string,
githubLogin: string,
): Promise<GithubRepoDto[]> {
let currentPage = 1;
let mergedGithubRepos: GithubRepoDto[] = [];
while (true) {
const fetchGithubReposRequest = await fetch(
`https://api.github.com/users/${githubLogin}/repos?&per_page=100&sort=updated&page=${currentPage}`,
`https://api.github.com/user/repos?&per_page=100&sort=updated&page=${currentPage}`,
{
method: "GET",
headers: {
Expand All @@ -22,7 +21,6 @@ export async function fetchPaginatedGithubRepoResult(
const parsedGithubRepos = JSON.parse(fetchedGithubRepos) as GithubRepoDto[];
mergedGithubRepos = mergedGithubRepos.concat(parsedGithubRepos);
currentPage = currentPage + 1;
if (parsedGithubRepos.length == 0)
return mergedGithubRepos.filter((githuRepo) => !githuRepo.private);
if (parsedGithubRepos.length == 0) return mergedGithubRepos;
}
}
1 change: 0 additions & 1 deletion src/app/api/github/repo/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export async function GET() {
if (session?.accessToken && session?.githubLogin) {
const githubRepos = await fetchPaginatedGithubRepoResult(
session.accessToken,
session.githubLogin,
);
return NextResponse.json({ success: true, gitRepos: githubRepos });
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/githubRepoList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { RegisteredGitRepo } from "@/app/api/github/repo/registered/fetchRegisteredRepos";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { ScrollArea } from "@radix-ui/react-scroll-area";
import { ScrollArea } from "./ui/scroll-area";

interface GithubRepoListProps {
githubRepos: RegisteredGitRepo[];
}

export function GithubRepoList({ githubRepos }: GithubRepoListProps) {
return (
<ScrollArea className="max-h-96">
<ScrollArea className="h-72 rounded-md">
<div className="space-y-8">
{githubRepos.map((repo) => (
<div className="flex items-center">
Expand Down
28 changes: 28 additions & 0 deletions src/components/teamCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { ScrollArea } from "./ui/scroll-area";
import { Team } from "@prisma/client";

interface TeamCardListProps {
teamArray: Team[];
}

export function TeamCardList({ teamArray }: TeamCardListProps) {
return (
<ScrollArea className="h-72 rounded-md">
<div className="space-y-8">
{teamArray.map((team) => (
<div className="flex items-center">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/01.png" alt="Avatar" />
<AvatarFallback>{team.name[0]}</AvatarFallback>
</Avatar>
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">{team.name}</p>
</div>
<div className="ml-auto font-medium">Active</div>
</div>
))}
</div>
</ScrollArea>
);
}

0 comments on commit 4f7ee45

Please sign in to comment.