Skip to content

Commit

Permalink
feat: MetricCard prototype (open-sauced#2942)
Browse files Browse the repository at this point in the history
  • Loading branch information
zeucapua authored Mar 28, 2024
1 parent 4f5bfd3 commit 1a57714
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
47 changes: 47 additions & 0 deletions components/Graphs/MetricCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof MetricCard> & { repository: string };
type Story = StoryObj<MetricCardAndRepositoryArgs>;

const meta: Meta<typeof MetricCard> = {
title: "Components/Graphs/MetricCard",
component: MetricCard,
};

export default meta;

export const StarsPerDay: Story = {
args: {
repository: "open-sauced/app",
},
render: ({ repository }) => <StarMetricCard repository={repository} />,
};

export const ForksPerDay: Story = {
args: {
repository: "open-sauced/app",
},
render: ({ repository }) => <ForkMetricCard repository={repository} />,
};

const StarMetricCard = ({ repository }: { repository: string }) => {
const { data: starStats, error: starError } = useFetchMetricStats({
repository,
variant: "stars",
range: 30,
});

return <MetricCard variant="stars" stats={starStats} />;
};

const ForkMetricCard = ({ repository }: { repository: string }) => {
const { data: forkStats, error: forkError } = useFetchMetricStats({
repository,
variant: "forks",
range: 30,
});

return <MetricCard variant="forks" stats={forkStats} />;
};
72 changes: 72 additions & 0 deletions components/Graphs/MetricCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Card className="w-full max-w-sm h-fit p-5 pl-6">
<section className="flex justify-between items-center">
<p className="text-xl font-semibold capitalize">{variant} per day</p>
<Button variant="default" className="!p-1 rounded-full">
<FaEllipsisVertical />
</Button>
</section>

<section className="flex justify-between items-center px-2 gap-8">
<p className="text-5xl font-bold">{humanizeNumber(total || 0, "abbreviation")}</p>
<div className="h-fit w-full pl-6">
<EChartsReact option={option} style={{ height: "100%", width: "100%" }} />
</div>
</section>

<section className="flex items-center gap-2">
<div className="flex items-center gap-1 px-2 py-0.5 border-green-300 border-2 rounded-full bg-green-200">
<FaArrowUp className="text-green-800" />
<p className="text-green-800">10%</p>
</div>
<p>vs. last period</p>
</section>
</Card>
);
}
41 changes: 41 additions & 0 deletions lib/hooks/api/useFetchMetricStats.ts
Original file line number Diff line number Diff line change
@@ -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<StatsType[], Error>(
endpoint,
publicApiFetcher as Fetcher<StatsType[], Error>
);

return {
data,
error,
isLoading,
mutate,
};
}
19 changes: 19 additions & 0 deletions pages/s/[org]/[repo]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: "" };
Expand Down Expand Up @@ -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 (
<ProfileLayout>
<SEO title={`${repoData.full_name} - OpenSauced Insights`} />
Expand All @@ -45,6 +59,11 @@ export default function RepoPage({ repoData, image }: { repoData: DbRepo; image:
<p className="text-xl">{repoData.description}</p>
</div>
</header>

<section className="flex gap-8 w-full justify-center">
<MetricCard variant="stars" stats={starStats} />
<MetricCard variant="forks" stats={forkStats} />
</section>
</ProfileLayout>
);
}

0 comments on commit 1a57714

Please sign in to comment.