diff --git a/.eslintrc b/.eslintrc index 585180b3e..8cf04d8ad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -53,7 +53,8 @@ "object": true, "array": false } - ] + ], + "testing-library/no-node-access": "off" } } ] diff --git a/README.md b/README.md index c32d82481..355babc4b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [🦀 Data Sources](#-data-sources) - [Aquarius](#aquarius) - [Ocean Protocol Subgraph](#ocean-protocol-subgraph) - - [3Box](#3box) + - [ENS](#ens) - [Purgatory](#purgatory) - [Network Metadata](#network-metadata) - [👩‍🎤 Storybook](#-storybook) @@ -194,37 +194,21 @@ function Component() { } ``` -### 3Box +### ENS -Publishers can create a profile on [3Box Hub](https://www.3box.io/hub) and when found, it will be displayed in the app. +Publishers can fill their account's [ENS domain](https://ens.domains) profile and when found, it will be displayed in the app. -For this our own [3box-proxy](https://github.com/oceanprotocol/3box-proxy) is used, within the app the utility method `get3BoxProfile()` can be used to get all info: +For this our own [ens-proxy](https://github.com/oceanprotocol/ens-proxy) is used, within the app the utility method `getEnsProfile()` is called as part of the `useProfile()` hook: ```tsx -import get3BoxProfile from '@utils/profile' +import { useProfile } from '@context/Profile' function Component() { - const [profile, setProfile] = useState() + const { profile } = useProfile() - useEffect(() => { - if (!account) return - const source = axios.CancelToken.source() - - async function get3Box() { - const profile = await get3BoxProfile(account, source.token) - if (!profile) return - - setProfile(profile) - } - get3Box() - - return () => { - source.cancel() - } - }, [account]) return (
- {profile.emoji} {profile.name} + {profile.avatar} {profile.name}
) } diff --git a/package-lock.json b/package-lock.json index 1b96a2014..5f97423ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "gray-matter": "^4.0.3", "is-url-superb": "^6.1.0", "js-cookie": "^3.0.1", - "jwt-decode": "^3.1.2", "lodash.debounce": "^4.0.8", "lodash.omit": "^4.5.0", "myetherwallet-blockies": "^0.1.1", @@ -28113,11 +28112,6 @@ "node": ">=8" } }, - "node_modules/jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" - }, "node_modules/keccak": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", @@ -63001,11 +62995,6 @@ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", "dev": true }, - "jwt-decode": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", - "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" - }, "keccak": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", diff --git a/package.json b/package.json index 2d8f93351..e32d89988 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "gray-matter": "^4.0.3", "is-url-superb": "^6.1.0", "js-cookie": "^3.0.1", - "jwt-decode": "^3.1.2", "lodash.debounce": "^4.0.8", "lodash.omit": "^4.5.0", "myetherwallet-blockies": "^0.1.1", diff --git a/src/@context/Profile.tsx b/src/@context/Profile/index.tsx similarity index 78% rename from src/@context/Profile.tsx rename to src/@context/Profile/index.tsx index bd895c6a0..ab6b46f28 100644 --- a/src/@context/Profile.tsx +++ b/src/@context/Profile/index.tsx @@ -7,15 +7,18 @@ import React, { useCallback, ReactNode } from 'react' -import { getUserSales, getUserTokenOrders } from '@utils/subgraph' -import { useUserPreferences } from './UserPreferences' +import { getUserTokenOrders } from '@utils/subgraph' +import { useUserPreferences } from '../UserPreferences' import { Asset, LoggerInstance } from '@oceanprotocol/lib' -import { getDownloadAssets, getPublishedAssets } from '@utils/aquarius' -import { accountTruncate } from '@utils/web3' +import { + getDownloadAssets, + getPublishedAssets, + getUserSales +} from '@utils/aquarius' import axios, { CancelToken } from 'axios' -import get3BoxProfile from '@utils/profile' import web3 from 'web3' -import { useMarketMetadata } from './MarketMetadata' +import { useMarketMetadata } from '../MarketMetadata' +import { getEnsProfile } from '@utils/ens' interface ProfileProviderValue { profile: Profile @@ -32,6 +35,14 @@ const ProfileContext = createContext({} as ProfileProviderValue) const refreshInterval = 10000 // 10 sec. +const clearedProfile: Profile = { + name: null, + avatar: null, + url: null, + description: null, + links: null +} + function ProfileProvider({ accountId, accountEns, @@ -56,9 +67,9 @@ function ProfileProvider({ }, [accountId]) // - // User profile: ENS + 3Box + // User profile: ENS // - const [profile, setProfile] = useState() + const [profile, setProfile] = useState({ name: accountEns }) useEffect(() => { if (!accountEns) return @@ -66,53 +77,22 @@ function ProfileProvider({ }, [accountId, accountEns]) useEffect(() => { - const clearedProfile: Profile = { - name: null, - accountEns: null, - image: null, - description: null, - links: null - } - - if (!accountId || !isEthAddress) { + if ( + !accountId || + accountId === '0x0000000000000000000000000000000000000000' || + !isEthAddress + ) { setProfile(clearedProfile) return } - const cancelTokenSource = axios.CancelToken.source() - async function getInfo() { - setProfile({ name: accountEns || accountTruncate(accountId), accountEns }) - - const profile3Box = await get3BoxProfile( - accountId, - cancelTokenSource.token - ) - if (profile3Box) { - const { name, emoji, description, image, links } = profile3Box - const newName = `${emoji || ''} ${name || accountTruncate(accountId)}` - const newProfile = { - name: newName, - image, - description, - links - } - setProfile((prevState) => ({ - ...prevState, - ...newProfile - })) - LoggerInstance.log('[profile] Found and set 3box profile.', newProfile) - } else { - // setProfile(clearedProfile) - LoggerInstance.log('[profile] No 3box profile found.') - } + const profile = await getEnsProfile(accountId) + setProfile(profile) + LoggerInstance.log(`[profile] ENS metadata for ${accountId}:`, profile) } getInfo() - - return () => { - cancelTokenSource.cancel() - } - }, [accountId, accountEns, isEthAddress]) + }, [accountId, isEthAddress]) // // PUBLISHED ASSETS diff --git a/src/@context/Web3.tsx b/src/@context/Web3.tsx index 7dcf1dd38..71e48f19a 100644 --- a/src/@context/Web3.tsx +++ b/src/@context/Web3.tsx @@ -13,7 +13,7 @@ import { infuraProjectId as infuraId } from '../../app.config' import WalletConnectProvider from '@walletconnect/web3-provider' import { LoggerInstance } from '@oceanprotocol/lib' import { isBrowser } from '@utils/index' -import { getEnsName } from '@utils/ens' +import { getEnsProfile } from '@utils/ens' import useNetworkMetadata, { getNetworkDataById, getNetworkDisplayName, @@ -32,6 +32,7 @@ interface Web3ProviderValue { web3ProviderInfo: IProviderInfo accountId: string accountEns: string + accountEnsAvatar: string balance: UserBalance networkId: number chainId: number @@ -54,8 +55,6 @@ const web3ModalTheme = { hover: 'var(--background-highlight)' } -// HEADS UP! We inline-require some packages so the SSR build does not break. -// We only need them client-side. const providerOptions = isBrowser ? { walletconnect: { @@ -99,6 +98,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { const [isTestnet, setIsTestnet] = useState() const [accountId, setAccountId] = useState() const [accountEns, setAccountEns] = useState() + const [accountEnsAvatar, setAccountEnsAvatar] = useState() const [web3Loading, setWeb3Loading] = useState(true) const [balance, setBalance] = useState({ eth: '0' @@ -192,24 +192,35 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { }, [accountId, approvedBaseTokens, networkId, web3, networkData]) // ----------------------------------- - // Helper: Get user ENS name + // Helper: Get user ENS info // ----------------------------------- - const getUserEnsName = useCallback(async () => { + const getUserEns = useCallback(async () => { if (!accountId) return try { - // const accountEns = await getEnsNameWithWeb3( - // accountId, - // web3Provider, - // `${networkId}` - // ) - const accountEns = await getEnsName(accountId) - setAccountEns(accountEns) - accountEns && + const profile = await getEnsProfile(accountId) + + if (!profile) { + setAccountEns(null) + setAccountEnsAvatar(null) + return + } + + setAccountEns(profile.name) + LoggerInstance.log( + `[web3] ENS name found for ${accountId}:`, + profile.name + ) + + if (profile.avatar) { + setAccountEnsAvatar(profile.avatar) LoggerInstance.log( - `[web3] ENS name found for ${accountId}:`, - accountEns + `[web3] ENS avatar found for ${accountId}:`, + profile.avatar ) + } else { + setAccountEnsAvatar(null) + } } catch (error) { LoggerInstance.error('[web3] Error: ', error.message) } @@ -275,11 +286,11 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { }, [getUserBalance]) // ----------------------------------- - // Get and set user ENS name + // Get and set user ENS info // ----------------------------------- useEffect(() => { - getUserEnsName() - }, [getUserEnsName]) + getUserEns() + }, [getUserEns]) // ----------------------------------- // Get and set network metadata @@ -337,7 +348,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { // ----------------------------------- async function logout() { /* eslint-disable @typescript-eslint/no-explicit-any */ - if (web3 && web3.currentProvider && (web3.currentProvider as any).close) { + if ((web3?.currentProvider as any)?.close) { await (web3.currentProvider as any).close() } /* eslint-enable @typescript-eslint/no-explicit-any */ @@ -402,6 +413,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement { web3ProviderInfo, accountId, accountEns, + accountEnsAvatar, balance, networkId, chainId, diff --git a/src/@hooks/contracts/useNftFactory.ts b/src/@hooks/useNftFactory.ts similarity index 100% rename from src/@hooks/contracts/useNftFactory.ts rename to src/@hooks/useNftFactory.ts diff --git a/src/@types/Profile.d.ts b/src/@types/Profile.d.ts index 5649571b4..e0c1d1a69 100644 --- a/src/@types/Profile.d.ts +++ b/src/@types/Profile.d.ts @@ -1,36 +1,12 @@ interface ProfileLink { - name: string + key: string value: string } interface Profile { - did?: string - name?: string - accountEns?: string + name: string + url?: string + avatar?: string description?: string - emoji?: string - image?: string links?: ProfileLink[] } - -interface ResponseData3Box { - name: string - description: string - website: string - status?: 'error' - /* eslint-disable camelcase */ - proof_did: string - proof_twitter: string - proof_github: string - /* eslint-enable camelcase */ - emoji: string - job: string - employer: string - location: string - memberSince: string - image: { - contentUrl: { - [key: string]: string - } - }[] -} diff --git a/src/@types/viewModels/AccountTeaserVM.d.ts b/src/@types/viewModels/AccountTeaserVM.d.ts deleted file mode 100644 index 0367993da..000000000 --- a/src/@types/viewModels/AccountTeaserVM.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -interface AccountTeaserVM { - address: string - nrSales: number -} diff --git a/src/@utils/aquarius.ts b/src/@utils/aquarius.ts index 5ca056db2..036139f2d 100644 --- a/src/@utils/aquarius.ts +++ b/src/@utils/aquarius.ts @@ -9,6 +9,11 @@ import { } from '../@types/aquarius/SearchQuery' import { transformAssetToAssetSelection } from './assetConvertor' +export interface UserSales { + id: string + totalSales: number +} + export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476 export function escapeEsReservedCharacters(value: string): string { @@ -397,6 +402,40 @@ export async function getTopPublishers( } } +export async function getTopAssetsPublishers( + chainIds: number[], + nrItems = 9 +): Promise { + const publishers: UserSales[] = [] + + const result = await getTopPublishers(chainIds, null) + const { topPublishers } = result.aggregations + + for (let i = 0; i < topPublishers.buckets.length; i++) { + publishers.push({ + id: topPublishers.buckets[i].key, + totalSales: parseInt(topPublishers.buckets[i].totalSales.value) + }) + } + + publishers.sort((a, b) => b.totalSales - a.totalSales) + + return publishers.slice(0, nrItems) +} + +export async function getUserSales( + accountId: string, + chainIds: number[] +): Promise { + try { + const result = await getPublishedAssets(accountId, chainIds, null) + const { totalOrders } = result.aggregations + return totalOrders.value + } catch (error) { + LoggerInstance.error('Error getUserSales', error.message) + } +} + export async function getDownloadAssets( dtList: string[], tokenOrders: OrdersData[], diff --git a/src/@utils/ens.test.ts b/src/@utils/ens.test.ts new file mode 100644 index 000000000..1cf27521a --- /dev/null +++ b/src/@utils/ens.test.ts @@ -0,0 +1,64 @@ +import { getEnsName, getEnsAddress, getEnsProfile } from './ens' + +describe('@utils/ens', () => { + jest.setTimeout(10000) + jest.retryTimes(2) + + test('getEnsName', async () => { + const ensName = await getEnsName( + '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0' + ) + expect(ensName).toBe('jellymcjellyfish.eth') + }) + + test('getEnsName with invalid address', async () => { + const ensName = await getEnsName('0x123') + expect(ensName).toBeUndefined() + }) + + test('getEnsName with empty address', async () => { + const ensName = await getEnsName('') + expect(ensName).toBeUndefined() + }) + + test('getEnsName with undefined address', async () => { + const ensName = await getEnsName(undefined) + expect(ensName).toBeUndefined() + }) + + test('getEnsAddress', async () => { + const ensAddress = await getEnsAddress('jellymcjellyfish.eth') + expect(ensAddress).toBe('0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0') + }) + + test('getEnsAddress with invalid address', async () => { + const ensAddress = await getEnsAddress('0x123') + expect(ensAddress).toBeUndefined() + }) + + test('getEnsAddress with empty address', async () => { + const ensAddress = await getEnsAddress('') + expect(ensAddress).toBeUndefined() + }) + + test('getEnsProfile', async () => { + const ensProfile = await getEnsProfile( + '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0' + ) + expect(ensProfile).toEqual({ + avatar: + 'https://metadata.ens.domains/mainnet/avatar/jellymcjellyfish.eth', + links: [ + { key: 'url', value: 'https://oceanprotocol.com' }, + { key: 'com.twitter', value: 'oceanprotocol' }, + { key: 'com.github', value: 'oceanprotocol' } + ], + name: 'jellymcjellyfish.eth' + }) + }) + + test('getEnsProfile with empty address', async () => { + const ensProfile = await getEnsProfile('') + expect(ensProfile).toBeUndefined() + }) +}) diff --git a/src/@utils/ens.ts b/src/@utils/ens.ts index 9f2807bc3..b367db96e 100644 --- a/src/@utils/ens.ts +++ b/src/@utils/ens.ts @@ -1,52 +1,24 @@ -import { gql, OperationContext, OperationResult } from 'urql' -import { fetchData } from './subgraph' +import { fetchData } from './fetch' -// make sure to only query for domains owned by account, so domains -// solely set by 3rd parties like *.gitcoin.eth won't show up -const UserEnsNames = gql` - query UserEnsDomains($accountId: String!) { - domains(where: { resolvedAddress: $accountId, owner: $accountId }) { - name - } - } -` - -const UserEnsAddress = gql` - query UserEnsDomainsAddress($name: String!) { - domains(where: { name: $name }) { - resolvedAddress { - id - } - } - } -` - -const ensSubgraphQueryContext: OperationContext = { - url: `https://api.thegraph.com/subgraphs/name/ensdomains/ens`, - requestPolicy: 'cache-and-network' -} +const apiUrl = 'https://ens-proxy.oceanprotocol.com/api' export async function getEnsName(accountId: string): Promise { - const response: OperationResult = await fetchData( - UserEnsNames, - { accountId: accountId.toLowerCase() }, - ensSubgraphQueryContext - ) - if (!response?.data?.domains?.length) return + if (!accountId || accountId === '') return - // Default order of response.data.domains seems to be by creation time, from oldest to newest. - // Pick the last one as that is what direct web3 calls do. - const { name } = response.data.domains.slice(-1)[0] - return name + const data = await fetchData(`${apiUrl}/name?accountId=${accountId}`) + return data?.name } -export async function getEnsAddress(ensName: string): Promise { - const response: OperationResult = await fetchData( - UserEnsAddress, - { name: ensName }, - ensSubgraphQueryContext - ) - if (!response?.data?.domains?.length) return - const { id } = response.data.domains[0].resolvedAddress - return id +export async function getEnsAddress(accountId: string): Promise { + if (!accountId || accountId === '' || !accountId.includes('.')) return + + const data = await fetchData(`${apiUrl}/address?name=${accountId}`) + return data?.address +} + +export async function getEnsProfile(accountId: string): Promise { + if (!accountId || accountId === '') return + + const data = await fetchData(`${apiUrl}/profile?address=${accountId}`) + return data?.profile } diff --git a/src/@utils/fetch.ts b/src/@utils/fetch.ts index 2c07ff444..305e06b3f 100644 --- a/src/@utils/fetch.ts +++ b/src/@utils/fetch.ts @@ -1,15 +1,24 @@ +import { LoggerInstance } from '@oceanprotocol/lib' import axios, { AxiosResponse } from 'axios' export async function fetchData(url: string): Promise { try { const response = await axios(url) - - if (response.status !== 200) { - return console.error('Non-200 response: ' + response.status) - } - - return response.data + return response?.data } catch (error) { - console.error('Error parsing json: ' + error.message) + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + LoggerInstance.error(`Non-200 response from ${url}:`, error.response) + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + LoggerInstance.error('No response with:', error.request) + } else { + // Something happened in setting up the request that triggered an Error + LoggerInstance.error('Error in setting up request:', error.message) + } + LoggerInstance.error(error.message) } } diff --git a/src/@utils/numbers.ts b/src/@utils/numbers.ts index 0d31581bf..e7b4a4327 100644 --- a/src/@utils/numbers.ts +++ b/src/@utils/numbers.ts @@ -1,14 +1,5 @@ import { Decimal } from 'decimal.js' -export function isValidNumber(value: any): boolean { - const isUndefinedValue = typeof value === 'undefined' - const isNullValue = value === null - const isNaNValue = isNaN(Number(value)) - const isEmptyString = value === '' - - return !isUndefinedValue && !isNullValue && !isNaNValue && !isEmptyString -} - // Run decimal.js comparison // http://mikemcl.github.io/decimal.js/#cmp export function compareAsBN(balance: string, price: string): boolean { diff --git a/src/@utils/pricingFeedback.ts b/src/@utils/pricingFeedback.ts deleted file mode 100644 index ad49d8cbd..000000000 --- a/src/@utils/pricingFeedback.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function getBuyDTFeedback(dtSymbol: string): { [key: number]: string } { - return { - 1: '1/3 Approving OCEAN ...', - 2: `2/3 Buying ${dtSymbol} ...`, - 3: `3/3 ${dtSymbol} bought.` - } -} - -export function getSellDTFeedback(dtSymbol: string): { [key: number]: string } { - return { - 1: '1/3 Approving OCEAN ...', - 2: `2/3 Selling ${dtSymbol} ...`, - 3: `3/3 ${dtSymbol} sold.` - } -} diff --git a/src/@utils/profile.ts b/src/@utils/profile.ts deleted file mode 100644 index b61bc1c4b..000000000 --- a/src/@utils/profile.ts +++ /dev/null @@ -1,94 +0,0 @@ -import axios, { AxiosResponse, CancelToken } from 'axios' -import jwtDecode from 'jwt-decode' - -// https://docs.3box.io/api/rest-api -const apiUri = 'https://3box.oceanprotocol.com' -const ipfsUrl = 'https://infura-ipfs.io' - -function decodeProof(proofJWT: string) { - if (!proofJWT) return - const proof = jwtDecode(proofJWT) as any - return proof -} - -function getLinks( - website: string, - twitterProof: string, - githubProof: string -): ProfileLink[] { - // Conditionally add links if they exist - const links = [ - ...(website ? [{ name: 'Website', value: website }] : []), - ...(twitterProof - ? [ - { - name: 'Twitter', - value: decodeProof(twitterProof).claim.twitter_handle - } - ] - : []), - ...(githubProof - ? [{ name: 'GitHub', value: githubProof.split('/')[3] }] - : []) - ] - - return links -} - -function transformResponse({ - name, - description, - website, - emoji, - image, - /* eslint-disable camelcase */ - proof_twitter, - proof_github, - proof_did -}: ResponseData3Box) { - /* eslint-enable camelcase */ - const links = getLinks(website, proof_twitter, proof_github) - - const profile: Profile = { - did: decodeProof(proof_did).iss, - // Conditionally add profile items if they exist - ...(name && { name }), - ...(description && { description }), - ...(emoji && { emoji }), - ...(image && { - image: `${ipfsUrl}/ipfs/${ - image.map( - (img: { contentUrl: { [key: string]: string } }) => - img.contentUrl['/'] - )[0] - }` - }), - ...(links.length && { links }) - } - - return profile -} - -export default async function get3BoxProfile( - accountId: string, - cancelToken: CancelToken -): Promise { - try { - const response = (await axios(`${apiUri}/profile/${accountId}`, { - cancelToken - })) as AxiosResponse - - if ( - !response || - !response.data || - response.status !== 200 || - response.data.status === 'error' - ) - return - - // LoggerInstance.log(`3Box profile found for ${accountId}`, response.data) - const profile = transformResponse(response.data) - return profile - // eslint-disable-next-line no-empty - } catch (error) {} -} diff --git a/src/@utils/purgatory.ts b/src/@utils/purgatory.ts index 9d9dae043..81ed2a33e 100644 --- a/src/@utils/purgatory.ts +++ b/src/@utils/purgatory.ts @@ -1,4 +1,3 @@ -import { Purgatory } from '@oceanprotocol/lib' import { fetchData } from './fetch' const purgatoryUrl = 'https://market-purgatory.oceanprotocol.com/api/' diff --git a/src/@utils/subgraph.ts b/src/@utils/subgraph.ts index 41ca897bf..c8ac601f9 100644 --- a/src/@utils/subgraph.ts +++ b/src/@utils/subgraph.ts @@ -6,16 +6,6 @@ import { AssetPreviousOrder } from '../@types/subgraph/AssetPreviousOrder' import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData' import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery' -import { getPublishedAssets, getTopPublishers } from '@utils/aquarius' -export interface UserLiquidity { - price: string - oceanBalance: string -} - -export interface PriceList { - [key: string]: string -} - const PreviousOrderQuery = gql` query AssetPreviousOrder($id: String!, $account: String!) { orders( @@ -153,29 +143,6 @@ export async function getOpcFees(chainId: number) { return opcFees } -export async function getPreviousOrders( - id: string, - account: string, - assetTimeout: string -): Promise { - const variables = { id, account } - const fetchedPreviousOrders: OperationResult = - await fetchData(PreviousOrderQuery, variables, null) - if (fetchedPreviousOrders.data?.orders?.length === 0) return null - if (assetTimeout === '0') { - return fetchedPreviousOrders?.data?.orders[0]?.tx - } else { - const expiry = - fetchedPreviousOrders?.data?.orders[0]?.createdTimestamp * 1000 + - Number(assetTimeout) * 1000 - if (Date.now() <= expiry) { - return fetchedPreviousOrders?.data?.orders[0]?.tx - } else { - return null - } - } -} - export async function getUserTokenOrders( accountId: string, chainIds: number[] @@ -201,40 +168,6 @@ export async function getUserTokenOrders( } } -export async function getUserSales( - accountId: string, - chainIds: number[] -): Promise { - try { - const result = await getPublishedAssets(accountId, chainIds, null) - const { totalOrders } = result.aggregations - return totalOrders.value - } catch (error) { - LoggerInstance.error('Error getUserSales', error.message) - } -} - -export async function getTopAssetsPublishers( - chainIds: number[], - nrItems = 9 -): Promise { - const publishers: AccountTeaserVM[] = [] - - const result = await getTopPublishers(chainIds, null) - const { topPublishers } = result.aggregations - - for (let i = 0; i < topPublishers.buckets.length; i++) { - publishers.push({ - address: topPublishers.buckets[i].key, - nrSales: parseInt(topPublishers.buckets[i].totalSales.value) - }) - } - - publishers.sort((a, b) => b.nrSales - a.nrSales) - - return publishers.slice(0, nrItems) -} - export async function getOpcsApprovedTokens( chainId: number ): Promise { diff --git a/src/components/@shared/AccountList/AccountList.tsx b/src/components/@shared/AccountList/AccountList.tsx deleted file mode 100644 index fc2a9fa6d..000000000 --- a/src/components/@shared/AccountList/AccountList.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { ReactElement } from 'react' -import styles from './index.module.css' -import classNames from 'classnames/bind' -import Loader from '../atoms/Loader' -import { useUserPreferences } from '@context/UserPreferences' -import AccountTeaser from '@shared/AccountTeaser/AccountTeaser' - -const cx = classNames.bind(styles) - -function LoaderArea() { - return ( -
- -
- ) -} - -declare type AccountListProps = { - accounts: AccountTeaserVM[] - isLoading: boolean - className?: string -} - -export default function AccountList({ - accounts, - isLoading, - className -}: AccountListProps): ReactElement { - const { chainIds } = useUserPreferences() - - const styleClasses = cx({ - accountList: true, - [className]: className - }) - - return accounts && (isLoading === undefined || isLoading === false) ? ( - <> -
- {accounts.length > 0 ? ( - accounts.map((account, index) => ( - - )) - ) : chainIds.length === 0 ? ( -
No network selected.
- ) : ( -
No results found.
- )} -
- - ) : ( - - ) -} diff --git a/src/components/@shared/AccountTeaser/AccountTeaser.tsx b/src/components/@shared/AccountTeaser/AccountTeaser.tsx deleted file mode 100644 index 4a20867a7..000000000 --- a/src/components/@shared/AccountTeaser/AccountTeaser.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { ReactElement, useEffect, useState } from 'react' -import Dotdotdot from 'react-dotdotdot' -import Link from 'next/link' -import styles from './AccountTeaser.module.css' -import Blockies from '../atoms/Blockies' -import { useCancelToken } from '@hooks/useCancelToken' -import get3BoxProfile from '@utils/profile' -import { accountTruncate } from '@utils/web3' - -declare type AccountTeaserProps = { - accountTeaserVM: AccountTeaserVM - place?: number -} - -export default function AccountTeaser({ - accountTeaserVM, - place -}: AccountTeaserProps): ReactElement { - const [profile, setProfile] = useState() - const newCancelToken = useCancelToken() - - useEffect(() => { - if (!accountTeaserVM) return - async function getProfileData() { - const profile = await get3BoxProfile( - accountTeaserVM.address, - newCancelToken() - ) - if (!profile) return - setProfile(profile) - } - getProfileData() - }, [accountTeaserVM, newCancelToken]) - - return ( - - - {place && {place}} - -
- - {profile?.name - ? profile?.name - : accountTruncate(accountTeaserVM.address)} - -

- {accountTeaserVM.nrSales} - {`${accountTeaserVM.nrSales === 1 ? ' sale' : ' sales'}`} -

-
-
- - ) -} diff --git a/src/components/@shared/Publisher/Add.module.css b/src/components/@shared/Publisher/Add.module.css deleted file mode 100644 index 3482d355f..000000000 --- a/src/components/@shared/Publisher/Add.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.add { - color: var(--brand-pink); -} - -.linksExternal { - composes: linksExternal from './index.module.css'; -} diff --git a/src/components/@shared/Publisher/Add.tsx b/src/components/@shared/Publisher/Add.tsx deleted file mode 100644 index f40437fc7..000000000 --- a/src/components/@shared/Publisher/Add.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { ReactElement } from 'react' -import External from '@images/external.svg' -import styles from './Add.module.css' - -export default function Add(): ReactElement { - return ( - - Add profile on 3Box - - ) -} diff --git a/src/components/@shared/Publisher/index.module.css b/src/components/@shared/Publisher/index.module.css index 93592f599..0bddf6f83 100644 --- a/src/components/@shared/Publisher/index.module.css +++ b/src/components/@shared/Publisher/index.module.css @@ -7,10 +7,3 @@ display: inline-block; } } - -.linksExternal { - width: 6px; - height: 6px; - display: inline-block; - fill: var(--color-secondary); -} diff --git a/src/components/@shared/Publisher/index.test.tsx b/src/components/@shared/Publisher/index.test.tsx new file mode 100644 index 000000000..ffe21a8db --- /dev/null +++ b/src/components/@shared/Publisher/index.test.tsx @@ -0,0 +1,56 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import * as axios from 'axios' +import Publisher from './' + +const account = '0x0000000000000000000000000000000000000000' + +jest.mock('axios') + +describe('Publisher', () => { + test('should return correct markup by default', async () => { + ;(axios as any).get.mockImplementationOnce(() => + Promise.resolve({ data: { name: 'jellymcjellyfish.eth' } }) + ) + + render() + + const element = await screen.findByRole('link') + expect(element).toBeInTheDocument() + expect(element).toContainHTML(' { + ;(axios as any).get.mockImplementationOnce(() => + Promise.resolve({ data: { name: null } }) + ) + + render() + + const element = await screen.findByText('0x…00000000') + expect(element).toBeInTheDocument() + }) + + test('should return correct markup in minimal state', async () => { + ;(axios as any).get.mockImplementationOnce(() => + Promise.resolve({ data: { name: null } }) + ) + + render() + + const element = await screen.findByText('0x…00000000') + expect(element).not.toHaveAttribute('href') + }) + + test('should return markup with empty account', async () => { + ;(axios as any).get.mockImplementationOnce(() => + Promise.resolve({ data: { name: null } }) + ) + + render() + + const element = await screen.findByRole('link') + expect(element).toBeInTheDocument() + }) +}) diff --git a/src/components/@shared/Publisher/index.tsx b/src/components/@shared/Publisher/index.tsx index 0eecd5fa0..55589b5b1 100644 --- a/src/components/@shared/Publisher/index.tsx +++ b/src/components/@shared/Publisher/index.tsx @@ -1,72 +1,47 @@ import React, { ReactElement, useEffect, useState } from 'react' import styles from './index.module.css' -import classNames from 'classnames/bind' import Link from 'next/link' -import get3BoxProfile from '@utils/profile' import { accountTruncate } from '@utils/web3' -import axios from 'axios' import { getEnsName } from '@utils/ens' import { useIsMounted } from '@hooks/useIsMounted' -const cx = classNames.bind(styles) +export interface PublisherProps { + account: string + minimal?: boolean + className?: string +} export default function Publisher({ account, minimal, className -}: { - account: string - minimal?: boolean - className?: string -}): ReactElement { +}: PublisherProps): ReactElement { const isMounted = useIsMounted() - const [profile, setProfile] = useState() - const [name, setName] = useState('') - const [accountEns, setAccountEns] = useState() + const [name, setName] = useState(accountTruncate(account)) useEffect(() => { - if (!account) return + if (!account || account === '') return // set default name on hook // to avoid side effect (UI not updating on account's change) setName(accountTruncate(account)) - const source = axios.CancelToken.source() - async function getExternalName() { - // ENS const accountEns = await getEnsName(account) if (accountEns && isMounted()) { - setAccountEns(accountEns) setName(accountEns) } - - // 3box - const profile = await get3BoxProfile(account, source.token) - if (!profile) return - setProfile(profile) - const { name, emoji } = profile - name && setName(`${emoji || ''} ${name}`) } getExternalName() - - return () => { - source.cancel() - } }, [account, isMounted]) - const styleClasses = cx({ - publisher: true, - [className]: className - }) - return ( -
+
{minimal ? ( name ) : ( <> - + {name} diff --git a/src/components/@shared/atoms/Blockies/index.module.css b/src/components/@shared/atoms/Avatar/index.module.css similarity index 93% rename from src/components/@shared/atoms/Blockies/index.module.css rename to src/components/@shared/atoms/Avatar/index.module.css index 928123b18..26ddad81d 100644 --- a/src/components/@shared/atoms/Blockies/index.module.css +++ b/src/components/@shared/atoms/Avatar/index.module.css @@ -1,4 +1,4 @@ -.blockies { +.avatar { width: var(--font-size-large); height: var(--font-size-large); border-radius: 50%; diff --git a/src/components/@shared/atoms/Avatar/index.stories.tsx b/src/components/@shared/atoms/Avatar/index.stories.tsx new file mode 100644 index 000000000..eb3b25a34 --- /dev/null +++ b/src/components/@shared/atoms/Avatar/index.stories.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' + +import Avatar, { AvatarProps } from '@shared/atoms/Avatar' + +export default { + title: 'Component/@shared/atoms/Avatar', + component: Avatar +} as ComponentMeta + +const Template: ComponentStory = (args) => + +interface Props { + args: AvatarProps +} + +export const DefaultWithBlockies: Props = Template.bind({}) +DefaultWithBlockies.args = { + accountId: '0x1234567890123456789012345678901234567890' +} + +export const CustomSource: Props = Template.bind({}) +CustomSource.args = { + accountId: '0x1234567890123456789012345678901234567890', + src: 'http://placekitten.com/g/300/300' +} + +export const Empty: Props = Template.bind({}) +Empty.args = { + accountId: null +} diff --git a/src/components/@shared/atoms/Avatar/index.test.tsx b/src/components/@shared/atoms/Avatar/index.test.tsx new file mode 100644 index 000000000..2d0030433 --- /dev/null +++ b/src/components/@shared/atoms/Avatar/index.test.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import testRender from '../../../../../.jest/testRender' +import Avatar from '@shared/atoms/Avatar' +import { DefaultWithBlockies, CustomSource, Empty } from './index.stories' +import { render } from '@testing-library/react' + +describe('Avatar', () => { + testRender() + + it('renders without crashing with custom source', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) + + it('renders empty without crashing', () => { + const { container } = render() + expect(container.firstChild).toBeInTheDocument() + }) +}) diff --git a/src/components/@shared/atoms/Avatar/index.tsx b/src/components/@shared/atoms/Avatar/index.tsx new file mode 100644 index 000000000..436fbdc2a --- /dev/null +++ b/src/components/@shared/atoms/Avatar/index.tsx @@ -0,0 +1,24 @@ +import { toDataUrl } from 'myetherwallet-blockies' +import React, { ReactElement } from 'react' +import styles from './index.module.css' + +export interface AvatarProps { + accountId: string + src?: string + className?: string +} + +export default function Avatar({ + accountId, + src, + className +}: AvatarProps): ReactElement { + return ( + + ) +} diff --git a/src/components/@shared/atoms/Blockies/index.stories.tsx b/src/components/@shared/atoms/Blockies/index.stories.tsx deleted file mode 100644 index 12137bf5f..000000000 --- a/src/components/@shared/atoms/Blockies/index.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' - -import Blockies, { BlockiesProps } from '@shared/atoms/Blockies' - -export default { - title: 'Component/@shared/atoms/Blockies', - component: Blockies -} as ComponentMeta - -const Template: ComponentStory = (args) => ( - -) - -interface Props { - args: BlockiesProps -} - -export const Default: Props = Template.bind({}) -Default.args = { - accountId: '0x1xxxxxxxxxx3Exxxxxx7xxxxxxxxxxxxF1fd' -} diff --git a/src/components/@shared/atoms/Blockies/index.test.tsx b/src/components/@shared/atoms/Blockies/index.test.tsx deleted file mode 100644 index b0bd9443c..000000000 --- a/src/components/@shared/atoms/Blockies/index.test.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import testRender from '../../../../../.jest/testRender' -import Blockies from '@shared/atoms/Blockies' -import { Default } from './index.stories' - -describe('Blockies', () => { - testRender() -}) diff --git a/src/components/@shared/atoms/Blockies/index.tsx b/src/components/@shared/atoms/Blockies/index.tsx deleted file mode 100644 index ba3a23070..000000000 --- a/src/components/@shared/atoms/Blockies/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { toDataUrl } from 'myetherwallet-blockies' -import React, { ReactElement } from 'react' -import styles from './index.module.css' - -export interface BlockiesProps { - accountId: string - className?: string - image?: string -} - -export default function Blockies({ - accountId, - className, - image -}: BlockiesProps): ReactElement { - if (!accountId) return null - - const blockies = toDataUrl(accountId) - - return ( - - ) -} diff --git a/src/components/Header/Wallet/Account.tsx b/src/components/Header/Wallet/Account.tsx index e81f4edd5..d19a01f0a 100644 --- a/src/components/Header/Wallet/Account.tsx +++ b/src/components/Header/Wallet/Account.tsx @@ -4,12 +4,13 @@ import { accountTruncate } from '@utils/web3' import Loader from '@shared/atoms/Loader' import styles from './Account.module.css' import { useWeb3 } from '@context/Web3' -import Blockies from '@shared/atoms/Blockies' +import Avatar from '@shared/atoms/Avatar' // Forward ref for Tippy.js // eslint-disable-next-line const Account = React.forwardRef((props, ref: any) => { - const { accountId, accountEns, web3Modal, connect } = useWeb3() + const { accountId, accountEns, accountEnsAvatar, web3Modal, connect } = + useWeb3() async function handleActivation(e: FormEvent) { // prevent accidentially submitting a form the button might be in @@ -30,7 +31,7 @@ const Account = React.forwardRef((props, ref: any) => { ref={ref} onClick={(e) => e.preventDefault()} > - + {accountTruncate(accountEns || accountId)} diff --git a/src/components/@shared/AccountTeaser/AccountTeaser.module.css b/src/components/Home/TopSales/Account/index.module.css similarity index 92% rename from src/components/@shared/AccountTeaser/AccountTeaser.module.css rename to src/components/Home/TopSales/Account/index.module.css index e042e0f7f..2e3353c5b 100644 --- a/src/components/@shared/AccountTeaser/AccountTeaser.module.css +++ b/src/components/Home/TopSales/Account/index.module.css @@ -1,4 +1,4 @@ -.blockies { +.avatar { aspect-ratio: 1/1; width: calc(var(--font-size-large) * 2) !important; height: calc(var(--font-size-large) * 2) !important; @@ -8,7 +8,7 @@ } .teaser { - composes: box from '../atoms/Box.module.css'; + composes: box from '@shared/atoms/Box.module.css'; padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2); color: var(--color-secondary); position: relative; diff --git a/src/components/Home/TopSales/Account/index.tsx b/src/components/Home/TopSales/Account/index.tsx new file mode 100644 index 000000000..a56048202 --- /dev/null +++ b/src/components/Home/TopSales/Account/index.tsx @@ -0,0 +1,53 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import Dotdotdot from 'react-dotdotdot' +import Link from 'next/link' +import styles from './index.module.css' +import { accountTruncate } from '@utils/web3' +import Avatar from '../../../@shared/atoms/Avatar' +import { getEnsProfile } from '@utils/ens' +import { UserSales } from '@utils/aquarius' + +declare type AccountProps = { + account: UserSales + place?: number +} + +export default function Account({ + account, + place +}: AccountProps): ReactElement { + const [profile, setProfile] = useState() + + useEffect(() => { + if (!account?.id) return + + async function getProfileData() { + const profile = await getEnsProfile(account.id) + if (!profile) return + setProfile(profile) + } + getProfileData() + }, [account?.id]) + + return ( + + + {place && {place}} + +
+ + {profile?.name ? profile?.name : accountTruncate(account.id)} + +

+ {account.totalSales} + {`${account.totalSales === 1 ? ' sale' : ' sales'}`} +

+
+
+ + ) +} diff --git a/src/components/Home/TopSales/AccountList/index.module.css b/src/components/Home/TopSales/AccountList/index.module.css new file mode 100644 index 000000000..2a4b6a7a3 --- /dev/null +++ b/src/components/Home/TopSales/AccountList/index.module.css @@ -0,0 +1,11 @@ +.list { + composes: assetList from '@shared/AssetList/index.module.css'; +} + +.loaderWrap { + composes: loaderWrap from '@shared/AssetList/index.module.css'; +} + +.empty { + composes: empty from '@shared/AssetList/index.module.css'; +} diff --git a/src/components/Home/TopSales/AccountList/index.tsx b/src/components/Home/TopSales/AccountList/index.tsx new file mode 100644 index 000000000..1cbc2f94a --- /dev/null +++ b/src/components/Home/TopSales/AccountList/index.tsx @@ -0,0 +1,43 @@ +import React, { ReactElement } from 'react' +import styles from './index.module.css' +import Loader from '../../../@shared/atoms/Loader' +import { useUserPreferences } from '@context/UserPreferences' +import Account from 'src/components/Home/TopSales/Account' +import { UserSales } from '@utils/aquarius' + +function LoaderArea() { + return ( +
+ +
+ ) +} + +declare type AccountListProps = { + accounts: UserSales[] + isLoading: boolean + className?: string +} + +export default function AccountList({ + accounts, + isLoading +}: AccountListProps): ReactElement { + const { chainIds } = useUserPreferences() + const emptyText = + chainIds.length === 0 ? 'No network selected.' : 'No results found.' + + return isLoading ? ( + + ) : ( +
+ {accounts?.length > 0 ? ( + accounts.map((account, index) => ( + + )) + ) : ( +
{emptyText}
+ )} +
+ ) +} diff --git a/src/components/Home/TopSales/index.module.css b/src/components/Home/TopSales/index.module.css new file mode 100644 index 000000000..d176386d7 --- /dev/null +++ b/src/components/Home/TopSales/index.module.css @@ -0,0 +1,3 @@ +.section { + composes: section from '../index.module.css'; +} diff --git a/src/components/Home/PublishersWithMostSales.tsx b/src/components/Home/TopSales/index.tsx similarity index 79% rename from src/components/Home/PublishersWithMostSales.tsx rename to src/components/Home/TopSales/index.tsx index c36b7857b..44eb3176c 100644 --- a/src/components/Home/PublishersWithMostSales.tsx +++ b/src/components/Home/TopSales/index.tsx @@ -1,11 +1,11 @@ import { useUserPreferences } from '@context/UserPreferences' import { LoggerInstance } from '@oceanprotocol/lib' -import AccountList from '@shared/AccountList/AccountList' -import { getTopAssetsPublishers } from '@utils/subgraph' +import AccountList from 'src/components/Home/TopSales/AccountList' +import { getTopAssetsPublishers, UserSales } from '@utils/aquarius' import React, { ReactElement, useEffect, useState } from 'react' import styles from './index.module.css' -export default function PublishersWithMostSales({ +export default function TopSales({ title, action }: { @@ -13,14 +13,14 @@ export default function PublishersWithMostSales({ action?: ReactElement }): ReactElement { const { chainIds } = useUserPreferences() - const [result, setResult] = useState([]) + const [result, setResult] = useState([]) const [loading, setLoading] = useState() useEffect(() => { async function init() { setLoading(true) if (chainIds.length === 0) { - const result: AccountTeaserVM[] = [] + const result: UserSales[] = [] setResult(result) setLoading(false) } else { diff --git a/src/components/Home/index.tsx b/src/components/Home/index.tsx index 8fdd86799..e256ee102 100644 --- a/src/components/Home/index.tsx +++ b/src/components/Home/index.tsx @@ -5,11 +5,11 @@ import Bookmarks from './Bookmarks' import { generateBaseQuery, queryMetadata } from '@utils/aquarius' import { Asset, LoggerInstance } from '@oceanprotocol/lib' import { useUserPreferences } from '@context/UserPreferences' -import styles from './index.module.css' import { useIsMounted } from '@hooks/useIsMounted' import { useCancelToken } from '@hooks/useCancelToken' import { SortTermOptions } from '../../@types/aquarius/SearchQuery' -import PublishersWithMostSales from './PublishersWithMostSales' +import TopSales from './TopSales' +import styles from './index.module.css' function sortElements(items: Asset[], sorted: string[]) { items.sort(function (a, b) { @@ -136,7 +136,7 @@ export default function HomePage(): ReactElement { } /> - + ) } diff --git a/src/components/Profile/Header/Account.tsx b/src/components/Profile/Header/Account.tsx index bbf2a676b..0aab25685 100644 --- a/src/components/Profile/Header/Account.tsx +++ b/src/components/Profile/Header/Account.tsx @@ -4,9 +4,10 @@ import ExplorerLink from '@shared/ExplorerLink' import NetworkName from '@shared/NetworkName' import Jellyfish from '@oceanprotocol/art/creatures/jellyfish/jellyfish-grid.svg' import Copy from '@shared/atoms/Copy' -import Blockies from '@shared/atoms/Blockies' +import Avatar from '@shared/atoms/Avatar' import styles from './Account.module.css' import { useProfile } from '@context/Profile' +import { accountTruncate } from '@utils/web3' export default function Account({ accountId @@ -19,28 +20,27 @@ export default function Account({ return (
- {profile?.image ? ( - - ) : accountId ? ( - ) : ( )}
-

{profile?.name}

+

+ {profile?.name || accountTruncate(accountId)} +

{accountId && ( - {profile?.accountEns || accountId} + {accountId} )}

diff --git a/src/components/Profile/Header/PublisherLinks.module.css b/src/components/Profile/Header/PublisherLinks.module.css index a7970a765..006de6983 100644 --- a/src/components/Profile/Header/PublisherLinks.module.css +++ b/src/components/Profile/Header/PublisherLinks.module.css @@ -24,5 +24,8 @@ } .linksExternal { - composes: linksExternal from '@shared/Publisher/index.module.css'; + width: 6px; + height: 6px; + display: inline-block; + fill: var(--color-secondary); } diff --git a/src/components/Profile/Header/PublisherLinks.tsx b/src/components/Profile/Header/PublisherLinks.tsx index 023ea4085..ca6f97b84 100644 --- a/src/components/Profile/Header/PublisherLinks.tsx +++ b/src/components/Profile/Header/PublisherLinks.tsx @@ -6,6 +6,39 @@ import { useProfile } from '@context/Profile' const cx = classNames.bind(styles) +function getLinkData(link: ProfileLink): { href: string; label: string } { + let href, label + + switch (link.key) { + case 'url': + href = link.value + label = 'Website' + break + case 'com.twitter': + href = `https://twitter.com/${link.value}` + label = 'Twitter' + break + case 'com.github': + href = `https://github.com/${link.value}` + label = 'GitHub' + break + case 'org.telegram': + href = `https://telegram.org/${link.value}` + label = 'Telegram' + break + case 'com.discord': + href = `https://discordapp.com/users/${link.value}` + label = 'Discord' + break + case 'com.reddit': + href = `https://reddit.com/u/${link.value}` + label = 'Reddit' + break + } + + return { href, label } +} + export default function PublisherLinks({ className }: { @@ -21,22 +54,17 @@ export default function PublisherLinks({ return (

{' — '} - {profile?.links?.map((link) => { - const href = - link.name === 'Twitter' - ? `https://twitter.com/${link.value}` - : link.name === 'GitHub' - ? `https://github.com/${link.value}` - : link.value.includes('http') // safeguard against urls without protocol defined - ? link.value - : `//${link.value}` - - return ( - - {link.name} - - ) - })} + {profile?.links?.map((link) => ( + + {getLinkData(link).label}{' '} + + + ))}
) } diff --git a/src/components/Profile/Header/index.tsx b/src/components/Profile/Header/index.tsx index 15afbe128..a09165606 100644 --- a/src/components/Profile/Header/index.tsx +++ b/src/components/Profile/Header/index.tsx @@ -43,8 +43,8 @@ export default function AccountHeader({ {isDescriptionTextClamped() ? ( ) : ( @@ -56,18 +56,9 @@ export default function AccountHeader({
Profile data from{' '} - {profile?.accountEns && ( - <> - {' '} - &{' '} - - )}
diff --git a/src/components/Publish/index.tsx b/src/components/Publish/index.tsx index 7035128ba..9e3cc6480 100644 --- a/src/components/Publish/index.tsx +++ b/src/components/Publish/index.tsx @@ -13,7 +13,7 @@ import Navigation from './Navigation' import { Steps } from './Steps' import { FormPublishData } from './_types' import { useUserPreferences } from '@context/UserPreferences' -import useNftFactory from '@hooks/contracts/useNftFactory' +import useNftFactory from '@hooks/useNftFactory' import { ProviderInstance, LoggerInstance, DDO } from '@oceanprotocol/lib' import { getOceanConfig } from '@utils/ocean' import { validationSchema } from './_validation' diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 4bb1949bd..d14b24f94 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -28,7 +28,7 @@ export default function PageProfile(): ReactElement { const pathAccount = router.query.account as string - // Path has ETH addreess + // Path has ETH address if (web3.utils.isAddress(pathAccount)) { const finalAccountId = pathAccount || accountId setFinalAccountId(finalAccountId) @@ -40,6 +40,11 @@ export default function PageProfile(): ReactElement { // Path has ENS name setFinalAccountEns(pathAccount) const resolvedAccountId = await getEnsAddress(pathAccount) + if ( + !resolvedAccountId || + resolvedAccountId === '0x0000000000000000000000000000000000000000' + ) + return setFinalAccountId(resolvedAccountId) } }