Skip to content

Commit 6e2e426

Browse files
committed
[DASH-605] Add profile OG image (#5775)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on refactoring the handling of gradients for avatars and updating the Open Graph image generation for user profiles. It introduces a new `GradientBlobbie` component and modifies the `GradientAvatar` to use this new component, enhancing the visual representation of user profiles. ### Detailed summary - Deleted `url-utils.ts` and `profile.tsx` files. - Introduced `title` and `description` variables in the profile page response. - Created `GradientBlobbie` component for gradient backgrounds. - Removed gradient logic from `GradientAvatar` and used `GradientBlobbie` instead. - Updated Open Graph image generation to include `GradientBlobbie` for users without an ENS image. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 4d89116 commit 6e2e426

File tree

6 files changed

+185
-340
lines changed

6 files changed

+185
-340
lines changed
Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,15 @@
11
import { Img } from "@/components/blocks/Img";
2-
import { useMemo } from "react";
32
import type { ThirdwebClient } from "thirdweb";
43
import { resolveSchemeWithErrorHandler } from "../../../lib/resolveSchemeWithErrorHandler";
54
import { cn } from "../../../lib/utils";
6-
7-
const gradients = [
8-
["#fca5a5", "#b91c1c"],
9-
["#fdba74", "#c2410c"],
10-
["#fcd34d", "#b45309"],
11-
["#fde047", "#a16207"],
12-
["#a3e635", "#4d7c0f"],
13-
["#86efac", "#15803d"],
14-
["#67e8f9", "#0e7490"],
15-
["#7dd3fc", "#0369a1"],
16-
["#93c5fd", "#1d4ed8"],
17-
["#a5b4fc", "#4338ca"],
18-
["#c4b5fd", "#6d28d9"],
19-
["#d8b4fe", "#7e22ce"],
20-
["#f0abfc", "#a21caf"],
21-
["#f9a8d4", "#be185d"],
22-
["#fda4af", "#be123c"],
23-
];
24-
25-
function getGradientForString(str: string) {
26-
const number = Math.abs(
27-
str.split("").reduce((acc, b, i) => acc + b.charCodeAt(0) * (i + 1), 0),
28-
);
29-
const index = number % gradients.length;
30-
return gradients[index];
31-
}
5+
import { GradientBlobbie } from "./GradientBlobbie";
326

337
export function GradientAvatar(props: {
348
src: string | undefined;
359
id: string | undefined;
3610
className: string;
3711
client: ThirdwebClient;
3812
}) {
39-
const gradient = useMemo(() => {
40-
if (!props.id) {
41-
return undefined;
42-
}
43-
return getGradientForString(props.id);
44-
}, [props.id]);
45-
4613
const resolvedSrc = props.src
4714
? resolveSchemeWithErrorHandler({
4815
client: props.client,
@@ -54,15 +21,7 @@ export function GradientAvatar(props: {
5421
<Img
5522
src={resolvedSrc}
5623
className={cn("rounded-full", props.className)}
57-
fallback={
58-
gradient ? (
59-
<div
60-
style={{
61-
background: `linear-gradient(45deg, ${gradient[0]} 0%, ${gradient[1]} 100%)`,
62-
}}
63-
/>
64-
) : undefined
65-
}
24+
fallback={props.id ? <GradientBlobbie id={props.id} /> : undefined}
6625
/>
6726
);
6827
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Note: This is also used in opengraph-image.tsx
2+
// don't use any hooks or client side stuff here
3+
4+
const gradients = [
5+
["#fca5a5", "#b91c1c"],
6+
["#fdba74", "#c2410c"],
7+
["#fcd34d", "#b45309"],
8+
["#fde047", "#a16207"],
9+
["#a3e635", "#4d7c0f"],
10+
["#86efac", "#15803d"],
11+
["#67e8f9", "#0e7490"],
12+
["#7dd3fc", "#0369a1"],
13+
["#93c5fd", "#1d4ed8"],
14+
["#a5b4fc", "#4338ca"],
15+
["#c4b5fd", "#6d28d9"],
16+
["#d8b4fe", "#7e22ce"],
17+
["#f0abfc", "#a21caf"],
18+
["#f9a8d4", "#be185d"],
19+
["#fda4af", "#be123c"],
20+
];
21+
22+
function getGradientForString(str: string) {
23+
const number = Math.abs(
24+
str.split("").reduce((acc, b, i) => acc + b.charCodeAt(0) * (i + 1), 0),
25+
);
26+
const index = number % gradients.length;
27+
return gradients[index];
28+
}
29+
30+
export function GradientBlobbie(props: {
31+
id: string;
32+
style?: React.CSSProperties;
33+
}) {
34+
const gradient = getGradientForString(props.id);
35+
return (
36+
<div
37+
style={{
38+
...props.style,
39+
background: gradient
40+
? `linear-gradient(45deg, ${gradient[0]} 0%, ${gradient[1]} 100%)`
41+
: undefined,
42+
}}
43+
/>
44+
);
45+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { GradientBlobbie } from "@/components/blocks/Avatars/GradientBlobbie";
2+
/* eslint-disable @next/next/no-img-element */
3+
import { getThirdwebClient } from "@/constants/thirdweb.server";
4+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
5+
import { notFound } from "next/navigation";
6+
import { ImageResponse } from "next/og";
7+
import { resolveAvatar } from "thirdweb/extensions/ens";
8+
import { shortenIfAddress } from "../../../../utils/usedapp-external";
9+
import { resolveAddressAndEns } from "./resolveAddressAndEns";
10+
11+
export const runtime = "edge";
12+
13+
export const size = {
14+
width: 1200,
15+
height: 630,
16+
};
17+
18+
type PageProps = {
19+
params: Promise<{
20+
addressOrEns: string;
21+
}>;
22+
};
23+
24+
export default async function Image(props: PageProps) {
25+
const client = getThirdwebClient();
26+
const params = await props.params;
27+
const resolvedInfo = await resolveAddressAndEns(params.addressOrEns);
28+
29+
if (!resolvedInfo) {
30+
notFound();
31+
}
32+
33+
const [inter700, profileBackground] = await Promise.all([
34+
fetch(new URL("og-lib/fonts/inter/700.ttf", import.meta.url)).then((res) =>
35+
res.arrayBuffer(),
36+
),
37+
fetch(
38+
new URL("og-lib/assets/profile/background.png", import.meta.url),
39+
).then((res) => res.arrayBuffer()),
40+
]);
41+
42+
const ensImage = resolvedInfo.ensName
43+
? await resolveAvatar({
44+
client: client,
45+
name: resolvedInfo.ensName,
46+
})
47+
: null;
48+
49+
const resolvedENSImageSrc = ensImage
50+
? resolveSchemeWithErrorHandler({
51+
client: client,
52+
uri: ensImage,
53+
})
54+
: null;
55+
56+
return new ImageResponse(
57+
<div
58+
tw="w-full h-full flex justify-center py-20 px-16"
59+
style={{
60+
background: "#0D0D12",
61+
fontFamily: "Inter",
62+
}}
63+
>
64+
<img
65+
// @ts-expect-error - this works fine
66+
src={profileBackground}
67+
width="1200px"
68+
height="630px"
69+
tw="absolute"
70+
alt=""
71+
/>
72+
73+
<div tw="w-full h-full flex flex-col justify-between">
74+
<div tw="flex flex-col w-full">
75+
{resolvedENSImageSrc ? (
76+
<img alt="" tw="w-32 h-32 rounded-full" src={resolvedENSImageSrc} />
77+
) : (
78+
<GradientBlobbie
79+
id={resolvedInfo.address}
80+
style={{
81+
width: "128px",
82+
height: "128px",
83+
borderRadius: "50%",
84+
}}
85+
/>
86+
)}
87+
88+
<h1 tw="text-7xl text-white font-bold my-5">
89+
{resolvedInfo.ensName || shortenIfAddress(resolvedInfo.address)}
90+
</h1>
91+
</div>
92+
<div tw="flex justify-end w-full items-end">
93+
<div tw="flex flex-shrink-0">
94+
<OgBrandIcon />
95+
</div>
96+
</div>
97+
</div>
98+
</div>,
99+
{
100+
...size,
101+
fonts: [
102+
{
103+
data: inter700,
104+
name: "Inter",
105+
weight: 700,
106+
},
107+
],
108+
},
109+
);
110+
}
111+
112+
const OgBrandIcon: React.FC = () => (
113+
// biome-ignore lint/a11y/noSvgWithoutTitle: not needed
114+
<svg
115+
xmlns="http://www.w3.org/2000/svg"
116+
width="59"
117+
height="36"
118+
viewBox="0 0 59 36"
119+
fill="none"
120+
>
121+
<path
122+
fillRule="evenodd"
123+
clipRule="evenodd"
124+
d="M0.157801 3.02898C-0.425237 1.57299 0.661334 0 2.25144 0H12.1233C13.0509 0 13.8725 0.545996 14.217 1.39099L22.0747 20.7869C22.2868 21.3068 22.2868 21.8918 22.0747 22.4248L17.1322 34.6058C16.3769 36.4647 13.7002 36.4647 12.9449 34.6058L0.157801 3.02898ZM19.227 2.96398C18.697 1.52099 19.7835 0 21.3471 0H29.9469C30.901 0 31.7491 0.584996 32.0671 1.45599L39.2093 20.8519C39.3816 21.3328 39.3816 21.8658 39.2093 22.3598L34.916 34.0208C34.2005 35.9707 31.3913 35.9707 30.6757 34.0208L19.227 2.96398ZM38.5336 0C36.9435 0 35.8569 1.57299 36.4399 3.02898L49.227 34.6058C49.9823 36.4647 52.659 36.4647 53.4143 34.6058L58.3569 22.4248C58.5689 21.8918 58.5689 21.3068 58.3569 20.7869L50.4991 1.39099C50.1546 0.545996 49.333 0 48.4055 0H38.5336Z"
125+
fill="white"
126+
fillOpacity="0.5"
127+
/>
128+
</svg>
129+
);

apps/dashboard/src/app/(dashboard)/profile/[addressOrEns]/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ export async function generateMetadata(props: PageProps): Promise<Metadata> {
3939
replaceDeployerAddress(resolvedInfo.ensName || resolvedInfo.address),
4040
);
4141

42+
const title = displayName;
43+
const description = `Visit ${displayName}'s profile. See their published contracts and deploy them in one click.`;
44+
4245
return {
43-
title: displayName,
44-
description: `Visit ${displayName}'s profile. See their published contracts and deploy them in one click.`,
46+
title,
47+
description,
48+
openGraph: {
49+
title,
50+
description,
51+
},
4552
};
4653
}

apps/dashboard/src/og-lib/url-utils.ts

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)