From ac79f72ec74ce830fb8ed022c53d79a27f2bc8f0 Mon Sep 17 00:00:00 2001 From: Ting Chien Meng Date: Mon, 2 Dec 2024 22:46:50 -0500 Subject: [PATCH 01/88] refactor contributor page --- docs/community/Accordion.tsx | 121 +++++++++++++++++ docs/community/Contributions.tsx | 216 +++++++++++++++++++++++++++++++ docs/community/Contributor.tsx | 64 +++++++++ docs/community/Contributors.tsx | 102 +++++++++++++++ docs/community/StatCard.tsx | 20 +++ docs/community/profiles.mdx | 15 +-- 6 files changed, 527 insertions(+), 11 deletions(-) create mode 100644 docs/community/Accordion.tsx create mode 100644 docs/community/Contributions.tsx create mode 100644 docs/community/Contributor.tsx create mode 100644 docs/community/Contributors.tsx create mode 100644 docs/community/StatCard.tsx diff --git a/docs/community/Accordion.tsx b/docs/community/Accordion.tsx new file mode 100644 index 0000000000..57fac2d9a0 --- /dev/null +++ b/docs/community/Accordion.tsx @@ -0,0 +1,121 @@ +import React, { useState } from "react"; +import { GitHubItem } from "./Contributions"; + +interface AccordionProps { + title: string; + isOpen: boolean; + onToggle: () => void; + data: GitHubItem[]; + loadMore?: () => void; +} + +export const Accordion: React.FC = ({ + title, + isOpen, + onToggle, + data, + loadMore, +}) => { + const [hoveredIndex, setHoveredIndex] = useState(null); + const [hoverLoadMore, setHoverLoadMore] = useState(false); + + return ( +
+
+
{title}
+
{isOpen ? "▼" : "▶"}
+
+ {isOpen && ( +
+ {data.map((entry, index) => ( +
+
setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + onClick={() => + window.open( + entry.html_url, + "_blank", + "noopener,noreferrer", + ) + } + > +
{entry.title}
+
+ {entry.created_at.split("T")[0]} +
+
+
+ ))} +
+ )} + {isOpen && loadMore && ( +
+ setHoverLoadMore(true)} + onMouseLeave={() => setHoverLoadMore(false)} + onClick={loadMore} + > + Load more... + +
+ )} +
+ ); +}; diff --git a/docs/community/Contributions.tsx b/docs/community/Contributions.tsx new file mode 100644 index 0000000000..d329608b34 --- /dev/null +++ b/docs/community/Contributions.tsx @@ -0,0 +1,216 @@ +import React, { useState, useEffect } from "react"; +import { Accordion } from "./Accordion"; +import { StatCard } from "./StatCard"; + +export interface GitHubItem { + html_url: string; + title: string; + created_at: string; +} + +export interface StatCardProps { + title: string; + value: number; +} + +const Contributions = ({ contributor }) => { + const [contributorStat, setContributorState] = useState( + [], + ); + const [commitsData, setCommitsData] = useState([]); + const [prsData, setPrsData] = useState([]); + const [issuesData, setIssuesData] = useState([]); + const [openAccordion, setOpenAccordion] = useState(null); + const [commitPage, setCommitPage] = useState(1); // Track the current page for commits + + useEffect(() => { + const fetchContributorStats = async () => { + const stats: StatCardProps[] = [ + { title: "Commits", value: contributor.contributions }, + ]; + + try { + await fetchCommits(commitPage); + + // Fetch PRs + const prResponse = await fetch( + `https://api.github.com/search/issues?q=type:pr+author:${contributor.login}+repo:ai16z/eliza`, + { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }, + ); + const prData = await prResponse.json(); + stats.push({ title: "PRs", value: prData.total_count }); + setPrsData(prData.items || []); + + console.log(prData); + + // Fetch Issues + const issueResponse = await fetch( + `https://api.github.com/search/issues?q=type:issue+author:${contributor.login}+repo:ai16z/eliza`, + { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }, + ); + const issueData = await issueResponse.json(); + stats.push({ title: "Issues", value: issueData.total_count }); + setIssuesData(issueData.items || []); + } catch (error) { + console.error("Error fetching contributor stats:", error); + } + + setContributorState(stats); + }; + + fetchContributorStats(); + }, [contributor.login]); + + const toggleAccordion = (section: string) => { + setOpenAccordion((prev) => (prev === section ? null : section)); + }; + + const fetchCommits = async (page: number) => { + try { + const commitResponse = await fetch( + `https://api.github.com/repos/ai16z/eliza/commits?author=${contributor.login}&page=${page}`, + { + headers: { + Accept: "application/vnd.github.v3+json", + }, + }, + ); + const commitData = await commitResponse.json(); + const commitItems = commitData.map((commit: any) => ({ + html_url: commit.html_url, + title: commit.commit.message, + created_at: commit.commit.author.date, + })); + setCommitsData((prevData) => [...prevData, ...commitItems]); // Append new commits + } catch (error) { + console.error("Error fetching commits:", error); + } + }; + + const accordionItems = [ + { + title: "Commits", + data: commitsData, + section: "commits", + loadMore: + commitsData.length >= contributor.contributions + ? undefined + : () => { + const nextPage = commitPage + 1; + fetchCommits(nextPage); + setCommitPage(nextPage); + }, + }, + { + title: "Pull Requests", + data: prsData, + section: "pullRequests", + }, + { + title: "Issues", + data: issuesData, + section: "issues", + }, + ]; + + return ( +
+
+
+ {`${contributor.login}'s +
+
+ {contributor.login} +
+
+ {contributor.contributions} contributions +
+
+
+
+
+ {contributorStat.map((stat, index) => ( + + ))} +
+
+ {accordionItems.map((item) => ( + toggleAccordion(item.section)} + data={item.data} + loadMore={item.loadMore} + /> + ))} +
+
+ ); +}; + +export default Contributions; diff --git a/docs/community/Contributor.tsx b/docs/community/Contributor.tsx new file mode 100644 index 0000000000..7df906c24e --- /dev/null +++ b/docs/community/Contributor.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import { ContributorProps } from "./Contributors"; + +const ContributorCard: React.FC = ({ + contributor, + onSelect, +}) => { + const [isHovered, setIsHovered] = useState(false); + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onClick={onSelect} + > +
+ {`${contributor.login}'s +
+
+ {contributor.login} +
+
+ {contributor.contributions} contributions +
+
+
+
+ ); +}; + +export default ContributorCard; diff --git a/docs/community/Contributors.tsx b/docs/community/Contributors.tsx new file mode 100644 index 0000000000..cbfcaf5c66 --- /dev/null +++ b/docs/community/Contributors.tsx @@ -0,0 +1,102 @@ +import React, { useEffect, useState } from "react"; +import ContributorCard from "./Contributor"; +import Contributions from "./Contributions"; + +export interface Contributor { + id: number; + login: string; + avatar_url: string; + html_url: string; + contributions: number; +} + +export interface ContributorProps { + contributor: Contributor; + onSelect: () => void; +} + +const Contributors: React.FC = () => { + const [selectedContributor, setSelectedContributor] = + useState(null); + const [contributors, setContributors] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchAllContributors = async () => { + let allContributors: Contributor[] = []; + let page = 1; + + try { + while (true) { + const response = await fetch( + `https://api.github.com/repos/ai16z/eliza/contributors?per_page=30&page=${page}`, + ); + if (!response.ok) { + throw new Error( + `Error fetching contributors: ${response.statusText}`, + ); + } + const data: Contributor[] = await response.json(); + + if (data.length === 0) { + break; + } + + allContributors = [...allContributors, ...data]; + page++; + } + + setContributors(allContributors); + } catch (err) { + setError(err instanceof Error ? err.message : "Unknown error"); + } finally { + setLoading(false); + } + }; + + fetchAllContributors(); + }, []); + + useEffect(() => { + console.log(contributors); + }, [contributors]); + + if (loading) { + return
Loading...
; + } + + if (error) { + return
Error: {error}
; + } + + return ( +
+ {selectedContributor ? ( + + ) : ( + contributors.map((contributor) => ( + { + setSelectedContributor(contributor); + }} + /> + )) + )} +
+ ); +}; + +export default Contributors; diff --git a/docs/community/StatCard.tsx b/docs/community/StatCard.tsx new file mode 100644 index 0000000000..4743b3fff8 --- /dev/null +++ b/docs/community/StatCard.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { StatCardProps } from "./Contributions"; + +export const StatCard: React.FC = ({ title, value }) => { + return ( +
+
{title}
+
{value}
+
+ ); +}; diff --git a/docs/community/profiles.mdx b/docs/community/profiles.mdx index 28224acecd..23c179097b 100644 --- a/docs/community/profiles.mdx +++ b/docs/community/profiles.mdx @@ -1,17 +1,10 @@ --- -title: GitHub Contributors +title: GitHub Contributors description: GitHub contributors to our project --- -# GitHub Contributors +import Contributors from "./Contributors"; -This is a quick and dirty implementation of profiles that are programmatically generated from github data from `ai16z/eliza` repo. I'm looking for some help to integrate into Docusaurus as react components. See the code for generating profiles here: https://github.com/ai16z/ai16z.github.io +# GitHub Contributors -