Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 7 - Create builders list page #29

Merged
merged 14 commits into from
Sep 23, 2024
Merged
57 changes: 57 additions & 0 deletions packages/nextjs/app/builders/_components/BuilderPicture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { useEffect, useState } from "react";
import Link from "next/link";
import { getAddress, isAddress } from "viem";
import { normalize } from "viem/ens";
import { useEnsAvatar, useEnsName } from "wagmi";
import { Address, BlockieAvatar } from "~~/components/scaffold-eth";
import { Builder } from "~~/types/builders";

type ProfilePictureProps = {
person: Builder;
};

export const BuilderPicture = ({ person }: ProfilePictureProps) => {
const [ensAvatar, setEnsAvatar] = useState<string | null>();
const checkSumAddress = person.address ? getAddress(person.address) : undefined;

const { data: fetchedEns } = useEnsName({
address: checkSumAddress,
chainId: 1,
query: {
enabled: isAddress(checkSumAddress ?? ""),
},
});

const { data: fetchedEnsAvatar } = useEnsAvatar({
name: fetchedEns ? normalize(fetchedEns) : undefined,
chainId: 1,
query: {
enabled: Boolean(fetchedEns),
gcTime: 30_000,
},
});

useEffect(() => {
setEnsAvatar(fetchedEnsAvatar);
}, [fetchedEnsAvatar]);

return (
<div className="text-center">
<div className="flex flex-col items-center justify-center gap-2" rel="noopener noreferrer">
<BlockieAvatar address={person.address as `0x${string}`} ensImage={ensAvatar} size={100} />

<div>
<Address address={person.address} size="xs" />
<Link
href={person.profileLink}
className="underline underline-offset-2 hover:underline-offset-4 transition-all"
>
See Profile
</Link>
</div>
</div>
</div>
);
};
24 changes: 24 additions & 0 deletions packages/nextjs/app/builders/_components/MentorPicture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Image from "next/image";
import Link from "next/link";
import { Mentor } from "~~/types/builders";

type ProfilePictureProps = {
person: Mentor;
};

export const MentorPicture = ({ person }: ProfilePictureProps) => {
return (
<div className="text-center">
<Link href={person.profileLink} className="link" target="_blank" rel="noopener noreferrer">
<Image
width={100}
height={100}
className="w-24 h-24 rounded-full mx-auto"
src={person.image}
alt={person.profileLink}
/>
{person.name}
</Link>
</div>
);
};
2 changes: 2 additions & 0 deletions packages/nextjs/app/builders/_components/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./MentorPicture";
export * from "./BuilderPicture";
102 changes: 102 additions & 0 deletions packages/nextjs/app/builders/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"use client";

import { useEffect, useState } from "react";
import { BuilderPicture, MentorPicture } from "./_components/index";
import type { NextPage } from "next";
import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";
import { Builder, Mentor } from "~~/types/builders";

const getRandomGender = () => {
return Math.random() < 0.5 ? "men" : "women";
};

const getRandomUserImage = () => {
const randomInt = Math.floor(Math.random() * 50);
const randomGender = getRandomGender();

return `https://randomuser.me/api/portraits/${randomGender}/${randomInt}.jpg`;
};

const getBuilderFromEvent = (event: any): Builder => ({
image: getRandomUserImage(),
Comment on lines +9 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what else we can do for profile pictures 🤔

Your design does a great job emphasizing the rounded portraits, so it seems silly to have random placeholders in the final version.

  • Is it possible to map builders to their GitHub profiles and fetch those avatars, just like you do for mentors?
  • If not, there are also BlockieAvatars (SE-2 docs) you can modify.
  • Here's where they are used in the BuidlGuidl app!
Screenshot 2024-09-19 at 2 52 20 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to obtain the GitHubs of the builders automatically, but I haven't found a way. For this, I suppose we would need to manually keep a record of all users and their GitHubs. I have therefore decided to use BlockieAvatars for the image.

Maybe somebody can help with this and find a way to get github profiles automatically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use ENS avatars. let me try something out...

profileLink: "builders/" + event.args.builder,
address: event.args.builder,
checkedIn: true,
});

const mentors: Mentor[] = [
{
name: "Eda",
image: "https://avatars.githubusercontent.com/u/22100698?v=4",
profileLink: "https://github.com/edakturk14",
checkedIn: false,
},
{
name: "Derrek",
image: "https://avatars.githubusercontent.com/u/80121818?v=4",
profileLink: "https://github.com/derrekcoleman",

checkedIn: false,
},
{
name: "Philip",
image: "https://avatars.githubusercontent.com/u/90064316?v=4",
profileLink: "https://github.com/phipsae",
checkedIn: false,
},
];

const Builders: NextPage = () => {
const [buildersCheckedIn, setBuildersCheckedIn] = useState(0);
const [builders, setBuilders] = useState<Builder[]>([]);

const { data: events, isLoading: isLoadingEvents } = useScaffoldEventHistory({
contractName: "BatchRegistry",
eventName: "CheckedIn",
fromBlock: 31231n,
watch: true,
filters: { first: true } as any,
blockData: true,
transactionData: true,
receiptData: true,
});

useEffect(() => {
if (!isLoadingEvents && events != undefined) {
setBuildersCheckedIn(events.length);
setBuilders(events.map(e => getBuilderFromEvent(e)));
}
}, [isLoadingEvents, events]);

return (
<>
<div className="text-center bg-secondary p-10">
<h1 className="text-4xl my-0">Batch #9 people 💻 </h1>
</div>
<div className="max-w-screen-lg mx-auto mt-10 p-6 bg-base-300 shadow-md rounded-lg">
<div className="container mx-auto px-4 py-8">
<div className="mb-12">
<h2 className="text-2xl font-semibold text-center mb-6">Mentors</h2>
<div className="grid grid-cols-3 gap-6">
{mentors.map((mentor, i) => (
<MentorPicture person={mentor} key={i} />
))}
</div>
</div>

<div>
<h2 className="text-2xl font-semibold text-center mb-6">Builders 🏗</h2>
<p className="text-sm font-bold text-center mb-8">Builders that checkedIn: {buildersCheckedIn}</p>
<div className="grid grid-cols-5 gap-20">
{[...new Map(builders.map(builder => [builder.address, builder])).values()].map((builder, i) => (
<BuilderPicture person={builder} key={i} />
))}
</div>
</div>
</div>
</div>
</>
);
};

export default Builders;
4 changes: 4 additions & 0 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const menuLinks: HeaderMenuLink[] = [
label: "Home",
href: "/",
},
{
label: "Builders",
href: "/builders",
},
{
label: "Debug Contracts",
href: "/debug",
Expand Down
16 changes: 16 additions & 0 deletions packages/nextjs/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ const nextConfig = {
config.externals.push("pino-pretty", "lokijs", "encoding");
return config;
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
port: '',
pathname: '/u/**',
},
{
protocol: 'https',
hostname: 'randomuser.me',
port: '',
pathname: '/api/portraits/**',
}
],
},
};

module.exports = nextConfig;
10 changes: 10 additions & 0 deletions packages/nextjs/types/builders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type Builder = {
image: string;
profileLink: string;
address?: `0x${string}`;
checkedIn: boolean;
};

export type Mentor = Builder & {
name: string;
};
Loading