diff --git a/cspell.json b/cspell.json index 3b35105fa48d..566f31e16a1b 100644 --- a/cspell.json +++ b/cspell.json @@ -243,7 +243,11 @@ "solana", "Görli", "resizer", - "Pageable" + "Pageable", + "metaverse", + "hexlify", + "contribs", + "contrib" ], "ignoreWords": [ "cryptopunks", @@ -346,7 +350,9 @@ "AICANFT", "CANFT", "Kodav", - "koda" + "koda", + "poap", + "irss" ], "ignoreRegExpList": ["/@servie/"], "overrides": [ diff --git a/package.json b/package.json index a988409f237d..9e8b39828041 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "preinstall": "npx only-allow pnpm", "postinstall": "patch-package", "prepare": "husky install", - "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js" + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", + "spellcheck": "cspell lint --no-must-find-files" }, "lint-staged": { "*": "prettier --write --ignore-unknown", diff --git a/packages/mask/package.json b/packages/mask/package.json index dff57ae3c59b..869f88efcfea 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -54,6 +54,7 @@ "@uniswap/sdk-core": "^3.0.1", "@uniswap/v2-sdk": "3.0.0-alpha.2", "@uniswap/v3-sdk": "^3.3.2", + "@unstoppabledomains/resolution": "^5.0.1", "@walletconnect/client": "^1.2.2", "anchorme": "^2.1.2", "arweave": "1.10.13", diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 1829ed2630cc..c143054eed52 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -18,7 +18,6 @@ "sign": "Sign", "reload": "Reload", "load": "Load", - "donation": "Donation", "load_all": "Load All", "supported_by": "Supported by", "no_data": "No Data", @@ -67,6 +66,7 @@ "unknown": "Unknown", "message": "Message", "dashboard_tab_collectibles": "Collectibles", + "dashboard_no_collection_found": "No collection found", "dashboard_no_collectible_found": "No collectible found", "days": "Every {{days}} days", "decrypted_postbox_add_recipients": "Append recipients", @@ -1010,5 +1010,15 @@ "plugin_profile_binding_rule3": "Twitter ID + {{suffix}} is ENS.", "plugin_profile_binding_rule4": "Address used to sign the avatar.", "plugin_profile_current_display_of": "Current display of {{type}}: ", + "plugin_profile_loading": "Loading...", + "plugin_profile_error_no_address": "Failed to find any valid address.", + "plugin_profile_address_name_ens": "ENS", + "plugin_profile_address_name_uns": "UNS", + "plugin_profile_address_name_dns": "DNS", + "plugin_profile_address_name_rns": "RNS", + "plugin_profile_address_name_address": "address", + "plugin_profile_rss3_inactive_project": "Inactive Project", + "plugin_profile_rss3_no_activity_time": "No activity time", + "plugin_profile_rss3_attended": "Attended", "plugin_vcent_last_offer_at": "LATEST OFFER at" } diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index d567a580cdb2..5a16b3fd3625 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -18,7 +18,6 @@ "sign": "crwdns10085:0crwdne10085:0", "reload": "crwdns10087:0crwdne10087:0", "load": "crwdns10089:0crwdne10089:0", - "donation": "crwdns10091:0crwdne10091:0", "load_all": "crwdns10093:0crwdne10093:0", "supported_by": "crwdns10095:0crwdne10095:0", "no_data": "crwdns10097:0crwdne10097:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index c183b9a9141c..cb5d41212706 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -18,7 +18,6 @@ "sign": "签名", "reload": "重新加载", "load": "加载", - "donation": "捐赠", "load_all": "加载全部", "supported_by": "技术支持:", "no_data": "暂无数据", @@ -888,10 +887,10 @@ "nft_avatar": "NFT 头像", "plugin_profile_no_wallets": "在此连接您的钱包。
此部分将显示给您的加密朋友。
显示内容包括数字艺术、捐赠、徽章
和其他链上的公开信息。", "plugin_profile_binding_rules_title": "Web3 按如下规则展示NFTs:", - "plugin_profile_binding_rule1": "您的Twitter 昵称为ENS。", - "plugin_profile_binding_rule2": "您的Twitter bio 包含 ENS(例如,vitalik.eth) 或有效地址。", - "plugin_profile_binding_rule3": "您的Twitter ID + {{suffix}} 是ENS。", - "plugin_profile_binding_rule4": "用于签署NFT 头像的地址。", - "plugin_profile_current_display_of": "当前显示 {{type}}: ", - "plugin_vcent_last_offer_at": "最新的OFFER为" + "plugin_profile_binding_rule1": "您的 Twitter 昵称为ENS。", + "plugin_profile_binding_rule2": "您的 Twitter bio 包含 ENS(例如,vitalik.eth) 或有效地址。", + "plugin_profile_binding_rule3": "您的 Twitter ID + {{suffix}} 是ENS。", + "plugin_profile_binding_rule4": "用于签署 NFT 头像的地址。", + "plugin_profile_current_display_of": "当前显示 {{type}}:", + "plugin_vcent_last_offer_at": "最新的 Offer 为" } diff --git a/packages/mask/src/database/Persona/types.ts b/packages/mask/src/database/Persona/types.ts index 94011842b08e..216b16cd2edd 100644 --- a/packages/mask/src/database/Persona/types.ts +++ b/packages/mask/src/database/Persona/types.ts @@ -6,6 +6,7 @@ export interface Profile extends Readonly { diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleCard.tsx similarity index 100% rename from packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx rename to packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleCard.tsx diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleItem.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleItem.tsx new file mode 100644 index 000000000000..2523db3dbd9e --- /dev/null +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleItem.tsx @@ -0,0 +1,47 @@ +import type { NonFungibleAssetProvider, ERC721TokenDetailed, Wallet } from '@masknet/web3-shared-evm' +import { Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { CollectibleCard } from './CollectibleCard' + +const useStyles = makeStyles()((theme) => ({ + card: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + position: 'relative', + padding: theme.spacing(1), + }, + description: { + background: theme.palette.mode === 'light' ? '#F7F9FA' : '#17191D', + alignSelf: 'stretch', + }, + name: { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + lineHeight: '36px', + paddingLeft: '8px', + }, +})) + +export interface CollectibleItemProps { + provider: NonFungibleAssetProvider + wallet?: Wallet + token: ERC721TokenDetailed + readonly?: boolean +} + +export function CollectibleItem(props: CollectibleItemProps) { + const { provider, wallet, token, readonly } = props + const { classes } = useStyles() + return ( +
+ +
+ + {token.info.name} + +
+
+ ) +} diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleList.tsx similarity index 57% rename from packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx rename to packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleList.tsx index 3c5801889ba4..1e581d44c960 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/CollectibleList.tsx @@ -4,19 +4,16 @@ import { useValueRef } from '@masknet/shared' import { ChainId, NonFungibleAssetProvider, - ERC721TokenCollectionInfo, ERC721TokenDetailed, useCollectibles, - useCollections, Wallet, } from '@masknet/web3-shared-evm' import { Box, Button, Skeleton, Typography } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' import { currentNonFungibleAssetDataProviderSettings } from '../../../../plugins/Wallet/settings' import { useI18N } from '../../../../utils' -import { CollectibleCard } from './CollectibleCard' +import { CollectibleItem } from './CollectibleItem' import { WalletMessages } from '../../../../plugins/Wallet/messages' -import { Image } from '../../../../components/shared/Image' export const CollectibleContext = createContext<{ collectiblesRetry: () => void @@ -50,17 +47,6 @@ const useStyles = makeStyles()((theme) => ({ position: 'relative', padding: theme.spacing(1), }, - description: { - background: theme.palette.mode === 'light' ? '#F7F9FA' : '#17191D', - alignSelf: 'stretch', - }, - name: { - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - overflow: 'hidden', - lineHeight: '36px', - paddingLeft: '8px', - }, loading: { position: 'absolute', bottom: 6, @@ -82,29 +68,6 @@ const useStyles = makeStyles()((theme) => ({ borderRadius: '50%', }, })) - -interface CollectibleItemProps { - provider: NonFungibleAssetProvider - wallet?: Wallet - token: ERC721TokenDetailed - readonly?: boolean -} - -function CollectibleItem(props: CollectibleItemProps) { - const { provider, wallet, token, readonly } = props - const { classes } = useStyles() - return ( -
- -
- - {token.info.name} - -
-
- ) -} - interface CollectibleListUIProps extends withClasses<'empty' | 'button' | 'text'> { provider: NonFungibleAssetProvider wallet?: Wallet @@ -115,12 +78,14 @@ interface CollectibleListUIProps extends withClasses<'empty' | 'button' | 'text' readonly?: boolean hasRetry?: boolean } + function CollectibleListUI(props: CollectibleListUIProps) { const { provider, wallet, collectibles, loading, collectiblesRetry, error, readonly, hasRetry = true } = props const classes = useStylesExtends(useStyles(), props) const { t } = useI18N() - WalletMessages.events.erc721TokensUpdated.on(collectiblesRetry) + useEffect(() => WalletMessages.events.erc721TokensUpdated.on(collectiblesRetry)) + if (loading) return ( @@ -157,11 +122,11 @@ function CollectibleListUI(props: CollectibleListUIProps) { {collectibles.map((x, i) => ( ))} @@ -171,16 +136,16 @@ function CollectibleListUI(props: CollectibleListUIProps) { ) } -export interface CollectibleListAddressProps extends withClasses<'empty' | 'button'> { +export interface CollectibleListProps extends withClasses<'empty' | 'button'> { + chainId: ChainId address: string collection?: string setCount: (count: number) => void } -export function CollectibleListAddress(props: CollectibleListAddressProps) { - const { address, collection, setCount } = props +export function CollectibleList(props: CollectibleListProps) { + const { chainId, address, collection, setCount } = props const provider = useValueRef(currentNonFungibleAssetDataProviderSettings) - const chainId = ChainId.Mainnet const [page, setPage] = useState(0) const classes = props.classes ?? {} @@ -189,7 +154,7 @@ export function CollectibleListAddress(props: CollectibleListAddressProps) { loading: collectiblesLoading, retry: collectiblesRetry, error: collectiblesError, - } = useCollectibles(address, chainId, provider, page, 50, collection) + } = useCollectibles(chainId, address, provider, page, 50, collection) const { collectibles = [], hasNextPage } = value const [rendCollectibles, setRendCollectibles] = useState([]) @@ -198,20 +163,15 @@ export function CollectibleListAddress(props: CollectibleListAddressProps) { }, [provider, address]) useEffect(() => { - if (collectibles.length) { - setRendCollectibles([...rendCollectibles, ...collectibles]) - if (hasNextPage) { - const timer = setTimeout(() => { - setPage(page + 1) - }, 1000) - - return () => { - clearTimeout(timer) - } - } + if (!collectibles.length) return + setRendCollectibles([...rendCollectibles, ...collectibles]) + if (!hasNextPage) return + const timer = setTimeout(() => { + setPage(page + 1) + }, 1000) + return () => { + clearTimeout(timer) } - - return () => {} }, [collectibles]) useEffect(() => { @@ -231,66 +191,3 @@ export function CollectibleListAddress(props: CollectibleListAddressProps) { /> ) } - -export function CollectionList({ address }: { address: string }) { - const provider = useValueRef(currentNonFungibleAssetDataProviderSettings) - const chainId = ChainId.Mainnet - const [page, setPage] = useState(0) - const { classes } = useStyles() - const [counts, setCounts] = useState([]) - const [rendCollections, setRendCollections] = useState([]) - - const { value = { collections: [], hasNextPage: false } } = useCollections(address, chainId, provider, page, 3) - const { collections = [], hasNextPage } = value - - useUpdateEffect(() => { - setPage(0) - }, [provider, address]) - - useEffect(() => { - if (collections.length) { - setRendCollections([...rendCollections, ...collections]) - if (hasNextPage) { - const timer = setTimeout(() => { - setPage(page + 1) - }, 3000) - - return () => { - clearTimeout(timer) - } - } - } - - return () => {} - }, [collections]) - - return ( - - {rendCollections.map((x, i) => ( - - - - {x.image ? : null} - - - {x.name} - {counts[i] ? `(${counts[i]})` : null} - - - { - counts[i] = count - setCounts(counts) - }} - /> - - ))} - - ) -} diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/index.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/index.tsx new file mode 100644 index 000000000000..5ed054b62d86 --- /dev/null +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectionList/index.tsx @@ -0,0 +1,151 @@ +import { createContext, useState, useEffect } from 'react' +import { useUpdateEffect } from 'react-use' +import { Box, Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { ChainId, ERC721TokenCollectionInfo, NonFungibleAssetProvider, useCollections } from '@masknet/web3-shared-evm' +import { Image } from '../../../../components/shared/Image' +import { CollectibleList } from './CollectibleList' +import { useI18N } from '../../../../utils' + +export const CollectibleContext = createContext<{ + collectiblesRetry: () => void +}>(null!) + +const useStyles = makeStyles()((theme) => ({ + root: { + display: 'grid', + flexWrap: 'wrap', + gridTemplateColumns: 'repeat(auto-fill, minmax(172px, 1fr))', + gridGap: theme.spacing(1), + }, + text: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100%', + }, + button: { + marginTop: theme.spacing(1), + }, + container: { + height: 'calc(100% - 52px)', + overflow: 'auto', + }, + card: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + position: 'relative', + padding: theme.spacing(1), + }, + description: { + background: theme.palette.mode === 'light' ? '#F7F9FA' : '#17191D', + alignSelf: 'stretch', + }, + name: { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + lineHeight: '36px', + paddingLeft: '8px', + }, + loading: { + position: 'absolute', + bottom: 6, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + }, + collectionWrap: { + width: '24px', + height: '24px', + borderRadius: '50%', + background: 'rgba(229,232,235,1)', + }, + collectionImg: { + objectFit: 'cover', + width: '100%', + height: '100%', + borderRadius: '50%', + }, +})) + +export interface CollectionListProps { + chainId: ChainId + address: string +} + +export function CollectionList({ address, chainId }: CollectionListProps) { + const { t } = useI18N() + const { classes } = useStyles() + const [page, setPage] = useState(0) + const [counts, setCounts] = useState([]) + const [rendCollections, setRendCollections] = useState([]) + + const { value = { collections: [], hasNextPage: false } } = useCollections( + chainId, + address, + NonFungibleAssetProvider.OPENSEA, + page, + 3, + ) + const { collections = [], hasNextPage } = value + + useUpdateEffect(() => { + setPage(0) + setRendCollections([]) + }, [address, chainId]) + + useEffect(() => { + if (!collections.length) return + setRendCollections([...rendCollections, ...collections]) + if (!hasNextPage) return + const timer = setTimeout(() => { + setPage(page + 1) + }, 3000) + return () => { + clearTimeout(timer) + } + }, [collections]) + + if (!collections.length) { + return ( + + {t('dashboard_no_collection_found')} + + ) + } + + return ( + + {rendCollections.map((x, i) => ( + + + + {x.image ? : null} + + + {x.name} + {counts[i] ? `(${counts[i]})` : null} + + + { + counts[i] = count + setCounts(counts) + }} + /> + + ))} + + ) +} diff --git a/packages/mask/src/extension/options-page/DashboardDialogs/Wallet/TransferNFT.tsx b/packages/mask/src/extension/options-page/DashboardDialogs/Wallet/TransferNFT.tsx index 36c3690b5672..4d79d03418d2 100644 --- a/packages/mask/src/extension/options-page/DashboardDialogs/Wallet/TransferNFT.tsx +++ b/packages/mask/src/extension/options-page/DashboardDialogs/Wallet/TransferNFT.tsx @@ -13,7 +13,7 @@ import { useRemoteControlledDialog } from '@masknet/shared' import { Image } from '../../../../components/shared/Image' import { WalletMessages } from '../../../../plugins/Wallet/messages' import { MaskIconOutlined } from '../../../../resources/MaskIcon' -import { CollectibleContext } from '../../DashboardComponents/CollectibleList' +import { CollectibleContext } from '../../DashboardComponents/CollectionList' // eslint-disable-next-line import/no-deprecated import { DashboardDialogCore, DashboardDialogWrapper, WrappedDialogProps } from '../Base' diff --git a/packages/mask/src/manifest.json b/packages/mask/src/manifest.json index d46e6a243549..f9fc4ff9aef6 100644 --- a/packages/mask/src/manifest.json +++ b/packages/mask/src/manifest.json @@ -10,6 +10,6 @@ "browser_action": { "default_popup": "popups.html" }, "homepage_url": "https://mask.io", "description": "The portal to the new & open Internet. Send encrypted message and decentralized Apps right on top of social networks.", - "web_accessible_resources": ["js/*", "*.png"], + "web_accessible_resources": ["js/*", "*.png", "*.css"], "content_security_policy": "script-src 'self' 'wasm-eval'; object-src 'self';" } diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx index 20aef9b42ba6..db3ebf0f0897 100644 --- a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx +++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx @@ -105,7 +105,7 @@ export function NFTAvatar(props: NFTAvatarProps) { loading, retry, error, - } = useCollectibles(account, ChainId.Mainnet, provider, page, 50) + } = useCollectibles(ChainId.Mainnet, account, provider, page, 50) const { collectibles, hasNextPage } = value const onClick = useCallback(async () => { diff --git a/packages/mask/src/plugins/Avatar/Services/index.ts b/packages/mask/src/plugins/Avatar/Services/index.ts index d5528e006f8f..de7511f874c1 100644 --- a/packages/mask/src/plugins/Avatar/Services/index.ts +++ b/packages/mask/src/plugins/Avatar/Services/index.ts @@ -28,7 +28,7 @@ export async function saveNFTAvatar(address: string, nft: AvatarMetaDB) { export async function getAddress(userId: string) { const address = await getUserAddress(userId) - return address + return (address ?? '') as string } export { getNFTContractVerifiedFromJSON } from './verified' diff --git a/packages/mask/src/plugins/Avatar/hooks/useUserOwnerAddress.ts b/packages/mask/src/plugins/Avatar/hooks/useUserOwnerAddress.ts index 9ced59fe96b5..6d0df27138a4 100644 --- a/packages/mask/src/plugins/Avatar/hooks/useUserOwnerAddress.ts +++ b/packages/mask/src/plugins/Avatar/hooks/useUserOwnerAddress.ts @@ -3,15 +3,13 @@ import type { AsyncState } from 'react-use/lib/useAsyncFn' import { PluginNFTAvatarRPC } from '../messages' const cache = new Map>() + export function useUserOwnerAddress(userId: string): AsyncState { return useAsync(async () => { if (!userId) return - let f = cache.get(userId) - if (!f) { - f = PluginNFTAvatarRPC.getAddress(userId) - cache.set(userId, f) + if (!cache.has(userId)) { + cache.set(userId, PluginNFTAvatarRPC.getAddress(userId)) } - const address = await f - return address - }, [userId, cache]) + return cache.get(userId) + }, [userId]) } diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts index fd6a87929f46..a3c11f21e11f 100644 --- a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts +++ b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts @@ -139,8 +139,8 @@ export const getNonFungibleTokenFn = } const tokenFromProvider = await context.getAssetsListNFT( - address.toLowerCase(), network?.chainId ?? ChainId.Mainnet, + address.toLowerCase(), NonFungibleAssetProvider.OPENSEA, pagination?.page ?? 0, pagination?.size ?? 20, diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/TokenCard.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/TokenCard.tsx index 74a501d5f1b1..627ae800a029 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/TokenCard.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/TokenCard.tsx @@ -1,8 +1,8 @@ +import { memo } from 'react' import { makeStyles } from '@masknet/theme' -import { ERC721ContractDetailed, NonFungibleAssetProvider, useERC721TokenDetailed } from '@masknet/web3-shared-evm' import { Typography } from '@mui/material' -import { memo } from 'react' -import { CollectibleCard } from '../../../../extension/options-page/DashboardComponents/CollectibleList/CollectibleCard' +import { ERC721ContractDetailed, NonFungibleAssetProvider, useERC721TokenDetailed } from '@masknet/web3-shared-evm' +import { CollectibleCard } from '../../../../extension/options-page/DashboardComponents/CollectionList/CollectibleCard' const useStyles = makeStyles()((theme) => ({ title: { diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/DAOPage.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/DAOPage.tsx index 6647a61e0e22..ffc81be94d1a 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/DAOPage.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/DAOPage.tsx @@ -1,8 +1,8 @@ +import { useState } from 'react' import IframeResizer from 'iframe-resizer-react' import { useTheme } from '@mui/material' -import type { Dao_Payload } from './hooks/useDao' -import { useState } from 'react' import type { ProfileIdentifier } from '@masknet/shared-base' +import type { Dao_Payload } from './hooks' interface DAOPageProps { payload: Dao_Payload | undefined diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/DonationsPage.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/DonationsPage.tsx index 02f1553eff8d..2393ada10c2e 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/DonationsPage.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/DonationsPage.tsx @@ -1,11 +1,68 @@ -import { Typography } from '@mui/material' +import { makeStyles } from '@masknet/theme' +import { CircularProgress, Link } from '@mui/material' +import urlcat from 'urlcat' +import type { GeneralAssetWithTags } from '../types' +import { RSS3_DEFAULT_IMAGE } from '../constants' +import { DonationCard } from './components' +import { useDonations } from './hooks' import { useI18N } from '../../../utils' -interface DonationPageProps {} + +const getDonationLink = (address: string, donation: GeneralAssetWithTags) => { + const { platform, identity, id, type } = donation + return urlcat('https://rss3.bio/:address/singlegitcoin/:platform/:identity/:id/:type', { + address, + platform, + identity, + id, + type, + }) +} + +const useStyles = makeStyles()((theme) => ({ + address: { + color: theme.palette.primary.main, + }, + link: { + '&:hover': { + textDecoration: 'none', + }, + }, +})) + +export interface DonationPageProps { + address: string +} + export function DonationPage(props: DonationPageProps) { + const { address } = props + const { classes } = useStyles() + const { donations, loading } = useDonations(address) const { t } = useI18N() + + if (loading) { + return ( +
+ +
+ ) + } return ( - - {t('donation')} - +
+ {donations.map((donation) => ( + + + + ))} +
) } diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfile.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfile.tsx index 9e96f2171a51..1bed7d12b73f 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfile.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfile.tsx @@ -1,18 +1,23 @@ import { useEffect, useMemo, useState } from 'react' -import { makeStyles, useStylesExtends } from '@masknet/theme' - -import { MaskMessages } from '../../../utils' +import { useUpdateEffect } from 'react-use' +import { Box, Typography } from '@mui/material' +import { unreachable } from '@dimensiondev/kit' +import { makeStyles } from '@masknet/theme' +import { AddressNameType, ChainId, useAddressNames } from '@masknet/web3-shared-evm' +import { MaskMessages, useI18N } from '../../../utils' import { useLocationChange } from '../../../utils/hooks/useLocationChange' -import { useCurrentVisitingIdentity } from '../../../components/DataSource/useActivatedUI' import { WalletsPage } from './WalletsPage' import { NFTPage } from './NFTPage' import { DonationPage } from './DonationsPage' +import { FootprintPage } from './FootprintPage' import { DAOPage } from './DAOPage' import { PageTags } from '../types' -import { unreachable } from '@dimensiondev/kit' import { PageTag } from './PageTag' -import { useDao } from './hooks/useDao' -import { useUpdateEffect } from 'react-use' +import { AddressViewer } from './components/AddressViewer' +import { NetworkSelector } from './components/NetworkSelector' +import { useCurrentVisitingIdentity } from '../../../components/DataSource/useActivatedUI' +import { useDao } from './hooks' +import type { Web3Plugin } from '@masknet/plugin-infra' const useStyles = makeStyles()((theme) => ({ root: { @@ -25,55 +30,132 @@ const useStyles = makeStyles()((theme) => ({ tags: { padding: theme.spacing(2), }, + metadata: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(1), + }, content: { position: 'relative', padding: theme.spacing(1), }, })) -interface EnhancedProfilePageProps extends withClasses<'text' | 'button'> {} +export interface EnhancedProfilePageProps {} export function EnhancedProfilePage(props: EnhancedProfilePageProps) { - const [show, setShow] = useState(false) - const classes = useStylesExtends(useStyles(), props) + const { t } = useI18N() + const { classes } = useStyles() + + //#region identity const identity = useCurrentVisitingIdentity() + const { value: addressNames, loading: loadingAddressNames } = useAddressNames(identity) + //#endregion + + const [hidden, setHidden] = useState(true) const { value: daoPayload } = useDao(identity.identifier) const [currentTag, setCurrentTag] = useState(PageTags.NFTTag) + const [selectedNetwork, setSelectedNetwork] = useState(null) + useLocationChange(() => { + setCurrentTag(PageTags.NFTTag) + setSelectedNetwork(null) MaskMessages.events.profileNFTsTabUpdated.sendToLocal('reset') }) useEffect(() => { return MaskMessages.events.profileNFTsPageUpdated.on((data) => { - setShow(data.show) + setHidden(!data.show) }) - }, []) + }, [identity]) + + useUpdateEffect(() => { + setCurrentTag(PageTags.NFTTag) + }, [identity.identifier]) + + const addressName = useMemo(() => { + const getAddressByType = (type: AddressNameType) => addressNames?.find((x) => x.type === type) + + const addressLiteral = getAddressByType(AddressNameType.ADDRESS) + const addressENS = getAddressByType(AddressNameType.ENS) + const addressUNS = getAddressByType(AddressNameType.UNS) + const addressGUN = getAddressByType(AddressNameType.GUN) + const addressRSS3 = getAddressByType(AddressNameType.RSS3) + const addressTheGraph = getAddressByType(AddressNameType.THE_GRAPH) + + switch (currentTag) { + case PageTags.WalletTag: + return + case PageTags.NFTTag: + return addressENS || addressUNS || addressRSS3 || addressLiteral || addressGUN || addressTheGraph + case PageTags.DonationTag: + return addressRSS3 || addressENS || addressUNS || addressLiteral || addressGUN || addressTheGraph + case PageTags.FootprintTag: + return addressRSS3 || addressENS || addressUNS || addressLiteral || addressGUN || addressTheGraph + case PageTags.DAOTag: + return + default: + unreachable(currentTag) + } + }, [currentTag, addressNames]) + + const address = addressName?.resolvedAddress ?? '' const content = useMemo(() => { switch (currentTag) { case PageTags.WalletTag: return case PageTags.NFTTag: - return + return case PageTags.DonationTag: - return + return + case PageTags.FootprintTag: + return case PageTags.DAOTag: return default: unreachable(currentTag) } - }, [currentTag, daoPayload, identity.identifier]) + }, [address, currentTag, daoPayload, identity.identifier, selectedNetwork]) - useUpdateEffect(() => { - setCurrentTag(PageTags.NFTTag) - }, [identity.identifier]) + if (hidden) return null - if (!show) return null + if (loadingAddressNames) + return ( +
+ + {t('plugin_profile_loading')} + +
+ ) + + if (!addressName) + return ( +
+ + {t('plugin_profile_error_no_address')} + +
+ ) return (
+
- + +
+
+ {currentTag === PageTags.NFTTag ? :
} +
{content}
diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfileTab.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfileTab.tsx index b583f310388f..c366dca0c389 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfileTab.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/EnhancedProfileTab.tsx @@ -1,6 +1,6 @@ -import { Typography } from '@mui/material' -import classnames from 'classnames' import { ReactElement, useCallback, useEffect, useState } from 'react' +import classnames from 'classnames' +import { Typography } from '@mui/material' import { MaskMessages } from '../../../utils' export interface EnhancedProfileTabProps extends withClasses<'tab' | 'button' | 'selected'> { diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/FootprintPage.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/FootprintPage.tsx new file mode 100644 index 000000000000..21cf9d77e585 --- /dev/null +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/FootprintPage.tsx @@ -0,0 +1,73 @@ +import urlcat from 'urlcat' +import { makeStyles } from '@masknet/theme' +import { CircularProgress, Link } from '@mui/material' +import type { GeneralAssetWithTags } from '../types' +import { RSS3_DEFAULT_IMAGE } from '../constants' +import { FootprintCard } from './components' +import { useFootprints, useRss3Profile } from './hooks' + +const useStyles = makeStyles()((theme) => ({ + address: { + color: theme.palette.primary.main, + }, + link: { + '&:hover': { + textDecoration: 'none', + }, + }, +})) + +const getFootprintLink = (address: string, footprint: GeneralAssetWithTags) => { + const { platform, identity, id, type } = footprint + return urlcat('https://rss3.bio/:address/singlefootprint/:platform/:identity/:id/:type', { + address, + platform, + identity, + id, + type, + }) +} + +export interface FootprintPageProps { + address: string +} + +export function FootprintPage(props: FootprintPageProps) { + const { address } = props + const { classes } = useStyles() + const { value: profile } = useRss3Profile(address) + const username = profile?.name + + const { footprints, loading } = useFootprints(address) + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+ {footprints.map((footprint) => ( + + + + ))} +
+ ) +} diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/NFTPage.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/NFTPage.tsx index 65cd490a5cb5..63527eed759a 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/NFTPage.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/NFTPage.tsx @@ -1,26 +1,13 @@ -import { getMaskColor, makeStyles, useStylesExtends } from '@masknet/theme' -import { - formatEthereumAddress, - resolveAddressLinkOnExplorer, - ChainId, - EthereumNameType, - useEthereumAddress, -} from '@masknet/web3-shared-evm' -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' -import { Box, Link, Typography, CircularProgress, Tooltip } from '@mui/material' -import { useCurrentVisitingIdentity } from '../../../components/DataSource/useActivatedUI' -import { CollectionList } from '../../../extension/options-page/DashboardComponents/CollectibleList' +import { getMaskColor, makeStyles } from '@masknet/theme' +import { Box, Typography } from '@mui/material' +import type { ChainId } from '@masknet/web3-shared-evm' import { useI18N } from '../../../utils' -import { useUserOwnerAddress } from '../../Avatar/hooks/useUserOwnerAddress' +import { CollectionList } from '../../../extension/options-page/DashboardComponents/CollectionList' const useStyles = makeStyles()((theme) => ({ root: { position: 'relative', }, - note: { - padding: `0 ${theme.spacing(1)}`, - textAlign: 'right', - }, text: { paddingTop: 36, paddingBottom: 36, @@ -28,51 +15,19 @@ const useStyles = makeStyles()((theme) => ({ color: getMaskColor(theme).textPrimary, }, }, - icon: { - color: getMaskColor(theme).textPrimary, - }, - iconContainer: { - display: 'inherit', - }, - tipList: { - listStyleType: 'decimal', - paddingLeft: 16, - }, })) -interface NFTPageProps extends withClasses<'text' | 'button'> {} +export interface NFTPageProps { + address: string + chainId: ChainId +} export function NFTPage(props: NFTPageProps) { - const classes = useStylesExtends(useStyles(), props) + const { address, chainId } = props const { t } = useI18N() - const identity = useCurrentVisitingIdentity() - const { loading: loadingENS, value } = useEthereumAddress( - identity.nickname ?? '', - identity.identifier.userId, - identity.bio ?? '', - ) - const { type, name, address } = value ?? {} - const { loading: loadingWalletGun, value: walletAddressGun } = useUserOwnerAddress(identity.identifier.userId) + const { classes } = useStyles() - const rulesTipMap = [ - t('plugin_profile_binding_rule1'), - t('plugin_profile_binding_rule2'), - t('plugin_profile_binding_rule3', { suffix: `".eth"` }), - t('plugin_profile_binding_rule4'), - ] - - const tooltipRender = ( -
- {t('plugin_profile_binding_rules_title')} -
    - {rulesTipMap.map((item, index) => { - return
  • {item}
  • - })} -
-
- ) - - if (!address && !walletAddressGun) + if (!address) return (
@@ -80,58 +35,10 @@ export function NFTPage(props: NFTPageProps) {
) + return (
- {loadingWalletGun || loadingENS ? ( - - - - ) : ( - <> - - - - {t('plugin_profile_current_display_of', { type: type })} - - {type === EthereumNameType.DEFAULT - ? formatEthereumAddress( - (address?.length === 0 ? walletAddressGun : address) ?? '', - 4, - ) - : name} - - -
- - - -
-
-
- - - )} +
) } diff --git a/packages/mask/src/plugins/Profile/SNSAdaptor/PageTag.tsx b/packages/mask/src/plugins/Profile/SNSAdaptor/PageTag.tsx index 17ee66423075..dcf77987e852 100644 --- a/packages/mask/src/plugins/Profile/SNSAdaptor/PageTag.tsx +++ b/packages/mask/src/plugins/Profile/SNSAdaptor/PageTag.tsx @@ -1,16 +1,18 @@ +import classNames from 'classnames' import { makeStyles } from '@masknet/theme' import { Button } from '@mui/material' -import classNames from 'classnames' import { useI18N } from '../../../utils' import { PageTags } from '../types' -import type { Dao_Payload } from './hooks/useDao' +import { useDonations, useFootprints, Dao_Payload } from './hooks' const useStyles = makeStyles()((theme) => ({ root: { - '>:nth-child(2)': { - marginLeft: theme.spacing(2), - marginRight: theme.spacing(2), - }, + display: 'flex', + gap: '8px', + flexWrap: 'wrap', + }, + hidden: { + display: 'none', }, button: { border: `1px solid ${theme.palette.text.primary} !important`, @@ -22,21 +24,41 @@ const useStyles = makeStyles()((theme) => ({ color: theme.palette.primary.main, borderRadius: 9999, }, - hidden: { - display: 'none', + connectRSS3: { + position: 'absolute', + right: '16px', + boxSizing: 'border-box', + height: 36, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 9999, + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + border: '0px solid', + backgroundColor: theme.palette.primary.main, + color: 'rgb(255, 255, 255)', + cursor: 'pointer', }, })) -interface PageTagProps { +export interface PageTagProps { tag: PageTags + address: string daoPayload: Dao_Payload | undefined onChange: (tag: PageTags) => void } export function PageTag(props: PageTagProps) { const { classes } = useStyles() - const { onChange, tag, daoPayload } = props + const { onChange, tag, address, daoPayload } = props const { t } = useI18N() + + const { donations } = useDonations(address) + const { footprints } = useFootprints(address) + const hasDonations = donations.length > 0 + const hasFootprints = footprints.length > 0 + return (
- + {hasDonations ? ( + + ) : null} + {hasFootprints ? ( + + ) : null} {daoPayload ? (