From 25d3b15e596611ded81e149346268a615c772c76 Mon Sep 17 00:00:00 2001 From: dwjanus Date: Thu, 22 Jan 2026 10:55:45 -0500 Subject: [PATCH 1/3] scaffolding --- public/configs/v1/env.json | 48 +- src/hooks/useEndpointsConfig.ts | 2 + src/hooks/useSharePnlImage.ts | 136 +++++ src/views/dialogs/SharePNLAnalyticsDialog.tsx | 549 ++++++++++-------- 4 files changed, 491 insertions(+), 244 deletions(-) create mode 100644 src/hooks/useSharePnlImage.ts diff --git a/public/configs/v1/env.json b/public/configs/v1/env.json index 27e491af9..df698a204 100644 --- a/public/configs/v1/env.json +++ b/public/configs/v1/env.json @@ -462,7 +462,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4dev.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -504,7 +505,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -549,7 +551,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "http://dev3-faucet-lb-public-1644791410.us-east-2.elb.amazonaws.com", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -594,7 +597,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4dev4.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -636,7 +640,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -680,7 +685,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geo": "https://api.dydx.exchange/v4/geo", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -724,7 +730,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "apps": { "ios": { @@ -780,7 +787,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -826,7 +834,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-staging-e2fb353831a4.herokuapp.com" + "spotApi": "https://dydx-solana-api-staging-e2fb353831a4.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [ "dydxvaloper1vvc9vl6z9pu0vt2y79d0ln8zp6qmpmrhxx99h4", @@ -874,7 +883,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -919,7 +929,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -964,7 +975,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1009,7 +1021,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1054,7 +1067,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1099,7 +1113,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1144,7 +1159,8 @@ "geoV2": "[geo v2 endpoint for mainnet]", "stakingAPR": "[staking APR endpoint for mainnet]", "affiliates": "[affiliates endpoint for mainnet]", - "spotApi": "[spot api endpoint for mainnet]" + "spotApi": "[spot api endpoint for mainnet]", + "pnlImageApi": "[pnl image api endpoint for mainnet]" }, "stakingValidators": [], "featureFlags": { diff --git a/src/hooks/useEndpointsConfig.ts b/src/hooks/useEndpointsConfig.ts index 2f724102f..49f71b356 100644 --- a/src/hooks/useEndpointsConfig.ts +++ b/src/hooks/useEndpointsConfig.ts @@ -19,6 +19,7 @@ export interface EndpointsConfig { affiliates?: string; spotApi: string; geoV2: string; + pnlImageApi: string; } export const useEndpointsConfig = () => { @@ -38,5 +39,6 @@ export const useEndpointsConfig = () => { affiliatesBaseUrl: endpointsConfig.affiliates, spotApi: endpointsConfig.spotApi, geoV2: endpointsConfig.geoV2, + pnlImageApi: endpointsConfig.pnlImageApi, }; }; diff --git a/src/hooks/useSharePnlImage.ts b/src/hooks/useSharePnlImage.ts new file mode 100644 index 000000000..3169467d4 --- /dev/null +++ b/src/hooks/useSharePnlImage.ts @@ -0,0 +1,136 @@ +import { logBonsaiError } from '@/bonsai/logs'; +import { useQuery } from '@tanstack/react-query'; + +import { IndexerPositionSide } from '@/types/indexer/indexerApiGen'; + +import { useAccounts } from '@/hooks/useAccounts'; + +import { getOpenPositions } from '@/state/accountSelectors'; +import { useAppSelector } from '@/state/appTypes'; + +import { Nullable } from '@/lib/typeUtils'; + +// import { useEndpointsConfig } from './useEndpointsConfig'; + +export type SharePnlImageParams = { + marketId: string; + side: Nullable; + leverage: Nullable; + oraclePrice: Nullable; + entryPrice: Nullable; + unrealizedPnl: Nullable; + type?: 'open' | 'closed' | 'liquidated' | undefined; +}; + +// Helper to convert image URL to base64 +const imageToBase64 = (imageUrl: string) => { + let base64 = null; + + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.src = imageUrl; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); + base64 = canvas.toDataURL('image/png'); + }; + img.onerror = () => { + // eslint-disable-next-line no-console + console.error('Failed to load image for base64 conversion:', imageUrl); + base64 = null; + }; + + return base64; +}; + +export const useSharePnlImage = ({ + marketId, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type = 'open', +}: SharePnlImageParams) => { + // const { pnlImageApi } = useEndpointsConfig(); + const pnlImageApi = 'https://image-generator.bonk.trade/generate-trade-card-web'; + // 'https://pp-image-generator-bonk-ab5fd4e66c9b.herokuapp.com/generate-trade-card-web'; + + // Get user wallet address for username + const { dydxAddress } = useAccounts(); + + // Get full position data from state + const openPositions = useAppSelector(getOpenPositions); + const position = openPositions?.find((p) => p.market === marketId); + const userImage = imageToBase64('/hedgie-profile.png'); + + const queryFn = async (): Promise => { + // if (!pnlImageApi || !dydxAddress) { + if (!dydxAddress) { + return undefined; + } + + // Build the request body matching the API's zod schema + const requestBody = { + ticker: marketId, + type, + leverage: leverage ?? 0, + username: dydxAddress, + isLong: side === IndexerPositionSide.LONG, + isCross: position?.marginMode === 'CROSS', + + // Optional fields - include if available + size: position?.unsignedSize.toNumber(), + userImage, + pnl: position?.realizedPnl.toNumber(), + uPnl: unrealizedPnl ?? undefined, + pnlPercentage: position?.updatedUnrealizedPnlPercent?.toNumber(), + entryPx: entryPrice ?? undefined, + liquidationPx: position?.liquidationPrice?.toNumber(), + markPx: oraclePrice ?? undefined, + }; + + const response = await fetch(pnlImageApi, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + logBonsaiError('useSharePnlImage', 'Failed to fetch share image', { response }); + throw new Error(`Failed to fetch share image: ${response.status}`); + } + + return response.blob(); + }; + + return useQuery({ + queryKey: [ + 'sharePnlImage', + marketId, + dydxAddress, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type, + position?.marginMode, + position?.unsignedSize.toString(), + position?.liquidationPrice?.toString(), + ], + queryFn, + enabled: Boolean(dydxAddress), + refetchOnWindowFocus: false, + refetchOnReconnect: false, + staleTime: 1000 * 60 * 2, // 2 minutes + retry: 2, + retryDelay: 1000, + retryOnMount: true, + }); +}; diff --git a/src/views/dialogs/SharePNLAnalyticsDialog.tsx b/src/views/dialogs/SharePNLAnalyticsDialog.tsx index 94ae1f72e..ab85f6630 100644 --- a/src/views/dialogs/SharePNLAnalyticsDialog.tsx +++ b/src/views/dialogs/SharePNLAnalyticsDialog.tsx @@ -1,38 +1,29 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; -import { BonsaiHelpers } from '@/bonsai/ontology'; -import { useToBlob } from '@hugocxl/react-to-image'; import styled from 'styled-components'; import tw from 'twin.macro'; import { AnalyticsEvents } from '@/constants/analytics'; -import { ASSET_ICON_MAP } from '@/constants/assets'; import { ButtonAction } from '@/constants/buttons'; import { DialogProps, SharePNLAnalyticsDialogProps } from '@/constants/dialogs'; import { STRING_KEYS } from '@/constants/localization'; -import { IndexerPositionSide } from '@/types/indexer/indexerApiGen'; -import { useCustomNotification } from '@/hooks/useCustomNotification'; -import { useAppSelectorWithArgs } from '@/hooks/useParameterizedSelector'; +// import { useCustomNotification } from '@/hooks/useCustomNotification'; +import { useSharePnlImage } from '@/hooks/useSharePnlImage'; import { useStringGetter } from '@/hooks/useStringGetter'; -import { LogoShortIcon } from '@/icons/logo-short'; import { layoutMixins } from '@/styles/layoutMixins'; -import { AssetIcon } from '@/components/AssetIcon'; import { Button } from '@/components/Button'; import { Dialog } from '@/components/Dialog'; import { Icon, IconName } from '@/components/Icon'; -import { Output, OutputType, ShowSign } from '@/components/Output'; -import { QrCode } from '@/components/QrCode'; -import { Tag, TagSign } from '@/components/Tag'; +import { LoadingSpinner } from '@/components/Loading/LoadingSpinner'; import { useAppDispatch } from '@/state/appTypes'; import { closeDialog } from '@/state/dialogs'; import { track } from '@/lib/analytics/analytics'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; -import { MustBigNumber } from '@/lib/numbers'; import { triggerTwitterIntent } from '@/lib/twitter'; const copyBlobToClipboard = async (blob: Blob | null) => { @@ -54,7 +45,6 @@ export const SharePNLAnalyticsDialog = ({ marketId, assetId, side, - sideLabel, leverage, oraclePrice, entryPrice, @@ -63,206 +53,309 @@ export const SharePNLAnalyticsDialog = ({ }: DialogProps) => { const stringGetter = useStringGetter(); const dispatch = useAppDispatch(); - const logoUrl = useAppSelectorWithArgs(BonsaiHelpers.assets.selectAssetLogo, assetId); const symbol = getDisplayableAssetFromBaseAsset(assetId); - const notify = useCustomNotification(); + // const notify = useCustomNotification(); + const isCopying = useRef(false); + const isSharing = useRef(false); const [isCopied, setIsCopied] = useState(false); - const [{ isLoading: isCopying }, convert, ref] = useToBlob({ - quality: 1.0, - onSuccess: async (blob) => { - await copyBlobToClipboard(blob); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }, - onError: (error) => { - // eslint-disable-next-line no-console - console.error('Failed to copy blob. ', error); - notify({ - title: stringGetter({ key: STRING_KEYS.ERROR }), - body: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG }), - slotTitleLeft: , - toastDuration: 5000, - }); - }, + const getPnlImage = useSharePnlImage({ + marketId, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type: 'open', }); - const [{ isLoading: isSharing }, convertShare, refShare] = useToBlob({ - quality: 1.0, - onSuccess: async (blob) => { - await copyBlobToClipboard(blob); - - triggerTwitterIntent({ - text: `${stringGetter({ - key: STRING_KEYS.TWEET_MARKET_POSITION, - params: { - MARKET: symbol, - }, - })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, - related: 'bonk_inu', - }); - - dispatch(closeDialog()); - }, - }); - - const sideSign = useMemo(() => { - switch (side) { - case IndexerPositionSide.LONG: - return TagSign.Positive; - case IndexerPositionSide.SHORT: - return TagSign.Negative; - default: - return TagSign.Neutral; - } - }, [side]); - - const unrealizedPnlIsNegative = MustBigNumber(unrealizedPnl).isNegative(); - - const [assetLeft, assetRight] = marketId.split('-'); - - const [logoBase64, setLogoBase64] = useState(null); - - const localLogoUrl = useMemo(() => { - if (assetId && Object.prototype.hasOwnProperty.call(ASSET_ICON_MAP, assetId)) { - return ASSET_ICON_MAP[assetId as keyof typeof ASSET_ICON_MAP]; - } - return logoUrl; - }, [logoUrl, assetId]); - - useEffect(() => { - if (!logoUrl) return; - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.src = logoUrl; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width || 26; - canvas.height = img.height || 26; - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); - setLogoBase64(canvas.toDataURL('image/png')); - }; - img.onerror = () => { - // eslint-disable-next-line no-console - console.error('Failed to load asset image. ', logoUrl); - setLogoBase64(null); - }; - }, [logoUrl]); + const pnlImage = useMemo(() => getPnlImage.data ?? undefined, [getPnlImage.data]); + + const copyPnlImage = async () => { + if (isCopying.current || !pnlImage) return; + isCopying.current = true; + await copyBlobToClipboard(pnlImage); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + isCopying.current = false; + }; + + const sharePnlImage = async () => { + if (isSharing.current || !pnlImage) return; + isSharing.current = true; + await copyBlobToClipboard(pnlImage); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + + triggerTwitterIntent({ + text: `${stringGetter({ + key: STRING_KEYS.TWEET_MARKET_POSITION, + params: { + MARKET: symbol, + }, + })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, + related: 'bonk_inu', + }); + isSharing.current = false; + + dispatch(closeDialog()); + }; return ( - <$ShareableCard - ref={(domNode) => { - if (domNode) { - ref(domNode); - refShare(domNode); - } - }} - > -
-
- - - - {assetLeft}/{assetRight} - - - {sideLabel} -
- - <$HighlightOutput - isNegative={unrealizedPnlIsNegative} - type={OutputType.CompactFiat} - value={unrealizedPnl} - showSign={ShowSign.Both} - /> -
- -
-
- -
-
- <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.ENTRY })} - - <$ShareableCardStatOutput type={OutputType.Fiat} value={entryPrice} withSubscript /> - - <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.INDEX })} - - <$ShareableCardStatOutput type={OutputType.Fiat} value={oraclePrice} withSubscript /> - - <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.LEVERAGE })} - - - <$ShareableCardStatOutput - type={OutputType.Multiple} - value={leverage} - showSign={ShowSign.None} - /> -
- -
- {import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL ? ( - <$QrCode - tw="rounded-0.25 bg-color-layer-3" - size={68} - value={import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL} - options={{ - cells: { - fill: 'var(--color-text-2)', - }, - finder: { - fill: 'var(--color-text-2)', - }, - }} - /> - ) : ( -
- )} -
+ <$ShareableCard> + {!pnlImage ? ( + + ) : ( + Shareable PNL Card + )} + +
+ <$Action + action={ButtonAction.Secondary} + slotLeft={} + onClick={() => { + track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); + copyPnlImage(); + }} + state={{ + isLoading: !!isCopying.current, + }} + > + {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} + + <$Action + action={ButtonAction.Primary} + slotLeft={} + onClick={() => { + track(AnalyticsEvents.SharePnlShared({ asset: assetId })); + sharePnlImage(); + }} + state={{ + isLoading: !!isSharing.current, + }} + > + {stringGetter({ key: STRING_KEYS.SHARE })} +
- -
- <$Action - action={ButtonAction.Secondary} - slotLeft={} - onClick={() => { - track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); - convert(); - }} - state={{ - isLoading: isCopying, - }} - > - {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} - - <$Action - action={ButtonAction.Primary} - slotLeft={} - onClick={() => { - track(AnalyticsEvents.SharePnlShared({ asset: assetId })); - convertShare(); - }} - state={{ - isLoading: isSharing, - }} - > - {stringGetter({ key: STRING_KEYS.SHARE })} - -
); }; + +// export const SharePNLAnalyticsDialog = ({ +// marketId, +// assetId, +// side, +// sideLabel, +// leverage, +// oraclePrice, +// entryPrice, +// unrealizedPnl, +// setIsOpen, +// }: DialogProps) => { +// const stringGetter = useStringGetter(); +// const dispatch = useAppDispatch(); +// const logoUrl = useAppSelectorWithArgs(BonsaiHelpers.assets.selectAssetLogo, assetId); +// const symbol = getDisplayableAssetFromBaseAsset(assetId); +// const notify = useCustomNotification(); +// const [isCopied, setIsCopied] = useState(false); + +// const [{ isLoading: isCopying }, convert, ref] = useToBlob({ +// quality: 1.0, +// onSuccess: async (blob) => { +// await copyBlobToClipboard(blob); +// setIsCopied(true); +// setTimeout(() => setIsCopied(false), 2000); +// }, +// onError: (error) => { +// // eslint-disable-next-line no-console +// console.error('Failed to copy blob. ', error); +// notify({ +// title: stringGetter({ key: STRING_KEYS.ERROR }), +// body: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG }), +// slotTitleLeft: , +// toastDuration: 5000, +// }); +// }, +// }); + +// const [{ isLoading: isSharing }, convertShare, refShare] = useToBlob({ +// quality: 1.0, +// onSuccess: async (blob) => { +// await copyBlobToClipboard(blob); + +// triggerTwitterIntent({ +// text: `${stringGetter({ +// key: STRING_KEYS.TWEET_MARKET_POSITION, +// params: { +// MARKET: symbol, +// }, +// })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, +// related: 'bonk_inu', +// }); + +// dispatch(closeDialog()); +// }, +// }); + +// const sideSign = useMemo(() => { +// switch (side) { +// case IndexerPositionSide.LONG: +// return TagSign.Positive; +// case IndexerPositionSide.SHORT: +// return TagSign.Negative; +// default: +// return TagSign.Neutral; +// } +// }, [side]); + +// const unrealizedPnlIsNegative = MustBigNumber(unrealizedPnl).isNegative(); + +// const [assetLeft, assetRight] = marketId.split('-'); + +// const [logoBase64, setLogoBase64] = useState(null); + +// const localLogoUrl = useMemo(() => { +// if (assetId && Object.prototype.hasOwnProperty.call(ASSET_ICON_MAP, assetId)) { +// return ASSET_ICON_MAP[assetId as keyof typeof ASSET_ICON_MAP]; +// } +// return logoUrl; +// }, [logoUrl, assetId]); + +// useEffect(() => { +// if (!logoUrl) return; + +// const img = new Image(); +// img.crossOrigin = 'anonymous'; +// img.src = logoUrl; +// img.onload = () => { +// const canvas = document.createElement('canvas'); +// canvas.width = img.width || 26; +// canvas.height = img.height || 26; +// const ctx = canvas.getContext('2d'); +// ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); +// setLogoBase64(canvas.toDataURL('image/png')); +// }; +// img.onerror = () => { +// // eslint-disable-next-line no-console +// console.error('Failed to load asset image. ', logoUrl); +// setLogoBase64(null); +// }; +// }, [logoUrl]); + +// return ( +// +// <$ShareableCard +// ref={(domNode) => { +// if (domNode) { +// ref(domNode); +// refShare(domNode); +// } +// }} +// > +//
+//
+// + +// +// {assetLeft}/{assetRight} +// + +// {sideLabel} +//
+ +// <$HighlightOutput +// isNegative={unrealizedPnlIsNegative} +// type={OutputType.CompactFiat} +// value={unrealizedPnl} +// showSign={ShowSign.Both} +// /> +//
+// +//
+//
+ +//
+//
+// <$ShareableCardStatLabel> +// {stringGetter({ key: STRING_KEYS.ENTRY })} +// +// <$ShareableCardStatOutput type={OutputType.Fiat} value={entryPrice} withSubscript /> + +// <$ShareableCardStatLabel> +// {stringGetter({ key: STRING_KEYS.INDEX })} +// +// <$ShareableCardStatOutput type={OutputType.Fiat} value={oraclePrice} withSubscript /> + +// <$ShareableCardStatLabel> +// {stringGetter({ key: STRING_KEYS.LEVERAGE })} +// + +// <$ShareableCardStatOutput +// type={OutputType.Multiple} +// value={leverage} +// showSign={ShowSign.None} +// /> +//
+ +//
+// {import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL ? ( +// <$QrCode +// tw="rounded-0.25 bg-color-layer-3" +// size={68} +// value={import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL} +// options={{ +// cells: { +// fill: 'var(--color-text-2)', +// }, +// finder: { +// fill: 'var(--color-text-2)', +// }, +// }} +// /> +// ) : ( +//
+// )} +//
+//
+// + +//
+// <$Action +// action={ButtonAction.Secondary} +// slotLeft={} +// onClick={() => { +// track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); +// convert(); +// }} +// state={{ +// isLoading: isCopying, +// }} +// > +// {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} +// +// <$Action +// action={ButtonAction.Primary} +// slotLeft={} +// onClick={() => { +// track(AnalyticsEvents.SharePnlShared({ asset: assetId })); +// convertShare(); +// }} +// state={{ +// isLoading: isSharing, +// }} +// > +// {stringGetter({ key: STRING_KEYS.SHARE })} +// +//
+//
+// ); +// }; const $Action = tw(Button)`flex-1`; const $ShareableCard = styled.div` @@ -275,30 +368,30 @@ const $ShareableCard = styled.div` padding: 1.75rem 1.25rem 1.25rem 1.25rem; border-radius: 0.5rem; `; -const $ShareableCardStatLabel = tw.div`text-right text-color-text-0 font-base-bold`; - -const $ShareableCardStatOutput = tw(Output)`font-base-bold text-color-text-2`; -const $QrCode = styled(QrCode)` - width: 5.25rem; - height: 5.25rem; - margin-top: 1rem; - margin-left: auto; - - svg { - border: none; - } -`; - -const $HighlightOutput = styled(Output)<{ isNegative?: boolean }>` - font-size: 2.25rem; - font-weight: var(--fontWeight-bold); - - color: var(--output-sign-color); - --secondary-item-color: currentColor; - --output-sign-color: ${({ isNegative }) => - isNegative !== undefined - ? isNegative - ? `var(--color-negative)` - : `var(--color-positive)` - : `var(--color-text-1)`}; -`; +// const $ShareableCardStatLabel = tw.div`text-right text-color-text-0 font-base-bold`; + +// const $ShareableCardStatOutput = tw(Output)`font-base-bold text-color-text-2`; +// const $QrCode = styled(QrCode)` +// width: 5.25rem; +// height: 5.25rem; +// margin-top: 1rem; +// margin-left: auto; + +// svg { +// border: none; +// } +// `; + +// const $HighlightOutput = styled(Output)<{ isNegative?: boolean }>` +// font-size: 2.25rem; +// font-weight: var(--fontWeight-bold); + +// color: var(--output-sign-color); +// --secondary-item-color: currentColor; +// --output-sign-color: ${({ isNegative }) => +// isNegative !== undefined +// ? isNegative +// ? `var(--color-negative)` +// : `var(--color-positive)` +// : `var(--color-text-1)`}; +// `; From 83725b6315dd4abdf1e3c202b8872b8feba870c3 Mon Sep 17 00:00:00 2001 From: dwjanus Date: Mon, 26 Jan 2026 14:04:40 -0500 Subject: [PATCH 2/3] image api working --- public/configs/v1/env.json | 24 +- src/hooks/useSharePnlImage.ts | 52 ++-- src/views/dialogs/SharePNLAnalyticsDialog.tsx | 259 +----------------- 3 files changed, 44 insertions(+), 291 deletions(-) diff --git a/public/configs/v1/env.json b/public/configs/v1/env.json index df698a204..2f968df21 100644 --- a/public/configs/v1/env.json +++ b/public/configs/v1/env.json @@ -463,7 +463,7 @@ "faucet": "https://faucet.v4dev.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -506,7 +506,7 @@ "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -552,7 +552,7 @@ "faucet": "http://dev3-faucet-lb-public-1644791410.us-east-2.elb.amazonaws.com", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -686,7 +686,7 @@ "geo": "https://api.dydx.exchange/v4/geo", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -731,7 +731,7 @@ "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "apps": { "ios": { @@ -788,7 +788,7 @@ "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -835,7 +835,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-staging-e2fb353831a4.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [ "dydxvaloper1vvc9vl6z9pu0vt2y79d0ln8zp6qmpmrhxx99h4", @@ -884,7 +884,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -976,7 +976,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1022,7 +1022,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1068,7 +1068,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1114,7 +1114,7 @@ "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", - "pnlImageApi": "https://image-generator.dydx.trade/generate-trade-card-web" + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { diff --git a/src/hooks/useSharePnlImage.ts b/src/hooks/useSharePnlImage.ts index 3169467d4..9d9ddff39 100644 --- a/src/hooks/useSharePnlImage.ts +++ b/src/hooks/useSharePnlImage.ts @@ -9,8 +9,9 @@ import { getOpenPositions } from '@/state/accountSelectors'; import { useAppSelector } from '@/state/appTypes'; import { Nullable } from '@/lib/typeUtils'; +import { truncateAddress } from '@/lib/wallet'; -// import { useEndpointsConfig } from './useEndpointsConfig'; +import { useEndpointsConfig } from './useEndpointsConfig'; export type SharePnlImageParams = { marketId: string; @@ -23,27 +24,20 @@ export type SharePnlImageParams = { }; // Helper to convert image URL to base64 -const imageToBase64 = (imageUrl: string) => { - let base64 = null; - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.src = imageUrl; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); - base64 = canvas.toDataURL('image/png'); - }; - img.onerror = () => { - // eslint-disable-next-line no-console - console.error('Failed to load image for base64 conversion:', imageUrl); - base64 = null; - }; - - return base64; +const imageToBase64 = async (imageUrl: string): Promise => { + try { + const response = await fetch(imageUrl); + const blob = await response.blob(); + return await new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = () => resolve(null); + reader.readAsDataURL(blob); + }); + } catch (error) { + // console.error('Failed to convert image to base64:', error); + return null; + } }; export const useSharePnlImage = ({ @@ -55,9 +49,7 @@ export const useSharePnlImage = ({ unrealizedPnl, type = 'open', }: SharePnlImageParams) => { - // const { pnlImageApi } = useEndpointsConfig(); - const pnlImageApi = 'https://image-generator.bonk.trade/generate-trade-card-web'; - // 'https://pp-image-generator-bonk-ab5fd4e66c9b.herokuapp.com/generate-trade-card-web'; + const { pnlImageApi } = useEndpointsConfig(); // Get user wallet address for username const { dydxAddress } = useAccounts(); @@ -65,26 +57,26 @@ export const useSharePnlImage = ({ // Get full position data from state const openPositions = useAppSelector(getOpenPositions); const position = openPositions?.find((p) => p.market === marketId); - const userImage = imageToBase64('/hedgie-profile.png'); const queryFn = async (): Promise => { - // if (!pnlImageApi || !dydxAddress) { if (!dydxAddress) { return undefined; } + const userImage = await imageToBase64('/hedgie-profile.png'); + // Build the request body matching the API's zod schema const requestBody = { + brand: 'bonk', ticker: marketId, type, leverage: leverage ?? 0, - username: dydxAddress, + username: truncateAddress(dydxAddress), isLong: side === IndexerPositionSide.LONG, isCross: position?.marginMode === 'CROSS', - // Optional fields - include if available size: position?.unsignedSize.toNumber(), - userImage, + userImage: userImage ?? undefined, pnl: position?.realizedPnl.toNumber(), uPnl: unrealizedPnl ?? undefined, pnlPercentage: position?.updatedUnrealizedPnlPercent?.toNumber(), diff --git a/src/views/dialogs/SharePNLAnalyticsDialog.tsx b/src/views/dialogs/SharePNLAnalyticsDialog.tsx index ab85f6630..e6e0ac01a 100644 --- a/src/views/dialogs/SharePNLAnalyticsDialog.tsx +++ b/src/views/dialogs/SharePNLAnalyticsDialog.tsx @@ -8,7 +8,6 @@ import { ButtonAction } from '@/constants/buttons'; import { DialogProps, SharePNLAnalyticsDialogProps } from '@/constants/dialogs'; import { STRING_KEYS } from '@/constants/localization'; -// import { useCustomNotification } from '@/hooks/useCustomNotification'; import { useSharePnlImage } from '@/hooks/useSharePnlImage'; import { useStringGetter } from '@/hooks/useStringGetter'; @@ -54,7 +53,6 @@ export const SharePNLAnalyticsDialog = ({ const stringGetter = useStringGetter(); const dispatch = useAppDispatch(); const symbol = getDisplayableAssetFromBaseAsset(assetId); - // const notify = useCustomNotification(); const isCopying = useRef(false); const isSharing = useRef(false); const [isCopied, setIsCopied] = useState(false); @@ -105,12 +103,18 @@ export const SharePNLAnalyticsDialog = ({ <$ShareableCard> {!pnlImage ? ( - +
+ +
) : ( - Shareable PNL Card + Shareable PNL Card )} -
+
<$Action action={ButtonAction.Secondary} slotLeft={} @@ -143,255 +147,12 @@ export const SharePNLAnalyticsDialog = ({ ); }; -// export const SharePNLAnalyticsDialog = ({ -// marketId, -// assetId, -// side, -// sideLabel, -// leverage, -// oraclePrice, -// entryPrice, -// unrealizedPnl, -// setIsOpen, -// }: DialogProps) => { -// const stringGetter = useStringGetter(); -// const dispatch = useAppDispatch(); -// const logoUrl = useAppSelectorWithArgs(BonsaiHelpers.assets.selectAssetLogo, assetId); -// const symbol = getDisplayableAssetFromBaseAsset(assetId); -// const notify = useCustomNotification(); -// const [isCopied, setIsCopied] = useState(false); - -// const [{ isLoading: isCopying }, convert, ref] = useToBlob({ -// quality: 1.0, -// onSuccess: async (blob) => { -// await copyBlobToClipboard(blob); -// setIsCopied(true); -// setTimeout(() => setIsCopied(false), 2000); -// }, -// onError: (error) => { -// // eslint-disable-next-line no-console -// console.error('Failed to copy blob. ', error); -// notify({ -// title: stringGetter({ key: STRING_KEYS.ERROR }), -// body: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG }), -// slotTitleLeft: , -// toastDuration: 5000, -// }); -// }, -// }); - -// const [{ isLoading: isSharing }, convertShare, refShare] = useToBlob({ -// quality: 1.0, -// onSuccess: async (blob) => { -// await copyBlobToClipboard(blob); - -// triggerTwitterIntent({ -// text: `${stringGetter({ -// key: STRING_KEYS.TWEET_MARKET_POSITION, -// params: { -// MARKET: symbol, -// }, -// })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, -// related: 'bonk_inu', -// }); - -// dispatch(closeDialog()); -// }, -// }); - -// const sideSign = useMemo(() => { -// switch (side) { -// case IndexerPositionSide.LONG: -// return TagSign.Positive; -// case IndexerPositionSide.SHORT: -// return TagSign.Negative; -// default: -// return TagSign.Neutral; -// } -// }, [side]); - -// const unrealizedPnlIsNegative = MustBigNumber(unrealizedPnl).isNegative(); - -// const [assetLeft, assetRight] = marketId.split('-'); - -// const [logoBase64, setLogoBase64] = useState(null); - -// const localLogoUrl = useMemo(() => { -// if (assetId && Object.prototype.hasOwnProperty.call(ASSET_ICON_MAP, assetId)) { -// return ASSET_ICON_MAP[assetId as keyof typeof ASSET_ICON_MAP]; -// } -// return logoUrl; -// }, [logoUrl, assetId]); - -// useEffect(() => { -// if (!logoUrl) return; - -// const img = new Image(); -// img.crossOrigin = 'anonymous'; -// img.src = logoUrl; -// img.onload = () => { -// const canvas = document.createElement('canvas'); -// canvas.width = img.width || 26; -// canvas.height = img.height || 26; -// const ctx = canvas.getContext('2d'); -// ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); -// setLogoBase64(canvas.toDataURL('image/png')); -// }; -// img.onerror = () => { -// // eslint-disable-next-line no-console -// console.error('Failed to load asset image. ', logoUrl); -// setLogoBase64(null); -// }; -// }, [logoUrl]); - -// return ( -// -// <$ShareableCard -// ref={(domNode) => { -// if (domNode) { -// ref(domNode); -// refShare(domNode); -// } -// }} -// > -//
-//
-// - -// -// {assetLeft}/{assetRight} -// - -// {sideLabel} -//
- -// <$HighlightOutput -// isNegative={unrealizedPnlIsNegative} -// type={OutputType.CompactFiat} -// value={unrealizedPnl} -// showSign={ShowSign.Both} -// /> -//
-// -//
-//
- -//
-//
-// <$ShareableCardStatLabel> -// {stringGetter({ key: STRING_KEYS.ENTRY })} -// -// <$ShareableCardStatOutput type={OutputType.Fiat} value={entryPrice} withSubscript /> - -// <$ShareableCardStatLabel> -// {stringGetter({ key: STRING_KEYS.INDEX })} -// -// <$ShareableCardStatOutput type={OutputType.Fiat} value={oraclePrice} withSubscript /> - -// <$ShareableCardStatLabel> -// {stringGetter({ key: STRING_KEYS.LEVERAGE })} -// - -// <$ShareableCardStatOutput -// type={OutputType.Multiple} -// value={leverage} -// showSign={ShowSign.None} -// /> -//
- -//
-// {import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL ? ( -// <$QrCode -// tw="rounded-0.25 bg-color-layer-3" -// size={68} -// value={import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL} -// options={{ -// cells: { -// fill: 'var(--color-text-2)', -// }, -// finder: { -// fill: 'var(--color-text-2)', -// }, -// }} -// /> -// ) : ( -//
-// )} -//
-//
-// - -//
-// <$Action -// action={ButtonAction.Secondary} -// slotLeft={} -// onClick={() => { -// track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); -// convert(); -// }} -// state={{ -// isLoading: isCopying, -// }} -// > -// {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} -// -// <$Action -// action={ButtonAction.Primary} -// slotLeft={} -// onClick={() => { -// track(AnalyticsEvents.SharePnlShared({ asset: assetId })); -// convertShare(); -// }} -// state={{ -// isLoading: isSharing, -// }} -// > -// {stringGetter({ key: STRING_KEYS.SHARE })} -// -//
-//
-// ); -// }; const $Action = tw(Button)`flex-1`; const $ShareableCard = styled.div` - ${layoutMixins.row} + ${layoutMixins.column} gap: 0.5rem; justify-content: space-between; align-items: flex-start; - margin-bottom: 1.25rem; - background-color: var(--color-layer-4); - padding: 1.75rem 1.25rem 1.25rem 1.25rem; border-radius: 0.5rem; `; -// const $ShareableCardStatLabel = tw.div`text-right text-color-text-0 font-base-bold`; - -// const $ShareableCardStatOutput = tw(Output)`font-base-bold text-color-text-2`; -// const $QrCode = styled(QrCode)` -// width: 5.25rem; -// height: 5.25rem; -// margin-top: 1rem; -// margin-left: auto; - -// svg { -// border: none; -// } -// `; - -// const $HighlightOutput = styled(Output)<{ isNegative?: boolean }>` -// font-size: 2.25rem; -// font-weight: var(--fontWeight-bold); - -// color: var(--output-sign-color); -// --secondary-item-color: currentColor; -// --output-sign-color: ${({ isNegative }) => -// isNegative !== undefined -// ? isNegative -// ? `var(--color-negative)` -// : `var(--color-positive)` -// : `var(--color-text-1)`}; -// `; From aafb15b91b51e0134bf51ef8a786b1de6c7146cf Mon Sep 17 00:00:00 2001 From: dwjanus Date: Tue, 27 Jan 2026 12:26:58 -0500 Subject: [PATCH 3/3] useSharePnlImage tweaks --- src/hooks/useSharePnlImage.ts | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/hooks/useSharePnlImage.ts b/src/hooks/useSharePnlImage.ts index 9d9ddff39..117eda081 100644 --- a/src/hooks/useSharePnlImage.ts +++ b/src/hooks/useSharePnlImage.ts @@ -1,6 +1,7 @@ import { logBonsaiError } from '@/bonsai/logs'; import { useQuery } from '@tanstack/react-query'; +import { timeUnits } from '@/constants/time'; import { IndexerPositionSide } from '@/types/indexer/indexerApiGen'; import { useAccounts } from '@/hooks/useAccounts'; @@ -23,23 +24,6 @@ export type SharePnlImageParams = { type?: 'open' | 'closed' | 'liquidated' | undefined; }; -// Helper to convert image URL to base64 -const imageToBase64 = async (imageUrl: string): Promise => { - try { - const response = await fetch(imageUrl); - const blob = await response.blob(); - return await new Promise((resolve) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result as string); - reader.onerror = () => resolve(null); - reader.readAsDataURL(blob); - }); - } catch (error) { - // console.error('Failed to convert image to base64:', error); - return null; - } -}; - export const useSharePnlImage = ({ marketId, side, @@ -63,8 +47,6 @@ export const useSharePnlImage = ({ return undefined; } - const userImage = await imageToBase64('/hedgie-profile.png'); - // Build the request body matching the API's zod schema const requestBody = { brand: 'bonk', @@ -76,7 +58,7 @@ export const useSharePnlImage = ({ isCross: position?.marginMode === 'CROSS', // Optional fields - include if available size: position?.unsignedSize.toNumber(), - userImage: userImage ?? undefined, + userImage: 'https://dydx.trade/hedgie-profile.png', pnl: position?.realizedPnl.toNumber(), uPnl: unrealizedPnl ?? undefined, pnlPercentage: position?.updatedUnrealizedPnlPercent?.toNumber(), @@ -120,9 +102,9 @@ export const useSharePnlImage = ({ enabled: Boolean(dydxAddress), refetchOnWindowFocus: false, refetchOnReconnect: false, - staleTime: 1000 * 60 * 2, // 2 minutes + staleTime: 2 * timeUnits.minute, // 2 minutes retry: 2, - retryDelay: 1000, + retryDelay: 1 * timeUnits.second, retryOnMount: true, }); };