1- import { useMemo } from 'react' ;
1+ import { useEffect , useMemo , useState } from 'react' ;
22
33import { BonsaiHelpers } from '@/bonsai/ontology' ;
44import { useToBlob } from '@hugocxl/react-to-image' ;
55import styled from 'styled-components' ;
66import tw from 'twin.macro' ;
77
88import { AnalyticsEvents } from '@/constants/analytics' ;
9+ import { ASSET_ICON_MAP } from '@/constants/assets' ;
910import { ButtonAction } from '@/constants/buttons' ;
1011import { DialogProps , SharePNLAnalyticsDialogProps } from '@/constants/dialogs' ;
1112import { STRING_KEYS } from '@/constants/localization' ;
1213import { IndexerPositionSide } from '@/types/indexer/indexerApiGen' ;
1314
15+ import { useCustomNotification } from '@/hooks/useCustomNotification' ;
1416import { useAppSelectorWithArgs } from '@/hooks/useParameterizedSelector' ;
1517import { useStringGetter } from '@/hooks/useStringGetter' ;
1618
@@ -38,8 +40,14 @@ const copyBlobToClipboard = async (blob: Blob | null) => {
3840 return ;
3941 }
4042
41- const item = new ClipboardItem ( { 'image/png' : blob } ) ;
42- await navigator . clipboard . write ( [ item ] ) ;
43+ try {
44+ const item = new ClipboardItem ( { 'image/png' : blob } ) ;
45+ await navigator . clipboard . write ( [ item ] ) ;
46+ } catch ( error ) {
47+ // eslint-disable-next-line no-console
48+ console . error ( 'Failed to copy blob. ' , error ) ;
49+ throw error ;
50+ }
4351} ;
4452
4553export const SharePNLAnalyticsDialog = ( {
@@ -56,12 +64,27 @@ export const SharePNLAnalyticsDialog = ({
5664 const stringGetter = useStringGetter ( ) ;
5765 const dispatch = useAppDispatch ( ) ;
5866 const logoUrl = useAppSelectorWithArgs ( BonsaiHelpers . assets . selectAssetLogo , assetId ) ;
59-
6067 const symbol = getDisplayableAssetFromBaseAsset ( assetId ) ;
68+ const notify = useCustomNotification ( ) ;
69+ const [ isCopied , setIsCopied ] = useState ( false ) ;
6170
6271 const [ { isLoading : isCopying } , convert , ref ] = useToBlob < HTMLDivElement > ( {
6372 quality : 1.0 ,
64- onSuccess : copyBlobToClipboard ,
73+ onSuccess : async ( blob ) => {
74+ await copyBlobToClipboard ( blob ) ;
75+ setIsCopied ( true ) ;
76+ setTimeout ( ( ) => setIsCopied ( false ) , 2000 ) ;
77+ } ,
78+ onError : ( error ) => {
79+ // eslint-disable-next-line no-console
80+ console . error ( 'Failed to copy blob. ' , error ) ;
81+ notify ( {
82+ title : stringGetter ( { key : STRING_KEYS . ERROR } ) ,
83+ body : stringGetter ( { key : STRING_KEYS . SOMETHING_WENT_WRONG } ) ,
84+ slotTitleLeft : < Icon iconName = { IconName . Warning } tw = "text-color-warning" /> ,
85+ toastDuration : 5000 ,
86+ } ) ;
87+ } ,
6588 } ) ;
6689
6790 const [ { isLoading : isSharing } , convertShare , refShare ] = useToBlob < HTMLDivElement > ( {
@@ -98,6 +121,36 @@ export const SharePNLAnalyticsDialog = ({
98121
99122 const [ assetLeft , assetRight ] = marketId . split ( '-' ) ;
100123
124+ const [ logoBase64 , setLogoBase64 ] = useState < string | null > ( null ) ;
125+
126+ const localLogoUrl = useMemo ( ( ) => {
127+ if ( assetId && Object . prototype . hasOwnProperty . call ( ASSET_ICON_MAP , assetId ) ) {
128+ return ASSET_ICON_MAP [ assetId as keyof typeof ASSET_ICON_MAP ] ;
129+ }
130+ return logoUrl ;
131+ } , [ logoUrl , assetId ] ) ;
132+
133+ useEffect ( ( ) => {
134+ if ( ! logoUrl ) return ;
135+
136+ const img = new Image ( ) ;
137+ img . crossOrigin = 'anonymous' ;
138+ img . src = logoUrl ;
139+ img . onload = ( ) => {
140+ const canvas = document . createElement ( 'canvas' ) ;
141+ canvas . width = img . width || 26 ;
142+ canvas . height = img . height || 26 ;
143+ const ctx = canvas . getContext ( '2d' ) ;
144+ ctx ?. drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ;
145+ setLogoBase64 ( canvas . toDataURL ( 'image/png' ) ) ;
146+ } ;
147+ img . onerror = ( ) => {
148+ // eslint-disable-next-line no-console
149+ console . error ( 'Failed to load asset image. ' , logoUrl ) ;
150+ setLogoBase64 ( null ) ;
151+ } ;
152+ } , [ logoUrl ] ) ;
153+
101154 return (
102155 < Dialog isOpen setIsOpen = { setIsOpen } title = { stringGetter ( { key : STRING_KEYS . SHARE_ACTIVITY } ) } >
103156 < $ShareableCard
@@ -110,7 +163,11 @@ export const SharePNLAnalyticsDialog = ({
110163 >
111164 < div tw = "flexColumn h-full" >
112165 < div tw = "row mb-0.75 gap-0.5" >
113- < AssetIcon logoUrl = { logoUrl } symbol = { assetId } tw = "[--asset-icon-size:1.625rem]" />
166+ < AssetIcon
167+ logoUrl = { logoBase64 ?? localLogoUrl }
168+ symbol = { assetId }
169+ tw = "[--asset-icon-size:1.625rem]"
170+ />
114171
115172 < span >
116173 < span tw = "text-color-text-2 font-base-bold" > { assetLeft } </ span > /{ assetRight }
@@ -126,7 +183,7 @@ export const SharePNLAnalyticsDialog = ({
126183 showSign = { ShowSign . Both }
127184 />
128185 < div className = "mt-auto flex h-auto max-h-[3rem] w-full justify-center" >
129- < LogoShortIcon tw = "h-auto w-full " />
186+ < LogoShortIcon tw = "h-auto w-auto object-scale-down " />
130187 </ div >
131188 </ div >
132189
@@ -176,7 +233,7 @@ export const SharePNLAnalyticsDialog = ({
176233 < div tw = "flex gap-1" >
177234 < $Action
178235 action = { ButtonAction . Secondary }
179- slotLeft = { < Icon iconName = { IconName . Copy } /> }
236+ slotLeft = { < Icon iconName = { isCopied ? IconName . Check : IconName . Copy } /> }
180237 onClick = { ( ) => {
181238 track ( AnalyticsEvents . SharePnlCopied ( { asset : assetId } ) ) ;
182239 convert ( ) ;
@@ -185,7 +242,7 @@ export const SharePNLAnalyticsDialog = ({
185242 isLoading : isCopying ,
186243 } }
187244 >
188- { stringGetter ( { key : STRING_KEYS . COPY } ) }
245+ { stringGetter ( { key : isCopied ? STRING_KEYS . COPIED : STRING_KEYS . COPY } ) }
189246 </ $Action >
190247 < $Action
191248 action = { ButtonAction . Primary }
0 commit comments