diff --git a/src/assets/crowdloan-advertising-banner.png b/src/assets/crowdloan-advertising-banner.png new file mode 100644 index 000000000..ccc2c00f3 Binary files /dev/null and b/src/assets/crowdloan-advertising-banner.png differ diff --git a/src/assets/crowdloan-banner.png b/src/assets/crowdloan-banner.png new file mode 100644 index 000000000..579b6b10b Binary files /dev/null and b/src/assets/crowdloan-banner.png differ diff --git a/src/assets/index.ts b/src/assets/index.ts index 8bf126a0f..c191948fa 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -41,4 +41,6 @@ export const Images = { avatarPlaceholder: require('./avatar-placeholder.png'), browserBanner: require('./browser-banner.png'), circleRobot: require('./circle-robot.png'), + crowdloanBanner: require('./crowdloan-banner.png'), + crowdloanAdvertisingBanner: require('./crowdloan-advertising-banner.png'), }; diff --git a/src/components/design-system-ui/modal/ModalBaseV2.tsx b/src/components/design-system-ui/modal/ModalBaseV2.tsx index b1edad017..cf12dc269 100644 --- a/src/components/design-system-ui/modal/ModalBaseV2.tsx +++ b/src/components/design-system-ui/modal/ModalBaseV2.tsx @@ -20,6 +20,7 @@ export interface SWModalProps { level?: number; onChangeModalVisible?: () => void; isUseForceHidden?: boolean; + disabledOnPressBackDrop?: boolean; } export type SWModalRefProps = { @@ -43,6 +44,7 @@ const ModalBaseV2 = React.forwardRef( onChangeModalVisible, isUseForceHidden, isAllowSwipeDown = true, + disabledOnPressBackDrop = false, }, ref, ) => { @@ -146,7 +148,12 @@ const ModalBaseV2 = React.forwardRef( <> {!isForcedHidden && isVisible && ( <> - + { // @ts-ignore diff --git a/src/components/design-system-ui/modal/SwModal.tsx b/src/components/design-system-ui/modal/SwModal.tsx index fe5c4f42d..b8608a8f2 100644 --- a/src/components/design-system-ui/modal/SwModal.tsx +++ b/src/components/design-system-ui/modal/SwModal.tsx @@ -29,6 +29,7 @@ export interface SWModalProps { modalBaseV2Ref?: React.RefObject; level?: number; isUseSafeAreaView?: boolean; + disabledOnPressBackDrop?: boolean; renderHeader?: React.ReactNode; } @@ -83,6 +84,7 @@ const SwModal = React.forwardRef( level, isUseSafeAreaView = true, renderHeader, + disabledOnPressBackDrop, }, ref, ) => { @@ -136,6 +138,7 @@ const SwModal = React.forwardRef( setVisible={setVisible} height={childrenHeight} ref={modalBaseV2Ref} + disabledOnPressBackDrop={disabledOnPressBackDrop} isUseForceHidden={Platform.OS === 'android'} onChangeModalVisible={onChangeModalVisible} isAllowSwipeDown={isAllowSwipeDown} diff --git a/src/hooks/campaign/useGetBannerByScreen.ts b/src/hooks/campaign/useGetBannerByScreen.ts new file mode 100644 index 000000000..e283064b9 --- /dev/null +++ b/src/hooks/campaign/useGetBannerByScreen.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; + +import { RootState } from 'stores/index'; +import { useSelector } from 'react-redux'; + +const useGetBannerByScreen = (screen: string) => { + const { banners } = useSelector((state: RootState) => state.campaign); + + return useMemo(() => { + return banners.filter(item => item.data.position.includes(screen)); + }, [banners, screen]); +}; + +export default useGetBannerByScreen; diff --git a/src/hooks/screen/Home/Crypto/useBuyToken.ts b/src/hooks/screen/Home/Crypto/useBuyToken.ts index 4a9e643f6..55675c0de 100644 --- a/src/hooks/screen/Home/Crypto/useBuyToken.ts +++ b/src/hooks/screen/Home/Crypto/useBuyToken.ts @@ -11,11 +11,10 @@ import { AccountType } from 'types/ui-types'; import { TokenItemType } from 'components/Modal/common/TokenSelector'; import { ModalRef } from 'types/modalRef'; import { InAppBrowser } from 'react-native-inappbrowser-reborn'; -import { ColorMap } from 'styles/color'; import { Linking, Platform } from 'react-native'; import { ServiceItem, baseServiceItems } from 'screens/Home/Crypto/ServiceModal'; import { BuyServiceInfo, CreateBuyOrderFunction, SupportService } from 'types/buy'; -import { createBanxaOrder, createCoinbaseOrder, createTransakOrder } from 'utils/buy'; +import { BrowserOptions, createBanxaOrder, createCoinbaseOrder, createTransakOrder } from 'utils/buy'; import { isEthereumAddress } from '@polkadot/util-crypto'; import { isAccountAll } from 'utils/accountAll'; import useAppLock from 'hooks/useAppLock'; @@ -201,7 +200,7 @@ export default function useBuyToken(currentSymbol?: string) { }, []); const onBuyToken = useCallback( - async (currentUrl?: string, animated = true) => { + async (currentUrl?: string) => { if (!selectedBuyAccount || !selectedBuyToken || !selectedService) { console.warn( 'no: selectedBuyAccount || selectedBuyToken || selectedService', @@ -243,39 +242,7 @@ export default function useBuyToken(currentSymbol?: string) { if (await InAppBrowser.isAvailable()) { // A delay to change the StatusBar when the browser is opened isOpenInAppBrowser.current = true; - await InAppBrowser.open(currentUrl || url, { - // iOS Properties - dismissButtonStyle: 'done', - preferredBarTintColor: ColorMap.dark1, - preferredControlTintColor: ColorMap.light, - animated, - modalEnabled: true, - enableBarCollapsing: false, - // Android Properties - showTitle: true, - toolbarColor: ColorMap.dark1, - secondaryToolbarColor: ColorMap.dark1, - navigationBarColor: ColorMap.dark1, - navigationBarDividerColor: 'white', - enableUrlBarHiding: true, - enableDefaultShare: true, - forceCloseOnRedirection: false, - // Specify full animation resource identifier(package:anim/name) - // or only resource name(in case of animation bundled with app). - animations: { - startEnter: 'slide_in_right', - startExit: 'slide_out_left', - endEnter: 'slide_in_left', - endExit: 'slide_out_right', - }, - headers: { - 'my-custom-header': 'my custom header value', - }, - hasBackButton: true, - browserPackage: undefined, - showInRecents: true, - includeReferrer: true, - }); + await InAppBrowser.open(currentUrl || url, BrowserOptions); isOpenInAppBrowser.current = false; } else { diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 07152a31b..fe6944f39 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -63,6 +63,7 @@ import { RequestAuthorizationBlock, RequestAuthorizationPerSite, RequestBondingSubmit, + RequestCampaignBannerComplete, RequestChangeMasterPassword, RequestConnectWalletConnect, RequestCronAndSubscriptionAction, @@ -1447,3 +1448,7 @@ export async function getMetadata(genesisHash?: string | null, isPartial = false return null; } + +export async function completeBannerCampaign(request: RequestCampaignBannerComplete): Promise { + return sendMessage('pri(campaign.banner.complete)', request); +} diff --git a/src/providers/DataContext.tsx b/src/providers/DataContext.tsx index 050768147..7d8b4c67e 100644 --- a/src/providers/DataContext.tsx +++ b/src/providers/DataContext.tsx @@ -32,6 +32,7 @@ import { subscribeXcmRefMap, subscribeConnectWCRequests, subscribeWalletConnectSessions, + subscribeProcessingCampaign, } from 'stores/utils'; import React, { useContext, useEffect, useRef } from 'react'; import { Provider } from 'react-redux'; @@ -304,6 +305,12 @@ export const DataContextProvider = ({ children }: DataContextProviderProps) => { }); // Features + _DataContext.addHandler({ + ...subscribeProcessingCampaign, + name: 'subscribeProcessingCampaign', + relatedStores: ['campaign'], + isStartImmediately: true, + }); _DataContext.addHandler({ ...subscribePrice, name: 'subscribePrice', diff --git a/src/screens/Home/Crowdloans/CampaignBannerModal/index.tsx b/src/screens/Home/Crowdloans/CampaignBannerModal/index.tsx new file mode 100644 index 000000000..04e89ee6f --- /dev/null +++ b/src/screens/Home/Crowdloans/CampaignBannerModal/index.tsx @@ -0,0 +1,103 @@ +import React, { useCallback, useRef } from 'react'; +import { Button, Icon, SwModal } from 'components/design-system-ui'; +import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; +import ModalStyle from './style'; +import { Linking, View } from 'react-native'; +import { ArrowCircleRight, XCircle } from 'phosphor-react-native'; +import FastImage from 'react-native-fast-image'; +import { InAppBrowser } from 'react-native-inappbrowser-reborn'; +import { BrowserOptions } from 'utils/buy'; +import { CampaignBanner, CampaignButton } from '@subwallet/extension-base/background/KoniTypes'; +import { completeBannerCampaign } from 'messaging/index'; + +interface Props { + visible: boolean; + setVisible: (value: boolean) => void; + banner: CampaignBanner; +} + +export type ButtonSchema = 'primary' | 'secondary' | 'warning' | 'danger' | 'ghost'; + +enum ButtonIcon { + // @ts-ignore + xCircle = XCircle, + // @ts-ignore + arrowCircleRight = ArrowCircleRight, +} + +const CampaignBannerModal = ({ visible, banner, setVisible }: Props) => { + const theme = useSubWalletTheme().swThemes; + const _style = ModalStyle(theme); + const isOpenInAppBrowser = useRef(false); + + const onPressJoinNow = async (url?: string) => { + if (url) { + if (await InAppBrowser.isAvailable()) { + isOpenInAppBrowser.current = true; + await InAppBrowser.open(url, BrowserOptions); + + isOpenInAppBrowser.current = false; + } else { + Linking.openURL(url); + } + } + }; + + const onCloseBanner = useCallback(() => { + setVisible(false); + completeBannerCampaign({ + slug: banner.slug, + }).catch(console.error); + }, [banner.slug, setVisible]); + + const onPressBtn = (item: CampaignButton) => { + return () => { + if (item.type === 'open_url') { + const url = item.metadata?.url as string | undefined; + + if (url) { + onPressJoinNow(url); + } + } + + if (item.metadata?.doneOnClick) { + onCloseBanner(); + } + }; + }; + + return ( + + + + + + {banner.buttons.map(item => ( + + ))} + + + + ); +}; + +export default CampaignBannerModal; diff --git a/src/screens/Home/Crowdloans/CampaignBannerModal/style/index.ts b/src/screens/Home/Crowdloans/CampaignBannerModal/style/index.ts new file mode 100644 index 000000000..44127209b --- /dev/null +++ b/src/screens/Home/Crowdloans/CampaignBannerModal/style/index.ts @@ -0,0 +1,24 @@ +import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; +import { ThemeTypes } from 'styles/themes'; +import { FontMedium } from 'styles/sharedStyles'; + +export interface CreateMasterPasswordStyle { + textStyle: TextStyle; + footerAreaStyle: ViewStyle; +} + +export default (theme: ThemeTypes) => + StyleSheet.create({ + textStyle: { + fontSize: theme.fontSize, + lineHeight: theme.fontSize * theme.lineHeight, + color: theme.colorTextLight4, + textAlign: 'center', + ...FontMedium, + }, + + footerAreaStyle: { + flexDirection: 'row', + gap: 16, + }, + }); diff --git a/src/screens/Home/Crowdloans/index.tsx b/src/screens/Home/Crowdloans/index.tsx index 52fc7ccc3..ba338cc86 100644 --- a/src/screens/Home/Crowdloans/index.tsx +++ b/src/screens/Home/Crowdloans/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import i18n from 'utils/i18n/i18n'; import { RocketLaunch } from 'phosphor-react-native'; @@ -11,8 +11,15 @@ import { useIsFocused } from '@react-navigation/native'; import { CrowdloanItemType } from 'types/index'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; -import { ListRenderItemInfo } from 'react-native'; +import { Linking, ListRenderItemInfo, TouchableOpacity } from 'react-native'; import { CrowdloanItem } from 'screens/Home/Crowdloans/CrowdloanItem'; +import FastImage from 'react-native-fast-image'; +import { Images } from 'assets/index'; +import { BUTTON_ACTIVE_OPACITY } from 'constants/index'; +import { InAppBrowser } from 'react-native-inappbrowser-reborn'; +import { BrowserOptions } from 'utils/buy'; +import useGetBannerByScreen from 'hooks/campaign/useGetBannerByScreen'; +import { CampaignBanner } from '@subwallet/extension-base/background/KoniTypes'; const renderListEmptyComponent = () => { return ( @@ -43,11 +50,35 @@ export const CrowdloansScreen = () => { { label: i18n.filterOptions.win, value: FilterValue.WINNER }, { label: i18n.filterOptions.fail, value: FilterValue.FAIL }, ]; - + const banners = useGetBannerByScreen('crowdloan'); + const isOpenInAppBrowser = useRef(false); const renderItem = ({ item }: ListRenderItemInfo) => { return ; }; + const openBanner = async (url: string) => { + if (await InAppBrowser.isAvailable()) { + isOpenInAppBrowser.current = true; + await InAppBrowser.open(url, BrowserOptions); + + isOpenInAppBrowser.current = false; + } else { + Linking.openURL(url); + } + }; + + const onPressBanner = useCallback((item: CampaignBanner) => { + return () => { + if (item.data.action === 'open_url') { + const url = item.data.metadata?.url as string | undefined; + + if (url) { + openBanner(url); + } + } + }; + }, []); + const crowdloanData = useMemo(() => { const result = items.sort( // @ts-ignore @@ -104,6 +135,26 @@ export const CrowdloansScreen = () => { filterFunction={getListByFilterOpt} isShowMainHeader placeholder={i18n.placeholder.searchProject} + beforeListItem={ + <> + {banners.map(item => ( + + + + ))} + + } /> ); diff --git a/src/screens/Home/index.tsx b/src/screens/Home/index.tsx index e331c470b..3ee421042 100644 --- a/src/screens/Home/index.tsx +++ b/src/screens/Home/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { BottomTabBarButtonProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import StakingScreen from './Staking/StakingScreen'; @@ -29,6 +29,10 @@ import i18n from 'utils/i18n/i18n'; import { RootStackParamList } from 'routes/index'; import { handleTriggerDeeplinkAfterLogin } from 'utils/deeplink'; import { isFirstOpen, setIsFirstOpen } from '../../AppNew'; +import CampaignBannerModal from 'screens/Home/Crowdloans/CampaignBannerModal'; +import { mmkvStore } from 'utils/storage'; +import useGetBannerByScreen from 'hooks/campaign/useGetBannerByScreen'; +import { CampaignBanner } from '@subwallet/extension-base/background/KoniTypes'; interface tabbarIconColor { color: string; @@ -183,6 +187,10 @@ export const Home = ({ navigation }: Props) => { const { hasMasterPassword, isReady, isLocked } = useSelector((state: RootState) => state.accountState); const [isLoading, setLoading] = useState(true); const appNavigatorDeepLinkStatus = useRef(AppNavigatorDeepLinkStatus.AVAILABLE); + const banners = useGetBannerByScreen('tokens'); + console.log('banners', banners); + const firstBanner = useMemo((): CampaignBanner | undefined => banners[0], [banners]); + const [campaignModalVisible, setCampaignModalVisible] = useState(false); useEffect(() => { if (isReady && isLoading) { @@ -198,6 +206,12 @@ export const Home = ({ navigation }: Props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isReady, isLoading, isLocked]); + useEffect(() => { + if (firstBanner) { + setCampaignModalVisible(true); + } + }, [firstBanner]); + if (isLoading) { return ( @@ -209,7 +223,11 @@ export const Home = ({ navigation }: Props) => { return ( <> + {!isLocked && } + {!isLocked && firstBanner && !isEmptyAccounts && ( + + )} ); }; diff --git a/src/stores/feature/Campaign.ts b/src/stores/feature/Campaign.ts new file mode 100644 index 000000000..b81545530 --- /dev/null +++ b/src/stores/feature/Campaign.ts @@ -0,0 +1,25 @@ +import { CampaignStore, ReduxStatus } from 'stores/types'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit/dist'; +import { CampaignBanner } from '@subwallet/extension-base/background/KoniTypes'; + +const initialState: CampaignStore = { + banners: [], + reduxStatus: ReduxStatus.INIT, +}; + +const campaignSlice = createSlice({ + initialState, + name: 'campaign', + reducers: { + updateBanner(state, action: PayloadAction) { + return { + ...state, + banners: action.payload, + reduxStatus: ReduxStatus.READY, + }; + }, + }, +}); + +export const { updateBanner } = campaignSlice.actions; +export default campaignSlice.reducer; diff --git a/src/stores/index.ts b/src/stores/index.ts index 38b8bebec..8fde9d8c0 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -22,6 +22,7 @@ import RequestStateReducer from './base/RequestState'; import SettingsReducer from './base/Settings'; import BalanceReducer from './feature/Balance'; import BondingReducer from './feature/Bonding'; +import CampaignReducer from './feature/Campaign'; import AssetRegistryReducer from './feature/common/AssetRegistry'; import ChainStoreReducer from './feature/common/ChainStore'; import CrowdloanReducer from './feature/Crowdloan'; @@ -42,7 +43,7 @@ const persistRootConfig = { key: 'root', version: 3, storage: AsyncStorage, - whitelist: ['mobileSettings', 'settings', 'appVersion'], + whitelist: ['mobileSettings', 'settings', 'appVersion', 'campaign'], blacklist: ['browser', 'price', 'balance', 'chainStore', 'assetRegistry'], migrate: async (state: any) => { if (state?._persist && state._persist.version < 3 && state.browser) { @@ -71,6 +72,7 @@ const rootReducer = combineReducers({ balance: persistReducer({ key: 'balance', storage: mmkvReduxStore } as PersistConfig, BalanceReducer), bonding: BondingReducer, walletConnect: WalletConnectReducer, + campaign: CampaignReducer, // Common chainStore: persistReducer( diff --git a/src/stores/types.ts b/src/stores/types.ts index 06b73c241..2c55d39fd 100644 --- a/src/stores/types.ts +++ b/src/stores/types.ts @@ -3,6 +3,7 @@ import { AddressBookState, AssetSetting, BalanceItem, + CampaignBanner, ChainStakingMetadata, ConfirmationDefinitions, ConfirmationsQueue, @@ -189,6 +190,10 @@ export interface BalanceStore extends BaseReduxStore { balanceMap: Record; } +export interface CampaignStore extends BaseReduxStore { + banners: CampaignBanner[]; +} + export type PriceStore = PriceJson; export interface CrowdloanStore extends BaseReduxStore { diff --git a/src/stores/utils/index.ts b/src/stores/utils/index.ts index 2fa3ba1e9..82b722559 100644 --- a/src/stores/utils/index.ts +++ b/src/stores/utils/index.ts @@ -9,6 +9,7 @@ import { AllLogoMap, AssetSetting, BalanceJson, + CampaignBanner, ChainStakingMetadata, ConfirmationsQueue, CrowdloanJson, @@ -449,6 +450,21 @@ export const subscribeWalletConnectSessions = lazySubscribeMessage( updateWalletConnectSessions, ); +/* Campaign */ +export const updateBanner = (data: CampaignBanner[]) => { + const filtered = data.filter(item => !item.isDone); + + store.dispatch({ type: 'campaign/updateBanner', payload: filtered }); +}; + +export const subscribeProcessingCampaign = lazySubscribeMessage( + 'pri(campaign.banner.subscribe)', + null, + updateBanner, + updateBanner, +); +/* Campaign */ + // export const updateChainValidators = (data: ChainValidatorParams) => { // store.dispatch({ type: 'bonding/updateChainValidators', payload: data }); // }; diff --git a/src/utils/buy/index.ts b/src/utils/buy/index.ts index c85b5a96c..b83ab7d69 100644 --- a/src/utils/buy/index.ts +++ b/src/utils/buy/index.ts @@ -1,6 +1,40 @@ -// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors -// SPDX-License-Identifier: Apache-2.0 +import { InAppBrowserOptions } from 'react-native-inappbrowser-reborn'; +import { ColorMap } from 'styles/color'; export { createBanxaOrder } from './banxa'; export { createCoinbaseOrder } from './coinbase'; export { createTransakOrder } from './transak'; + +export const BrowserOptions: InAppBrowserOptions = { + // iOS Properties + dismissButtonStyle: 'done', + preferredBarTintColor: ColorMap.dark1, + preferredControlTintColor: ColorMap.light, + animated: true, + modalEnabled: true, + enableBarCollapsing: false, + // Android Properties + showTitle: true, + toolbarColor: ColorMap.dark1, + secondaryToolbarColor: ColorMap.dark1, + navigationBarColor: ColorMap.dark1, + navigationBarDividerColor: 'white', + enableUrlBarHiding: true, + enableDefaultShare: true, + forceCloseOnRedirection: false, + // Specify full animation resource identifier(package:anim/name) + // or only resource name(in case of animation bundled with app). + animations: { + startEnter: 'slide_in_right', + startExit: 'slide_out_left', + endEnter: 'slide_in_left', + endExit: 'slide_out_right', + }, + headers: { + 'my-custom-header': 'my custom header value', + }, + hasBackButton: true, + browserPackage: undefined, + showInRecents: true, + includeReferrer: true, +}; diff --git a/src/utils/i18n/en_US.ts b/src/utils/i18n/en_US.ts index 8580c2188..0736b7cba 100644 --- a/src/utils/i18n/en_US.ts +++ b/src/utils/i18n/en_US.ts @@ -403,6 +403,8 @@ export const en = { addNft: 'Add NFT', addToken: 'Add token', addNetwork: 'Add network', + iDontCare: "I don't care", + joinNow: 'Join now', }, inputLabel: { selectAcc: 'Select account', diff --git a/src/utils/i18n/ja_JP.ts b/src/utils/i18n/ja_JP.ts index c1f19ffa1..f39f98faf 100644 --- a/src/utils/i18n/ja_JP.ts +++ b/src/utils/i18n/ja_JP.ts @@ -404,6 +404,8 @@ export const ja = { addNft: 'Add NFT', addToken: 'Add token', addNetwork: 'Add network', + iDontCare: "I don't care", + joinNow: 'Join now', }, inputLabel: { selectAcc: 'アカウントを選択', diff --git a/src/utils/i18n/ru_RU.ts b/src/utils/i18n/ru_RU.ts index a40f4a31b..f039c56ad 100644 --- a/src/utils/i18n/ru_RU.ts +++ b/src/utils/i18n/ru_RU.ts @@ -406,6 +406,8 @@ export const ru = { addNft: 'Add NFT', addToken: 'Add token', addNetwork: 'Add network', + iDontCare: "I don't care", + joinNow: 'Join now', }, inputLabel: { selectAcc: 'Выбрать аккаунт', diff --git a/src/utils/i18n/vi_VN.ts b/src/utils/i18n/vi_VN.ts index 1ddc4c3f7..b3a4065bc 100644 --- a/src/utils/i18n/vi_VN.ts +++ b/src/utils/i18n/vi_VN.ts @@ -402,6 +402,8 @@ export const vi = { addNft: 'Nhập NFT', addToken: 'Nhập token', addNetwork: 'Nhập mạng', + iDontCare: "I don't care", + joinNow: 'Join now', }, inputLabel: { selectAcc: 'Chọn tài khoản', diff --git a/src/utils/i18n/zh_CN.ts b/src/utils/i18n/zh_CN.ts index ccc04e357..80e89b367 100644 --- a/src/utils/i18n/zh_CN.ts +++ b/src/utils/i18n/zh_CN.ts @@ -398,6 +398,8 @@ export const zh = { addNft: '导入NFT', addToken: '导入代币', addNetwork: '导入网络', + iDontCare: "I don't care", + joinNow: 'Join now', }, inputLabel: { selectAcc: '选择账户',