new branch with devops sorted

This commit is contained in:
Kumaraguru 2023-09-07 00:43:24 +05:30
commit 09881b1249
50 changed files with 10174 additions and 0 deletions

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

39
.gitignore vendored Normal file
View File

@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.env
.idea/

34
README.md Normal file
View File

@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,17 @@
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET(request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const supabase = createRouteHandlerClient({ cookies });
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(requestUrl.origin);
}

View File

@ -0,0 +1,40 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export async function POST(request: Request) {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{
error: "Unauthenticated!",
},
{ status: 401 }
);
}
const {
id,
}: {
id: number;
} = await request.json();
const db = await initiateDb();
await db.from("benchmarks").delete().eq("id", id);
return NextResponse.json({
data: (
await db
.from("benchmarks")
.select()
.order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,15 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const db = await initiateDb();
return NextResponse.json({
data: (
await db
.from("benchmarks")
.select()
.order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,48 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export async function POST(request: Request) {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{
error: "Unauthenticated!",
},
{ status: 401 }
);
}
const {
id,
name,
value,
}: {
id: number | boolean | null;
name: string;
value: object;
} = await request.json();
const db = await initiateDb();
if (id) {
await db.from("benchmarks").update({ name, value }).eq("id", id);
} else {
await db.from("benchmarks").insert({ name, value });
}
return NextResponse.json({
data: (
await db
.from("benchmarks")
.select()
.order("created_at", { ascending: false })
).data,
});
}

14
app/api/docker/route.js Normal file
View File

@ -0,0 +1,14 @@
import axios from "axios";
import { NextResponse } from "next/server";
export async function GET() {
try {
const response = await axios.get(
`https://hub.docker.com/v2/repositories/statusteam/nim-waku`
);
return NextResponse.json(response.data);
} catch (error) {
throw new Error("Failed to fetch Docker Hub image data");
}
}

View File

@ -0,0 +1,40 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export async function POST(request: Request) {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{
error: "Unauthenticated!",
},
{ status: 401 }
);
}
const {
id,
}: {
id: number;
} = await request.json();
const db = await initiateDb();
await db.from("ecosystem").delete().eq("id", id);
return NextResponse.json({
data: (
await db
.from("ecosystem")
.select()
.order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,15 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const db = await initiateDb();
return NextResponse.json({
data: (
await db
.from("ecosystem")
.select()
.order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,56 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export async function POST(request) {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{
error: "Unauthenticated!",
},
{ status: 401 }
);
}
const db = await initiateDb();
const body = await request.formData();
let data = {};
body.forEach((field, key) => (data[key] = field));
const { id, name, description, url, image } = data;
if (id !== "false") {
await db.from("ecosystem").update({ name, description, url }).eq("id", id);
if (image) {
await db.storage.from("public").update(`public/${id}.png`, image);
}
} else {
const { data } = await db
.from("ecosystem")
.upsert({ name, description, url })
.select();
if (image) {
await db.storage.from("public").upload(`public/${data[0].id}.png`, image);
}
}
return NextResponse.json({
data: (
await db
.from("ecosystem")
.select()
.order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,12 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const db = await initiateDb();
return NextResponse.json({
data: (
await db.from("saves").select().order("created_at", { ascending: false })
).data,
});
}

View File

@ -0,0 +1,33 @@
import initiateDb from "@/utils/db";
import { NextResponse } from "next/server";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
export async function POST(request: Request) {
const supabase = createRouteHandlerClient({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
return NextResponse.json(
{
error: "Unauthenticated!",
},
{ status: 401 }
);
}
const { name, data }: { name: string; data: object } = await request.json();
const db = await initiateDb();
await db.from("saves").insert({ name, data });
return NextResponse.json({
data: (
await db.from("saves").select().order("created_at", { ascending: false })
).data,
});
}

27
app/api/twitter/route.js Normal file
View File

@ -0,0 +1,27 @@
import axios from "axios";
import addOAuthInterceptor from "axios-oauth-1.0a";
import { NextResponse } from "next/server";
export async function GET() {
const client = axios.create();
const options = {
algorithm: "HMAC-SHA1",
key: process.env.TWITTER_CONSUMER_KEY,
secret: process.env.TWITTER_CONSUMER_SECRET,
token: process.env.TWITTER_ACCESS_TOKEN,
tokenSecret: process.env.TWITTER_TOKEN_SECRET,
};
addOAuthInterceptor(client, options);
const { data } = await client.get(
"https://api.twitter.com/2/users/me?user.fields=public_metrics",
{
headers: {
Accept: "application/json",
},
}
);
return NextResponse.json(data.data.public_metrics);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

4
app/globals.css Normal file
View File

@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

17
app/layout.js Normal file
View File

@ -0,0 +1,17 @@
import './globals.css'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Waku Metrics',
description: 'Metrics dashboard for the waku network and ecosystem',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}

10
app/page.js Normal file
View File

@ -0,0 +1,10 @@
import Router from '@/components/Router'
import Image from 'next/image'
export default function Home() {
return (
<main className='bg-black w-full min-h-screen'>
<Router />
</main>
)
}

216
components/Benchmarks.js Normal file
View File

@ -0,0 +1,216 @@
import React, { useEffect, useState } from "react";
import "react-responsive-modal/styles.css";
import { Modal } from "react-responsive-modal";
import Loader from "@/components/atoms/Loader";
import axios from "axios";
import initiateDb from "@/utils/dbPublic";
export default function Benchmarks(props) {
const [open, setOpen] = useState(false);
const onOpenModal = () => setOpen(true);
const onCloseModal = () => {
setCurrentBenchmark(false);
setName(null);
setValue(null);
setOpen(false);
};
const [benchmarks, setBenchmarks] = useState(false);
const [currentBenchmark, setCurrentBenchmark] = useState(false);
const [name, setName] = useState(null);
const [value, setValue] = useState(null);
useEffect(() => {
(async () => {
const supabase = initiateDb();
const data = await supabase
.from("benchmarks")
.select()
.order("created_at", { ascending: false });
setBenchmarks(data.data);
})();
}, []);
async function handleSet(e) {
e.preventDefault();
const res = await axios.post("/api/benchmarks/set", {
id: currentBenchmark ? currentBenchmark.id : false,
name,
value,
});
setBenchmarks(res.data.data);
onCloseModal();
}
async function handleDelete(benchmark) {
if (confirm("Are you sure to delete the benchmark?")) {
const res = await axios.post("/api/benchmarks/delete", {
id: benchmark.id,
});
setBenchmarks(res.data.data);
}
}
const closeIcon = (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="m7 7l10 10M7 17L17 7"
/>
</svg>
);
const customStyles = {
content: {
borderRadius: "20px",
backgroundColor: "black",
bgColor: "black",
},
};
if (benchmarks === false) {
return <Loader />;
}
return (
<div className="">
<div className="flex justify-between items-center">
<h1 className="text-2xl">Benchmarks</h1>
<h1 className="text-[#707071] hidden md:block">Powered by Waku</h1>
</div>
<Modal
style={customStyles}
classNames="bg-black"
closeIcon={closeIcon}
open={open}
onClose={onCloseModal}
center
>
<div className="rounded-xl w-40 h-40 ">
<div className="m-4">
<h1 className="text-xl">
{currentBenchmark ? "Edit" : "Add new"} benchmark
</h1>
<div className="mt-4">
<form onSubmit={(e) => handleSet(e)} className="space-y-3">
<input
className="border-[#2c2c2c3e] border-2 w-full rounde -lg p-2 text-sm border-2 rounded-lg"
placeholder="Label"
value={name}
required={true}
onChange={(e) => setName(e.target.value)}
/>
<br />
<input
className="border-[#2c2c2c3e] border-2 w-full rounde -lg p-2 text-sm border-2 rounded-lg"
placeholder="Value"
required={true}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<br />
<div className="flex justify-end">
<button className="bg-black rounded-lg text-white p-2">
{currentBenchmark ? "Update benchmark" : "Add benchmark"}
</button>
</div>
</form>
</div>
</div>
</div>
</Modal>
{props.isLoggedIn && (
<div className="flex justify-end mt-10 space-x-5">
<button
onClick={onOpenModal}
className="flex space-x-3 items-center bg-[#202021] p-2 rounded-xl text-white hover:border-white hover:border-2"
>
<h1>Add benchmark</h1>
<svg
className="h-5 w-5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z"
/>
</svg>
</button>
</div>
)}
{benchmarks.length ? (
<div className="flow-root rounded-lg border border-gray-100 py-3 shadow-sm dark:border-gray-700 mt-10">
<dl className="-my-3 divide-y divide-gray-100 text-sm dark:divide-gray-700">
{benchmarks.map((benchmark, index) => (
<div
key={index}
className="grid grid-cols-1 gap-1 p-3 even:bg-gray-50 even:dark:bg-[#202021] sm:grid-cols-5 sm:gap-4"
>
<dt className="font-medium text-gray-900 dark:text-white">
{benchmark.name}
</dt>
<dd className="text-gray-700 dark:text-gray-200 sm:col-span-2">
{benchmark.value}
</dd>
<dd className="sm:col-span-2">
{props.isLoggedIn && (
<div className="flex space-x-2 items-center justify-end">
<svg
onClick={() => {
setCurrentBenchmark(benchmark);
setName(benchmark.name);
setValue(benchmark.value);
onOpenModal();
}}
className="w-7 h-7 rounded-xl p-1 hover:bg-yellow-200"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#FFF"
d="M5 19h1.4l8.625-8.625l-1.4-1.4L5 17.6V19ZM19.3 8.925l-4.25-4.2l1.4-1.4q.575-.575 1.413-.575t1.412.575l1.4 1.4q.575.575.6 1.388t-.55 1.387L19.3 8.925ZM17.85 10.4L7.25 21H3v-4.25l10.6-10.6l4.25 4.25Zm-3.525-.725l-.7-.7l1.4 1.4l-.7-.7Z"
/>
</svg>
<svg
className="w-7 h-7 rounded-xl p-1 hover:bg-red-200"
viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg"
onClick={() => handleDelete(benchmark)}
>
<path
fill="#FFF"
d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16ZM96 40a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8v8H96Zm96 168H64V64h128Zm-80-104v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Z"
/>
</svg>
</div>
)}
</dd>
</div>
))}
</dl>
</div>
) : (
<div>No benchmarks yet!</div>
)}
</div>
);
}

View File

@ -0,0 +1,595 @@
import React, { useEffect, useState } from "react";
import CommunityMetricsCard from "@/components/molecules/CommunityMetricsCard";
export default function CommunityMetrics(props) {
return (
<div className="">
<div className="flex justify-between items-center">
<h1 className="text-2xl">
{props.save !== undefined ? props.save.name : "Community metrics"}
</h1>
<h1 className="text-[#707071] hidden md:block">Powered by Waku</h1>
</div>
{props.setCurrent !== undefined && (
<div className={"flex row mt-2 justify-start"}>
<button
className={"bg-gray-400 p-2 rounded"}
onClick={() => props.setCurrent(null)}
>
Back
</button>
</div>
)}
<div className="space-y-5 mt-5 justify-between">
<h1 className="text-[#707071]">Community channels </h1>
<CommunityMetricsCard
title={"Discord members"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.discord}
previous={props.previous?.data?.stats?.discord}
/>
<CommunityMetricsCard
title={"X (Twitter) followers"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.twitter}
previous={props.previous?.data?.stats?.twitter}
/>
<h1 className="text-[#707071]">GitHub</h1>
<CommunityMetricsCard
title={"Total GitHub followers (organization wide)"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github}
previous={props.previous?.data?.stats?.github}
/>
<CommunityMetricsCard
title={"Total GitHub stars (organization wide)"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.total?.stars}
previous={props.previous?.data?.stats?.github_repos?.total?.stars}
/>
<CommunityMetricsCard
title={"Total GitHub forks (organization wide)"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.total?.forks}
previous={props.previous?.data?.stats?.github_repos?.total?.forks}
/>
<CommunityMetricsCard
title={"Total GitHub stars (nwaku)"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.nwaku?.stars}
previous={props.previous?.data?.stats?.github_repos?.nwaku?.stars}
/>
<CommunityMetricsCard
title={"Total GitHub forks (nwaku)"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.nwaku?.forks}
previous={props.previous?.data?.stats?.github_repos?.nwaku?.forks}
/>
<h1 className="text-[#707071]">SDKs</h1>
<CommunityMetricsCard
title={"NPM downloads"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.npm}
previous={props.previous?.data?.stats?.npm}
/>
<CommunityMetricsCard
title={"js-waku stars"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku"]?.stars}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku"]?.stars
}
/>
<CommunityMetricsCard
title={"js-waku forks"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku"]?.forks}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku"]?.forks
}
/>
<CommunityMetricsCard
title={"js-waku open issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku"]?.open_issues}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku"]?.open_issues
}
/>
<CommunityMetricsCard
title={"Rust downloads"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.rust}
previous={props.previous?.data?.stats?.rust}
/>
<CommunityMetricsCard
title={"waku-rust-bindings stars"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["waku-rust-bindings"]?.stars}
previous={
props.previous?.data?.stats?.github_repos?.["waku-rust-bindings"]
?.stars
}
/>
<CommunityMetricsCard
title={"waku-rust-bindings forks"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["waku-rust-bindings"]?.forks}
previous={
props.previous?.data?.stats?.github_repos?.["waku-rust-bindings"]
?.forks
}
/>
<CommunityMetricsCard
title={"waku-rust-bindings open issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={
props.stats?.github_repos?.["waku-rust-bindings"]?.open_issues
}
previous={
props.previous?.data?.stats?.github_repos?.["waku-rust-bindings"]
?.open_issues
}
/>
<CommunityMetricsCard
title={"go-waku stars"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["go-waku"]?.stars}
previous={
props.previous?.data?.stats?.github_repos?.["go-waku"]?.stars
}
/>
<CommunityMetricsCard
title={"go-waku forks"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["go-waku"]?.forks}
previous={
props.previous?.data?.stats?.github_repos?.["go-waku"]?.forks
}
/>
<CommunityMetricsCard
title={"go-waku open issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["go-waku"]?.open_issues}
previous={
props.previous?.data?.stats?.github_repos?.["go-waku"]?.open_issues
}
/>
<CommunityMetricsCard
title={"waku-react-native stars"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["waku-react-native"]?.stars}
previous={
props.previous?.data?.stats?.github_repos?.["waku-react-native"]
?.stars
}
/>
<CommunityMetricsCard
title={"waku-react-native forks"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["waku-react-native"]?.forks}
previous={
props.previous?.data?.stats?.github_repos?.["waku-react-native"]
?.forks
}
/>
<CommunityMetricsCard
title={"waku-react-native open issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={
props.stats?.github_repos?.["waku-react-native"]?.open_issues
}
previous={
props.previous?.data?.stats?.github_repos?.["waku-react-native"]
?.open_issues
}
/>
<CommunityMetricsCard
title={"js-waku-examples stars"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku-examples"]?.stars}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku-examples"]
?.stars
}
/>
<CommunityMetricsCard
title={"js-waku-examples forks"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku-examples"]?.forks}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku-examples"]
?.forks
}
/>
<CommunityMetricsCard
title={"js-waku-examples open issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.github_repos?.["js-waku-examples"]?.open_issues}
previous={
props.previous?.data?.stats?.github_repos?.["js-waku-examples"]
?.open_issues
}
/>
<CommunityMetricsCard
title={"nwaku docker pulls"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats?.docker}
previous={props.previous?.data?.docker}
/>
</div>
</div>
);
}

262
components/Ecosystem.js Normal file
View File

@ -0,0 +1,262 @@
import React, { useState } from "react";
import "react-responsive-modal/styles.css";
import { Modal } from "react-responsive-modal";
import { useEffect } from "react";
import axios from "axios";
import Loader from "@/components/atoms/Loader";
import initiateDb from "@/utils/dbPublic";
export default function Ecosystem(props) {
const [open, setOpen] = useState(false);
const onOpenModal = () => setOpen(true);
const onCloseModal = () => {
setCurrent(false);
setImage(null);
setName(null);
setDescription(null);
setUrl(null);
setOpen(false);
};
const [ecosystem, setEcosystem] = useState(false);
const [current, setCurrent] = useState(false);
const [image, setImage] = useState(null);
const [name, setName] = useState(null);
const [description, setDescription] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
(async () => {
const supabase = initiateDb();
const data = await supabase
.from("ecosystem")
.select()
.order("created_at", { ascending: false });
setEcosystem(data.data);
})();
}, []);
async function handleSet(e) {
e.preventDefault();
const data = new FormData();
data.append("id", current ? current.id : false);
if (image !== null) {
data.append("image", image);
}
data.append("name", name);
data.append("description", description);
data.append("url", url);
const res = await axios.post("/api/ecosystem/set", data);
setEcosystem(res.data.data);
onCloseModal();
}
async function handleDelete(project) {
if (confirm("Are you sure to delete the project?")) {
const res = await axios.post("/api/ecosystem/delete", {
id: project.id,
});
setEcosystem(res.data.data);
}
}
const closeIcon = (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="m7 7l10 10M7 17L17 7"
/>
</svg>
);
const customStyles = {
content: {
borderRadius: "20px",
backgroundColor: "black",
bgColor: "black",
},
};
if (ecosystem === false) {
return <Loader />;
}
return (
<div className="">
<div className="flex justify-between items-center">
<h1 className="text-2xl">Ecosystem directory</h1>
<h1 className="text-[#707071] hidden md:block">Powered by Waku</h1>
</div>
<Modal
style={customStyles}
classNames="bg-black"
closeIcon={closeIcon}
open={open}
onClose={onCloseModal}
center
>
<div className="rounded-xl w-40 h-40 ">
<div className="m-4">
<h1 className="text-xl">{current ? "Edit" : "Add new"} project</h1>
<div className="mt-4">
<form onSubmit={(e) => handleSet(e)} className="space-y-3">
<label>
<svg
className="w-14 h-14 bg-black rounded-full p-2"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M14 7.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0Zm-1 0a.5.5 0 1 0-1 0a.5.5 0 0 0 1 0ZM3 6a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6Zm3-2a2 2 0 0 0-2 2v8c0 .373.102.722.28 1.02l4.669-4.588a1.5 1.5 0 0 1 2.102 0l4.67 4.588A1.99 1.99 0 0 0 16 14V6a2 2 0 0 0-2-2H6Zm0 12h8c.37 0 .715-.1 1.012-.274l-4.662-4.58a.5.5 0 0 0-.7 0l-4.662 4.58A1.99 1.99 0 0 0 6 16Z"
/>
</svg>
<input
type="file"
required={current === false}
onChange={(e) => setImage(e.target.files[0])}
className="hidden"
name="file1"
accept={".png"}
/>
</label>
<input
className="border-[#2c2c2c3e] border-2 w-full rounde -lg p-2 text-sm border-2 rounded-lg"
placeholder="Project name"
value={name}
required={true}
onChange={(e) => setName(e.target.value)}
/>
<br />
<textarea
className="border-[#2c2c2c3e] align-top w-full h-20 border-2 rounded-lg p-2 text-sm"
placeholder="Project description"
value={description}
required={true}
onChange={(e) => setDescription(e.target.value)}
/>
<br />
<input
className="border-[#2c2c2c3e] border-2 w-full rounde -lg p-2 text-sm border-2 rounded-lg"
placeholder="Project URL"
value={url}
required={true}
onChange={(e) => setUrl(e.target.value)}
/>
<br />
<div className="flex justify-end">
<button className="bg-black rounded-lg text-white p-2">
{current ? "Edit" : "Add"} project
</button>
</div>
</form>
</div>
</div>
</div>
</Modal>
{props.isLoggedIn && (
<div className="flex justify-end mt-10 space-x-5">
<button
onClick={onOpenModal}
className="flex space-x-3 items-center bg-[#202021] p-2 rounded-xl text-white hover:border-white hover:border-2"
>
<h1>Add project</h1>
<svg
className="h-5 w-5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z"
/>
</svg>
</button>
</div>
)}
{ecosystem.length ? (
<div className="mt-10 space-y-10">
{ecosystem.map((project, index) => (
<div
key={index}
className="flex md:w-auto items-end justify-between rounded-lg bg-white p-6 dark:bg-[#202021]"
>
<div className="flex items-center gap-4">
<img
className="rounded-full w-16 h-16 bg-white"
src={`https://wmpoapaaynbjbqtjjtdy.supabase.co/storage/v1/object/public/public/public/${project.id}.png`}
alt={"Project image"}
/>
<div className="space-y-3">
<p className="text-lg text-[#707071] dark:text-white">
{project.name}
</p>
<p className="text-xs font-medium text-gray-900 dark:text-gray-400">
{project.description}
</p>
</div>
</div>
{props.isLoggedIn && (
<div className="flex space-x-2 items-center">
<svg
onClick={() => {
setCurrent(project);
setName(project.name);
setDescription(project.description);
setUrl(project.url);
onOpenModal();
}}
className="w-7 h-7 rounded-xl p-1 hover:bg-yellow-200"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#000000"
d="M5 19h1.4l8.625-8.625l-1.4-1.4L5 17.6V19ZM19.3 8.925l-4.25-4.2l1.4-1.4q.575-.575 1.413-.575t1.412.575l1.4 1.4q.575.575.6 1.388t-.55 1.387L19.3 8.925ZM17.85 10.4L7.25 21H3v-4.25l10.6-10.6l4.25 4.25Zm-3.525-.725l-.7-.7l1.4 1.4l-.7-.7Z"
/>
</svg>
<svg
className="w-7 h-7 rounded-xl p-1 hover:bg-red-200"
viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg"
onClick={() => handleDelete(project)}
>
<path
fill="#000000"
d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16ZM96 40a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8v8H96Zm96 168H64V64h128Zm-80-104v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Z"
/>
</svg>
</div>
)}
</div>
))}
</div>
) : (
<div>No projects added yet!</div>
)}
</div>
);
}

View File

@ -0,0 +1,17 @@
import React from 'react'
export default function NetworkMetrics() {
return (
<div className=''>
<div className='flex justify-between items-center'>
<h1 className='text-2xl'>Network metrics</h1>
<h1 className='text-[#707071] hidden md:block'>Powered by Waku</h1>
</div>
<div className="grid mt-60 px-4 place-content-center">
<h1 className='text-[#707071] hidden md:block'>Work in progress</h1>
</div>
</div>
)
}

199
components/Overview.js Normal file
View File

@ -0,0 +1,199 @@
import React from "react";
import OverviewCard from "@/components/molecules/OverviewCard";
export default function Overview(props) {
return (
<div className="">
<div className="flex justify-between items-center">
<h1 className="text-2xl">Overview</h1>
<h1 className="text-[#707071] hidden md:block">Powered by Waku</h1>
</div>
<div className="md:flex flex-wrap md:space-x-5 space-y-5 md:space-y-0 mt-10 justify-between">
<OverviewCard
title={"Discord members"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats.discord}
previous={props.previous?.data?.stats?.discord}
/>
<OverviewCard
title={"X (Twitter) followers"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats.twitter}
previous={props.previous?.data?.stats?.twitter}
/>
<OverviewCard
title={"Github followers"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats.github}
previous={props.previous?.data?.stats?.github}
/>
<OverviewCard
title={"Github Open Issues"}
icon={
<span className="hidden rounded-full bg-gray-100 p-2 text-gray-600 dark:bg-black dark:text-gray-300 sm:block">
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"
/>
</svg>
</span>
}
isLoading={props.isLoading}
current={props.stats.github_repos?.total?.open_issues}
previous={
props.previous?.data?.stats?.github_repos?.total?.open_issues
}
/>
</div>
<div className="md:flex justify-between md:space-x-10 mt-10 space-y-10 md:space-y-0">
<div
id="communitymetricsbox"
className="md:w-1/2 border-[#202021] hover:border-white border-4 rounded-lg p-6"
>
<div className="flex justify-between items-center">
<h1 className="text-xl">Community metrics</h1>
<svg
className="w-10 h-10"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M3 3a2 2 0 1 1 4 0a2 2 0 0 1-4 0Zm2-1a1 1 0 1 0 0 2a1 1 0 0 0 0-2Zm4.779 2.584a2 2 0 1 1 2.442-3.168A2 2 0 0 1 9.78 4.584ZM11 2a1 1 0 1 0 0 2a1 1 0 0 0 0-2ZM2.5 6h2.67c-.11.313-.17.65-.17 1H2.5a.5.5 0 0 0-.5.5c0 .817.325 1.423.838 1.835c.236.19.519.343.839.455a2.5 2.5 0 0 0-.532.868a3.733 3.733 0 0 1-.933-.543C1.46 9.51 1 8.616 1 7.5A1.5 1.5 0 0 1 2.5 6Zm3.768 0a2 2 0 1 0 3.466 2a2 2 0 0 0-3.466-2Zm1.508.025A1.003 1.003 0 0 1 9 7a1 1 0 1 1-1.224-.975Zm5.386 3.31c-.236.19-.519.343-.839.455a2.5 2.5 0 0 1 .531.868c.34-.139.655-.32.934-.543C14.54 9.51 15 8.616 15 7.5A1.5 1.5 0 0 0 13.5 6h-2.67c.11.313.17.65.17 1h2.5a.5.5 0 0 1 .5.5c0 .817-.325 1.423-.838 1.835ZM10.5 10a1.5 1.5 0 0 1 1.5 1.5c0 1.116-.459 2.01-1.212 2.615C10.047 14.71 9.053 15 8 15c-1.053 0-2.047-.29-2.788-.885C4.46 13.51 4 12.616 4 11.5A1.496 1.496 0 0 1 5.5 10h5Zm0 1h-5a.5.5 0 0 0-.5.5c0 .817.325 1.423.838 1.835C6.364 13.757 7.12 14 8 14c.88 0 1.636-.243 2.162-.665c.513-.412.838-1.018.838-1.835a.5.5 0 0 0-.5-.5Z"
/>
</svg>
</div>
<div className="text-xs mt-4 text-[#737378]">
Community metrics include data related to adoption, user base,
social audience and outreach.
</div>
<div className="flex space-x-3 mt-5">
<span className="inline-flex items-center justify-center rounded-full bg-[#737378] px-2.5 py-0.5 text-[#000000]">
<p className="whitespace-nowrap text-xs">Beta</p>
</span>
</div>
</div>
<div
id="networkmetricsbox"
className="md:w-1/2 border-[#202021] hover:border-white border-4 rounded-lg p-6"
>
<div className="flex justify-between items-center">
<h1 className="text-xl">Network metrics</h1>
<svg
className="w-10 h-10"
viewBox="0 0 2048 2048"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M1836 517q42 87 63 184t21 195q0 131-36 252t-102 227t-162 190t-212 141v86H768v128h256v128H384v-128h256v-128H0V896h128q0-124 32-238t90-214t140-181t181-140t214-91t239-32q130 0 252 36t227 102t190 162t141 212h2v5zm-147-5q-73-127-187-217t-254-134q48 78 77 169t47 182h317zm103 384q0-132-44-256h-356q16 128 16 255t0 257h340q44-124 44-256zm-512 0q0-65-4-128t-12-128H784q-8 64-12 127t-4 129h512zm-256-763q-29 0-55 21t-49 56t-41 78t-33 85t-25 79t-15 60h436q-5-23-15-60t-24-79t-33-85t-41-77t-49-56t-56-22zm-224 28q-140 43-254 133T359 512h317q17-90 46-181t78-170zM300 640q-44 124-44 256h384q0-65 4-128t12-128H300zM128 1664h1152v-640H128v640zm1280-103q88-51 159-122t123-159h-282v281z"
/>
</svg>
</div>
<div className="text-xs mt-4 text-[#737378]">
Network metrics include data related to Waku node delivery and network
</div>
<div className="flex space-x-3 mt-5">
<span className="inline-flex items-center justify-center rounded-full bg-[#737378] px-2.5 py-0.5 text-[#000000]">
<p className="whitespace-nowrap text-xs">Work in progress</p>
</span>
</div>
</div>
</div>
<div
id="docs"
className="mt-10 border-[#202021] hover:border-white border-4 rounded-lg p-6"
>
<div className="flex justify-between items-center">
<h1 className="text-xl">Documentation</h1>
<svg
className="w-10 h-10"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="none"
stroke="#ffffff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2ZM7 7h10M7 12h10M7 17h6"
/>
</svg>
</div>
</div>
<div
id="propose-a-metric"
className="mt-10 border-[#202021] hover:border-white border-4 rounded-lg p-6"
>
<div className="flex justify-between items-center">
<h1 className="text-xl">Propose a new metric</h1>
<svg
className="w-10 h-10"
viewBox="0 0 26 26"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M12.906-.031a1 1 0 0 0-.125.031A1 1 0 0 0 12 1v1H3a3 3 0 0 0-3 3v13c0 1.656 1.344 3 3 3h9v.375l-5.438 2.719a1.006 1.006 0 0 0 .875 1.812L12 23.625V24a1 1 0 1 0 2 0v-.375l4.563 2.281a1.006 1.006 0 0 0 .875-1.812L14 21.375V21h9c1.656 0 3-1.344 3-3V5a3 3 0 0 0-3-3h-9V1a1 1 0 0 0-1.094-1.031zM2 5h22v13H2V5zm18.875 1a1 1 0 0 0-.594.281L17 9.563L14.719 7.28a1 1 0 0 0-1.594.219l-2.969 5.188l-1.219-3.063a1 1 0 0 0-1.656-.344l-3 3a1.016 1.016 0 1 0 1.439 1.44l1.906-1.906l1.438 3.562a1 1 0 0 0 1.812.125l3.344-5.844l2.062 2.063a1 1 0 0 0 1.438 0l4-4A1 1 0 0 0 20.875 6z"
/>
</svg>
</div>
</div>
</div>
);
}

637
components/Router.js Normal file
View File

@ -0,0 +1,637 @@
"use client"; //
import React from "react";
import { useState } from "react";
import Benchmarks from "./Benchmarks";
import CommunityMetrics from "./CommunityMetrics";
import Ecosystem from "./Ecosystem";
import NetworkMetrics from "./NetworkMetrics";
import Overview from "./Overview";
import Timeline from "./Timeline";
import { useEffect } from "react";
import Npm from "@/utils/npm";
import Discord from "@/utils/discord";
import Github from "@/utils/github";
import Twitter from "@/utils/twitter";
import Rust from "@/utils/rust_package";
import Docker from "@/utils/docker";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";
export default function Router() {
const [mobileMenu, setMobileMenu] = useState(false);
const [screen, setScreen] = useState(0);
const [session, setSession] = useState(false);
const [stats, setStats] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [overviewLoading, setOverviewLoading] = useState(true);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [saves, setSaves] = useState({});
const supabase = createClientComponentClient();
supabase.auth.onAuthStateChange((event, session) => {
if (event === "SIGNED_OUT") {
setSession(false);
}
});
useEffect(() => {
(async () => {
if (isLoading) {
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
setSession(false);
} else {
setSession(session);
setIsLoggedIn(true);
}
const discord = await Discord.getServerMembers("j5pGbn7MHZ");
const github = await Github.getOrganizationFollowers("waku-org");
const repos = [
"nwaku",
"js-waku",
"go-waku",
"waku-react-native",
"js-waku-examples",
"waku-rust-bindings",
];
const github_repos = await Github.getReposStats("waku-org", repos);
// const twitter = await Twitter.getFollowers();
const twitter = 2;
setOverviewLoading(false);
setStats({
twitter,
discord,
github,
github_repos,
});
await (async () => {
const npm = await Npm.getDownloadsLastWeek("@waku/core");
// console.log(Golang.getDownloads("waku-org/go-waku@v0.5.2/waku"));
const rust = await Rust.getDownloads("waku-bindings");
const docker = await Docker.getPulls();
const data = await supabase
.from("saves")
.select()
.order("created_at", { ascending: false });
setSaves(data.data);
setStats({
...stats,
npm,
discord,
github,
github_repos,
rust,
twitter,
docker,
});
setIsLoading(false);
})();
}
})();
}, []);
const router = useRouter();
const handleSignIn = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
queryParams: {
access_type: "offline",
prompt: "consent",
},
},
});
};
const handleSignOut = async () => {
await supabase.auth.signOut();
router.push("/");
};
return (
<div className="text-white flex justify-between">
{mobileMenu === true ? (
<div
id="mobile-nav"
className="flex h-screen flex-col justify-between bg-[#202021] w-full md:w-auto md:hidden"
>
<div className="px-4 py-6">
<div className="flex justify-between">
<img
src="https://waku.org/theme/image/logo.svg"
className="w-20"
/>
<button
onClick={() => setMobileMenu(false)}
className="md:hidden"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M18.3 5.71a.996.996 0 0 0-1.41 0L12 10.59L7.11 5.7A.996.996 0 1 0 5.7 7.11L10.59 12L5.7 16.89a.996.996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996.996 0 1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z"
/>
</svg>
</button>
</div>
<ul className="mt-6 space-y-1">
<li>
<div
onClick={() => {
setScreen(0);
setMobileMenu(false);
}}
className={
screen === 0
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Overview
</div>
</li>
<li>
<details className="group [&_summary::-webkit-details-marker]:hidden">
<summary className="flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700">
<span className="text-sm font-medium">Metrics</span>
<span className="shrink-0 transition duration-300 group-open:-rotate-180">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</span>
</summary>
<ul className="mt-2 space-y-1 px-4">
<li>
<div
onClick={() => {
setScreen(1);
setMobileMenu(false);
}}
className={
screen === 1
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Community
</div>
</li>
<li>
<div
onClick={() => {
setScreen(2);
setMobileMenu(false);
}}
className={
screen === 2
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Network
</div>
</li>
</ul>
</details>
</li>
<li>
<details className="group [&_summary::-webkit-details-marker]:hidden">
<summary className="flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700">
<span className="text-sm font-medium"> Growth </span>
<span className="shrink-0 transition duration-300 group-open:-rotate-180">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</span>
</summary>
<ul className="mt-2 space-y-1 px-4">
<li>
<div
onClick={() => {
setScreen(3);
setMobileMenu(false);
}}
className={
screen === 3
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Timeline
</div>
</li>
<li>
<div
onClick={() => {
setScreen(4);
setMobileMenu(false);
}}
className={
screen === 4
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Ecosystem
</div>
</li>
</ul>
</details>
</li>
<li>
<div
onClick={() => {
setScreen(5);
setMobileMenu(false);
}}
className={
screen === 5
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Benchmarks
</div>
</li>
</ul>
</div>
<div className="sticky inset-x-0 bottom-0 border-x-4 border-[#202021] mb-1">
<div
onClick={() => {
session === false ? handleSignIn() : handleSignOut();
}}
className="flex space-x-3 items-center gap-2 bg-[#000000] p-4 hover:bg-gray-50 rounded-lg"
>
<div className="h-10 w-10 flex items-center justify-center bg-[#202021] rounded-full object-cover">
{session && session?.user?.user_metadata?.avatar_url ? (
<img src={session?.user?.user_metadata?.avatar_url} />
) : (
<svg
width="20"
height="20"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M8 8a3 3 0 1 0 0-6a3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0a2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1s1-4 6-4s6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664h10Z"
/>
</svg>
)}
</div>
<div>
<p className="text-xs">
<strong className="block font-medium">
{session === false
? "Login as core contributor"
: "Sign Out"}
</strong>
</p>
</div>
</div>
</div>
</div>
) : (
<div className="block md:hidden w-full">
<div
id="mobile-header"
className="flex justify-between w-full px-4 py-6"
>
<img src="https://waku.org/theme/image/logo.svg" className="w-20" />
<button onClick={() => setMobileMenu(true)} className="md:hidden">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M4 18h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1zm0-5h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1zM3 7c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1z"
/>
</svg>
</button>
</div>
<div className="p-5 w-full">
{(screen === 0 && (
<Overview
stats={stats}
isLoading={overviewLoading}
previous={Object.values(saves)[Object.keys(saves).length - 1]}
/>
)) ||
(screen === 1 && (
<CommunityMetrics
stats={stats}
isLoading={isLoading}
previous={
Object.keys(saves).length
? Object.values(saves)[Object.keys(saves).length - 1]
: {}
}
/>
)) ||
(screen === 2 && <NetworkMetrics />) ||
(screen === 3 && (
<Timeline
stats={stats}
saves={saves}
setSaves={setSaves}
isLoading={isLoading}
isLoggedIn={isLoggedIn}
/>
)) ||
(screen === 4 && <Ecosystem isLoggedIn={isLoggedIn} />) ||
(screen === 5 && <Benchmarks isLoggedIn={isLoggedIn} />)}
</div>
</div>
)}
<div className="hidden md:flex w-full">
<div
id="desktop-nav"
className="flex sticky top-0 h-screen flex-col justify-between bg-[#202021] w-full md:w-1/6 "
>
<div className="px-4 py-6">
<div className="flex justify-between">
<img
src="https://waku.org/theme/image/logo.svg"
className="w-20"
/>
<button
onClick={() => setMobileMenu(false)}
className="md:hidden"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M18.3 5.71a.996.996 0 0 0-1.41 0L12 10.59L7.11 5.7A.996.996 0 1 0 5.7 7.11L10.59 12L5.7 16.89a.996.996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996.996 0 1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z"
/>
</svg>
</button>
</div>
<ul className="mt-6 space-y-1">
<li>
<div
onClick={() => setScreen(0)}
className={
screen === 0
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Overview
</div>
</li>
<li>
<details className="group [&_summary::-webkit-details-marker]:hidden">
<summary className="flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700">
<span className="text-sm font-medium">Metrics</span>
<span className="shrink-0 transition duration-300 group-open:-rotate-180">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</span>
</summary>
<ul className="mt-2 space-y-1 px-4">
<li>
<div
onClick={() => setScreen(1)}
className={
screen === 1
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Community
</div>
</li>
<li>
<div
onClick={() => setScreen(2)}
className={
screen === 2
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Network
</div>
</li>
</ul>
</details>
</li>
<li>
<details className="group [&_summary::-webkit-details-marker]:hidden">
<summary className="flex cursor-pointer items-center justify-between rounded-lg px-4 py-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700">
<span className="text-sm font-medium"> Growth </span>
<span className="shrink-0 transition duration-300 group-open:-rotate-180">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</span>
</summary>
<ul className="mt-2 space-y-1 px-4">
<li>
<div
onClick={() => setScreen(3)}
className={
screen === 3
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Timeline
</div>
</li>
<li>
<div
onClick={() => setScreen(4)}
className={
screen === 4
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Ecosystem
</div>
</li>
</ul>
</details>
</li>
<li>
<div
onClick={() => setScreen(5)}
className={
screen === 5
? "block rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700"
: "block rounded-lg px-4 py-2 text-sm font-medium text-gray-500 hover:bg-black hover:text-white"
}
>
Benchmarks
</div>
</li>
</ul>
</div>
<div className="sticky inset-x-0 bottom-0 border-x-4 border-[#202021] mb-1">
<div
onClick={() => {
session === false ? handleSignIn() : handleSignOut();
}}
className="flex space-x-3 items-center gap-2 bg-[#000000] p-4 hover:bg-gray-50 rounded-lg"
>
<div className="h-10 w-10 flex items-center justify-center bg-[#202021] rounded-full object-cover">
{session && session?.user?.user_metadata?.avatar_url ? (
<img src={session?.user?.user_metadata?.avatar_url} />
) : (
<svg
width="20"
height="20"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M8 8a3 3 0 1 0 0-6a3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0a2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1s1-4 6-4s6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664h10Z"
/>
</svg>
)}
</div>
<div>
<p className="text-xs">
<strong className="block font-medium">
{session === false
? "Login as core contributor"
: "Sign Out"}
</strong>
</p>
</div>
</div>
</div>
</div>
<div className="p-10 w-full">
{(screen === 0 && (
<Overview
stats={stats}
isLoading={overviewLoading}
previous={
Object.keys(saves).length
? Object.values(saves)[Object.keys(saves).length - 1]
: {}
}
/>
)) ||
(screen === 1 && (
<CommunityMetrics
stats={stats}
isLoading={isLoading}
previous={
Object.keys(saves).length
? Object.values(saves)[Object.keys(saves).length - 1]
: {}
}
/>
)) ||
(screen === 2 && <NetworkMetrics />) ||
(screen === 3 && (
<Timeline
stats={stats}
saves={saves}
setSaves={setSaves}
isLoading={isLoading}
isLoggedIn={isLoggedIn}
/>
)) ||
(screen === 4 && <Ecosystem isLoggedIn={isLoggedIn} />) ||
(screen === 5 && <Benchmarks isLoggedIn={isLoggedIn} />)}
</div>
</div>
</div>
);
}

181
components/Timeline.js Normal file
View File

@ -0,0 +1,181 @@
import React, { useState } from "react";
import "react-responsive-modal/styles.css";
import { Modal } from "react-responsive-modal";
import axios from "axios";
import CommunityMetrics from "@/components/CommunityMetrics";
import Loader from "@/components/atoms/Loader";
export default function Timeline(props) {
const [open, setOpen] = useState(false);
const onOpenModal = () => setOpen(true);
const onCloseModal = () => setOpen(false);
const [current, setCurrent] = useState(null);
const [previous, setPrevious] = useState(null);
const [name, setName] = useState("");
async function handleNewSave(e) {
e.preventDefault();
const res = await axios.post("/api/saves/set", {
name,
data: {
stats: props.stats,
},
});
props.setSaves(res.data.data);
setName(null);
onCloseModal();
}
const closeIcon = (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="none"
stroke="#000000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="m7 7l10 10M7 17L17 7"
/>
</svg>
);
const customStyles = {
content: {
borderRadius: "20px",
backgroundColor: "black",
bgColor: "black",
},
};
if (props.isLoading) {
return <Loader />;
}
if (current !== null) {
return (
<CommunityMetrics
stats={props.saves[current]?.data?.stats}
isLoading={false}
previous={previous ? props.saves[previous]?.data?.stats : {}}
setCurrent={setCurrent}
save={props.saves[current]}
/>
);
}
return (
<div className="">
<div className="flex justify-between items-center">
<h1 className="text-2xl">Timeline</h1>
<h1 className="text-[#707071] hidden md:block">Powered by Waku</h1>
</div>
<Modal
style={customStyles}
className="bg-black"
closeIcon={closeIcon}
open={open}
onClose={onCloseModal}
center
>
<div className="rounded-xl w-40 h-40 ">
<div className="m-4">
<h1 className="text-xl">Snapshot</h1>
<div className="mt-4">
<form onSubmit={(e) => handleNewSave(e)} className="space-y-3">
<input
className="border-[#2c2c2c3e] border-2 w-full rounde -lg p-2 text-sm border-2 rounded-lg"
placeholder="Name"
value={name}
required={true}
onChange={(e) => setName(e.target.value)}
/>
<br />
<div className="flex justify-end">
<button className="bg-black rounded-lg text-white p-2">
Create snapshot
</button>
</div>
</form>
</div>
</div>
</div>
</Modal>
{props.isLoggedIn && (
<div className="flex justify-end mt-10 space-x-5">
<button
onClick={onOpenModal}
className="flex space-x-3 items-center bg-[#202021] p-2 rounded-xl text-white hover:border-white hover:border-2"
>
<h1>Create snapshot</h1>
<svg
className="h-5 w-5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#ffffff"
d="M18 13h-5v5c0 .55-.45 1-1 1s-1-.45-1-1v-5H6c-.55 0-1-.45-1-1s.45-1 1-1h5V6c0-.55.45-1 1-1s1 .45 1 1v5h5c.55 0 1 .45 1 1s-.45 1-1 1z"
/>
</svg>
</button>
</div>
)}
{props.saves?.length ? (
<div className="overflow-x-auto mt-10 rounded-lg">
<table className="min-w-full divide-y-2 divide-black bg-white text-sm dark:divide-black dark:bg-[#202021]">
<thead className="divide-y divide-gray-200 dark:divide-gray-700">
<tr>
<td className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-white">
Name
</td>
<td className="whitespace-nowrap font-medium px-4 py-2 text-gray-700 dark:text-gray-200">
Date
</td>
<td className="whitespace-nowrap px-4 py-2"></td>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{props.saves.map((save, index) => (
<tr key={index}>
<td className="whitespace-nowrap px-4 py-2 text-gray-400">
{save.name}
</td>
<td className="whitespace-nowrap px-4 py-2 text-gray-400">
{new Date(save.created_at).toDateString()}
</td>
<td className="whitespace-nowrap px-4 py-2">
<button
onClick={() => {
setCurrent(index);
setPrevious(
index !== props.saves?.length - 1 ? index + 1 : null
);
}}
className="inline-block rounded bg-black px-4 py-2 text-xs font-medium text-white hover:bg-indigo-700"
>
View snapshot
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div>Snapshot data</div>
)}
</div>
);
}

View File

@ -0,0 +1,5 @@
"use client";
export default function Loader() {
return <div>Loading...</div>;
}

View File

@ -0,0 +1,23 @@
import Percentage from "@/components/molecules/Percentage";
export default function CommunityMetricsCard(props) {
return (
<div className="flex md:w-auto items-end justify-between rounded-lg bg-white p-6 dark:bg-[#202021]">
<div className="flex items-center gap-4">
{props.icon}
<div className="space-y-3">
<p className="text-sm text-[#707071] dark:text-gray-400">
{props.title}
</p>
<p className="text-2xl font-medium text-gray-900 dark:text-white">
{props.isLoading ? "..." : props.current}
</p>
</div>
</div>
<Percentage previous={props.previous} current={props.current} />
</div>
);
}

View File

@ -0,0 +1,23 @@
import React from "react";
import Percentage from "@/components/molecules/Percentage";
export default function OverviewCard(props) {
return (
<div className="flex md:w-auto items-end justify-between rounded-lg bg-white p-6 dark:bg-[#202021]">
<div className="flex items-center gap-4">
{props.icon}
<div className="space-y-3">
<p className="text-sm text-[#707071] dark:text-gray-400">
{props.title}
</p>
<p className="text-2xl font-medium text-gray-900 dark:text-white">
{props.isLoading ? "..." : props.current}
</p>
</div>
</div>
<Percentage previous={props.previous} current={props.current} />
</div>
);
}

View File

@ -0,0 +1,53 @@
export default function Percentage(props) {
const previous = props.previous ?? 0;
const value = (
((props.current - props.previous) / props.previous) *
100
).toFixed(0);
return (
<div
className={`inline-flex gap-2 rounded p-1 ${
props.current >= previous
? "bg-green-700 text-white"
: "bg-red-700 text-white"
}`}
>
{props.current >= previous ? (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"
/>
</svg>
)}
<span className="text-xs font-medium">
{" "}
{`${value && !isNaN(value) ? value : 0}%`}{" "}
</span>
</div>
);
}

7
jsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}

9
middleware.js Normal file
View File

@ -0,0 +1,9 @@
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";
export async function middleware(req) {
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req, res });
await supabase.auth.getSession();
return res;
}

4
next.config.js Normal file
View File

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
module.exports = nextConfig

4116
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "metrics",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@supabase/auth-helpers-nextjs": "^0.7.4",
"@supabase/supabase-js": "^2.33.1",
"autoprefixer": "10.4.15",
"axios": "^1.5.0",
"axios-oauth-1.0a": "^0.3.6",
"cheerio": "^1.0.0-rc.12",
"encoding": "^0.1.13",
"eslint": "8.48.0",
"eslint-config-next": "13.4.19",
"next": "13.4.19",
"postcss": "8.4.28",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-responsive-modal": "^6.4.2",
"reactjs-popup": "^2.0.5",
"tailwindcss": "3.3.3",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/react": "18.2.21"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
public/next.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/vercel.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

18
tailwind.config.js Normal file
View File

@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [],
}

37
tsconfig.json Normal file
View File

@ -0,0 +1,37 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"paths": {
"@/*": ["./*"]
},
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}

84
utils/auth.js Normal file
View File

@ -0,0 +1,84 @@
import axios from "axios";
export async function login() {
return (window.location.href = `https://github.com/login/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID}&scope=repo read:org read:project`);
}
export async function isLoggedIn() {
if (
!localStorage.getItem("access_token") ||
!localStorage.getItem("token_expiration") ||
localStorage.getItem("access_token") === "undefined"
) {
return false;
}
if (
localStorage.getItem("token_expiration") < Math.floor(Date.now() / 1000)
) {
if (
!localStorage.getItem("refresh_token") ||
localStorage.getItem("refresh_token_expiration") <
Math.floor(Date.now() / 1000)
) {
return false;
} else {
await refreshToken();
}
}
return true;
}
export async function logout() {
localStorage.clear();
window.location.href = "/";
}
export async function getToken(code) {
const data = (await axios.get(`/api/verify?code=${code}`)).data;
if (!data.access_token) {
await logout();
return false;
}
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem(
"token_expiration",
Math.floor(Date.now() / 1000) + data.expires_in - 5
);
localStorage.setItem(
"refresh_token_expiration",
Math.floor(Date.now() / 1000) + data.refresh_token_expires_in - 5
);
}
export async function refreshToken() {
const token = localStorage.getItem("refresh_token");
if (!token) {
await logout();
return false;
}
const data = (await axios.get(`/api/refresh?token=${token}`)).data;
if (!data.access_token) {
await logout();
return false;
}
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem(
"token_expiration",
Math.floor(Date.now() / 1000) + data.expires_in - 5
);
localStorage.setItem(
"refresh_token_expiration",
Math.floor(Date.now() / 1000) + data.refresh_token_expires_in - 5
);
}

10
utils/db.ts Normal file
View File

@ -0,0 +1,10 @@
import { createClient } from "@supabase/supabase-js";
export default function initiateDb(): any {
const supabaseUrl: string = process.env.SUPABASE_URL;
const supabaseKey: string = process.env.SUPABASE_KEY;
return createClient(supabaseUrl, supabaseKey, {
auth: { persistSession: false },
});
}

11
utils/dbPublic.js Normal file
View File

@ -0,0 +1,11 @@
import { createClient } from "@supabase/supabase-js";
export default function initiateDb() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
auth: { persistSession: false },
}
);
}

11
utils/discord.js Normal file
View File

@ -0,0 +1,11 @@
import axios from "axios";
export default class Discord {
static async getServerMembers(invite) {
return (
await axios.get(
`https://discord.com/api/v9/invites/${invite}?with_counts=true`
)
).data.approximate_member_count;
}
}

7
utils/docker.js Normal file
View File

@ -0,0 +1,7 @@
import axios from "axios";
export default class Docker {
static async getPulls() {
return (await axios.get(`/api/docker`)).data.pull_count;
}
}

191
utils/github.js Normal file
View File

@ -0,0 +1,191 @@
import axios from "axios";
import { isLoggedIn, logout } from "./auth";
export default class Github {
static async getOrganizationFollowers(organization) {
return (await axios.get(`https://api.github.com/orgs/${organization}`)).data
.followers;
}
static async getRepoStars(repo) {
return (await axios.get(`https://api.github.com/repos/${repo}`)).data
.stargazers_count;
}
static async getRepoForks(repo) {
return (await axios.get(`https://api.github.com/repos/${repo}`)).data
.forks_count;
}
static async getRepoStats(repo) {
const data = (await axios.get(`https://api.github.com/repos/${repo}`)).data;
return { stars: data.stargazers_count, forks: data.forks_count };
}
static async getReposStats(organization, repos) {
let data = (
await axios.get(`https://api.github.com/orgs/${organization}/repos`)
).data;
let total_stars = 0;
let total_forks = 0;
let total_open_issues = 0;
data.forEach((repo) => {
total_stars += repo.stargazers_count;
total_forks += repo.forks_count;
total_open_issues += repo.open_issues_count;
});
data = data?.length
? data.filter((repo) => repos.indexOf(repo.name) !== -1)
: [];
let stats = {};
data.forEach((repo) => {
stats[repo.name] = {
stars: repo.stargazers_count,
forks: repo.forks_count,
open_issues: repo.open_issues_count,
};
});
return {
...stats,
total: {
stars: total_stars,
forks: total_forks,
open_issues: total_open_issues,
},
};
}
static async getEvents() {
if (!(await isLoggedIn())) {
await logout();
}
const organization = process.env.NEXT_PUBLIC_GITHUB_ORGANIZATION;
const project_id = process.env.NEXT_PUBLIC_GITHUB_EVENTS_PROJECT;
const res = await axios.post(
"https://api.github.com/graphql",
{
query: `query{organization(login:"${organization}"){name projectV2(number:${project_id}){number title items(first:100,orderBy:{direction:DESC,field:POSITION}){nodes{fieldValueByName(name:"Status"){... on ProjectV2ItemFieldSingleSelectValue{name}}content{... on Issue{title assignees(first:3){nodes{login url}}resourcePath}... on DraftIssue{title assignees(first:3){nodes{login url}}}... on PullRequest{title assignees(first:3){nodes{login url}}}}}}}}}`,
},
{
headers: {
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
}
);
let events = { Upcoming: [], Completed: [] };
res.data.data.organization.projectV2.items.nodes.forEach((item) => {
if (
item.fieldValueByName?.name !== "Upcoming Events" &&
item.fieldValueByName?.name !== "Completed Events"
) {
return;
}
events[
item.fieldValueByName.name == "Upcoming Events"
? "Upcoming"
: "Completed"
].push({
title: item.content.title,
assignees: item.content.assignees.nodes,
url: item.content.resourcePath
? `https://github.com/${item.content.resourcePath}`
: false,
});
});
return events;
}
static async getEcosystemDirectory() {
if (!(await isLoggedIn())) {
await logout();
}
const organization = process.env.NEXT_PUBLIC_GITHUB_ORGANIZATION;
const project_id =
process.env.NEXT_PUBLIC_GITHUB_ECOSYSTEM_DIRECTORY_PROJECT;
const res = await axios.post(
"https://api.github.com/graphql",
{
query: `query{organization(login:"${organization}"){name projectV2(number:${project_id}){number title items(first:100,orderBy:{direction:DESC,field:POSITION}){nodes{createdAt updatedAt fieldValueByName(name:"Status"){... on ProjectV2ItemFieldSingleSelectValue{name}}content{... on Issue{title assignees(first:3){nodes{login url}}resourcePath}... on DraftIssue{title assignees(first:3){nodes{login url}}}... on PullRequest{title assignees(first:3){nodes{login url}}}}}}}}}`,
},
{
headers: {
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
}
);
let projects = {
"Active Users": [],
"In Progress": [],
Icebox: [],
"No Status": [],
Prioritized: [],
Todo: [],
Competitors: [],
};
const allowed_values = [
"Active Users",
"In Progress",
"Icebox",
"Epics",
null,
"Prioritized",
"Todo",
"Competitors",
];
const all_projects = res.data.data.organization.projectV2.items.nodes.sort(
(a, b) => {
let da = new Date(a.createdAt),
db = new Date(b.createdAt);
return da - db;
}
);
projects["latest_3"] = all_projects
.slice(Math.max(all_projects.length - 3, 0))
.reverse();
all_projects.forEach((item) => {
if (
allowed_values.indexOf(item.fieldValueByName?.name) === -1 &&
item.fieldValueByName != null
) {
return;
}
projects[
item.fieldValueByName === null
? "No Status"
: item.fieldValueByName.name === "Epics"
? "Icebox"
: item.fieldValueByName.name
].push({
title: item.content.title,
updatedAt: item.updatedAt,
url: item.content.resourcePath
? `https://github.com/${item.content.resourcePath}`
: false,
assignees: item.content.assignees.nodes,
});
});
return projects;
}
}

21
utils/golang.js Normal file
View File

@ -0,0 +1,21 @@
import axios from "axios";
import * as cheerio from "cheerio";
export default class Golang {
static async getDownloads(pack) {
const data = (await axios.get(`https://pkg.go.dev/github.com/${pack}`))
.data;
if (!data) {
console.error(`Could not fetch download count for package ${pack}`);
return null;
}
const $ = cheerio.load(data);
const downloadCountText = $(
'.DetailsItem:contains("Go to Imports")'
).text();
return Number(downloadCountText);
}
}

9
utils/npm.js Normal file
View File

@ -0,0 +1,9 @@
import axios from "axios";
export default class Npm {
static async getDownloadsLastWeek(pack) {
return (
await axios.get(`https://api.npmjs.org/downloads/point/last-week/${pack}`)
).data.downloads;
}
}

8
utils/rust_package.js Normal file
View File

@ -0,0 +1,8 @@
import axios from "axios";
export default class Rust {
static async getDownloads(crate) {
return (await axios.get(`https://crates.io/api/v1/crates/${crate}`)).data
.crate.downloads;
}
}

7
utils/twitter.js Normal file
View File

@ -0,0 +1,7 @@
import axios from "axios";
export default class Twitter {
static async getFollowers() {
return (await axios.get(`/api/twitter`)).data.followers_count;
}
}

2948
yarn.lock Normal file

File diff suppressed because it is too large Load Diff