diff --git a/components/Graphs/MetricCard.stories.tsx b/components/Graphs/MetricCard.stories.tsx new file mode 100644 index 000000000..b909b7eec --- /dev/null +++ b/components/Graphs/MetricCard.stories.tsx @@ -0,0 +1,47 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { useFetchMetricStats } from "lib/hooks/api/useFetchMetricStats"; +import MetricCard from "./MetricCard"; + +type MetricCardAndRepositoryArgs = React.ComponentProps & { repository: string }; +type Story = StoryObj; + +const meta: Meta = { + title: "Components/Graphs/MetricCard", + component: MetricCard, +}; + +export default meta; + +export const StarsPerDay: Story = { + args: { + repository: "open-sauced/app", + }, + render: ({ repository }) => , +}; + +export const ForksPerDay: Story = { + args: { + repository: "open-sauced/app", + }, + render: ({ repository }) => , +}; + +const StarMetricCard = ({ repository }: { repository: string }) => { + const { data: starStats, error: starError } = useFetchMetricStats({ + repository, + variant: "stars", + range: 30, + }); + + return ; +}; + +const ForkMetricCard = ({ repository }: { repository: string }) => { + const { data: forkStats, error: forkError } = useFetchMetricStats({ + repository, + variant: "forks", + range: 30, + }); + + return ; +}; diff --git a/components/Graphs/MetricCard.tsx b/components/Graphs/MetricCard.tsx new file mode 100644 index 000000000..8a611852a --- /dev/null +++ b/components/Graphs/MetricCard.tsx @@ -0,0 +1,72 @@ +import EChartsReact from "echarts-for-react"; +import { FaArrowUp, FaEllipsisVertical } from "react-icons/fa6"; +import { StatsType } from "lib/hooks/api/useFetchMetricStats"; +import Card from "components/atoms/Card/card"; +import Button from "components/shared/Button/button"; +import humanizeNumber from "lib/utils/humanizeNumber"; + +type MetricCardProps = { + stats: StatsType[] | undefined; + variant: "stars" | "forks"; +}; + +export default function MetricCard({ stats, variant }: MetricCardProps) { + const seriesData = stats + ?.map((stat) => (variant === "stars" ? stat.star_count || 0 : stat.forks_count || 0)) + .reverse(); + const bucketData = stats?.map((stat) => new Date(stat.bucket).toDateString()).reverse(); + + const option = { + xAxis: { + type: "category", + data: bucketData, + show: false, + }, + yAxis: { + type: "value", + show: false, + }, + tooltip: { + trigger: "axis", + axisPointer: { + type: "shadow", + }, + }, + series: [ + { + data: seriesData, + symbol: "none", + type: variant === "stars" ? "line" : "bar", + }, + ], + color: "hsla(19, 100%, 50%, 1)", + }; + + const total = seriesData?.reduce((stat, currentValue) => (stat || 0) + (currentValue || 0), 0); + + return ( + +
+

{variant} per day

+ +
+ +
+

{humanizeNumber(total || 0, "abbreviation")}

+
+ +
+
+ +
+
+ +

10%

+
+

vs. last period

+
+
+ ); +} diff --git a/lib/hooks/api/useFetchMetricStats.ts b/lib/hooks/api/useFetchMetricStats.ts new file mode 100644 index 000000000..cf8e82818 --- /dev/null +++ b/lib/hooks/api/useFetchMetricStats.ts @@ -0,0 +1,41 @@ +import useSWR, { Fetcher } from "swr"; +import { publicApiFetcher } from "lib/utils/public-api-fetcher"; + +type UseFetchMetricStatsProps = { + repository: string; + variant: "stars" | "forks"; // TODO: add other MetricCard types + range: number; +}; + +export type StatsType = { + bucket: string; + star_count?: number; + forks_count?: number; +}; + +export function useFetchMetricStats({ repository, variant, range }: UseFetchMetricStatsProps) { + const query = new URLSearchParams(); + query.set("repo", repository); + query.set("range", range.toString()); + + const endpoint = () => { + switch (variant) { + case "stars": + return `histogram/stars?${query.toString()}`; + case "forks": + return `histogram/forks?${query.toString()}`; + } + }; + + const { data, error, isLoading, mutate } = useSWR( + endpoint, + publicApiFetcher as Fetcher + ); + + return { + data, + error, + isLoading, + mutate, + }; +} diff --git a/pages/s/[org]/[repo]/index.tsx b/pages/s/[org]/[repo]/index.tsx index 064005154..da14a7ca4 100644 --- a/pages/s/[org]/[repo]/index.tsx +++ b/pages/s/[org]/[repo]/index.tsx @@ -2,10 +2,12 @@ import { GetServerSidePropsContext } from "next"; import { createPagesServerClient } from "@supabase/auth-helpers-nextjs"; import { fetchApiData } from "helpers/fetchApiData"; import { getAllFeatureFlags } from "lib/utils/server/feature-flags"; +import { useFetchMetricStats } from "lib/hooks/api/useFetchMetricStats"; import SEO from "layouts/SEO/SEO"; import ProfileLayout from "layouts/profile"; import Avatar from "components/atoms/Avatar/avatar"; +import MetricCard from "components/Graphs/MetricCard"; export async function getServerSideProps(context: GetServerSidePropsContext) { const { org, repo } = context.params ?? { org: "", repo: "" }; @@ -35,6 +37,18 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { } export default function RepoPage({ repoData, image }: { repoData: DbRepo; image: string }) { + const { data: starStats, error: starError } = useFetchMetricStats({ + repository: repoData.full_name, + variant: "stars", + range: 30, + }); + + const { data: forkStats, error: forkError } = useFetchMetricStats({ + repository: repoData.full_name, + variant: "forks", + range: 30, + }); + return ( @@ -45,6 +59,11 @@ export default function RepoPage({ repoData, image }: { repoData: DbRepo; image:

{repoData.description}

+ +
+ + +
); }