Skip to content

Commit 58149fa

Browse files
notrablightwalker-ethshrugs
authored
feat(apps/ensadmin): show more social links on name page (#1071)
Co-authored-by: lightwalker.eth <126201998+lightwalker-eth@users.noreply.github.com> Co-authored-by: shrugs <mattgcondon@gmail.com>
1 parent 6b5bfd0 commit 58149fa

File tree

13 files changed

+321
-140
lines changed

13 files changed

+321
-140
lines changed

.changeset/quick-doodles-read.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ensadmin": minor
3+
---
4+
5+
Add support for telegram, linkedin and reddit profiles on name detail page

apps/ensadmin/src/app/name/[name]/SocialLinks.tsx

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

apps/ensadmin/src/app/name/[name]/AdditionalRecords.tsx renamed to apps/ensadmin/src/app/name/[name]/_components/AdditionalRecords.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,37 @@ interface AdditionalRecordsProps {
66
texts: Record<string, unknown> | null | undefined;
77
}
88

9-
const EXCLUDED_RECORDS = [
9+
const RECORDS_ALREADY_DISPLAYED_ELSEWHERE = [
1010
"description",
1111
"url",
1212
"email",
1313
"com.twitter",
1414
"com.github",
1515
"com.farcaster",
16+
"org.telegram",
17+
"com.linkedin",
18+
"com.reddit",
1619
"avatar",
1720
"header",
1821
"name",
1922
];
2023

2124
export function AdditionalRecords({ texts }: AdditionalRecordsProps) {
22-
if (!texts) {
23-
return null;
24-
}
25+
if (!texts) return null;
2526

26-
const additionalRecords = Object.entries(texts).filter(
27-
([key]) => !EXCLUDED_RECORDS.includes(key),
27+
const records = Object.entries(texts).filter(
28+
([key]) => !RECORDS_ALREADY_DISPLAYED_ELSEWHERE.includes(key),
2829
);
2930

30-
if (additionalRecords.length === 0) {
31-
return null;
32-
}
31+
if (records.length === 0) return null;
3332

3433
return (
3534
<Card>
3635
<CardHeader>
3736
<CardTitle>Additional Records</CardTitle>
3837
</CardHeader>
3938
<CardContent className="space-y-3">
40-
{additionalRecords.map(([key, value]) => (
39+
{records.map(([key, value]) => (
4140
<div key={key} className="flex items-start justify-between">
4241
<span className="text-sm font-medium text-gray-500 min-w-0 flex-1">{key}</span>
4342
<span className="text-sm text-gray-900 ml-4 break-all">{String(value)}</span>
File renamed without changes.

apps/ensadmin/src/app/name/[name]/ProfileSkeleton.tsx renamed to apps/ensadmin/src/app/name/[name]/_components/NameDetailPageSkeleton.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,23 @@ import { Skeleton } from "@/components/ui/skeleton";
55

66
export function NameDetailPageSkeleton() {
77
return (
8-
<div className="container mx-auto p-6 max-w-4xl">
9-
<div className="mb-8">
10-
<div className="flex items-center gap-4 mb-4">
11-
<Skeleton className="h-20 w-20 rounded-full" />
12-
<div className="space-y-2">
13-
<Skeleton className="h-8 w-48" />
14-
<div className="flex items-center gap-2">
15-
<Skeleton className="h-6 w-20" />
16-
<Skeleton className="h-8 w-32" />
8+
<div className="container mx-auto p-6 max-w-4xl flex flex-col gap-8">
9+
<Card>
10+
<div className="h-48"></div>
11+
<CardContent className="pt-6">
12+
<div className="flex items-center gap-4 mb-4 -mt-16">
13+
<Skeleton className="h-20 w-20 rounded-full" />
14+
<div className="space-y-2">
15+
<Skeleton className="h-8 w-48" />
16+
<div className="flex items-center gap-2">
17+
<Skeleton className="h-6 w-20" />
18+
<Skeleton className="h-8 w-32" />
19+
</div>
1720
</div>
1821
</div>
19-
</div>
20-
</div>
21-
22-
<ProfileSkeleton />
23-
</div>
24-
);
25-
}
22+
</CardContent>
23+
</Card>
2624

27-
export function ProfileSkeleton() {
28-
return (
29-
<div className="space-y-6">
3025
<Card>
3126
<CardHeader>
3227
<Skeleton className="h-6 w-32" />

apps/ensadmin/src/app/name/[name]/ProfileHeader.tsx renamed to apps/ensadmin/src/app/name/[name]/_components/ProfileHeader.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ExternalLinkWithIcon } from "@/components/external-link-with-icon";
44
import { NameDisplay } from "@/components/identity/utils";
55
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
66
import { Card, CardContent } from "@/components/ui/card";
7+
import { beautifyUrl } from "@/lib/beautify-url";
78

89
interface ProfileHeaderProps {
910
name: string;
@@ -19,19 +20,35 @@ export function ProfileHeader({ name, avatarUrl, headerImage, websiteUrl }: Prof
1920
const getValidHeaderImageUrl = (headerImage: string | null | undefined): string | null => {
2021
if (!headerImage) return null;
2122

23+
let url: URL;
2224
try {
23-
const url = new URL(headerImage);
24-
if (url.protocol === "http:" || url.protocol === "https:") {
25-
return headerImage;
26-
}
27-
// For any other URI types (ipfs, data, NFT URIs, etc.), fallback to default
25+
url = new URL(headerImage);
26+
} catch {
2827
return null;
28+
}
29+
30+
if (url.protocol === "http:" || url.protocol === "https:") return headerImage;
31+
32+
// For any other URI types (ipfs, data, NFT URIs, etc.), fallback to default
33+
return null;
34+
};
35+
36+
const normalizeWebsiteUrl = (url: string | null | undefined): URL | null => {
37+
if (!url) return null;
38+
39+
try {
40+
try {
41+
return new URL(url);
42+
} catch {
43+
return new URL(`https://${url}`);
44+
}
2945
} catch {
3046
return null;
3147
}
3248
};
3349

3450
const validHeaderImageUrl = getValidHeaderImageUrl(headerImage);
51+
const normalizedWebsiteUrl = normalizeWebsiteUrl(websiteUrl);
3552

3653
return (
3754
<Card className="overflow-hidden mb-8">
@@ -57,12 +74,9 @@ export function ProfileHeader({ name, avatarUrl, headerImage, websiteUrl }: Prof
5774
<NameDisplay className="text-3xl font-bold" name={name} />
5875
</h1>
5976
<div className="flex items-center gap-3 mt-1">
60-
{websiteUrl && (
61-
<ExternalLinkWithIcon
62-
href={websiteUrl.startsWith("http") ? websiteUrl : `https://${websiteUrl}`}
63-
className="text-sm"
64-
>
65-
{websiteUrl.replace(/^https?:\/\//, "")}
77+
{normalizedWebsiteUrl && (
78+
<ExternalLinkWithIcon href={normalizedWebsiteUrl.toString()} className="text-sm">
79+
{beautifyUrl(normalizedWebsiteUrl)}
6680
</ExternalLinkWithIcon>
6781
)}
6882
</div>

apps/ensadmin/src/app/name/[name]/ProfileInformation.tsx renamed to apps/ensadmin/src/app/name/[name]/_components/ProfileInformation.tsx

File renamed without changes.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"use client";
2+
3+
import { useMemo } from "react";
4+
5+
import { ExternalLinkWithIcon } from "@/components/external-link-with-icon";
6+
import { LinkedInIcon } from "@/components/icons/LinkedInIcon";
7+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8+
import { SiFarcaster, SiGithub, SiReddit, SiTelegram, SiX } from "@icons-pack/react-simple-icons";
9+
10+
const SOCIAL_LINK_KEYS = [
11+
"com.twitter",
12+
"com.farcaster",
13+
"com.github",
14+
"org.telegram",
15+
"com.linkedin",
16+
"com.reddit",
17+
] as const;
18+
19+
type SocialLinkKey = (typeof SOCIAL_LINK_KEYS)[number];
20+
type SocialLinkValue = string;
21+
22+
export function SocialLinks({
23+
links,
24+
}: { links: { key: SocialLinkKey; value: SocialLinkValue }[] }) {
25+
if (links.length === 0) return null;
26+
27+
return (
28+
<Card>
29+
<CardHeader>
30+
<CardTitle>Social Links</CardTitle>
31+
</CardHeader>
32+
<CardContent className="gap-3 flex flex-col md:flex-row flex-wrap">
33+
{links.map(({ key, value }) => {
34+
switch (key) {
35+
case "com.twitter": {
36+
return (
37+
<div key={key} className="inline-flex items-center gap-2">
38+
<SiX size={16} className="text-gray-500" />
39+
<ExternalLinkWithIcon href={`https://twitter.com/${value}`} className="text-sm">
40+
@{value}
41+
</ExternalLinkWithIcon>
42+
</div>
43+
);
44+
}
45+
case "com.github": {
46+
return (
47+
<div key={key} className="inline-flex items-center gap-2">
48+
<SiGithub size={16} className="text-gray-500" />
49+
<ExternalLinkWithIcon href={`https://github.com/${value}`} className="text-sm">
50+
{value}
51+
</ExternalLinkWithIcon>
52+
</div>
53+
);
54+
}
55+
case "com.farcaster": {
56+
return (
57+
<div key={key} className="inline-flex items-center gap-2">
58+
<SiFarcaster size={16} className="text-gray-500" />
59+
<ExternalLinkWithIcon href={`https://warpcast.com/${value}`} className="text-sm">
60+
@{value}
61+
</ExternalLinkWithIcon>
62+
</div>
63+
);
64+
}
65+
case "org.telegram": {
66+
return (
67+
<div key={key} className="inline-flex items-center gap-2">
68+
<SiTelegram size={16} className="text-gray-500" />
69+
<ExternalLinkWithIcon href={`https://t.me/${value}`} className="text-sm">
70+
@{value}
71+
</ExternalLinkWithIcon>
72+
</div>
73+
);
74+
}
75+
case "com.linkedin": {
76+
return (
77+
<div key={key} className="inline-flex items-center gap-2">
78+
<LinkedInIcon className="text-gray-500 size-4 fill-current" />
79+
<ExternalLinkWithIcon
80+
href={`https://linkedin.com/in/${value}`}
81+
className="text-sm"
82+
>
83+
{value}
84+
</ExternalLinkWithIcon>
85+
</div>
86+
);
87+
}
88+
case "com.reddit": {
89+
return (
90+
<div key={key} className="inline-flex items-center gap-2">
91+
<SiReddit size={16} className="text-gray-500" />
92+
<ExternalLinkWithIcon href={`https://reddit.com/u/${value}`} className="text-sm">
93+
u/{value}
94+
</ExternalLinkWithIcon>
95+
</div>
96+
);
97+
}
98+
default:
99+
console.warn(`Unsupported Social provided: '${key}' with value '${value}'.`);
100+
return null;
101+
}
102+
})}
103+
</CardContent>
104+
</Card>
105+
);
106+
}
107+
108+
SocialLinks.Texts = function SocialLinksTexts({
109+
texts,
110+
}: { texts: Record<string, string | null | undefined> }) {
111+
const links = useMemo(
112+
() =>
113+
SOCIAL_LINK_KEYS
114+
// map social keys to a set of links
115+
.map((key) => ({ key, value: texts[key] }))
116+
// filter those links by those that exist
117+
.filter(
118+
(link): link is { key: SocialLinkKey; value: SocialLinkValue } =>
119+
typeof link.value === "string",
120+
),
121+
[texts],
122+
);
123+
124+
return <SocialLinks links={links} />;
125+
};

0 commit comments

Comments
 (0)