Skip to content

Commit

Permalink
builder page ready
Browse files Browse the repository at this point in the history
- created new builder page
- cleaned up old files
  • Loading branch information
ishtails committed Sep 25, 2024
1 parent 68bf7e9 commit 221ef1f
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 135 deletions.
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,113 @@
"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);
if (canvasRef.current) {
resizeObserver.observe(canvasRef.current);
}

return () => {
if (canvasRef.current) {
resizeObserver.unobserve(canvasRef.current);
}
};
}, [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

0 comments on commit 221ef1f

Please sign in to comment.