11"use client" ;
2- import { CheckIcon , CircleSlashIcon , CopyIcon , XIcon } from "lucide-react" ;
2+ import { CircleSlashIcon , XIcon } from "lucide-react" ;
33import { useMemo } from "react" ;
44import { isAddress , type ThirdwebClient , ZERO_ADDRESS } from "thirdweb" ;
55import { Blobbie , type SocialProfile , useSocialProfiles } from "thirdweb/react" ;
66import { Img } from "@/components/blocks/Img" ;
77import { Avatar , AvatarFallback , AvatarImage } from "@/components/ui/avatar" ;
8- import { Badge } from "@/components/ui/badge" ;
98import { Button } from "@/components/ui/button" ;
109import {
1110 HoverCard ,
1211 HoverCardContent ,
1312 HoverCardTrigger ,
1413} from "@/components/ui/hover-card" ;
1514import { ToolTipLabel } from "@/components/ui/tooltip" ;
16- import { useClipboard } from "@/hooks/useClipboard" ;
1715import { cn } from "@/lib/utils" ;
1816import { resolveSchemeWithErrorHandler } from "@/utils/resolveSchemeWithErrorHandler" ;
17+ import { CopyTextButton } from "../ui/CopyTextButton" ;
18+ import { Skeleton } from "../ui/skeleton" ;
1919
20- export function WalletAddress ( props : {
20+ type WalletAddressProps = {
2121 address : string | undefined ;
2222 shortenAddress ?: boolean ;
2323 className ?: string ;
2424 iconClassName ?: string ;
2525 client : ThirdwebClient ;
2626 fallbackIcon ?: React . ReactNode ;
27- } ) {
27+ } ;
28+
29+ export function WalletAddress ( props : WalletAddressProps ) {
30+ const profiles = useSocialProfiles ( {
31+ address : props . address ,
32+ client : props . client ,
33+ } ) ;
34+
35+ return (
36+ < WalletAddressUI
37+ { ...props }
38+ profiles = { {
39+ data : profiles . data || [ ] ,
40+ isPending : profiles . isPending ,
41+ } }
42+ />
43+ ) ;
44+ }
45+
46+ export function WalletAddressUI (
47+ props : WalletAddressProps & {
48+ profiles : {
49+ data : SocialProfile [ ] ;
50+ isPending : boolean ;
51+ } ;
52+ } ,
53+ ) {
2854 // default back to zero address if no address provided
2955 const address = useMemo ( ( ) => props . address || ZERO_ADDRESS , [ props . address ] ) ;
3056
31- const [ shortenedAddress , lessShortenedAddress ] = useMemo ( ( ) => {
57+ const [ shortenedAddress , _lessShortenedAddress ] = useMemo ( ( ) => {
3258 return [
3359 props . shortenAddress !== false
3460 ? `${ address . slice ( 0 , 6 ) } ...${ address . slice ( - 4 ) } `
@@ -37,17 +63,10 @@ export function WalletAddress(props: {
3763 ] ;
3864 } , [ address , props . shortenAddress ] ) ;
3965
40- const profiles = useSocialProfiles ( {
41- address : address ,
42- client : props . client ,
43- } ) ;
44-
45- const { onCopy, hasCopied } = useClipboard ( address , 2000 ) ;
46-
4766 if ( ! isAddress ( address ) ) {
4867 return (
4968 < ToolTipLabel hoverable label = { address } >
50- < span className = "flex items-center gap-2 underline-offset-4 hover:underline" >
69+ < span className = "flex items-center gap-2 underline-offset-4 hover:underline w-fit " >
5170 < div className = "flex size-5 items-center justify-center rounded-full border bg-background" >
5271 < XIcon className = "size-4 text-muted-foreground" />
5372 </ div >
@@ -88,89 +107,82 @@ export function WalletAddress(props: {
88107 < WalletAvatar
89108 address = { address }
90109 iconClassName = { props . iconClassName }
91- profiles = { profiles . data || [ ] }
110+ profiles = { props . profiles . data }
92111 thirdwebClient = { props . client }
93112 fallbackIcon = { props . fallbackIcon }
94113 />
95114 ) }
96115 < span className = "cursor-pointer font-mono max-w-full truncate" >
97- { profiles . data ?. [ 0 ] ?. name || shortenedAddress }
116+ { props . profiles . data ?. [ 0 ] ?. name || shortenedAddress }
98117 </ span >
99118 </ Button >
100119 </ HoverCardTrigger >
101120 < HoverCardContent
102- className = "w-80 border-border"
121+ className = "w-[calc(100vw-2rem)] lg:w-[450px] border-border rounded-xl p-4 lg:p-6 "
103122 onClick = { ( e ) => {
104123 // do not close the hover card when clicking anywhere in the content
105124 e . stopPropagation ( ) ;
106125 } }
107126 >
108127 < div className = "space-y-4" >
109- < div className = "flex items-center justify-between" >
110- < h3 className = "font-semibold text-lg" > Wallet Address</ h3 >
111- < Button
112- className = "flex items-center gap-2"
113- onClick = { onCopy }
114- size = "sm"
115- variant = "outline"
116- >
117- { hasCopied ? (
118- < CheckIcon className = "h-4 w-4" />
119- ) : (
120- < CopyIcon className = "h-4 w-4" />
121- ) }
122- { hasCopied ? "Copied!" : "Copy" }
123- </ Button >
128+ < div className = "space-y-1" >
129+ < h3 className = "font-medium text-sm" > Wallet Address</ h3 >
130+
131+ < CopyTextButton
132+ textToShow = { address }
133+ textToCopy = { address }
134+ tooltip = "Copy address"
135+ copyIconPosition = "right"
136+ variant = "ghost"
137+ className = "text-muted-foreground -translate-x-1.5"
138+ />
124139 </ div >
125- < p className = "rounded bg-muted p-2 text-center font-mono text-sm" >
126- { lessShortenedAddress }
127- </ p >
128- < h3 className = "font-semibold text-lg" > Social Profiles</ h3 >
129- { profiles . isPending ? (
130- < p className = "text-muted-foreground text-sm" > Loading profiles...</ p >
131- ) : ! profiles . data ?. length ? (
132- < p className = "text-muted-foreground text-sm" > No profiles found</ p >
133- ) : (
134- profiles . data ?. map ( ( profile ) => {
135- const walletAvatarLink = resolveSchemeWithErrorHandler ( {
136- client : props . client ,
137- uri : profile . avatar ,
138- } ) ;
139-
140- return (
141- < div
142- className = "flex flex-row items-center gap-2"
143- key = { profile . type + profile . name }
144- >
145- { walletAvatarLink && (
146- < Avatar >
147- < AvatarImage
148- alt = { profile . name }
149- className = "object-cover"
150- src = { walletAvatarLink }
151- />
152- { profile . name && (
153- < AvatarFallback >
154- { profile . name . slice ( 0 , 2 ) }
155- </ AvatarFallback >
156- ) }
157- </ Avatar >
158- ) }
159- < div className = "flex w-full flex-col gap-1" >
160- < div className = "flex w-full flex-row items-center justify-between gap-4" >
161- < h4 className = "font-semibold text-md" > { profile . name } </ h4 >
162- < Badge variant = "outline" > { profile . type } </ Badge >
140+
141+ < div className = "space-y-1 border-t pt-5 border-dashed" >
142+ < h3 className = "font-medium text-sm" > Social Profiles</ h3 >
143+
144+ { props . profiles . isPending ? (
145+ < Skeleton className = "h-4 w-[50%]" />
146+ ) : ! props . profiles . data ?. length ? (
147+ < p className = "text-muted-foreground text-sm" > No profiles found</ p >
148+ ) : (
149+ < div className = "!mt-2" >
150+ { props . profiles . data ?. map ( ( profile ) => {
151+ const walletAvatarLink = resolveSchemeWithErrorHandler ( {
152+ client : props . client ,
153+ uri : profile . avatar ,
154+ } ) ;
155+
156+ return (
157+ < div
158+ className = "flex flex-row items-center gap-3 py-2"
159+ key = { profile . type + profile . name }
160+ >
161+ < Avatar className = "size-9" >
162+ < AvatarImage
163+ alt = { profile . name }
164+ className = "object-cover"
165+ src = { walletAvatarLink }
166+ />
167+ { profile . name && (
168+ < AvatarFallback >
169+ { profile . name . slice ( 0 , 2 ) }
170+ </ AvatarFallback >
171+ ) }
172+ </ Avatar >
173+
174+ < div className = "space-y-0.5" >
175+ < h4 className = "text-sm leading-none" > { profile . name } </ h4 >
176+ < span className = "text-muted-foreground text-xs leading-none capitalize" >
177+ { profile . type === "ens" ? "ENS" : profile . type }
178+ </ span >
179+ </ div >
163180 </ div >
164- { profile . bio && (
165- < p className = "line-clamp-1 whitespace-normal text-muted-foreground text-sm" >
166- { profile . bio }
167- </ p >
168- ) }
169- </ div >
170- </ div >
171- ) ;
172- } )
173- ) }
181+ ) ;
182+ } ) }
183+ </ div >
184+ ) }
185+ </ div >
174186 </ div >
175187 </ HoverCardContent >
176188 </ HoverCard >
0 commit comments