Skip to content

Commit 2656bd9

Browse files
committed
feat: profile avatar support web3.bio
1 parent ddc1c3a commit 2656bd9

5 files changed

Lines changed: 155 additions & 94 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright 2024 OpenBuild
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Image from 'next/image';
18+
import Web3BioIcon from 'public/images/svg/web3bio.svg';
19+
20+
import Avatar from '@/components/Avatar';
21+
22+
function ProfileAvatar({
23+
data,
24+
className,
25+
}: {
26+
data: {
27+
base: { user_avatar?: string; user_nick_name: string };
28+
web3Bio?: Array<{
29+
avatar: string;
30+
}>;
31+
};
32+
className: string;
33+
}) {
34+
const size = 110;
35+
const avatarClassName = 'rounded-full object-fill';
36+
37+
const baseAvatar = data?.base?.user_avatar;
38+
const web3BioAvatar = data?.web3Bio?.filter(v => v.avatar).find(v => v.avatar)?.avatar ?? '';
39+
const avatarAlt = data?.base?.user_nick_name;
40+
const showBaseAvatar = (baseAvatar && !baseAvatar.includes('/config/avatar')) || !web3BioAvatar;
41+
42+
return (
43+
<div className={className}>
44+
{showBaseAvatar ? (
45+
<Avatar
46+
size={size}
47+
src={baseAvatar}
48+
className={avatarClassName}
49+
defaultSrc="https://s3.us-west-1.amazonaws.com/file.openbuild.xyz/config/avatar/04.svg"
50+
alt={avatarAlt}
51+
/>
52+
) : (
53+
<>
54+
<Image
55+
src={web3BioAvatar}
56+
alt={avatarAlt}
57+
className={avatarClassName}
58+
width={size}
59+
height={size}
60+
unoptimized
61+
/>
62+
<Image
63+
src={Web3BioIcon}
64+
alt="web3bio"
65+
className="size-6 rounded-full absolute right-1 bottom-1 border-1 border-white"
66+
/>
67+
</>
68+
)}
69+
</div>
70+
);
71+
}
72+
73+
export default ProfileAvatar;

src/domain/profile/widgets/profile-card/ProfileCard.js

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616

1717
import clsx from 'clsx';
1818
import { useSession } from 'next-auth/react';
19-
import Image from 'next/image';
2019
import Link from 'next/link';
2120
import { usePathname, useRouter } from 'next/navigation';
22-
import Web3BioIcon from 'public/images/svg/web3bio.svg';
23-
import { useMemo, useState } from 'react';
21+
import { useState } from 'react';
2422
import { toast } from 'react-toastify';
2523
import useSWR from 'swr';
2624

27-
import Avatar from '@/components/Avatar';
2825
import { Button } from '@/components/Button';
2926
import { RepositioningIcon } from '@/components/Icons';
3027
import { SvgIcon } from '@/components/Image';
@@ -34,6 +31,13 @@ import { useUser } from '#/state/application/hooks';
3431

3532
import SocialInfoWidget from '../social-info';
3633
import IdentitySwitch from './IdentitySwitch';
34+
import ProfileAvatar from './ProfileAvatar';
35+
36+
function getUserDesc(data) {
37+
const baseUserDesc = data?.base.user_bio;
38+
const web3BioDesc = data?.web3Bio?.find(v => v.description)?.description;
39+
return baseUserDesc || web3BioDesc || '--';
40+
}
3741

3842
function ProfileCardWidget({ className, data }) {
3943
const router = useRouter();
@@ -47,12 +51,6 @@ function ProfileCardWidget({ className, data }) {
4751
const handle = data?.base.user_handle;
4852
const creatorAvailable = data.base?.user_project_owner;
4953

50-
const userDesc = useMemo(() => {
51-
const baseUserDesc = data?.base.user_bio;
52-
const web3BioDesc = data?.web3Bio?.find(v => v.description)?.description;
53-
return baseUserDesc || web3BioDesc || '--';
54-
}, [data]);
55-
5654
const follow = async () => {
5755
if (status !== 'authenticated') {
5856
router.push(`/signin?from=${pathname}`);
@@ -86,23 +84,7 @@ function ProfileCardWidget({ className, data }) {
8684
return (
8785
<div className={clsx('relative md:w-[360px] md:rounded-lg md:p-6 md:bg-white', className)}>
8886
<div className="flex flex-col gap-2 items-center">
89-
<div className="relative">
90-
<Avatar
91-
className="-mt-[104px] md:mt-0"
92-
size={110}
93-
src={data.base.user_avatar}
94-
defaultSrc="https://s3.us-west-1.amazonaws.com/file.openbuild.xyz/config/avatar/04.svg"
95-
alt={data?.base.user_nick_name}
96-
/>
97-
{data.web3Bio && (
98-
<Image
99-
src={Web3BioIcon}
100-
alt="web3bio"
101-
className="size-6 rounded-full absolute right-1 bottom-1 border-1 border-white"
102-
/>
103-
)}
104-
</div>
105-
87+
<ProfileAvatar data={data} className="-mt-[104px] md:mt-0 relative" />
10688
<h6 className="text-[24px] leading-none">
10789
<a href={`/u/${handle}`}>{data?.base.user_nick_name}</a>
10890
</h6>
@@ -114,7 +96,7 @@ function ProfileCardWidget({ className, data }) {
11496
</p>
11597
</div>
11698
)}
117-
<p className="text-sm text-center">{userDesc}</p>
99+
<p className="text-sm text-center">{getUserDesc(data)}</p>
118100
</div>
119101
<div className="my-6 flex gap-7 justify-center text-sm">
120102
<Link href={`/u/${handle}/followers`}>

src/domain/profile/widgets/social-info/SocialInfo.js

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,9 @@ import Web3BioProfile from './Web3BioProfile';
2424
const web3BioSocialKeyMap = {
2525
user_github: 'github',
2626
user_discord: 'discord',
27-
user_twitter: 'x',
27+
user_x: 'twitter',
2828
};
2929

30-
function web3BioLink(type, { web3Bio = [] }) {
31-
const web3BioLinks = web3Bio.reduce((p, c) => (c.links ? { ...p, ...c.links } : p), {});
32-
return web3BioLinks[web3BioSocialKeyMap[type]]?.link;
33-
}
34-
3530
function socialsInfo(type, link, web3BioLink) {
3631
const showWeb3Bio = !link && web3BioLink;
3732

@@ -66,21 +61,21 @@ function socialsInfo(type, link, web3BioLink) {
6661
}
6762

6863
function SocialInfoWidget({ className, data }) {
69-
const socials = useMemo(
70-
() =>
71-
Object.keys(data.social)
72-
.map(i => socialsInfo(i, data.social[i], web3BioLink(i, data)))
73-
.filter(s => {
74-
if (!s) {
75-
return false;
76-
}
64+
const socials = useMemo(() => {
65+
const web3BioSocials = (data?.web3Bio ?? []).reduce((p, c) => (c.links ? { ...p, ...c.links } : p), {});
7766

78-
const enabled = s.enableKey ? data.base[s.enableKey] : true;
67+
return Object.keys(data.social)
68+
.map(i => socialsInfo(i, data.social[i], web3BioSocials[web3BioSocialKeyMap[i]]?.link))
69+
.filter(s => {
70+
if (!s) {
71+
return false;
72+
}
7973

80-
return enabled && !!s.link;
81-
}),
82-
[data],
83-
);
74+
const enabled = s.enableKey ? data.base[s.enableKey] : true;
75+
76+
return enabled && !!s.link;
77+
});
78+
}, [data]);
8479

8580
return (
8681
<div className={className}>

src/domain/profile/widgets/social-info/Web3BioProfile.js

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import clsx from 'clsx';
18+
1719
import { SvgIcon } from '@/components/Image';
1820
import { capitalize } from '@/utils';
1921

@@ -27,6 +29,25 @@ const specialTextMap = {
2729
linkedin: 'LinkedIn',
2830
};
2931

32+
const socialsConfig = {
33+
farcaster: {
34+
icon: 'farcaster-purple',
35+
class: 'bg-[#8A63D2]/10 border-[#8A63D2]/10',
36+
},
37+
lens: {
38+
icon: 'lens-green',
39+
class: 'bg-[#6BC674]/10 border-[#6BC674]/10',
40+
},
41+
basenames: {
42+
icon: 'basenames-blue',
43+
class: 'bg-[#0052FF]/10 border-[#0052FF]/10',
44+
},
45+
default: {
46+
icon: 'link',
47+
class: 'bg-[#1A1A1A]/10 border-[#1A1A1A]/10',
48+
},
49+
};
50+
3051
function filterExistsInOpenBuild(links, social) {
3152
return Object.entries(links).filter(([k]) => !socialKeyMap[k] || !(socialKeyMap[k] in social));
3253
}
@@ -35,31 +56,6 @@ function resolveLinks({ social, web3Bio = [] }) {
3556
return web3Bio.reduce((p, c) => [].concat(p, filterExistsInOpenBuild(c.links, social)), []);
3657
}
3758

38-
function socialsConfig(type) {
39-
switch (type) {
40-
case 'farcaster':
41-
return {
42-
icon: 'farcaster-purple',
43-
color: '#8A63D21A',
44-
};
45-
case 'lens':
46-
return {
47-
icon: 'lens-green',
48-
color: '#6BC6741A',
49-
};
50-
case 'basenames':
51-
return {
52-
icon: 'basenames-blue',
53-
color: '#0052FF1A',
54-
};
55-
default:
56-
return {
57-
icon: 'link',
58-
color: '#1A1A1A1A',
59-
};
60-
}
61-
}
62-
6359
function Web3BioProfile({ data }) {
6460
const links = resolveLinks(data);
6561

@@ -69,28 +65,27 @@ function Web3BioProfile({ data }) {
6965
<div className="mt-6 text-xs flex gap-1">
7066
<p className="uppercase opacity-60 font-bold flex-1">Onchain Identities</p>
7167
<div className="opacity-40">Built with</div>
72-
<a className="" href={`https://web3.bio/${data.social.user_wallet}`} target="_blank" rel="noreferrer">
73-
web3.bio
68+
<a href={`https://web3.bio/${data.social.user_wallet}`} target="_blank" rel="noreferrer">
69+
Web3.bio
7470
</a>
7571
</div>
7672

7773
<div className="mt-3 flex gap-2 flex-wrap">
78-
{links.map(([k, profile]) => (
79-
<a
80-
key={`web3bio-social-${k}`}
81-
className="flex gap-1 rounded-[6px] px-1 h-7 text-sm border items-center"
82-
style={{
83-
borderColor: socialsConfig(k).color,
84-
backgroundColor: socialsConfig(k).color,
85-
}}
86-
href={profile.link}
87-
target="_blank"
88-
rel="noreferrer"
89-
>
90-
<SvgIcon name={socialsConfig(k).icon} size={16} />
91-
{specialTextMap[k] || capitalize(k)}
92-
</a>
93-
))}
74+
{links.map(([k, profile]) => {
75+
const socialConfig = socialsConfig[k] ?? socialsConfig.default;
76+
return (
77+
<a
78+
key={`web3bio-social-${k}`}
79+
className={clsx(socialConfig.class, 'flex gap-1 rounded-[6px] px-1 h-7 text-sm border items-center')}
80+
href={profile.link}
81+
target="_blank"
82+
rel="noreferrer"
83+
>
84+
<SvgIcon name={socialConfig.icon} size={16} />
85+
{specialTextMap[k] || capitalize(k)}
86+
</a>
87+
);
88+
})}
9489
</div>
9590
</>
9691
)
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,23 @@ import clsx from 'clsx';
1818

1919
import Image from '../Image';
2020

21-
function Avatar({ className, size, src, defaultSrc, alt, user }) {
21+
function Avatar({
22+
size,
23+
className,
24+
src,
25+
defaultSrc,
26+
alt,
27+
user,
28+
}: {
29+
size: number;
30+
className?: string;
31+
src?: string;
32+
defaultSrc?: string;
33+
alt?: string;
34+
user?: { user_handle: string; user_nick_name: string; user_avatar: string };
35+
}) {
2236
const resolvedAlt = alt || (user && user.user_nick_name) || '';
23-
const imgClassName = `rounded-full object-fill w-[${size}px] h-[${size}px]`;
37+
const imgClassName = 'rounded-full object-fill';
2438
const imgProps = {
2539
width: size,
2640
height: size,
@@ -29,10 +43,12 @@ function Avatar({ className, size, src, defaultSrc, alt, user }) {
2943
};
3044

3145
return user ? (
32-
<a className={clsx('block', `w-[${size}px]`, `h-[${size}px]`, className)} href={`/u/${user.user_handle}`}>
46+
<a className={clsx('block', className)} href={`/u/${user.user_handle}`}>
3347
<Image {...imgProps} className={imgClassName} alt={resolvedAlt} />
3448
</a>
35-
) : <Image {...imgProps} className={clsx(imgClassName, className)} alt={resolvedAlt} />;
49+
) : (
50+
<Image {...imgProps} className={clsx(imgClassName, className)} alt={resolvedAlt} />
51+
);
3652
}
3753

3854
export default Avatar;

0 commit comments

Comments
 (0)