From 763a0edd57b5552dda8cae3e800231a1abb2c947 Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Mon, 29 Sep 2025 18:40:51 +0900 Subject: [PATCH 1/2] Create new view by tag --- components/Layout/SearchBlock.js | 20 +-- pages/_app.js | 1 + pages/account/tag/[...id].js | 267 +++++++++++++++++++++++++++++++ styles/pages/account-tag.scss | 146 +++++++++++++++++ 4 files changed, 425 insertions(+), 9 deletions(-) create mode 100644 pages/account/tag/[...id].js create mode 100644 styles/pages/account-tag.scss diff --git a/components/Layout/SearchBlock.js b/components/Layout/SearchBlock.js index 08579167..37510a11 100644 --- a/components/Layout/SearchBlock.js +++ b/components/Layout/SearchBlock.js @@ -5,6 +5,7 @@ import { useRouter } from 'next/router' import axios from 'axios' import { useSearchParams } from 'next/navigation' import Link from 'next/link' +import { classicAddressToXAddress } from 'ripple-address-codec' import { IoMdClose } from 'react-icons/io' import { @@ -153,9 +154,9 @@ export default function SearchBlock({ searchPlaceholderText, tab = null, userDat const searchOnChange = (option) => { if (!option) return if (option.username && !option.username.includes('-')) { - onSearch(option.username) + onSearch(option.username, option?.tag) } else { - onSearch(option.address) + onSearch(option.address, option?.tag) } } @@ -178,7 +179,7 @@ export default function SearchBlock({ searchPlaceholderText, tab = null, userDat } } - const onSearch = async (si) => { + const onSearch = async (si, tag = null) => { setErrorMessage('') let searchFor = null @@ -279,14 +280,15 @@ export default function SearchBlock({ searchPlaceholderText, tab = null, userDat return } - if (isValidPayString(searchFor) || isValidXAddress(searchFor)) { + if (isValidPayString(searchItem) || isValidXAddress(searchItem)) { // the check for paystring/xAddress should be before the check for addressOrUsername, // as if there is no destination tag, we will treat it as an address or username - - // we need to resolve paystring and x-address first before redirecting! - // if there is a tag - - // get the new page which we can show an address and a tag - router.push('/account/' + encodeURI(searchFor) + addParams) //replace with a new page to show a tag + if(tag) { + const xAddress = classicAddressToXAddress(searchFor, tag) + router.push('/account/tag/' + encodeURI(xAddress) + addParams) + } else { + router.push('/account/' + encodeURI(searchFor) + addParams) + } return } diff --git a/pages/_app.js b/pages/_app.js index adc2ce99..6df5cac2 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -25,6 +25,7 @@ const WalletConnectModalSign = dynamic( import '../styles/globals.css' import '../styles/ui.scss' import '../styles/components/nprogress.css' +import '../styles/pages/account-tag.scss' import { ThemeProvider } from '../components/Layout/ThemeContext' import { fetchCurrentFiatRate } from '../utils/common' diff --git a/pages/account/tag/[...id].js b/pages/account/tag/[...id].js new file mode 100644 index 00000000..b7d64d41 --- /dev/null +++ b/pages/account/tag/[...id].js @@ -0,0 +1,267 @@ +import { useTranslation } from 'next-i18next' +import { useEffect } from 'react' +import { useRouter } from 'next/router' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { axiosServer, passHeaders } from '../../../utils/axios' +import { xAddressToClassicAddress } from 'ripple-address-codec' + +import SEO from '../../../components/SEO' +import SearchBlock from '../../../components/Layout/SearchBlock' +import { + FaFacebook, + FaInstagram, + FaLinkedin, + FaMedium, + FaReddit, + FaTelegram, + FaYoutube, + FaXTwitter +} from 'react-icons/fa6' + +export async function getServerSideProps(context) { + const { locale, query, req } = context + let resolvedData = null + let errorMessage = null + + const { id } = query + const searchInput = id ? (Array.isArray(id) ? id[0] : id) : '' + + if (searchInput) { + try { + let address = null + let destinationTag = null + + try { + const decoded = xAddressToClassicAddress(searchInput) + address = decoded.classicAddress + destinationTag = decoded.tag + } catch (error) { + errorMessage = 'Invalid xAddress format' + } + + if (address) { + // Fetch account data for the resolved address + const accountResponse = await axiosServer({ + method: 'get', + url: `v2/address/${address}?username=true&service=true&verifiedDomain=true&parent=true&nickname=true&inception=true&flare=true&blacklist=true&payString=true&ledgerInfo=true&xamanMeta=true&bithomp=true&obligations=true`, + headers: passHeaders(req) + }) + + resolvedData = { + originalInput: searchInput, + address, + destinationTag, + accountData: accountResponse?.data + } + } + } catch (error) { + errorMessage = 'Error processing request' + } + } + + return { + props: { + resolvedData: resolvedData || {}, + errorMessage: errorMessage || null, + ...(await serverSideTranslations(locale, ['common', 'account'])) + } + } +} + +export default function AccountTag({ resolvedData, errorMessage }) { + const { t } = useTranslation() + const router = useRouter() + + // Function to render social links dynamically based on account data + const renderSocialLinks = (socialAccounts) => { + if (!socialAccounts) return null + + return ( +
+ {socialAccounts.twitter && ( + + + + )} + {socialAccounts.youtube && ( + + + + )} + {socialAccounts.linkedin && ( + + + + )} + {socialAccounts.instagram && ( + + + + )} + {socialAccounts.telegram && ( + + + + )} + {socialAccounts.facebook && ( + + + + )} + {socialAccounts.medium && ( + + + + )} + {socialAccounts.reddit && ( + + + + )} +
+ ) + } + + useEffect(() => { + // If no destination tag found, redirect to regular account page + if (resolvedData?.address && (!resolvedData?.destinationTag || resolvedData?.destinationTag === false)) { + router.replace(`/account/${resolvedData.address}`) + return + } + }, [resolvedData, router]) + + if (errorMessage) { + return ( + <> + + +
+
+ {errorMessage} +
+
+ + ) + } + + if (!resolvedData?.address || !resolvedData?.destinationTag) { + return ( + <> + + +
+
+ +
+ {t('general.loading')} +
+
+ + ) + } + + const { originalInput, address, destinationTag, accountData } = resolvedData + + const userData = { + username: accountData?.username, + service: accountData?.service?.name, + address: accountData?.address || address + } + + return ( + <> + + + +
+
+
+ X-ADDRESS DETAILS +
+ +
+ {originalInput} +
+ +
+ This address belongs to{' '} + + {accountData?.service?.domain || 'crypto.com'} + {' '} + ({accountData?.service?.name || 'Crypto.com exchange'}). +
+ + {accountData?.bithomp?.avatar && ( +
+ {accountData.bithomp.name} +
+ )} + + {renderSocialLinks(accountData?.service?.socialAccounts)} + +
+
+ Service address: +
+ +
+
+
+ User destination tag: +
+
+ {destinationTag} +
+
+
+
+ + ) +} diff --git a/styles/pages/account-tag.scss b/styles/pages/account-tag.scss new file mode 100644 index 00000000..8cc55618 --- /dev/null +++ b/styles/pages/account-tag.scss @@ -0,0 +1,146 @@ +/* Account Tag Page Styles */ +.account-tag-container { + max-width: 600px; + margin: 0 auto; + padding: 0px 20px 40px; + text-align: center; + font-family: Arial, sans-serif; + box-shadow: 0px 0px 0px 5px rgba(0, 0, 0, 0.15); +} + +.account-tag-title { + font-size: 18px; + font-weight: bold; + color: #333; + margin: 0px 0px 10px; + padding: 10px 0px; + border-bottom: 1.5px solid rgb(136, 136, 136); +} + +.account-tag-xaddress { + text-align: center; + margin-bottom: 10px; +} + +.account-tag-service-description { + font-size: 14px; + margin-bottom: 20px; + color: #333; +} + +.account-tag-service-link { + color: #28a745; + text-decoration: none; + font-weight: bold; +} + +.account-tag-service-link:hover { + text-decoration: underline; +} + +.account-tag-logo-container { + margin-bottom: 20px; +} + +.account-tag-logo { + width: 100px; + height: 100px; + object-fit: contain; +} + +.account-tag-social-icons { + display: flex; + justify-content: center; + gap: 15px; + margin-bottom: 30px; + font-size: 24px; + color: #666; + + a { + color: inherit; + text-decoration: none; + transition: color 0.3s ease; + + &:hover { + color: #007bff; + } + + svg { + transition: color 0.3s ease; + cursor: pointer; + } + } +} + +.account-tag-details { + display: flex; + justify-content: space-between; + text-align: left; + font-size: 14px; +} + +.account-tag-detail-label { + width: 25%; + text-align: right; + margin-bottom: 10px; +} + +.account-tag-detail-label span { + color: #333; +} + +.account-tag-detail-value { + width: 70%; + margin-bottom: 20px; +} + +.account-tag-address-link { + font-weight: bold; + font-size: 16px; + color: #333; + text-decoration: underline; + font-family: 'Courier New', monospace; +} + +.account-tag-destination-tag { + font-family: 'Courier New', monospace; + font-weight: bold; + color: #333; +} + +.account-tag-header { + margin-bottom: 30px; +} + +.account-tag-page-title { + color: #333; + margin-bottom: 10px; +} + +/* Mobile Responsive */ +@media (max-width: 768px) { + .account-tag-container { + padding: 20px 15px; + } + + .account-tag-title { + font-size: 16px; + } + + .account-tag-xaddress { + font-size: 14px; + } + + .account-tag-service-description { + font-size: 13px; + } + + .account-tag-social-icons { + gap: 10px; + font-size: 20px; + } + + .account-tag-details { + font-size: 13px; + } +} From 290006f1cd1c456c1026536f84debb7583aa4c4a Mon Sep 17 00:00:00 2001 From: pandablue0809 Date: Tue, 30 Sep 2025 21:25:24 +0900 Subject: [PATCH 2/2] remove default url --- pages/account/tag/[...id].js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/account/tag/[...id].js b/pages/account/tag/[...id].js index b7d64d41..a2a1be74 100644 --- a/pages/account/tag/[...id].js +++ b/pages/account/tag/[...id].js @@ -224,10 +224,10 @@ export default function AccountTag({ resolvedData, errorMessage }) {
This address belongs to{' '} - - {accountData?.service?.domain || 'crypto.com'} + + {accountData?.service?.domain} {' '} - ({accountData?.service?.name || 'Crypto.com exchange'}). + ({accountData?.service?.name}).
{accountData?.bithomp?.avatar && (