Skip to content

Commit b9d7932

Browse files
authored
Fix MPT icon logo rendering (#1271)
## High Level Overview of Change Fix a bug where token icon logo isn't rendered correctly when users provide an icon URL without HTTP/HTTPS protocol in metadata. For example, ``` { "asset_class": "rwa", "asset_subclass": "stablecoin", "ticker": "RLUSD", "name": "MPT RLUSD - Short", "desc": "Ripple's RLUSD using the MPT standard - Short", "icon": "s1.xrplmeta.org/icon/5CEA036903.png", "issuer_name": "Ripple", "uris": [ ... ], "additional_info": { ... } } ``` <!-- Please include a summary/list of the changes. If too broad, please consider splitting into multiple PRs. --> ### Context of Change <!-- Please include the context of a change. If a bug fix, when was the bug introduced? What was the behavior? If a new feature, why was this architecture chosen? What were the alternatives? If a refactor, how is this better than the previous implementation? If there is a design document for this feature, please link it here. --> ### Type of Change <!-- Please check relevant options, delete irrelevant ones. --> - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Refactor (non-breaking change that only restructures code) - [ ] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Translation Updates - [ ] Release ### Codebase Modernization <!-- In an effort to modernize the codebase, you should convert the files that you work with to React Hooks and TypeScript, and update tests to use the React Testing Library instead of Enzyme. If this is not possible (e.g. it's too many changes, touching too many files, etc.) please explain why here. --> - [ ] Updated files to React Hooks - [ ] Updated files to TypeScript - [ ] Updated tests to React Testing Library ## Before / After Before - https://testnet.xrpl.org/mpt/00CB8B1D9A62293225214846706F863B5D1C75CDF1EC0C00 After - https://testnet.dev.ripplex.io/mpt/00CB8B1D9A62293225214846706F863B5D1C75CDF1EC0C00 <!-- If just refactoring / back-end changes, this can be just an in-English description of the change at a technical level. If a UI change, screenshots should be included. --> ## Test Plan <!-- Please describe the tests that you ran to verify your changes and provide instructions so that others can reproduce. --> <!-- ## Future Tasks For future tasks related to PR. -->
1 parent e6c5d0e commit b9d7932

File tree

4 files changed

+83
-2
lines changed

4 files changed

+83
-2
lines changed

src/containers/Token/MPT/Header/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
shortenDomain,
99
shortenMPTID,
1010
stripHttpProtocol,
11+
convertToHttpURL,
1112
} from '../../../shared/utils'
1213
import { CopyableText } from '../../../shared/components/CopyableText'
1314
import DomainLink from '../../../shared/components/DomainLink'
@@ -102,6 +103,9 @@ export const Header = (props: Props) => {
102103
// Only show MPT issuance ID if ticker exists (since we show ticker in header, need to show ID somewhere)
103104
const showMPTIssuanceId = !!ticker
104105

106+
// Convert logo URL to HTTP/HTTPS format (handles IPFS URLs)
107+
const RenderedLogoUrl = logoUrl ? convertToHttpURL(logoUrl) : undefined
108+
105109
// Get all URIs for dropdown, filtering out items without uri
106110
const allUris = (uris || []).filter((u) => u.uri)
107111

@@ -137,11 +141,11 @@ export const Header = (props: Props) => {
137141
</div>
138142
<div className="section box-header">
139143
<div className="token-info-group">
140-
{logoUrl ? (
144+
{RenderedLogoUrl ? (
141145
<img
142146
className="token-logo"
143147
alt={`${ticker || mptIssuanceId} logo`}
144-
src={logoUrl}
148+
src={RenderedLogoUrl}
145149
/>
146150
) : (
147151
<DefaultTokenIcon className="token-logo no-logo" />

src/containers/Token/MPT/test/Header/Header.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ describe('MPT Header component', () => {
115115
wrapper.unmount()
116116
})
117117

118+
it('displays logo URL without protocol by prefixing https', () => {
119+
const dataWithNoProtocolUrl = {
120+
...mockMPTData,
121+
parsedMPTMetadata: {
122+
...mockMPTData.parsedMPTMetadata,
123+
icon: 'example.com/logo.png',
124+
},
125+
}
126+
const wrapper = createWrapper({ data: dataWithNoProtocolUrl })
127+
const logo = wrapper.find('img.token-logo')
128+
expect(logo.length).toBe(1)
129+
expect(logo.prop('src')).toBe('https://example.com/logo.png')
130+
wrapper.unmount()
131+
})
132+
118133
it('displays default logo when no icon', () => {
119134
const wrapper = createWrapper({ data: mockMPTDataNoMetadata })
120135
expect(wrapper.find('.token-logo.no-logo').length).toBeGreaterThanOrEqual(1)

src/containers/shared/test/utils.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
shortenNFTTokenID,
1414
shortenMPTID,
1515
stripHttpProtocol,
16+
convertToHttpURL,
1617
} from '../utils'
1718

1819
describe('utils', () => {
@@ -271,4 +272,38 @@ describe('Shorten utils', () => {
271272
expect(shortenMPTID(shortMPTID)).toBe(shortMPTID)
272273
})
273274
})
275+
276+
describe('convertToHttpUrl', () => {
277+
it('converts IPFS URLs to HTTP URLs', () => {
278+
expect(
279+
convertToHttpURL(
280+
'ipfs://QmXhvvWs3HaFkJvDuYvanj2pv31yFQGJewfEhfme1Sv47Y',
281+
),
282+
).toBe(
283+
'https://ipfs.io/ipfs/QmXhvvWs3HaFkJvDuYvanj2pv31yFQGJewfEhfme1Sv47Y',
284+
)
285+
})
286+
287+
it('preserves https:// URLs as-is', () => {
288+
expect(convertToHttpURL('https://example.com/logo.png')).toBe(
289+
'https://example.com/logo.png',
290+
)
291+
})
292+
293+
it('adds https:// to plain domain URLs', () => {
294+
expect(convertToHttpURL('logo.svgcdn.com/logos/openai-icon.png')).toBe(
295+
'https://logo.svgcdn.com/logos/openai-icon.png',
296+
)
297+
})
298+
299+
it('handles empty strings', () => {
300+
expect(convertToHttpURL('')).toBe('')
301+
})
302+
303+
it('handles other protocols', () => {
304+
expect(convertToHttpURL('ftp://example.com/file.txt')).toBe(
305+
'ftp://example.com/file.txt',
306+
)
307+
})
308+
})
274309
})

src/containers/shared/utils.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,30 @@ export const shortenMPTID = (
557557

558558
export const shortenTxHash = (txHash = '') =>
559559
txHash.length > 12 ? `${txHash.slice(0, 6)}...${txHash.slice(-6)}` : txHash
560+
561+
/**
562+
* Converts URLs to HTTP/HTTPS format, handling IPFS URLs and plain domains
563+
* @param {string} url - The URL to convert (can be ipfs://, https://, http://, or plain domain)
564+
* @returns {string} The converted HTTP/HTTPS URL
565+
*/
566+
export const convertToHttpURL = (url) => {
567+
if (!url) {
568+
return url
569+
}
570+
571+
// Handle IPFS URLs - convert to HTTP
572+
if (url.startsWith('ipfs://')) {
573+
return url.replace('ipfs://', 'https://ipfs.io/ipfs/')
574+
}
575+
576+
// Matches a protocol (e.g. 'http://' or 'https://') at the start of a string
577+
const PROTOCOL_REGEX = /^([a-z][a-z0-9+\-.]*):\/\//
578+
579+
// If URL already has a protocol, return as is
580+
if (PROTOCOL_REGEX.test(url)) {
581+
return url
582+
}
583+
584+
// Otherwise, assume it's a plain domain and add https://
585+
return `https://${url}`
586+
}

0 commit comments

Comments
 (0)