From 59f16ee0ef306754918b2721ec435180dc610132 Mon Sep 17 00:00:00 2001 From: "e-peirong.li" Date: Tue, 18 Mar 2025 21:05:50 +0800 Subject: [PATCH 1/5] feat: add web3bio mark of profile avatar --- .../widgets/profile-card/ProfileCard.js | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/domain/profile/widgets/profile-card/ProfileCard.js b/src/domain/profile/widgets/profile-card/ProfileCard.js index d600e99..bdac4fb 100644 --- a/src/domain/profile/widgets/profile-card/ProfileCard.js +++ b/src/domain/profile/widgets/profile-card/ProfileCard.js @@ -16,8 +16,10 @@ import clsx from 'clsx'; import { useSession } from 'next-auth/react'; +import Image from 'next/image'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; +import Web3BioIcon from 'public/images/svg/web3bio.svg'; import { useState } from 'react'; import { toast } from 'react-toastify'; import useSWR from 'swr'; @@ -78,27 +80,43 @@ function ProfileCardWidget({ className, data }) { return (
- +
+ + {data.web3Bio && ( + web3bio + )} +
+
{data?.base.user_nick_name}
- {!creatorAvailable &&
- -

- {data.base?.user_city}, {data.base?.user_country} -

-
} + {!creatorAvailable && ( +
+ +

+ {data.base?.user_city}, {data.base?.user_country} +

+
+ )}

{data?.base.user_bio !== '' ? data?.base.user_bio : '--'}

- {data?.follow?.followers} followers - {data?.follow?.following} following + + {data?.follow?.followers} followers + + + {data?.follow?.following} following +
{user?.base.user_id === data?.base?.user_id ? ( @@ -107,11 +125,13 @@ function ProfileCardWidget({ className, data }) { Edit - ) : ((status === 'authenticated' && followData?.follow) || followLoading ? + ) : (status === 'authenticated' && followData?.follow) || followLoading ? ( : + ) : ( + @@ -127,9 +147,7 @@ function ProfileCardWidget({ className, data }) { + Follow
} */} - {creatorAvailable && ( - - )} + {creatorAvailable && } ); From 168ee27911a07789c3472dfeb6d9c86c173fbf1a Mon Sep 17 00:00:00 2001 From: "e-peirong.li" Date: Tue, 18 Mar 2025 21:33:08 +0800 Subject: [PATCH 2/5] feat: profile desc support web3bio --- src/domain/profile/widgets/profile-card/ProfileCard.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/domain/profile/widgets/profile-card/ProfileCard.js b/src/domain/profile/widgets/profile-card/ProfileCard.js index bdac4fb..46efe54 100644 --- a/src/domain/profile/widgets/profile-card/ProfileCard.js +++ b/src/domain/profile/widgets/profile-card/ProfileCard.js @@ -20,7 +20,7 @@ import Image from 'next/image'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; import Web3BioIcon from 'public/images/svg/web3bio.svg'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { toast } from 'react-toastify'; import useSWR from 'swr'; @@ -47,6 +47,12 @@ function ProfileCardWidget({ className, data }) { const handle = data?.base.user_handle; const creatorAvailable = data.base?.user_project_owner; + const userDesc = useMemo(() => { + const baseUserDesc = data?.base.user_bio; + const web3BioDesc = data?.web3Bio?.find(v => v.description)?.description; + return baseUserDesc || web3BioDesc || '--'; + }, [data]); + const follow = async () => { if (status !== 'authenticated') { router.push(`/signin?from=${pathname}`); @@ -108,7 +114,7 @@ function ProfileCardWidget({ className, data }) {

)} -

{data?.base.user_bio !== '' ? data?.base.user_bio : '--'}

+

{userDesc}

From 199f95ce9aa4c9b9512190be92d7dbe9118d5027 Mon Sep 17 00:00:00 2001 From: "e-peirong.li" Date: Wed, 19 Mar 2025 17:35:11 +0800 Subject: [PATCH 3/5] feat: profile social info support web3.bio --- public/images/svg/web3bio-2.svg | 11 ++++++++ .../profile/widgets/social-info/SocialInfo.js | 28 +++++++++++++++---- .../profile/widgets/social-info/SocialLink.js | 22 +++++++++++---- 3 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 public/images/svg/web3bio-2.svg diff --git a/public/images/svg/web3bio-2.svg b/public/images/svg/web3bio-2.svg new file mode 100644 index 0000000..956e5f9 --- /dev/null +++ b/public/images/svg/web3bio-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/domain/profile/widgets/social-info/SocialInfo.js b/src/domain/profile/widgets/social-info/SocialInfo.js index effdc09..0a78d71 100644 --- a/src/domain/profile/widgets/social-info/SocialInfo.js +++ b/src/domain/profile/widgets/social-info/SocialInfo.js @@ -21,28 +21,44 @@ import { SvgIcon } from '@/components/Image'; import SocialLink from './SocialLink'; import Web3BioProfile from './Web3BioProfile'; -function socialsInfo(type, link) { +const web3BioSocialKeyMap = { + user_github: 'github', + user_discord: 'discord', + user_twitter: 'x', +}; + +function web3BioLink(type, { web3Bio = [] }) { + const web3BioLinks = web3Bio.reduce((p, c) => (c.links ? { ...p, ...c.links } : p), {}); + return web3BioLinks[web3BioSocialKeyMap[type]]?.link; +} + +function socialsInfo(type, link, web3BioLink) { + const showWeb3Bio = !link && web3BioLink; + switch (type) { case 'user_github': return { name: 'GitHub', icon: 'github-black', - link: link && `https://github.com/${link}`, + link: link ? `https://github.com/${link}` : web3BioLink, enableKey: 'user_show_github', + showWeb3Bio, }; case 'user_x': return { name: 'X', icon: 'x-black', - link: link && `https://x.com/${link}`, + link: link ? `https://x.com/${link}` : web3BioLink, enableKey: 'user_show_x', + showWeb3Bio, }; case 'user_discord': return { name: 'Discord', icon: 'discord-black', - link: link && `https://discord.com/invite/${link}`, + link: link ? `https://discord.com/invite/${link}` : web3BioLink, enableKey: 'user_show_discord', + showWeb3Bio, }; default: return null; @@ -53,7 +69,7 @@ function SocialInfoWidget({ className, data }) { const socials = useMemo( () => Object.keys(data.social) - .map(i => socialsInfo(i, data.social[i])) + .map(i => socialsInfo(i, data.social[i], web3BioLink(i, data))) .filter(s => { if (!s) { return false; @@ -73,7 +89,7 @@ function SocialInfoWidget({ className, data }) {

Social Profiles

{socials.map(i => ( - + {i.name} ))} diff --git a/src/domain/profile/widgets/social-info/SocialLink.js b/src/domain/profile/widgets/social-info/SocialLink.js index bfc53f0..de681af 100644 --- a/src/domain/profile/widgets/social-info/SocialLink.js +++ b/src/domain/profile/widgets/social-info/SocialLink.js @@ -14,19 +14,31 @@ * limitations under the License. */ +import Image from 'next/image'; +import Web3BioIcon from 'public/images/svg/web3bio-2.svg'; + import { SvgIcon } from '@/components/Image'; -function SocialLink({ url, icon, extra, children }) { +function SocialLink({ url, icon, showWeb3Bio, children }) { return ( - -
+ +
{icon && }

{children}

- {extra} + {showWeb3Bio && ( +
+ web3bio +
+ )}
- +
); } From ddc1c3a26437f520f1928ff40254ecacc466673a Mon Sep 17 00:00:00 2001 From: "e-peirong.li" Date: Thu, 20 Mar 2025 21:08:59 +0800 Subject: [PATCH 4/5] feat: update web3bio profile --- public/images/svg/basenames-blue.svg | 10 +++ public/images/svg/farcaster-purple.svg | 3 + public/images/svg/lens-green.svg | 10 +++ .../widgets/social-info/Web3BioProfile.js | 68 ++++++++++++++++--- 4 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 public/images/svg/basenames-blue.svg create mode 100644 public/images/svg/farcaster-purple.svg create mode 100644 public/images/svg/lens-green.svg diff --git a/public/images/svg/basenames-blue.svg b/public/images/svg/basenames-blue.svg new file mode 100644 index 0000000..c723a8d --- /dev/null +++ b/public/images/svg/basenames-blue.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/svg/farcaster-purple.svg b/public/images/svg/farcaster-purple.svg new file mode 100644 index 0000000..cd73094 --- /dev/null +++ b/public/images/svg/farcaster-purple.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/svg/lens-green.svg b/public/images/svg/lens-green.svg new file mode 100644 index 0000000..2767a81 --- /dev/null +++ b/public/images/svg/lens-green.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/domain/profile/widgets/social-info/Web3BioProfile.js b/src/domain/profile/widgets/social-info/Web3BioProfile.js index f4668c2..a928860 100644 --- a/src/domain/profile/widgets/social-info/Web3BioProfile.js +++ b/src/domain/profile/widgets/social-info/Web3BioProfile.js @@ -14,10 +14,9 @@ * limitations under the License. */ +import { SvgIcon } from '@/components/Image'; import { capitalize } from '@/utils'; -import SocialLink from './SocialLink'; - const socialKeyMap = { github: 'user_github', discord: 'user_discord', @@ -36,18 +35,65 @@ function resolveLinks({ social, web3Bio = [] }) { return web3Bio.reduce((p, c) => [].concat(p, filterExistsInOpenBuild(c.links, social)), []); } +function socialsConfig(type) { + switch (type) { + case 'farcaster': + return { + icon: 'farcaster-purple', + color: '#8A63D21A', + }; + case 'lens': + return { + icon: 'lens-green', + color: '#6BC6741A', + }; + case 'basenames': + return { + icon: 'basenames-blue', + color: '#0052FF1A', + }; + default: + return { + icon: 'link', + color: '#1A1A1A1A', + }; + } +} + function Web3BioProfile({ data }) { const links = resolveLinks(data); - return links.length > 0 && ( - <> -

More from web3.bio

-
- {links.map(([k, profile]) => ( - {specialTextMap[k] || capitalize(k)} - ))} -
- + return ( + links.length > 0 && ( + <> +
+

Onchain Identities

+
Built with
+ + web3.bio + +
+ +
+ {links.map(([k, profile]) => ( + + + {specialTextMap[k] || capitalize(k)} + + ))} +
+ + ) ); } From fe4df66292e62f3029e75b4721ef0b3b7c0d4123 Mon Sep 17 00:00:00 2001 From: "e-peirong.li" Date: Sat, 22 Mar 2025 22:44:44 +0800 Subject: [PATCH 5/5] feat: profile avatar support web3.bio --- .../widgets/profile-card/ProfileAvatar.tsx | 65 +++++++++++++++ .../widgets/profile-card/ProfileCard.js | 38 +++------ .../profile/widgets/social-info/SocialInfo.js | 33 ++++---- .../widgets/social-info/Web3BioProfile.js | 81 +++++++++---------- .../components/Avatar/{index.js => index.tsx} | 24 +++++- 5 files changed, 147 insertions(+), 94 deletions(-) create mode 100644 src/domain/profile/widgets/profile-card/ProfileAvatar.tsx rename src/shared/components/Avatar/{index.js => index.tsx} (66%) diff --git a/src/domain/profile/widgets/profile-card/ProfileAvatar.tsx b/src/domain/profile/widgets/profile-card/ProfileAvatar.tsx new file mode 100644 index 0000000..bf91706 --- /dev/null +++ b/src/domain/profile/widgets/profile-card/ProfileAvatar.tsx @@ -0,0 +1,65 @@ +/** + * Copyright 2024 OpenBuild + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Image from 'next/image'; +import Web3BioIcon from 'public/images/svg/web3bio.svg'; + +import Avatar from '#/shared/components/Avatar'; + +function ProfileAvatar({ + data, +}: { + data: { + base: { user_avatar?: string; user_nick_name: string }; + web3Bio?: Array<{ + avatar: string; + }>; + }; +}) { + const size = 110; + const className = 'rounded-full object-fill'; + + const baseAvatar = data?.base?.user_avatar; + const web3BioAvatar = data?.web3Bio?.find(v => v.avatar)?.avatar ?? ''; + const avatarAlt = data?.base?.user_nick_name; + const showBaseAvatar = baseAvatar || !web3BioAvatar; + + return ( +
+ {showBaseAvatar && ( + + )} + {!showBaseAvatar && ( + <> + {avatarAlt} + web3bio + + )} +
+ ); +} + +export default ProfileAvatar; diff --git a/src/domain/profile/widgets/profile-card/ProfileCard.js b/src/domain/profile/widgets/profile-card/ProfileCard.js index 46efe54..f2ba492 100644 --- a/src/domain/profile/widgets/profile-card/ProfileCard.js +++ b/src/domain/profile/widgets/profile-card/ProfileCard.js @@ -16,15 +16,12 @@ import clsx from 'clsx'; import { useSession } from 'next-auth/react'; -import Image from 'next/image'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; -import Web3BioIcon from 'public/images/svg/web3bio.svg'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { toast } from 'react-toastify'; import useSWR from 'swr'; -import Avatar from '@/components/Avatar'; import { Button } from '@/components/Button'; import { RepositioningIcon } from '@/components/Icons'; import { SvgIcon } from '@/components/Image'; @@ -34,6 +31,13 @@ import { useUser } from '#/state/application/hooks'; import SocialInfoWidget from '../social-info'; import IdentitySwitch from './IdentitySwitch'; +import ProfileAvatar from './ProfileAvatar'; + +function userDesc(data) { + const baseUserDesc = data?.base.user_bio; + const web3BioDesc = data?.web3Bio?.find(v => v.description)?.description; + return baseUserDesc || web3BioDesc || '--'; +} function ProfileCardWidget({ className, data }) { const router = useRouter(); @@ -47,12 +51,6 @@ function ProfileCardWidget({ className, data }) { const handle = data?.base.user_handle; const creatorAvailable = data.base?.user_project_owner; - const userDesc = useMemo(() => { - const baseUserDesc = data?.base.user_bio; - const web3BioDesc = data?.web3Bio?.find(v => v.description)?.description; - return baseUserDesc || web3BioDesc || '--'; - }, [data]); - const follow = async () => { if (status !== 'authenticated') { router.push(`/signin?from=${pathname}`); @@ -86,23 +84,7 @@ function ProfileCardWidget({ className, data }) { return (
-
- - {data.web3Bio && ( - web3bio - )} -
- +
{data?.base.user_nick_name}
@@ -114,7 +96,7 @@ function ProfileCardWidget({ className, data }) {

)} -

{userDesc}

+

{userDesc(data)}

diff --git a/src/domain/profile/widgets/social-info/SocialInfo.js b/src/domain/profile/widgets/social-info/SocialInfo.js index 0a78d71..7e62c1a 100644 --- a/src/domain/profile/widgets/social-info/SocialInfo.js +++ b/src/domain/profile/widgets/social-info/SocialInfo.js @@ -24,14 +24,9 @@ import Web3BioProfile from './Web3BioProfile'; const web3BioSocialKeyMap = { user_github: 'github', user_discord: 'discord', - user_twitter: 'x', + user_x: 'twitter', }; -function web3BioLink(type, { web3Bio = [] }) { - const web3BioLinks = web3Bio.reduce((p, c) => (c.links ? { ...p, ...c.links } : p), {}); - return web3BioLinks[web3BioSocialKeyMap[type]]?.link; -} - function socialsInfo(type, link, web3BioLink) { const showWeb3Bio = !link && web3BioLink; @@ -66,21 +61,21 @@ function socialsInfo(type, link, web3BioLink) { } function SocialInfoWidget({ className, data }) { - const socials = useMemo( - () => - Object.keys(data.social) - .map(i => socialsInfo(i, data.social[i], web3BioLink(i, data))) - .filter(s => { - if (!s) { - return false; - } + const socials = useMemo(() => { + const web3BioSocials = (data?.web3Bio ?? []).reduce((p, c) => (c.links ? { ...p, ...c.links } : p), {}); - const enabled = s.enableKey ? data.base[s.enableKey] : true; + return Object.keys(data.social) + .map(i => socialsInfo(i, data.social[i], web3BioSocials[web3BioSocialKeyMap[i]]?.link)) + .filter(s => { + if (!s) { + return false; + } - return enabled && !!s.link; - }), - [data], - ); + const enabled = s.enableKey ? data.base[s.enableKey] : true; + + return enabled && !!s.link; + }); + }, [data]); return (
diff --git a/src/domain/profile/widgets/social-info/Web3BioProfile.js b/src/domain/profile/widgets/social-info/Web3BioProfile.js index a928860..47eee94 100644 --- a/src/domain/profile/widgets/social-info/Web3BioProfile.js +++ b/src/domain/profile/widgets/social-info/Web3BioProfile.js @@ -14,6 +14,8 @@ * limitations under the License. */ +import clsx from 'clsx'; + import { SvgIcon } from '@/components/Image'; import { capitalize } from '@/utils'; @@ -27,6 +29,25 @@ const specialTextMap = { linkedin: 'LinkedIn', }; +const socialsConfig = { + farcaster: { + icon: 'farcaster-purple', + class: 'bg-[#8A63D2]/10 border-[#8A63D2]/10', + }, + lens: { + icon: 'lens-green', + class: 'bg-[#6BC674]/10 border-[#6BC674]/10', + }, + basenames: { + icon: 'basenames-blue', + class: 'bg-[#0052FF]/10 border-[#0052FF]/10', + }, + default: { + icon: 'link', + class: 'bg-[#1A1A1A]/10 border-[#1A1A1A]/10', + }, +}; + function filterExistsInOpenBuild(links, social) { return Object.entries(links).filter(([k]) => !socialKeyMap[k] || !(socialKeyMap[k] in social)); } @@ -35,31 +56,6 @@ function resolveLinks({ social, web3Bio = [] }) { return web3Bio.reduce((p, c) => [].concat(p, filterExistsInOpenBuild(c.links, social)), []); } -function socialsConfig(type) { - switch (type) { - case 'farcaster': - return { - icon: 'farcaster-purple', - color: '#8A63D21A', - }; - case 'lens': - return { - icon: 'lens-green', - color: '#6BC6741A', - }; - case 'basenames': - return { - icon: 'basenames-blue', - color: '#0052FF1A', - }; - default: - return { - icon: 'link', - color: '#1A1A1A1A', - }; - } -} - function Web3BioProfile({ data }) { const links = resolveLinks(data); @@ -69,28 +65,27 @@ function Web3BioProfile({ data }) {

Onchain Identities

Built with
- - web3.bio + + Web3.bio
- {links.map(([k, profile]) => ( - - - {specialTextMap[k] || capitalize(k)} - - ))} + {links.map(([k, profile]) => { + const socialConfig = socialsConfig[k] ?? socialsConfig.default; + return ( + + + {specialTextMap[k] || capitalize(k)} + + ); + })}
) diff --git a/src/shared/components/Avatar/index.js b/src/shared/components/Avatar/index.tsx similarity index 66% rename from src/shared/components/Avatar/index.js rename to src/shared/components/Avatar/index.tsx index 9c5963e..6c0eaef 100644 --- a/src/shared/components/Avatar/index.js +++ b/src/shared/components/Avatar/index.tsx @@ -18,9 +18,23 @@ import clsx from 'clsx'; import Image from '../Image'; -function Avatar({ className, size, src, defaultSrc, alt, user }) { +function Avatar({ + size, + className, + src, + defaultSrc, + alt, + user, +}: { + size: number; + className?: string; + src?: string; + defaultSrc?: string; + alt?: string; + user?: { user_handle: string; user_nick_name: string; user_avatar: string }; +}) { const resolvedAlt = alt || (user && user.user_nick_name) || ''; - const imgClassName = `rounded-full object-fill w-[${size}px] h-[${size}px]`; + const imgClassName = 'rounded-full object-fill'; const imgProps = { width: size, height: size, @@ -29,10 +43,12 @@ function Avatar({ className, size, src, defaultSrc, alt, user }) { }; return user ? ( - + {resolvedAlt} - ) : {resolvedAlt}; + ) : ( + {resolvedAlt} + ); } export default Avatar;