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

init - new builder page #34

Merged
merged 6 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"use client";

import React, { useEffect, useRef, useState } from "react";
import { cn } from "../_lib/utils";

interface ShootingStar {
id: number;
x: number;
y: number;
angle: number;
scale: number;
speed: number;
distance: number;
}

interface ShootingStarsProps {
minSpeed?: number;
maxSpeed?: number;
minDelay?: number;
maxDelay?: number;
starColor?: string;
trailColor?: string;
starWidth?: number;
starHeight?: number;
className?: string;
}

const getRandomStartPoint = () => {
const side = Math.floor(Math.random() * 4);
const offset = Math.random() * window.innerWidth;

switch (side) {
case 0:
return { x: offset, y: 0, angle: 45 };
case 1:
return { x: window.innerWidth, y: offset, angle: 135 };
case 2:
return { x: offset, y: window.innerHeight, angle: 225 };
case 3:
return { x: 0, y: offset, angle: 315 };
default:
return { x: 0, y: 0, angle: 45 };
}
};
export const ShootingStars: React.FC<ShootingStarsProps> = ({
minSpeed = 10,
maxSpeed = 30,
minDelay = 1200,
maxDelay = 4200,
starColor = "#9E00FF",
trailColor = "#2EB9DF",
starWidth = 10,
starHeight = 1,
className,
}) => {
const [star, setStar] = useState<ShootingStar | null>(null);
const svgRef = useRef<SVGSVGElement>(null);

useEffect(() => {
const createStar = () => {
const { x, y, angle } = getRandomStartPoint();
const newStar: ShootingStar = {
id: Date.now(),
x,
y,
angle,
scale: 1,
speed: Math.random() * (maxSpeed - minSpeed) + minSpeed,
distance: 0,
};
setStar(newStar);

const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay;
setTimeout(createStar, randomDelay);
};

createStar();
}, [minSpeed, maxSpeed, minDelay, maxDelay]);

useEffect(() => {
const moveStar = () => {
if (star) {
setStar(prevStar => {
if (!prevStar) return null;
const newX = prevStar.x + prevStar.speed * Math.cos((prevStar.angle * Math.PI) / 180);
const newY = prevStar.y + prevStar.speed * Math.sin((prevStar.angle * Math.PI) / 180);
const newDistance = prevStar.distance + prevStar.speed;
const newScale = 1 + newDistance / 100;
if (newX < -20 || newX > window.innerWidth + 20 || newY < -20 || newY > window.innerHeight + 20) {
return null;
}
return {
...prevStar,
x: newX,
y: newY,
distance: newDistance,
scale: newScale,
};
});
}
};

const animationFrame = requestAnimationFrame(moveStar);
return () => cancelAnimationFrame(animationFrame);
}, [star]);

return (
<svg ref={svgRef} className={cn("w-full h-full absolute inset-0", className)}>
{star && (
<rect
key={star.id}
x={star.x}
y={star.y}
width={starWidth * star.scale}
height={starHeight}
fill="url(#gradient)"
transform={`rotate(${star.angle}, ${star.x + (starWidth * star.scale) / 2}, ${star.y + starHeight / 2})`}
/>
)}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style={{ stopColor: trailColor, stopOpacity: 0 }} />
<stop offset="100%" style={{ stopColor: starColor, stopOpacity: 1 }} />
</linearGradient>
</defs>
</svg>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";

import React, { RefObject, useCallback, useEffect, useRef, useState } from "react";
import { cn } from "../_lib/utils";

interface StarProps {
x: number;
y: number;
radius: number;
opacity: number;
twinkleSpeed: number | null;
}

interface StarBackgroundProps {
starDensity?: number;
allStarsTwinkle?: boolean;
twinkleProbability?: number;
minTwinkleSpeed?: number;
maxTwinkleSpeed?: number;
className?: string;
}

export const StarsBackground: React.FC<StarBackgroundProps> = ({
starDensity = 0.00015,
allStarsTwinkle = true,
twinkleProbability = 0.7,
minTwinkleSpeed = 0.5,
maxTwinkleSpeed = 1,
className,
}) => {
const [stars, setStars] = useState<StarProps[]>([]);
const canvasRef: RefObject<HTMLCanvasElement> = useRef<HTMLCanvasElement>(null);

const generateStars = useCallback(
(width: number, height: number): StarProps[] => {
const area = width * height;
const numStars = Math.floor(area * starDensity);
return Array.from({ length: numStars }, () => {
const shouldTwinkle = allStarsTwinkle || Math.random() < twinkleProbability;
return {
x: Math.random() * width,
y: Math.random() * height,
radius: Math.random() * 0.05 + 0.5,
opacity: Math.random() * 0.5 + 0.5,
twinkleSpeed: shouldTwinkle ? minTwinkleSpeed + Math.random() * (maxTwinkleSpeed - minTwinkleSpeed) : null,
};
});
},
[starDensity, allStarsTwinkle, twinkleProbability, minTwinkleSpeed, maxTwinkleSpeed],
);

useEffect(() => {
const updateStars = () => {
if (canvasRef.current) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;

const { width, height } = canvas.getBoundingClientRect();
canvas.width = width;
canvas.height = height;
setStars(generateStars(width, height));
}
};

updateStars();

const resizeObserver = new ResizeObserver(updateStars);
const currentCanvas = canvasRef.current; // Copy the ref value to a variable
if (currentCanvas) {
resizeObserver.observe(currentCanvas);
}

return () => {
if (currentCanvas) {
resizeObserver.unobserve(currentCanvas);
}
};
}, [starDensity, allStarsTwinkle, twinkleProbability, minTwinkleSpeed, maxTwinkleSpeed, generateStars]);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

let animationFrameId: number;

const render = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
stars.forEach(star => {
ctx.beginPath();
ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`;
ctx.fill();

if (star.twinkleSpeed !== null) {
star.opacity = 0.5 + Math.abs(Math.sin((Date.now() * 0.001) / star.twinkleSpeed) * 0.5);
}
});

animationFrameId = requestAnimationFrame(render);
};

render();

return () => {
cancelAnimationFrame(animationFrameId);
};
}, [stars]);

return <canvas ref={canvasRef} className={cn("h-full w-full absolute inset-0", className)} />;
};
117 changes: 117 additions & 0 deletions packages/nextjs/app/builders/_components/BuilderCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { normalize } from "path";
import { getAddress, isAddress } from "viem";
import { useEnsAvatar, useEnsName } from "wagmi";
import Arrow_Icon from "~~/components/Arrow_Icon";
import { Address, BlockieAvatar } from "~~/components/scaffold-eth";
import { Builder, Mentor } from "~~/types/builders";

type Props = {
mentor?: Mentor;
builder?: Builder;
};

const banners = [
"/banner_1.jpg",
"/banner_2.jpg",
"/banner_3.jpg",
"/banner_4.jpg",
"/banner_5.jpg",
"/banner_6.jpg",
"/banner_7.jpg",
];

const getRandomBanner = () => {
const randomIndex = Math.floor(Math.random() * banners.length);
return banners[randomIndex];
};

const BuilderCard = ({ mentor, builder }: Props) => {
const [ensAvatar, setEnsAvatar] = useState<string | null>();
const checkSumAddress = builder?.address ? getAddress(builder.address) : undefined;
const [banner, setBanner] = useState<string>("");

useEffect(() => {
setBanner(getRandomBanner());
}, []);

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="z-10 border rounded-xl h-[15rem] sm:min-w-[22rem] min-w-[17rem] w-full font-light dark:border-zinc-700 border-zinc-400 flex flex-col text-text-zinc-700 dark:text-zinc-300 shadow-md">
<div className="w-full relative flex flex-col flex-grow-[20] rounded-t-xl">
{banner ? (
<Image
src={banner}
alt="banner"
priority={true}
width={1000}
height={1000}
className="rounded-t-xl object-cover max-h-24"
/>
) : (
<div className="bg-gradient-to-r from-st_cyan/10 to-st_purple/10 rounded-t-xl h-24 w-full" />
)}

<div className="rounded-full z-10 top-10 left-5 absolute dark:border-zinc-700 scale-90 sm:scale-100 border-zinc-400 border-4">
{mentor && <Image src={mentor.image} alt="builder" width={110} height={110} className="rounded-full" />}

{builder && <BlockieAvatar address={builder.address as `0x${string}`} ensImage={ensAvatar} size={100} />}
</div>

<div className="text-end">
<div
className="bg-clip-text text-transparent
dark:bg-[linear-gradient(to_right,theme(colors.indigo.500),theme(colors.indigo.200),theme(colors.rose.500),theme(colors.fuchsia.500),theme(colors.sky.500),theme(colors.indigo.200),theme(colors.indigo.500))]
bg-[linear-gradient(to_right,theme(colors.indigo.500),theme(colors.indigo.800),theme(colors.sky.500),theme(colors.fuchsia.500),theme(colors.sky.500),theme(colors.indigo.800),theme(colors.indigo.500))] bg-[length:200%_auto] animate-gradient font-bold text-2xl"
>
{mentor && <p className="pr-4 sm:pr-8 uppercase">{mentor.name}</p>}

{builder ? (
<div className="w-full flex justify-end pt-14 sm:pt-12 pr-4">
<Address address={builder.address} size="sm" />
</div>
) : null}
</div>
</div>
</div>

<hr className="border-t dark:border-zinc-700 border-zinc-400" />

<div className="hover:bg-st_cyan/10 rounded-b-xl duration-75 transition-all dark:border-zinc-700 border-zinc-400 flex-grow">
<Link
href={mentor ? mentor.profileLink : builder ? builder.profileLink : ""}
target="_blank"
className="flex items-center justify-between h-full px-4"
>
<p className="font-medium flex flex-col items-center justify-center">View Profile</p>

<Arrow_Icon />
</Link>
</div>
</div>
);
};

export default BuilderCard;
Loading
Loading