diff --git a/public/images/svg/basenames-blue.svg b/public/images/svg/basenames-blue.svg new file mode 100644 index 00000000..c723a8d3 --- /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 00000000..cd730947 --- /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 00000000..2767a814 --- /dev/null +++ b/public/images/svg/lens-green.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/svg/web3bio-2.svg b/public/images/svg/web3bio-2.svg new file mode 100644 index 00000000..956e5f9b --- /dev/null +++ b/public/images/svg/web3bio-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + 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 00000000..81e8b31e --- /dev/null +++ b/src/domain/profile/widgets/profile-card/ProfileAvatar.tsx @@ -0,0 +1,73 @@ +/** + * 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 '@/components/Avatar'; + +function ProfileAvatar({ + data, + className, +}: { + data: { + base: { user_avatar?: string; user_nick_name: string }; + web3Bio?: Array<{ + avatar: string; + }>; + }; + className: string; +}) { + const size = 110; + const avatarClassName = 'rounded-full object-fill'; + + const baseAvatar = data?.base?.user_avatar; + const web3BioAvatar = data?.web3Bio?.filter(v => v.avatar).find(v => v.avatar)?.avatar ?? ''; + const avatarAlt = data?.base?.user_nick_name; + const showBaseAvatar = (baseAvatar && !baseAvatar.includes('/config/avatar')) || !web3BioAvatar; + + return ( +
+ {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 d600e99d..4e0fca80 100644 --- a/src/domain/profile/widgets/profile-card/ProfileCard.js +++ b/src/domain/profile/widgets/profile-card/ProfileCard.js @@ -22,7 +22,6 @@ 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'; @@ -32,6 +31,13 @@ import { useUser } from '#/state/application/hooks'; import SocialInfoWidget from '../social-info'; import IdentitySwitch from './IdentitySwitch'; +import ProfileAvatar from './ProfileAvatar'; + +function getUserDesc(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(); @@ -78,27 +84,27 @@ function ProfileCardWidget({ className, data }) { return (
- +
{data?.base.user_nick_name}
- {!creatorAvailable &&
- -

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

-
} -

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

+ {!creatorAvailable && ( +
+ +

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

+
+ )} +

{getUserDesc(data)}

- {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 +113,13 @@ function ProfileCardWidget({ className, data }) { Edit - ) : ((status === 'authenticated' && followData?.follow) || followLoading ? + ) : (status === 'authenticated' && followData?.follow) || followLoading ? ( : + ) : ( + @@ -127,9 +135,7 @@ function ProfileCardWidget({ className, data }) { + Follow
} */} - {creatorAvailable && ( - - )} + {creatorAvailable && } ); diff --git a/src/domain/profile/widgets/social-info/SocialInfo.js b/src/domain/profile/widgets/social-info/SocialInfo.js index effdc09b..7e62c1a1 100644 --- a/src/domain/profile/widgets/social-info/SocialInfo.js +++ b/src/domain/profile/widgets/social-info/SocialInfo.js @@ -21,28 +21,39 @@ 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_x: 'twitter', +}; + +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; @@ -50,21 +61,21 @@ function socialsInfo(type, link) { } function SocialInfoWidget({ className, data }) { - const socials = useMemo( - () => - Object.keys(data.social) - .map(i => socialsInfo(i, data.social[i])) - .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 (
@@ -73,7 +84,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 bfc53f02..de681af2 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 +
+ )}
- +
); } diff --git a/src/domain/profile/widgets/social-info/Web3BioProfile.js b/src/domain/profile/widgets/social-info/Web3BioProfile.js index f4668c29..47eee945 100644 --- a/src/domain/profile/widgets/social-info/Web3BioProfile.js +++ b/src/domain/profile/widgets/social-info/Web3BioProfile.js @@ -14,9 +14,10 @@ * limitations under the License. */ -import { capitalize } from '@/utils'; +import clsx from 'clsx'; -import SocialLink from './SocialLink'; +import { SvgIcon } from '@/components/Image'; +import { capitalize } from '@/utils'; const socialKeyMap = { github: 'user_github', @@ -28,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)); } @@ -39,15 +59,36 @@ function resolveLinks({ social, web3Bio = [] }) { 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]) => { + 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 9c5963ed..6c0eaeff 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;