diff --git a/packages/nextjs/app/builders/page.tsx b/packages/nextjs/app/builders/page.tsx new file mode 100644 index 0000000..4275729 --- /dev/null +++ b/packages/nextjs/app/builders/page.tsx @@ -0,0 +1,16 @@ +import { promises as fs } from "fs"; +import type { NextPage } from "next"; +import BuildersList from "~~/components/BuildersList"; + +const Builders: NextPage = async () => { + const file = await fs.readdir(process.cwd() + "/app/builders"); + const builderProfiles = file.filter((file: string) => file.startsWith("0x") && file.length == 42); + + return ( + <> + + + ); +}; + +export default Builders; diff --git a/packages/nextjs/components/BuildersList.tsx b/packages/nextjs/components/BuildersList.tsx new file mode 100644 index 0000000..324c6a3 --- /dev/null +++ b/packages/nextjs/components/BuildersList.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import Link from "next/link"; +import { Address } from "~~/components/scaffold-eth"; +import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth"; + +type Builder = { + address: string; + hasPersonalPage: boolean; +}; + +export default function Page({ builderProfiles }: { builderProfiles: string[] }) { + const [builders, setBuilders] = useState([] as Builder[]); + const [isLoading, setIsLoading] = useState(true); + const elementRef = useRef(null); + + const { data: events } = useScaffoldEventHistory({ + contractName: "BatchRegistry", + eventName: "CheckedIn", + fromBlock: 126461494n, + }); + + useEffect(() => { + if (elementRef.current) { + setIsLoading(false); + } + }, []); + + useEffect(() => { + const fetchBuilders = async () => { + if (!events) return; + + // Filter out duplicate addresses + const filteredAddresses: string[] = []; + events.forEach(event => { + const address = event.args.builder; + if (!address) return; + if (!filteredAddresses.find(addr => address === addr)) filteredAddresses.push(address); + }); + + const builders = await Promise.all( + filteredAddresses.map(async address => { + if (address) + return { + address, + // Check if the builder has a personal page + hasPersonalPage: !!builderProfiles.find(bp => bp === address), + }; + }), + ); + setBuilders( + builders + .filter(builder => !!builder) + // Sort by personal apge availability + .sort((a, b) => +(b.hasPersonalPage === true) - +(a.hasPersonalPage === true)), + ); + }; + + fetchBuilders().catch(console.error); + }, [events]); + + return ( +
+
+
+

+ Builders +

+
+
+
+
+ {isLoading ? ( + Loading builder pages + ) : ( + + + + + + + + + + {builders.map((builder, id) => ( + + + + + + ))} + +
ENS or AddressBuilder Page
{id + 1} +
+
+ {builder.hasPersonalPage ? ( + + Builder Page + + ) : ( + <>Not available + )} +
+ )} +
+
+
+
+
+ ); +} diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index aead9dc..078b498 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { FaUsers } from "react-icons/fa"; import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline"; import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; import { useOutsideClick } from "~~/hooks/scaffold-eth"; @@ -24,6 +25,11 @@ export const menuLinks: HeaderMenuLink[] = [ href: "/debug", icon: , }, + { + label: "Builders", + href: "/builders", + icon: , + }, ]; export const HeaderMenuLinks = () => {