From 04b5fc57f3c9450a0edefe441dcfe90f307c60b6 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Thu, 15 Aug 2024 01:40:28 +0300 Subject: [PATCH 01/37] fix: RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED flag android share support --- app/components/ShareButton.tsx | 19 ++++++++++++++----- app/i18n/i18n_en.ts | 3 ++- app/i18n/i18n_ru.ts | 3 ++- app/i18n/schema.ts | 3 ++- package.json | 2 +- yarn.lock | 8 ++++---- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/components/ShareButton.tsx b/app/components/ShareButton.tsx index cce6db96f..847b459eb 100644 --- a/app/components/ShareButton.tsx +++ b/app/components/ShareButton.tsx @@ -4,6 +4,7 @@ import ShareIcon from '@assets/ic_share_address.svg'; import { t } from "../i18n/t"; import Share from 'react-native-share'; import { useTheme } from "../engine/hooks"; +import { useToaster } from "./toast/ToastProvider"; const size = { height: 56, @@ -28,17 +29,25 @@ export const ShareButton = memo(({ onScreenCapture?: () => Promise<{ uri: string }> }) => { const theme = useTheme(); + const toaster = useToaster(); const onShare = useCallback(async () => { let screenShot: { uri: string } | undefined; if (onScreenCapture) { screenShot = await onScreenCapture(); } - Share.open({ - title: t('receive.share.title'), - message: body, - url: screenShot?.uri, - }); + try { + await Share.open({ + title: t('receive.share.title'), + message: body, + url: screenShot?.uri, + }); + } catch { + toaster.show({ + type: 'error', + message: t('receive.share.error') + }); + } }, [body]); return ( diff --git a/app/i18n/i18n_en.ts b/app/i18n/i18n_en.ts index 81e7ddab5..e8866e984 100644 --- a/app/i18n/i18n_en.ts +++ b/app/i18n/i18n_en.ts @@ -160,7 +160,8 @@ const schema: PrepareSchema = { title: 'Receive', subtitle: 'Only send TON Blockchain assets to this address. Other assets will be lost forever', share: { - title: 'My Tonhub Address' + title: 'My Tonhub Address', + error: 'Failed to share address, please try again or contact support' } }, transfer: { diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index c499f974d..9657f8d3d 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -144,7 +144,8 @@ const schema: PrepareSchema = { "title": "Получить", "subtitle": "Отправляйте на этот адрес только токены блокчейна TON. Другие активы будут потеряны навсегда", "share": { - "title": "My Tonhub Address" + "title": "Мой Tonhub адрес", + "error": "Не удалось поделиыться адресом, попробуйте еще раз или обратитесь в службу поддержки" } }, "transfer": { diff --git a/app/i18n/schema.ts b/app/i18n/schema.ts index 55b0d8986..41f91e498 100644 --- a/app/i18n/schema.ts +++ b/app/i18n/schema.ts @@ -162,7 +162,8 @@ export type LocalizationSchema = { title: string, subtitle: string, share: { - title: string + title: string, + error: string } }, transfer: { diff --git a/package.json b/package.json index 60bfeb5f7..17e6aa097 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "react-native-reanimated": "^3.6.1", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.0", - "react-native-share": "^9.4.1", + "react-native-share": "^10.2.1", "react-native-sse": "^1.1.0", "react-native-svg": "14.0.0", "react-native-tab-view": "^3.5.2", diff --git a/yarn.lock b/yarn.lock index 9640e378e..237721844 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9195,10 +9195,10 @@ react-native-screens@~3.22.0: react-freeze "^1.0.0" warn-once "^0.1.0" -react-native-share@^9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-9.4.1.tgz#1b6d96015009e3878bfc4346940602c1cffff525" - integrity sha512-jm4qA5J5+ytWA8UFg6s8iEfdZYGPW+t5oreSuzrPt0assjvBUlFaoqYGGwGR5RJ8BIpjzOJYvx/c9MjXB4ApUg== +react-native-share@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-10.2.1.tgz#baf94848c2acee6e52f6b28e05c47fa5fa9402be" + integrity sha512-Z2LWGYWH7raM4H6Oauttv1tEhaB43XSWJAN8iS6oaSG9CnyrUBeYFF4QpU1AH5RgNeylXQdN8CtbizCHHt6coQ== react-native-sse@^1.1.0: version "1.2.0" From c80804efe36ab9a874552c6ccb1fdb3626e5fd59 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Thu, 15 Aug 2024 01:40:37 +0300 Subject: [PATCH 02/37] fix: typo --- app/fragments/apps/components/inject/createInjectSource.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/fragments/apps/components/inject/createInjectSource.ts b/app/fragments/apps/components/inject/createInjectSource.ts index 420c0ce49..d62a6314c 100644 --- a/app/fragments/apps/components/inject/createInjectSource.ts +++ b/app/fragments/apps/components/inject/createInjectSource.ts @@ -221,7 +221,7 @@ export const authAPI = (params: { lastAuthTime?: number, isSecured: boolean }) = export const dappWalletAPI = ` window['dapp-wallet'] = (() => { - let __DAPP_WALLET_AVAILIBLE = true; + let __DAPP_WALLET_AVAILABLE = true; let inProgress = false; let currentCallback = null; @@ -290,7 +290,7 @@ window['dapp-wallet'] = (() => { } } - const obj = { __DAPP_WALLET_AVAILIBLE, isEnabled, checkIfCardIsAlreadyAdded, canAddCard, addCardToWallet, __response }; + const obj = { __DAPP_WALLET_AVAILABLE, isEnabled, checkIfCardIsAlreadyAdded, canAddCard, addCardToWallet, __response }; Object.freeze(obj); return obj; })(); From 7af9fa123d84d0f26176cb48ab426fe49af824f2 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 16 Aug 2024 03:39:09 +0300 Subject: [PATCH 03/37] fix: fixing liquid staking input amount state reducer --- .../staking/LiquidStakingTransferFragment.tsx | 41 ++----------------- .../staking/liquidStakingAmountReducer.ts | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 app/utils/staking/liquidStakingAmountReducer.ts diff --git a/app/fragments/staking/LiquidStakingTransferFragment.tsx b/app/fragments/staking/LiquidStakingTransferFragment.tsx index 3f099ba7a..41c6b4e13 100644 --- a/app/fragments/staking/LiquidStakingTransferFragment.tsx +++ b/app/fragments/staking/LiquidStakingTransferFragment.tsx @@ -31,43 +31,10 @@ import { Typography } from '../../components/styles'; import { useValidAmount } from '../../utils/useValidAmount'; import IcTonIcon from '@assets/ic-ton-acc.svg'; +import { LiquidStakingAmountAction, liquidStakingAmountReducer } from '../../utils/staking/liquidStakingAmountReducer'; export type LiquidStakingTransferParams = Omit; -type AmountAction = { type: 'ton', amount: string } | { type: 'wsTon', amount: string }; -type AmountState = { ton: string, wsTon: string }; - -function reduceAmountState(withdrawRate: bigint, depositRate: bigint, type: 'withdraw' | 'top_up') { - return (state: AmountState, action: AmountAction): AmountState => { - try { - const amount = action.amount.replace(',', '.').replaceAll(' ', ''); - if (action.type === 'ton') { - const ton = formatInputAmount(action.amount, 9, { skipFormattingDecimals: true }, state.ton); - const computed = parseFloat(amount) * parseFloat(fromNano(type === 'withdraw' ? withdrawRate : depositRate)) || 0 - const wsTon = fromNano(toNano(computed.toFixed(9))); - - if (ton === state.ton) { - return state; - } - - return { ton, wsTon }; - } - - const wsTon = formatInputAmount(action.amount, 9, { skipFormattingDecimals: true }, state.wsTon); - const computed = parseFloat(amount) * parseFloat(fromNano(type === 'withdraw' ? withdrawRate : depositRate)) || 0; - const ton = fromNano(toNano(computed.toFixed(9))); - - if (wsTon === state.wsTon) { - return state; - } - - return { ton, wsTon }; - } catch { - return state; - } - } -} - export const LiquidStakingTransferFragment = fragment(() => { const theme = useTheme(); const network = useNetwork(); @@ -101,7 +68,7 @@ export const LiquidStakingTransferFragment = fragment(() => { } if (params?.action === 'top_up' && params.amount) { - initAmount = reduceAmountState( + initAmount = liquidStakingAmountReducer( liquidStaking?.rateWithdraw ?? 0n, liquidStaking?.rateDeposit ?? 0n, 'top_up' @@ -109,7 +76,7 @@ export const LiquidStakingTransferFragment = fragment(() => { } const [amount, dispatchAmount] = useReducer( - reduceAmountState( + liquidStakingAmountReducer( liquidStaking?.rateWithdraw ?? 0n, liquidStaking?.rateDeposit ?? 0n, params?.action === 'withdraw' ? 'withdraw' : 'top_up' @@ -131,7 +98,7 @@ export const LiquidStakingTransferFragment = fragment(() => { return 0n; }, [params.action, member, account]); - const onSetAmount = useCallback((action: AmountAction) => { + const onSetAmount = useCallback((action: LiquidStakingAmountAction) => { setMinAmountWarn(undefined); dispatchAmount(action); }, []); diff --git a/app/utils/staking/liquidStakingAmountReducer.ts b/app/utils/staking/liquidStakingAmountReducer.ts new file mode 100644 index 000000000..36d09430c --- /dev/null +++ b/app/utils/staking/liquidStakingAmountReducer.ts @@ -0,0 +1,41 @@ +import { fromNano, toNano } from "@ton/core"; +import { formatInputAmount } from "../formatCurrency"; + +export type LiquidStakingAmountAction = { type: 'ton', amount: string } | { type: 'wsTon', amount: string }; +type AmountState = { ton: string, wsTon: string }; + +export function liquidStakingAmountReducer(withdrawRate: bigint, depositRate: bigint, type: 'withdraw' | 'top_up') { + return (state: AmountState, action: LiquidStakingAmountAction): AmountState => { + if (action.amount === '' || action.amount === '') { + return { ton: '', wsTon: '' }; + } + try { + const rate = fromNano(type === 'withdraw' ? withdrawRate : depositRate); + + const amount = action.amount.replace(',', '.').replaceAll(' ', ''); + if (action.type === 'ton') { + const ton = formatInputAmount(action.amount, 9, { skipFormattingDecimals: true }, state.ton); + const computed = parseFloat(amount) * (1 / parseFloat(rate)); + const wsTon = fromNano(toNano(computed.toFixed(9))); + + if (ton === state.ton) { + return state; + } + + return { ton, wsTon }; + } + + const wsTon = formatInputAmount(action.amount, 9, { skipFormattingDecimals: true }, state.wsTon); + const computed = parseFloat(amount) * parseFloat(rate); + const ton = fromNano(toNano(computed.toFixed(9))); + + if (wsTon === state.wsTon) { + return state; + } + + return { ton, wsTon }; + } catch { + return state; + } + } +} \ No newline at end of file From ff3b5467699ccdc264ee6f964096824660c5810b Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 16 Aug 2024 18:08:46 +0300 Subject: [PATCH 04/37] feat: adding webview preload component --- app/Navigation.tsx | 3 +++ app/components/WebViewPreloader.tsx | 18 ++++++++++++++++++ app/components/webview/DAppWebView.tsx | 2 -- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 app/components/WebViewPreloader.tsx diff --git a/app/Navigation.tsx b/app/Navigation.tsx index b28090dd0..cab1322f3 100644 --- a/app/Navigation.tsx +++ b/app/Navigation.tsx @@ -96,6 +96,8 @@ import { PendingTxsWatcher } from './components/PendingTxsWatcher'; import { TonconnectWatcher } from './components/TonconnectWatcher'; import { SessionWatcher } from './components/SessionWatcher'; import { MandatoryAuthSetupFragment } from './fragments/secure/MandatoryAuthSetupFragment'; +import { WebViewPreloader } from './components/WebViewPreloader'; +import { holdersUrl } from './engine/api/holders/fetchAccountState'; const Stack = createNativeStackNavigator(); Stack.Navigator.displayName = 'MainStack'; @@ -469,6 +471,7 @@ export const Navigation = memo(() => { + ); diff --git a/app/components/WebViewPreloader.tsx b/app/components/WebViewPreloader.tsx new file mode 100644 index 000000000..4978133a5 --- /dev/null +++ b/app/components/WebViewPreloader.tsx @@ -0,0 +1,18 @@ +import { memo } from "react"; +import { View } from "react-native"; +import WebView from "react-native-webview"; + +export const WebViewPreloader = memo(({ url }: { url: string }) => { + + return ( + + console.log('Preloaded', url)} + /> + + ); +}); \ No newline at end of file diff --git a/app/components/webview/DAppWebView.tsx b/app/components/webview/DAppWebView.tsx index 5853c8e16..eb530db67 100644 --- a/app/components/webview/DAppWebView.tsx +++ b/app/components/webview/DAppWebView.tsx @@ -22,7 +22,6 @@ import DeviceInfo from 'react-native-device-info'; import { processEmitterMessage } from "./utils/processEmitterMessage"; import { getLastAuthTimestamp, useKeysAuth } from "../secure/AuthWalletKeys"; import { getLockAppWithAuthState } from "../../engine/state/lockAppWithAuthState"; -import { useLockAppWithAuthState } from "../../engine/hooks/settings"; import WalletService, { addCardRequestSchema } from "../../modules/WalletService"; import { getHoldersToken } from "../../engine/hooks/holders/useHoldersAccountStatus"; import { getCurrentAddress } from "../../storage/appState"; @@ -72,7 +71,6 @@ export const DAppWebView = memo(forwardRef((props: DAppWebViewProps, ref: Forwar const navigation = useTypedNavigation(); const toaster = useToaster(); const markRefIdShown = useMarkBannerHidden(); - const [, setLockAppWithAuth] = useLockAppWithAuthState(); const [loaded, setLoaded] = useState(false); From b2fefc40b722a314d635d5db8301b9745301236f Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 01:06:36 +0300 Subject: [PATCH 05/37] wip: adding reload & support buttons for holders --- app/components/ScreenHeader.tsx | 2 +- .../holders/HoldersLandingFragment.tsx | 50 +++- .../holders/components/AccountPlaceholder.tsx | 246 ++++++++++++++++++ .../components/HoldersAppComponent.tsx | 216 +++++++++------ assets/ic-comment.png | Bin 0 -> 282 bytes assets/ic-comment@2x.png | Bin 0 -> 424 bytes assets/ic-comment@3x.png | Bin 0 -> 622 bytes assets/ic-reload.png | Bin 0 -> 383 bytes assets/ic-reload@2x.png | Bin 0 -> 672 bytes assets/ic-reload@3x.png | Bin 0 -> 888 bytes 10 files changed, 424 insertions(+), 90 deletions(-) create mode 100644 app/fragments/holders/components/AccountPlaceholder.tsx create mode 100644 assets/ic-comment.png create mode 100644 assets/ic-comment@2x.png create mode 100644 assets/ic-comment@3x.png create mode 100644 assets/ic-reload.png create mode 100644 assets/ic-reload@2x.png create mode 100644 assets/ic-reload@3x.png diff --git a/app/components/ScreenHeader.tsx b/app/components/ScreenHeader.tsx index 926c76ca1..504cb4872 100644 --- a/app/components/ScreenHeader.tsx +++ b/app/components/ScreenHeader.tsx @@ -128,7 +128,7 @@ export const ScreenHeader = memo(( { const acc = useSelectedAccount()!; @@ -29,6 +30,7 @@ export const HoldersLandingFragment = fragment(() => { const { isTestnet } = useNetwork(); const webRef = useRef(null); const authContext = useKeysAuth(); + const { showActionSheetWithOptions } = useActionSheet(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); const [currency,] = usePrimaryCurrency(); @@ -184,10 +186,45 @@ export const HoldersLandingFragment = fragment(() => { } }, [onContentProcessDidTerminate, onEnroll]); + const [renderKey, setRenderKey] = useState(0); + + const onReload = useCallback(() => { + setRenderKey(renderKey + 1); + }, []); + + const onSupport = useCallback(() => { + const tonhubOptions = [t('common.cancel'), t('settings.support.telegram'), t('settings.support.form')]; + const cancelButtonIndex = 0; + + const tonhubSupportSheet = () => { + showActionSheetWithOptions({ + options: tonhubOptions, + title: t('settings.support.title'), + cancelButtonIndex, + }, (selectedIndex?: number) => { + switch (selectedIndex) { + case 1: + openWithInApp('https://t.me/WhalesSupportBot'); + break; + case 2: + openWithInApp('https://airtable.com/appWErwfR8x0o7vmz/shr81d2H644BNUtPN'); + break; + default: + break; + } + }); + } + + tonhubSupportSheet(); + }, []); + return ( - + { lockScroll: true }} webviewDebuggingEnabled={isTestnet} - loader={(p) => } + loader={(p) => ( + + )} /> { + // const GLOW_COLOR = "#1976edFF"; + const GLOW_BG_COLOR = "#1976ed00"; // Should be the same color as GLOW_COLOR but fully transparent + // const { height: screenHeight, width: screenWidth } = Dimensions.get("window"); + const rotation = useSharedValue(0); + + const screenWidth = 132; + const screenHeight = 36; + const centerX = width / 2; + const centerY = height / 2; + const centerVec = vec(centerX, centerY); + + useEffect(() => { + rotation.value = withRepeat( + withTiming(2, { + duration: 4000, + easing: Easing.linear, + }), + -1, + false, + ); + }, []); + + const animatedRotation = useDerivedValue(() => { + return [{ rotate: Math.PI * rotation.value }]; + }, [rotation]); + + const GlowGradient = () => { + return ( + + + + ); + }; + + return ( + + + {/* Blurred Glow */} + + + + + + {/* Outline */} + + + {/* Box overlay */} + + + + ); +}); + +export const AccountPlaceholder = memo(({ + theme, + onReload, + onSupport +}: { + theme: ThemeType, + onReload?: () => void, + onSupport?: () => void +}) => { + const safeArea = useSafeAreaInsets(); + + const rotation = useSharedValue(0); + + useEffect(() => { + rotation.value = withRepeat( + withTiming(1, { + duration: 2000, + easing: Easing.linear, + }), + -1, // Infinite repeat + false // Do not reverse + ); + + return () => { + rotation.value = 0; // Reset the animation value when the component unmounts + }; + }, [rotation]); + + const animatedStyle = useAnimatedStyle(() => { + return {}; + }); + + return ( + + + + + + + + + + + + + + + + + + {onReload && ( + + + + )} + {onSupport && ( + + + + )} + + + ); +}); diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 645033810..968b7956c 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Linking, Platform, View } from 'react-native'; +import { Linking, Platform, Pressable, View } from 'react-native'; import { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'; import { extractDomain } from '../../../engine/utils/extractDomain'; import { useTypedNavigation } from '../../../utils/useTypedNavigation'; @@ -25,93 +25,17 @@ import { DAppWebView, DAppWebViewProps } from '../../../components/webview/DAppW import { ThemeType } from '../../../engine/state/theme'; import { useDimensions } from '@react-native-community/hooks'; import { HoldersAccounts } from '../../../engine/hooks/holders/useHoldersAccounts'; +import { Image } from 'expo-image'; +import { openWithInApp } from '../../../utils/openWithInApp'; +import { t } from '../../../i18n/t'; +import { useActionSheet } from '@expo/react-native-action-sheet'; export function normalizePath(path: string) { return path.replaceAll('.', '_'); } import IcHolders from '@assets/ic_holders.svg'; - -const AccountPlaceholder = memo(({ theme }: { theme: ThemeType }) => { - const safeArea = useSafeAreaInsets(); - - return ( - - - - - - - - - - - - - - - ); -}); +import { AccountPlaceholder } from './AccountPlaceholder'; const CardPlaceholder = memo(({ theme }: { theme: ThemeType }) => { const dimensions = useDimensions(); @@ -226,7 +150,17 @@ export const HoldersPlaceholder = memo(() => { ); }); -export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'account' | 'create' | 'prepaid' }) => { +export const HoldersLoader = memo(({ + loaded, + type, + onReload: onReaload, + onSupport +}: { + loaded: boolean, + type: 'account' | 'create' | 'prepaid', + onReload?: () => void, + onSupport?: () => void +}) => { const theme = useTheme(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); @@ -254,7 +188,13 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a const placeholder = useMemo(() => { if (type === 'account') { - return ; + return ( + + ); } if (type === 'prepaid') { @@ -262,7 +202,7 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a } return ; - }, [type, theme]); + }, [type, theme, showClose]); return ( + // {!!onReaload && ( + // [ + // { + // opacity: pressed ? 0.5 : 1, + // backgroundColor: theme.surfaceOnElevation, + // borderRadius: 32, + // height: 32, width: 32, + // justifyContent: 'center', alignItems: 'center', + // } + // ]} + // onPress={onReaload} + // > + // + // + // )} + // {!!onSupport && ( + // [ + // { + // opacity: pressed ? 0.5 : 1, + // backgroundColor: theme.surfaceOnElevation, + // borderRadius: 32, + // height: 32, width: 32, + // justifyContent: 'center', alignItems: 'center', + // } + // ]} + // onPress={onSupport} + // > + // + // + // )} + // + // ) : undefined} /> )} @@ -317,6 +314,7 @@ export const HoldersAppComponent = memo(( const [currency,] = usePrimaryCurrency(); const selectedAccount = useSelectedAccount(); const url = holdersUrl(isTestnet); + const { showActionSheetWithOptions } = useActionSheet(); const source = useMemo(() => { const queryParams = new URLSearchParams({ @@ -477,8 +475,51 @@ export const HoldersAppComponent = memo(( injectSource ]); + const [renderKey, setRenderKey] = useState(0); + + const onReaload = useCallback(() => { + setRenderKey(renderKey + 1); + }, []); + + const onSupport = useCallback(() => { + const tonhubOptions = [ + t('common.cancel'), + t('settings.support.telegram'), + t('settings.support.form'), + t('settings.support.holders') + ]; + const cancelButtonIndex = 0; + + const tonhubSupportSheet = () => { + showActionSheetWithOptions({ + options: tonhubOptions, + title: t('settings.support.title'), + cancelButtonIndex, + }, (selectedIndex?: number) => { + switch (selectedIndex) { + case 1: + openWithInApp('https://t.me/WhalesSupportBot'); + break; + case 2: + openWithInApp('https://airtable.com/appWErwfR8x0o7vmz/shr81d2H644BNUtPN'); + break; + case 3: + openWithInApp('https://help.holders.io/en'); + break; + default: + break; + } + }); + } + + tonhubSupportSheet(); + }, []); + return ( - + )} /> diff --git a/assets/ic-comment.png b/assets/ic-comment.png new file mode 100644 index 0000000000000000000000000000000000000000..29c475f06fa51cc68a84a2dd79208c1bc0fcb6e1 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#^`0({Ar*{wr@ZBDRuFMr|CUMiVBQ7ZQyP6v z>YL^^lr;5S5G-JJX+5H$Z})Yggn!Y04a@248Rm#SO|DtfAKk&5tljrRjBR)C%I4-J z_Z0gLr|c1$aC}8bqo9t*x~Ad!C@xVMG+o_^1o}CYKtAzf`&)7dp-yrS$)BdTqH%*K8t89OGYWzopr0E2aB{r~^~ literal 0 HcmV?d00001 diff --git a/assets/ic-comment@2x.png b/assets/ic-comment@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..16843a5b64b0119fdfbc02681519f5816072d4a0 GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=UMD&3!dxy!+}V|r<^$|12M-2V6eUSyo0XS*zQ z5uZfMXTL>a8r%MOeEy*>I%{UGoJEJ)g|5sunoB#X88)cDQK?jZ=)HT%yx-RMCvEI& zIvp*`I(_?_i4i%Kt5X|4P5OUglWlI^MUfvHfBJqbp5nH5#fnw0>hC`ZjuSd`eN8Q7J{an^LB{Ts5WuC1S literal 0 HcmV?d00001 diff --git a/assets/ic-comment@3x.png b/assets/ic-comment@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..979f52e8766c7e7fb5ca37fab6d7f63f32819be3 GIT binary patch literal 622 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!oCO|{#S9FJ79h;%I?XTvD9BhG z}*t%TW# zNjgBSfmg^Zf%z2621Wx$1IN=WPp4@ouXbIvz)mms(vfeKOZ;OZR=iR-uP<}ZTsp<; zD&yN#F>ZS4r}rN}UGnmB>b5i8r)QrwW`4xIBKDbHnZnFnF*_xyOpYX76AiuGeDTxa z_NX(v7V%UU9o&%KJ0;sHjM?ntm69i18)iOfnkIQ5O{Bg<;^HFB1mn|Fc-LL*>sTFo z?qt9I^KMyvE#As=^RCXHWzo$yaqi{MS2K>+SUy^5U#4%S`o77gUcX#OYD!vw>%XTJ zbz-3l9ak<0^KWJr|FbU0F@4HnE?y5-$y0?=QWu)UnwD3+)JfAkeM>Lty5rHyO-fr9 zaOi3{ad%xnXRcfz`E*7r+mfbx8pm}^6hyx`THbSJ4sz_D^4QWu!Iu9M|3tH>TAw@D zR!jYpe}D46-~K6GTf)4ycP6^?H=H#o-*RBWyFMe$;;5JzJJ@p4GHb2x=_VMT)Rmpd zVD{1HMZ_uN%tP}`mW2B~k8Y?pUb6Xjj!*wFz4aNEfpz`8_I9=t11)p+^KE_gymUqE zkF&3DzuT}krJw!jKA#(T&rC!XEqq>7RGm@p?6~>(@AI|^&W*=1EEgF@^{sF^Ef|_% zsnk48*$7D0uio^_W{>0Fz>N{LCqAxqUpggdmGk{xExkz#e(v}WOdbrLu6{1-oD!M< D(Ig4+ literal 0 HcmV?d00001 diff --git a/assets/ic-reload.png b/assets/ic-reload.png new file mode 100644 index 0000000000000000000000000000000000000000..a7118a594a74cff9a4708e3c5fe410689555ef53 GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zeW?}mKoX^V~AItw`4^g)(J)eGkyIb|Sb#t9Mc_rJ| z|H&|&IAhz!pJI&BA=8o!?}#m7y)~JmJ}%|OwCgo>eJ>hHiyGnsZ#vGmJ!r6Eary;g zw$isJE*h_#ac6niiifLjUSr>)PQCq0ZWO)hbsT6C{czSD*6 aA#dQX@T3)4Mc;to#o+1c=d#Wzp$P!|)tX8G literal 0 HcmV?d00001 diff --git a/assets/ic-reload@2x.png b/assets/ic-reload@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d116034151f2519820b430ec56d1bb2c501c74e7 GIT binary patch literal 672 zcmV;R0$=@!P)j{00009a7bBm000&x z000&x0ZCFM@Bjb+0drDELIAGL9O(c600d`2O+f$vv5yPuGkaU8&6U3b$U(B{*_J%@g)Zugqig;obd27auSpw-NrL`v;5ea6>3xLfPCvXcTMm2r{W$%b z;?Dn@$Ip3T1Vhd1Y96OnOSo%O^J{P1d(v>{eV#Ldw*$xhBz(be>3tT$+WSSt3^lLD zKXB;6hM?WoS1M+xd1d4DiD3ay<1Oj%GwdIk5c=EAT$_#+1-m>U9)OlS+)}v16CMYk zB@aS@O-|oL^se8K4?pFLZEtfUWH4R2<{J)muC=`3%zlz$7=~dOhG7_nk(B>P@g4D#Lcl%%0000l*h=_=Yh=_=Yh=_=YD)RyLF-Ps#pT7O-f6`30ZI5l+we6*CuMutE zwjXVCcz=N?;I3_J#0NdpL0S&O7htjBuQ1%p9WKY9yQo9M+M*bQd)zg9n@2eOM=VIFr{%cC zJ;LEzEPz9qh8u%ISe!q>Lx4NzAH~;!eYrj3 z{JE`=&?ii1mF>R&X_Wo))M*ts<`eSzjq^iG(s^l4@J(Q!5A0h_Jm$4%FGD2_e$M3j z6KR8L&^@lpt@z;O1iv6n_)UMTn=!YXY%o9k2I($f3P#zU^nJ_}b$o;A!8Ir=bn#i) z-k?wh<6cbKqs%oBt(>$dm`p(0iPsRioPTZ09B+ogFEw%FjE_Ob4=^)#jV9Jg#@dGz5`u^ zLHI+T0VO9BW+-%y?H5q za`=v`LsyZE`Ib0)`Uo-s#`fk=tLnYT+zjRiF6AVb14fdeVQS!qFD}yrTTBm}OT05! z#0S1ZH;yy#L?p~TPJ6WcTi#NZp-3iQhD}67L_|bHL_|bHL_|ciKz{+slA6d;z@uCM O0000 Date: Fri, 23 Aug 2024 01:52:09 +0300 Subject: [PATCH 06/37] fix: adding loading state & alerts --- .../holders/components/AccountPlaceholder.tsx | 77 ++++++++++--------- .../components/HoldersAppComponent.tsx | 76 ++++-------------- app/i18n/i18n_en.ts | 1 + app/i18n/i18n_ru.ts | 1 + app/i18n/schema.ts | 1 + 5 files changed, 56 insertions(+), 100 deletions(-) diff --git a/app/fragments/holders/components/AccountPlaceholder.tsx b/app/fragments/holders/components/AccountPlaceholder.tsx index 862ae4daf..6f0a8da35 100644 --- a/app/fragments/holders/components/AccountPlaceholder.tsx +++ b/app/fragments/holders/components/AccountPlaceholder.tsx @@ -1,7 +1,7 @@ import { memo, useEffect } from "react"; import { ThemeType } from "../../../engine/state/theme"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import Animated, { Easing, FadeInDown, FadeInUp, interpolate, useAnimatedStyle, useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated"; +import Animated, { Easing, Extrapolate, Extrapolation, FadeInDown, FadeInUp, interpolate, useAnimatedStyle, useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated"; import { Platform, View, StyleSheet, TextInput, Dimensions } from "react-native"; import { } from "react-native"; import { @@ -102,34 +102,46 @@ const GlowingBorderView = memo(({ width, height, glowSize, blurRadius, theme, bo export const AccountPlaceholder = memo(({ theme, onReload, - onSupport + onSupport, + showClose }: { theme: ThemeType, onReload?: () => void, - onSupport?: () => void + onSupport?: () => void, + showClose?: boolean }) => { const safeArea = useSafeAreaInsets(); - - const rotation = useSharedValue(0); + const animation = useSharedValue(0); useEffect(() => { - rotation.value = withRepeat( + animation.value = withRepeat( withTiming(1, { - duration: 2000, - easing: Easing.linear, + duration: 500, + easing: Easing.bezier(0.25, 0.1, 0.25, 1) }), - -1, // Infinite repeat - false // Do not reverse + -1, + true, ); + }, []); - return () => { - rotation.value = 0; // Reset the animation value when the component unmounts + const animatedStyles = useAnimatedStyle(() => { + const opacity = interpolate( + animation.value, + [0, 1], + [1, theme.style === 'dark' ? 0.75 : 1], + Extrapolation.CLAMP + ); + const scale = interpolate( + animation.value, + [0, 1], + [1, 1.01], + Extrapolation.CLAMP + ) + return { + opacity: opacity, + transform: [{ scale: scale }], }; - }, [rotation]); - - const animatedStyle = useAnimatedStyle(() => { - return {}; - }); + }, [theme.style]); return ( - + }, animatedStyles]}> - - - - + - + {onReload && ( diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 968b7956c..5ddfc53ee 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Linking, Platform, Pressable, View } from 'react-native'; +import { Linking, Platform, View } from 'react-native'; import { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'; import { extractDomain } from '../../../engine/utils/extractDomain'; import { useTypedNavigation } from '../../../utils/useTypedNavigation'; @@ -25,17 +25,17 @@ import { DAppWebView, DAppWebViewProps } from '../../../components/webview/DAppW import { ThemeType } from '../../../engine/state/theme'; import { useDimensions } from '@react-native-community/hooks'; import { HoldersAccounts } from '../../../engine/hooks/holders/useHoldersAccounts'; -import { Image } from 'expo-image'; import { openWithInApp } from '../../../utils/openWithInApp'; import { t } from '../../../i18n/t'; import { useActionSheet } from '@expo/react-native-action-sheet'; +import { AccountPlaceholder } from './AccountPlaceholder'; export function normalizePath(path: string) { return path.replaceAll('.', '_'); } import IcHolders from '@assets/ic_holders.svg'; -import { AccountPlaceholder } from './AccountPlaceholder'; +import { ToastDuration, useToaster } from '../../../components/toast/ToastProvider'; const CardPlaceholder = memo(({ theme }: { theme: ThemeType }) => { const dimensions = useDimensions(); @@ -162,6 +162,7 @@ export const HoldersLoader = memo(({ onSupport?: () => void }) => { const theme = useTheme(); + const toaster = useToaster(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); const [showClose, setShowClose] = useState(false); @@ -183,7 +184,15 @@ export const HoldersLoader = memo(({ useEffect(() => { setTimeout(() => { setShowClose(true); - }, 3000); + }, 5000); + + setTimeout(() => { + toaster.show({ + type: 'error', + message: t('products.holders.loadingLonger'), + duration: ToastDuration.LONG + }); + }, 12000); }, []); const placeholder = useMemo(() => { @@ -191,6 +200,7 @@ export const HoldersLoader = memo(({ return ( @@ -231,63 +241,6 @@ export const HoldersLoader = memo(({ android: { top: safeArea.top - 6 } }) ]} - // rightButton={showClose ? ( - // - // {!!onReaload && ( - // [ - // { - // opacity: pressed ? 0.5 : 1, - // backgroundColor: theme.surfaceOnElevation, - // borderRadius: 32, - // height: 32, width: 32, - // justifyContent: 'center', alignItems: 'center', - // } - // ]} - // onPress={onReaload} - // > - // - // - // )} - // {!!onSupport && ( - // [ - // { - // opacity: pressed ? 0.5 : 1, - // backgroundColor: theme.surfaceOnElevation, - // borderRadius: 32, - // height: 32, width: 32, - // justifyContent: 'center', alignItems: 'center', - // } - // ]} - // onPress={onSupport} - // > - // - // - // )} - // - // ) : undefined} /> )} @@ -534,7 +487,6 @@ export const HoldersAppComponent = memo(( diff --git a/app/i18n/i18n_en.ts b/app/i18n/i18n_en.ts index 81e7ddab5..c2368ad9d 100644 --- a/app/i18n/i18n_en.ts +++ b/app/i18n/i18n_en.ts @@ -501,6 +501,7 @@ const schema: PrepareSchema = { }, holders: { title: 'Bank account', + loadingLonger: 'Loading takes longer than usual, try reloading or contact support, if the problem persists', accounts: { title: 'Payment accounts', prepaidTitle: 'Prepaid cards', diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index c499f974d..0240bc07c 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -501,6 +501,7 @@ const schema: PrepareSchema = { }, "holders": { "title": "Банковский счет", + "loadingLonger": "Загрузка занимает больше времени, чем обычно, попробуйте перезагрузить или обратитесь в службу поддержки, если проблема сохраняется", "accounts": { "title": "Счета", "prepaidTitle": 'Prepaid карты', diff --git a/app/i18n/schema.ts b/app/i18n/schema.ts index 55b0d8986..b38b63c7f 100644 --- a/app/i18n/schema.ts +++ b/app/i18n/schema.ts @@ -503,6 +503,7 @@ export type LocalizationSchema = { }, holders: { title: string, + loadingLonger: string, accounts: { title: string, prepaidTitle: string, From fd85dc171dddbb38c65b8b751b4b4ce97d4b97dd Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 10:48:15 +0300 Subject: [PATCH 07/37] fix: ru text --- app/i18n/i18n_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index c499f974d..3d1bcb919 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -883,7 +883,7 @@ const schema: PrepareSchema = { }, "delete": "Удалить контакт", "empty": "У вас ещё нет контактов", - "description": "Нажмите и подержите адрес, чтобы добавить его в контакты, или выберите его из списка недавних контактов ниже", + "description": "Нажмите и удерживайте адрес или выберите его из списка ниже, чтобы добавить в контакты", "contactAddress": "Адрес контакта", "search": "Имя или адрес кошелька", "new": "Новый контакт" From 72bf47d458e0057ee715e38bea567eb39cac6146 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 14:00:17 +0300 Subject: [PATCH 08/37] wip: fixing toast message & adding tracking --- app/analytics/mixpanel.ts | 2 ++ app/components/WebViewPreloader.tsx | 6 +---- app/components/toast/ToastProvider.tsx | 2 +- .../webview/utils/processEmitterMessage.ts | 2 +- .../components/HoldersAppComponent.tsx | 27 ++++++++++++++++--- app/i18n/i18n_en.ts | 2 +- app/i18n/i18n_ru.ts | 2 +- 7 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/analytics/mixpanel.ts b/app/analytics/mixpanel.ts index c4726c19e..238d6c9a4 100644 --- a/app/analytics/mixpanel.ts +++ b/app/analytics/mixpanel.ts @@ -13,6 +13,8 @@ export enum MixpanelEvent { HoldersEnrollment = 'holders_entrollment', HoldersInfo = 'holders_info', HoldersInfoClose = 'holders_info_close', + HoldersLoadingTime = 'holders_loading_time', + holdersLongLoadingTime = 'holders_long_loading_time', HoldersEnrollmentClose = 'holders_entrollment_close', HoldersClose = 'holders_close', Connect = 'connect', diff --git a/app/components/WebViewPreloader.tsx b/app/components/WebViewPreloader.tsx index 4978133a5..11375e73a 100644 --- a/app/components/WebViewPreloader.tsx +++ b/app/components/WebViewPreloader.tsx @@ -3,16 +3,12 @@ import { View } from "react-native"; import WebView from "react-native-webview"; export const WebViewPreloader = memo(({ url }: { url: string }) => { - return ( - console.log('Preloaded', url)} - /> + ); }); \ No newline at end of file diff --git a/app/components/toast/ToastProvider.tsx b/app/components/toast/ToastProvider.tsx index 9262d06ce..6dd0a3e0d 100644 --- a/app/components/toast/ToastProvider.tsx +++ b/app/components/toast/ToastProvider.tsx @@ -115,7 +115,7 @@ export const Toast = memo(({ {Icon && ( )} - + {message} diff --git a/app/components/webview/utils/processEmitterMessage.ts b/app/components/webview/utils/processEmitterMessage.ts index 08fcc7c11..1b7c79291 100644 --- a/app/components/webview/utils/processEmitterMessage.ts +++ b/app/components/webview/utils/processEmitterMessage.ts @@ -20,7 +20,7 @@ export function processEmitterMessage( case DAppEmitterEvents.APP_READY: setTimeout(() => { setLoaded(true); - }, 200); + }, 100); break; default: break; diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 5ddfc53ee..63cbb628b 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -9,7 +9,7 @@ import { protectNavigation } from '../../apps/components/protect/protectNavigati import { getLocales } from 'react-native-localize'; import { useLinkNavigator } from '../../../useLinkNavigator'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { HoldersAppParams } from '../HoldersAppFragment'; import Animated, { Easing, Extrapolation, interpolate, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; import { useDAppBridge, usePrimaryCurrency } from '../../../engine/hooks'; @@ -172,9 +172,20 @@ export const HoldersLoader = memo(({ return { opacity: opacity.value }; }); + const longLoadingTimerRef = useRef(null); + + const start = useMemo(() => { + return Date.now(); + }, []); + const trackLoadingtime = useCallback(() => { + trackEvent(MixpanelEvent.HoldersLoadingTime, { type, duration: Date.now() - start }); + }, []); + useEffect(() => { if (loaded) { + longLoadingTimerRef.current && clearTimeout(longLoadingTimerRef.current); opacity.value = withTiming(0, { duration: 350, easing: Easing.inOut(Easing.ease) }); + trackLoadingtime(); } else { setShowClose(false); opacity.value = 1; @@ -182,17 +193,27 @@ export const HoldersLoader = memo(({ }, [loaded]); useEffect(() => { - setTimeout(() => { + const showCloseTimer = setTimeout(() => { setShowClose(true); }, 5000); - setTimeout(() => { + if (longLoadingTimerRef.current) { + clearTimeout(longLoadingTimerRef.current); + } + + longLoadingTimerRef.current = setTimeout(() => { + trackEvent(MixpanelEvent.holdersLongLoadingTime, { type, duration: 12000 }); toaster.show({ type: 'error', message: t('products.holders.loadingLonger'), duration: ToastDuration.LONG }); }, 12000); + + return () => { + longLoadingTimerRef.current && clearTimeout(longLoadingTimerRef.current); + clearTimeout(showCloseTimer); + } }, []); const placeholder = useMemo(() => { diff --git a/app/i18n/i18n_en.ts b/app/i18n/i18n_en.ts index c2368ad9d..f9feb144c 100644 --- a/app/i18n/i18n_en.ts +++ b/app/i18n/i18n_en.ts @@ -501,7 +501,7 @@ const schema: PrepareSchema = { }, holders: { title: 'Bank account', - loadingLonger: 'Loading takes longer than usual, try reloading or contact support, if the problem persists', + loadingLonger: 'Loading takes longer than usual, try reloading or contact support', accounts: { title: 'Payment accounts', prepaidTitle: 'Prepaid cards', diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index 0240bc07c..c2a447349 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -501,7 +501,7 @@ const schema: PrepareSchema = { }, "holders": { "title": "Банковский счет", - "loadingLonger": "Загрузка занимает больше времени, чем обычно, попробуйте перезагрузить или обратитесь в службу поддержки, если проблема сохраняется", + "loadingLonger": "Загрузка занимает больше времени, чем обычно, попробуйте еще раз или обратитесь в службу поддержки", "accounts": { "title": "Счета", "prepaidTitle": 'Prepaid карты', From 3b694812af8331adf40552847c48ed559ba366c3 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 14:03:12 +0300 Subject: [PATCH 09/37] fix: toast duration --- app/fragments/holders/components/HoldersAppComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 63cbb628b..b1f1f0f4f 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -29,13 +29,13 @@ import { openWithInApp } from '../../../utils/openWithInApp'; import { t } from '../../../i18n/t'; import { useActionSheet } from '@expo/react-native-action-sheet'; import { AccountPlaceholder } from './AccountPlaceholder'; +import { ToastDuration, useToaster } from '../../../components/toast/ToastProvider'; export function normalizePath(path: string) { return path.replaceAll('.', '_'); } import IcHolders from '@assets/ic_holders.svg'; -import { ToastDuration, useToaster } from '../../../components/toast/ToastProvider'; const CardPlaceholder = memo(({ theme }: { theme: ThemeType }) => { const dimensions = useDimensions(); @@ -206,7 +206,7 @@ export const HoldersLoader = memo(({ toaster.show({ type: 'error', message: t('products.holders.loadingLonger'), - duration: ToastDuration.LONG + duration: ToastDuration.DEFAULT }); }, 12000); From 1ff5de624a28d9f4b8378a16a0c3d146bcac6a29 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 14:55:28 +0300 Subject: [PATCH 10/37] fix: tg scheme open for holders --- app/components/webview/DAppWebView.tsx | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/components/webview/DAppWebView.tsx b/app/components/webview/DAppWebView.tsx index 5853c8e16..2396962fe 100644 --- a/app/components/webview/DAppWebView.tsx +++ b/app/components/webview/DAppWebView.tsx @@ -1,5 +1,5 @@ import { ForwardedRef, RefObject, forwardRef, memo, useCallback, useEffect, useMemo, useReducer, useState } from "react"; -import { KeyboardAvoidingView, Platform, View, StyleSheet, ActivityIndicator, BackHandler } from "react-native"; +import { KeyboardAvoidingView, Platform, View, StyleSheet, ActivityIndicator, BackHandler, Linking } from "react-native"; import WebView, { WebViewMessageEvent, WebViewNavigation, WebViewProps } from "react-native-webview"; import { useNetwork, useTheme } from "../../engine/hooks"; import { WebViewErrorComponent } from "./WebViewErrorComponent"; @@ -22,10 +22,11 @@ import DeviceInfo from 'react-native-device-info'; import { processEmitterMessage } from "./utils/processEmitterMessage"; import { getLastAuthTimestamp, useKeysAuth } from "../secure/AuthWalletKeys"; import { getLockAppWithAuthState } from "../../engine/state/lockAppWithAuthState"; -import { useLockAppWithAuthState } from "../../engine/hooks/settings"; import WalletService, { addCardRequestSchema } from "../../modules/WalletService"; import { getHoldersToken } from "../../engine/hooks/holders/useHoldersAccountStatus"; import { getCurrentAddress } from "../../storage/appState"; +import { WebViewSourceUri } from "react-native-webview/lib/WebViewTypes"; +import { holdersUrl } from "../../engine/api/holders/fetchAccountState"; export type DAppWebViewProps = WebViewProps & { useMainButton?: boolean; @@ -72,7 +73,6 @@ export const DAppWebView = memo(forwardRef((props: DAppWebViewProps, ref: Forwar const navigation = useTypedNavigation(); const toaster = useToaster(); const markRefIdShown = useMarkBannerHidden(); - const [, setLockAppWithAuth] = useLockAppWithAuthState(); const [loaded, setLoaded] = useState(false); @@ -98,15 +98,28 @@ export const DAppWebView = memo(forwardRef((props: DAppWebViewProps, ref: Forwar } ); + + const safelyOpenUrl = useCallback((url: string) => { try { + const scheme = new URL(url).protocol.replace(':', ''); + const sourceUrl = (props.source as WebViewSourceUri)?.uri; + + if ( + scheme === 'tg' + && !!sourceUrl + && sourceUrl.startsWith(holdersUrl(isTestnet)) + ) { + Linking.openURL(url); + return; + } let pageDomain = extractDomain(url); if (isSafeDomain(pageDomain)) { openWithInApp(url); return; } } catch { } - }, []); + }, [props.source]); const onNavigation = useCallback((url: string) => { if (!props.useQueryAPI) { @@ -331,11 +344,7 @@ export const DAppWebView = memo(forwardRef((props: DAppWebViewProps, ref: Forwar // Basic open url if (data.name === 'openUrl' && data.args.url) { try { - let pageDomain = extractDomain(data.args.url); - if (isSafeDomain(pageDomain)) { - openWithInApp(data.args.url); - return; - } + safelyOpenUrl(data.args.url); } catch { warn('Failed to open url'); return; From a88163cf5325071d442c0ad696b2d43b8cdd4086 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 16:38:08 +0300 Subject: [PATCH 11/37] feat: adding holders any path push url resolver --- app/fragments/holders/HoldersAppFragment.tsx | 3 +- .../components/HoldersAppComponent.tsx | 9 +++- app/useLinkNavigator.ts | 44 ++++++++++++++++--- app/utils/resolveUrl.ts | 10 +++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/app/fragments/holders/HoldersAppFragment.tsx b/app/fragments/holders/HoldersAppFragment.tsx index e94f2849d..f05d3ef61 100644 --- a/app/fragments/holders/HoldersAppFragment.tsx +++ b/app/fragments/holders/HoldersAppFragment.tsx @@ -15,7 +15,8 @@ export type HoldersAppParams = | { type: 'account', id: string } | { type: 'prepaid', id: string } | { type: 'create' } - | { type: 'transactions', query: { [key: string]: string | undefined } }; + | { type: 'transactions', query: { [key: string]: string | undefined } } + | { type: 'path', path: string }; export const HoldersAppFragment = fragment(() => { const theme = useTheme(); diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 645033810..0aedc1781 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -345,6 +345,9 @@ export const HoldersAppComponent = memo(( } } break; + case 'path': + route = `/${props.variant.path ?? ''}`; + break; } const url = `${props.endpoint}${route}?${queryParams.toString()}`; @@ -491,7 +494,11 @@ export const HoldersAppComponent = memo(( webviewDebuggingEnabled={isTestnet} loader={(p) => ( )} diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index 5935c3849..06dfd6db9 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -481,6 +481,16 @@ function getNeedsEnrollment(url: string, address: string, isTestnet: boolean, qu } function resolveAndNavigateToHolders(params: { + type: 'holders-transactions', + query: { [key: string]: string | undefined }, + navigation: TypedNavigation, + selected: SelectedAccount, + updateAppState: (value: AppState, isTestnet: boolean) => void, + isTestnet: boolean, + queryClient: QueryClient +} | { + type: 'holders-path', + path: string, query: { [key: string]: string | undefined }, navigation: TypedNavigation, selected: SelectedAccount, @@ -488,7 +498,7 @@ function resolveAndNavigateToHolders(params: { isTestnet: boolean, queryClient: QueryClient }) { - const { query, navigation, selected, updateAppState, queryClient, isTestnet } = params + const { type, query, navigation, selected, updateAppState, queryClient, isTestnet } = params const addresses = query['addresses']?.split(','); if (!addresses || addresses.length === 0) { @@ -498,10 +508,15 @@ function resolveAndNavigateToHolders(params: { const isSelectedAddress = addresses.find((a) => Address.parse(a).equals(selected.address)); const transactionId = query['transactionId']; - const holdersNavParams: HoldersAppParams = { - type: 'transactions', - query: { transactionId } - } + const holdersNavParams: HoldersAppParams = type === 'holders-transactions' + ? { + type: 'transactions', + query: { transactionId } + } + : { + type: 'path', + path: params.path + } const url = holdersUrl(isTestnet); @@ -686,6 +701,24 @@ export function useLinkNavigator( } resolveAndNavigateToHolders({ + type: 'holders-transactions', + navigation, + query: resolved.query, + selected, + updateAppState, + isTestnet, + queryClient + }); + break; + } + case 'holders-path': { + if (!selected) { + return; + } + + resolveAndNavigateToHolders({ + type: 'holders-path', + path: resolved.path, navigation, query: resolved.query, selected, @@ -693,6 +726,7 @@ export function useLinkNavigator( isTestnet, queryClient }); + break; } } diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index 141634735..6686dfa8e 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -68,6 +68,10 @@ export type ResolvedUrl = { } | { type: 'holders-transactions', query: { [key: string]: string | undefined } +} | { + type: 'holders-path', + path: string, + query: { [key: string]: string | undefined } } export function isUrl(str: string): boolean { @@ -87,6 +91,12 @@ function resolveHoldersUrl(url: Url>): Resolv type: 'holders-transactions', query: url.query } + } else if (!!url.query && url.query.path) { + return { + type: 'holders-path', + path: decodeURIComponent(url.query.path), + query: url.query + } } return { type: 'error', error: ResolveUrlError.InvalidHoldersPath }; From 0f5c51a6645e3cd76b41e17f0664b000a42c431a Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 23 Aug 2024 17:45:49 +0300 Subject: [PATCH 12/37] add: new OKX addresses --- app/secure/KnownWallets.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/secure/KnownWallets.ts b/app/secure/KnownWallets.ts index 0f58019e6..49db4e74b 100644 --- a/app/secure/KnownWallets.ts +++ b/app/secure/KnownWallets.ts @@ -494,6 +494,42 @@ const knownWalletsMainnet = { ic: Img_OKX, requireMemo: true }, + [Address.parse('EQDxXbLGLNhq_whg05xJH8c6MTlfr-tZReZESYViJgx4Lg4_').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('UQDn6G2gh0LtkzQ0_-uPCKY8fhAO6ELiX1manL8IkVdKbDEu').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('UQADON7zS4TG7pE0oEqxYBRQvkRjQKN64lneV8s3vbWQzTjL').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('UQCjCknscl6fVyRLZq9MLaerdgBLT86A06NLHNDDd2Krztab').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('UQAk2h57jakB5bPdD5jDl_625aXqSDGVqLa9BoLPLFMnrnbQ').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('EQB_LoTHI9i2trGqz4EMjCarI8IgIrRkuHEEstMGWw6nC3Nw').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('EQD5vcDeRhwaLgAvralVC7sJXI-fc2aNcMUXqcx-BQ-OWnOZ').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, + [Address.parse('EQABMMdzRuntgt9nfRB61qd1wR-cGPagXA3ReQazVYUNrT7p').toString()]: { name: 'EXMO Deposit', ic: Img_EXMO_Deposit, From 8c007f3db69c39f3d3e7cc3cd769b5b331e6db4b Mon Sep 17 00:00:00 2001 From: Garrethta Date: Wed, 4 Sep 2024 20:15:55 +0400 Subject: [PATCH 13/37] feat: handling holders invite-deeplinks --- app/components/browser/BrowserExtensions.tsx | 2 +- .../products/HoldersAccountItem.tsx | 10 +++--- .../products/HoldersPrepaidCard.tsx | 10 +++--- app/components/products/ProductsComponent.tsx | 21 ++++++------ app/engine/api/holders/fetchAccounts.ts | 2 +- .../api/holders/fetchAddressInviteCheck.ts | 24 ++++++++++++++ .../api/holders/fetchAddressWhitelistCheck.ts | 21 ------------ .../api/holders/fetchApplePayCredentials.ts | 2 +- app/engine/api/holders/fetchCardItem.ts | 2 +- .../api/holders/fetchCardsTransactions.ts | 2 +- ...fetchAccountState.ts => fetchUserState.ts} | 28 ++++++++-------- ...fetchAccountToken.ts => fetchUserToken.ts} | 9 +++--- app/engine/effects/onHoldersEnroll.ts | 10 +++--- .../holders/watchHoldersAccountUpdates.ts | 2 +- app/engine/holdersWatcher.ts | 6 ++-- .../hooks/holders/useCardTransactions.ts | 4 +-- app/engine/hooks/holders/useClearHolders.ts | 2 +- .../hooks/holders/useHasHoldersProducts.ts | 6 ++-- .../hooks/holders/useHoldersAccountStatus.ts | 12 +++---- .../hooks/holders/useHoldersAccounts.ts | 6 ++-- app/engine/hooks/holders/useHoldersEnroll.ts | 22 +++++++++---- ...sWhitelisted.ts => useIsHoldersInvited.ts} | 8 ++--- app/engine/queries.ts | 2 +- app/fragments/SettingsFragment.tsx | 4 +-- app/fragments/holders/HoldersAppFragment.tsx | 19 ++++++++--- .../holders/HoldersLandingFragment.tsx | 8 ++--- .../components/HoldersAppComponent.tsx | 30 ++++++++++------- .../secure/components/TransferSingleView.tsx | 2 +- app/fragments/wallet/ProductsFragment.tsx | 17 +++++----- app/useLinkNavigator.ts | 32 ++++++++++++++++--- app/utils/resolveUrl.ts | 13 ++++++++ app/utils/useTypedNavigation.ts | 6 ++-- 32 files changed, 206 insertions(+), 138 deletions(-) create mode 100644 app/engine/api/holders/fetchAddressInviteCheck.ts delete mode 100644 app/engine/api/holders/fetchAddressWhitelistCheck.ts rename app/engine/api/holders/{fetchAccountState.ts => fetchUserState.ts} (71%) rename app/engine/api/holders/{fetchAccountToken.ts => fetchUserToken.ts} (87%) rename app/engine/hooks/holders/{useIsHoldersWhitelisted.ts => useIsHoldersInvited.ts} (63%) diff --git a/app/components/browser/BrowserExtensions.tsx b/app/components/browser/BrowserExtensions.tsx index 2f485239a..9fb3eedc0 100644 --- a/app/components/browser/BrowserExtensions.tsx +++ b/app/components/browser/BrowserExtensions.tsx @@ -11,7 +11,7 @@ import { extractDomain } from "../../engine/utils/extractDomain"; import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"; import { useDimensions } from "@react-native-community/hooks"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { holdersUrl as resolveHoldersUrl } from '../../engine/api/holders/fetchAccountState'; +import { holdersUrl as resolveHoldersUrl } from '../../engine/api/holders/fetchUserState'; import { Typography } from "../styles"; import { ConnectedApp } from "../../engine/hooks/dapps/useTonConnectExtenstions"; diff --git a/app/components/products/HoldersAccountItem.tsx b/app/components/products/HoldersAccountItem.tsx index 6b1005d2a..aa5dcb5b3 100644 --- a/app/components/products/HoldersAccountItem.tsx +++ b/app/components/products/HoldersAccountItem.tsx @@ -7,7 +7,7 @@ import { useTypedNavigation } from "../../utils/useTypedNavigation"; import Animated from "react-native-reanimated"; import { useAnimatedPressedInOut } from "../../utils/useAnimatedPressedInOut"; import { useIsConnectAppReady, useJettonContent, usePrice, useTheme } from "../../engine/hooks"; -import { HoldersAccountState, holdersUrl } from "../../engine/api/holders/fetchAccountState"; +import { HoldersUserState, holdersUrl } from "../../engine/api/holders/fetchUserState"; import { GeneralHoldersAccount, GeneralHoldersCard } from "../../engine/api/holders/fetchAccounts"; import { PerfText } from "../basic/PerfText"; import { Typography } from "../styles"; @@ -18,7 +18,7 @@ import { HoldersAccountStatus } from "../../engine/hooks/holders/useHoldersAccou import { WImage } from "../WImage"; import { toBnWithDecimals } from "../../utils/withDecimals"; import { toNano } from "@ton/core"; -import { HoldersAppParams } from "../../fragments/holders/HoldersAppFragment"; +import { HoldersAppParams, HoldersAppParamsType } from "../../fragments/holders/HoldersAppFragment"; import { getAccountName } from "../../utils/holders/getAccountName"; import IcTonIcon from '@assets/ic-ton-acc.svg'; @@ -70,7 +70,7 @@ export const HoldersAccountItem = memo((props: { return true; } - if (holdersAccStatus.state === HoldersAccountState.NeedEnrollment) { + if (holdersAccStatus.state === HoldersUserState.NeedEnrollment) { return true; } @@ -86,12 +86,12 @@ export const HoldersAccountItem = memo((props: { props.onBeforeOpen?.(); if (needsEnrollment) { - const onEnrollType: HoldersAppParams = { type: 'account', id: props.account.id }; + const onEnrollType: HoldersAppParams = { type: HoldersAppParamsType.Account, id: props.account.id }; navigation.navigateHoldersLanding({ endpoint: url, onEnrollType }, props.isTestnet); return; } - navigation.navigateHolders({ type: 'account', id: props.account.id }, props.isTestnet); + navigation.navigateHolders({ type: HoldersAppParamsType.Account, id: props.account.id }, props.isTestnet); }, [props.account, needsEnrollment, props.isTestnet]); const { onPressIn, onPressOut, animatedStyle } = useAnimatedPressedInOut(); diff --git a/app/components/products/HoldersPrepaidCard.tsx b/app/components/products/HoldersPrepaidCard.tsx index 90461f302..ac919670e 100644 --- a/app/components/products/HoldersPrepaidCard.tsx +++ b/app/components/products/HoldersPrepaidCard.tsx @@ -6,7 +6,7 @@ import { useTypedNavigation } from "../../utils/useTypedNavigation"; import Animated from "react-native-reanimated"; import { useAnimatedPressedInOut } from "../../utils/useAnimatedPressedInOut"; import { useIsConnectAppReady, useTheme } from "../../engine/hooks"; -import { HoldersAccountState, holdersUrl } from "../../engine/api/holders/fetchAccountState"; +import { HoldersUserState, holdersUrl } from "../../engine/api/holders/fetchUserState"; import { GeneralHoldersCard, PrePaidHoldersCard } from "../../engine/api/holders/fetchAccounts"; import { PerfText } from "../basic/PerfText"; import { Typography } from "../styles"; @@ -15,7 +15,7 @@ import { toNano } from "@ton/core"; import { CurrencySymbols } from "../../utils/formatCurrency"; import { HoldersAccountCard } from "./HoldersAccountCard"; import { HoldersAccountStatus } from "../../engine/hooks/holders/useHoldersAccountStatus"; -import { HoldersAppParams } from "../../fragments/holders/HoldersAppFragment"; +import { HoldersAppParams, HoldersAppParamsType } from "../../fragments/holders/HoldersAppFragment"; import { useLockAppWithAuthState } from "../../engine/hooks/settings"; export const HoldersPrepaidCard = memo((props: { @@ -50,7 +50,7 @@ export const HoldersPrepaidCard = memo((props: { return true; } - if (holdersAccStatus.state === HoldersAccountState.NeedEnrollment) { + if (holdersAccStatus.state === HoldersUserState.NeedEnrollment) { return true; } @@ -62,12 +62,12 @@ export const HoldersPrepaidCard = memo((props: { props.onBeforeOpen?.(); if (needsEnrollment) { - const onEnrollType: HoldersAppParams = { type: 'prepaid', id: card.id }; + const onEnrollType: HoldersAppParams = { type: HoldersAppParamsType.Prepaid, id: card.id }; navigation.navigateHoldersLanding({ endpoint: url, onEnrollType }, props.isTestnet); return; } - navigation.navigateHolders({ type: 'prepaid', id: card.id }, props.isTestnet); + navigation.navigateHolders({ type: HoldersAppParamsType.Prepaid, id: card.id }, props.isTestnet); }, [card, needsEnrollment, props.onBeforeOpen, props.isTestnet]); const { onPressIn, onPressOut, animatedStyle } = useAnimatedPressedInOut(); diff --git a/app/components/products/ProductsComponent.tsx b/app/components/products/ProductsComponent.tsx index af64ef000..424e62ca8 100644 --- a/app/components/products/ProductsComponent.tsx +++ b/app/components/products/ProductsComponent.tsx @@ -13,7 +13,7 @@ import { JettonsHiddenComponent } from "./JettonsHiddenComponent" import { SelectedAccount } from "../../engine/types" import { DappsRequests } from "../../fragments/wallet/products/DappsRequests" import { ProductBanner } from "./ProductBanner" -import { HoldersAccountState, holdersUrl } from "../../engine/api/holders/fetchAccountState" +import { HoldersUserState, holdersUrl } from "../../engine/api/holders/fetchUserState" import { PendingTransactions } from "../../fragments/wallet/views/PendingTransactions" import { Typography } from "../styles" import { useBanners } from "../../engine/hooks/banners" @@ -22,9 +22,10 @@ import { MixpanelEvent, trackEvent } from "../../analytics/mixpanel" import { AddressFormatUpdate } from "./AddressFormatUpdate" import { TonProductComponent } from "./TonProductComponent" import { SpecialJettonProduct } from "./SpecialJettonProduct" -import { useIsHoldersWhitelisted } from "../../engine/hooks/holders/useIsHoldersWhitelisted" +import { useIsHoldersInvited } from "../../engine/hooks/holders/useIsHoldersInvited" import OldWalletIcon from '@assets/ic_old_wallet.svg'; +import { HoldersAppParamsType } from "../../fragments/holders/HoldersAppFragment" export const ProductsComponent = memo(({ selected }: { selected: SelectedAccount }) => { const theme = useTheme(); @@ -37,11 +38,11 @@ export const ProductsComponent = memo(({ selected }: { selected: SelectedAccount const banners = useBanners(); const url = holdersUrl(isTestnet); const isHoldersReady = useIsConnectAppReady(url); - const isHoldersWhitelisted = useIsHoldersWhitelisted(selected!.address, isTestnet); - const showHoldersBuiltInBanner = (holdersAccounts?.accounts?.length ?? 0) === 0 && isHoldersWhitelisted; + const isHoldersInvited = useIsHoldersInvited(selected!.address, isTestnet); + const showHoldersBuiltInBanner = (holdersAccounts?.accounts?.length ?? 0) === 0 && isHoldersInvited; const needsEnrolment = useMemo(() => { - if (holdersAccStatus?.state === HoldersAccountState.NeedEnrollment) { + if (holdersAccStatus?.state === HoldersUserState.NeedEnrollment) { return true; } return false; @@ -67,10 +68,10 @@ export const ProductsComponent = memo(({ selected }: { selected: SelectedAccount const onHoldersPress = useCallback(() => { if (needsEnrolment || !isHoldersReady) { - navigation.navigateHoldersLanding({ endpoint: url, onEnrollType: { type: 'create' } }, isTestnet); + navigation.navigateHoldersLanding({ endpoint: url, onEnrollType: { type: HoldersAppParamsType.Create } }, isTestnet); return; } - navigation.navigateHolders({ type: 'create' }, isTestnet); + navigation.navigateHolders({ type: HoldersAppParamsType.Create }, isTestnet); }, [needsEnrolment, isHoldersReady, isTestnet]); const onProductBannerPress = useCallback((product: ProductAd) => { @@ -102,7 +103,7 @@ export const ProductsComponent = memo(({ selected }: { selected: SelectedAccount - {(!isHoldersWhitelisted && !!banners?.product) && ( + {(!isHoldersInvited && !!banners?.product) && ( {t('common.balances')} diff --git a/app/engine/api/holders/fetchAccounts.ts b/app/engine/api/holders/fetchAccounts.ts index bce349e07..fb0919e3b 100644 --- a/app/engine/api/holders/fetchAccounts.ts +++ b/app/engine/api/holders/fetchAccounts.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { Address } from "@ton/core"; import { z } from "zod"; -import { holdersEndpoint } from "./fetchAccountState"; +import { holdersEndpoint } from "./fetchUserState"; const networksSchema = z.union([ z.literal('ton-mainnet'), diff --git a/app/engine/api/holders/fetchAddressInviteCheck.ts b/app/engine/api/holders/fetchAddressInviteCheck.ts new file mode 100644 index 000000000..99f5f422b --- /dev/null +++ b/app/engine/api/holders/fetchAddressInviteCheck.ts @@ -0,0 +1,24 @@ +import axios from "axios"; +import { holdersEndpoint } from "./fetchUserState"; +import { z } from "zod"; +import { Address } from "@ton/core"; + +const inviteCheckCodec = z.object({ + allowed: z.boolean(), +}); + +export async function fetchAddressInviteCheck(address: string, isTestnet: boolean) { + const endpoint = holdersEndpoint(isTestnet); + const formattedAddress = Address.parse(address).toString({ testOnly: isTestnet }); + + const res = await axios.post(`https://${endpoint}/v2/invite/wallet/check`, { wallet: formattedAddress }); + + const parsed = inviteCheckCodec.safeParse(res.data); + + if (!parsed.success) { + console.warn('Failed to parse invite check response', parsed.error); + return false; + } + + return parsed.data.allowed; +} \ No newline at end of file diff --git a/app/engine/api/holders/fetchAddressWhitelistCheck.ts b/app/engine/api/holders/fetchAddressWhitelistCheck.ts deleted file mode 100644 index 3a3d94908..000000000 --- a/app/engine/api/holders/fetchAddressWhitelistCheck.ts +++ /dev/null @@ -1,21 +0,0 @@ -import axios from "axios"; -import { holdersEndpoint } from "./fetchAccountState"; -import { z } from "zod"; - -const whitelistCheckCodec = z.object({ - allowed: z.boolean(), -}); - -export async function fetchAddressWhitelistCheck(address: string, isTestnet: boolean) { - const endpoint = holdersEndpoint(isTestnet); - const res = await axios.post(`https://${endpoint}/v2/whitelist/wallet/check`, { wallet: address }); - - const parsed = whitelistCheckCodec.safeParse(res.data); - - if (!parsed.success) { - console.warn('Failed to parse whitelist check response', parsed.error); - return false; - } - - return parsed.data.allowed; -} \ No newline at end of file diff --git a/app/engine/api/holders/fetchApplePayCredentials.ts b/app/engine/api/holders/fetchApplePayCredentials.ts index d869f9059..67febfa51 100644 --- a/app/engine/api/holders/fetchApplePayCredentials.ts +++ b/app/engine/api/holders/fetchApplePayCredentials.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { holdersEndpoint } from "./fetchAccountState"; +import { holdersEndpoint } from "./fetchUserState"; import { z } from "zod"; const cardCredentialsCodec = z.object({ diff --git a/app/engine/api/holders/fetchCardItem.ts b/app/engine/api/holders/fetchCardItem.ts index 9321669ea..14598b9d4 100644 --- a/app/engine/api/holders/fetchCardItem.ts +++ b/app/engine/api/holders/fetchCardItem.ts @@ -1,6 +1,6 @@ import axios from "axios"; import * as t from "io-ts"; -import { holdersEndpoint } from "./fetchAccountState"; +import { holdersEndpoint } from "./fetchUserState"; export const cardItemCodec = t.type({ ok: t.boolean, diff --git a/app/engine/api/holders/fetchCardsTransactions.ts b/app/engine/api/holders/fetchCardsTransactions.ts index 468e28103..05b981ba3 100644 --- a/app/engine/api/holders/fetchCardsTransactions.ts +++ b/app/engine/api/holders/fetchCardsTransactions.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { holdersEndpoint } from "./fetchAccountState"; +import { holdersEndpoint } from "./fetchUserState"; export type CountryCode = 'AC' | 'AD' | 'AE' | 'AF' | 'AG' | 'AI' | 'AL' | 'AM' | 'AO' | 'AR' | 'AS' | 'AT' | 'AU' | 'AW' | 'AX' | 'AZ' | 'BA' | 'BB' | 'BD' | 'BE' | 'BF' | 'BG' | 'BH' | 'BI' | 'BJ' | 'BL' | 'BM' | 'BN' | 'BO' | 'BQ' | 'BR' | 'BS' | 'BT' | 'BW' | 'BY' | 'BZ' | 'CA' | 'CC' | 'CD' | 'CF' | 'CG' | 'CH' | 'CI' | 'CK' | 'CL' | 'CM' | 'CN' | 'CO' | 'CR' | 'CU' | 'CV' | 'CW' | 'CX' | 'CY' | 'CZ' | 'DE' | 'DJ' | 'DK' | 'DM' | 'DO' | 'DZ' | 'EC' | 'EE' | 'EG' | 'EH' | 'ER' | 'ES' | 'ET' | 'FI' | 'FJ' | 'FK' | 'FM' | 'FO' | 'FR' | 'GA' | 'GB' | 'GD' | 'GE' | 'GF' | 'GG' | 'GH' | 'GI' | 'GL' | 'GM' | 'GN' | 'GP' | 'GQ' | 'GR' | 'GT' | 'GU' | 'GW' | 'GY' | 'HK' | 'HN' | 'HR' | 'HT' | 'HU' | 'ID' | 'IE' | 'IL' | 'IM' | 'IN' | 'IO' | 'IQ' | 'IR' | 'IS' | 'IT' | 'JE' | 'JM' | 'JO' | 'JP' | 'KE' | 'KG' | 'KH' | 'KI' | 'KM' | 'KN' | 'KP' | 'KR' | 'KW' | 'KY' | 'KZ' | 'LA' | 'LB' | 'LC' | 'LI' | 'LK' | 'LR' | 'LS' | 'LT' | 'LU' | 'LV' | 'LY' | 'MA' | 'MC' | 'MD' | 'ME' | 'MF' | 'MG' | 'MH' | 'MK' | 'ML' | 'MM' | 'MN' | 'MO' | 'MP' | 'MQ' | 'MR' | 'MS' | 'MT' | 'MU' | 'MV' | 'MW' | 'MX' | 'MY' | 'MZ' | 'NA' | 'NC' | 'NE' | 'NF' | 'NG' | 'NI' | 'NL' | 'NO' | 'NP' | 'NR' | 'NU' | 'NZ' | 'OM' | 'PA' | 'PE' | 'PF' | 'PG' | 'PH' | 'PK' | 'PL' | 'PM' | 'PR' | 'PS' | 'PT' | 'PW' | 'PY' | 'QA' | 'RE' | 'RO' | 'RS' | 'RU' | 'RW' | 'SA' | 'SB' | 'SC' | 'SD' | 'SE' | 'SG' | 'SH' | 'SI' | 'SJ' | 'SK' | 'SL' | 'SM' | 'SN' | 'SO' | 'SR' | 'SS' | 'ST' | 'SV' | 'SX' | 'SY' | 'SZ' | 'TA' | 'TC' | 'TD' | 'TG' | 'TH' | 'TJ' | 'TK' | 'TL' | 'TM' | 'TN' | 'TO' | 'TR' | 'TT' | 'TV' | 'TW' | 'TZ' | 'UA' | 'UG' | 'US' | 'UY' | 'UZ' | 'VA' | 'VC' | 'VE' | 'VG' | 'VI' | 'VN' | 'VU' | 'WF' | 'WS' | 'XK' | 'YE' | 'YT' | 'ZA' | 'ZM' | 'ZW'; diff --git a/app/engine/api/holders/fetchAccountState.ts b/app/engine/api/holders/fetchUserState.ts similarity index 71% rename from app/engine/api/holders/fetchAccountState.ts rename to app/engine/api/holders/fetchUserState.ts index 27bdb6e80..6c914e49b 100644 --- a/app/engine/api/holders/fetchAccountState.ts +++ b/app/engine/api/holders/fetchUserState.ts @@ -10,11 +10,11 @@ export const holdersUrlProd = 'https://tonhub.holders.io'; export const holdersEndpoint = (isTestnet: boolean) => isTestnet ? holdersEndpointStage : holdersEndpointProd; export const holdersUrl = (isTestnet: boolean) => isTestnet ? holdersUrlStage : holdersUrlProd; -export type AccountState = z.infer; +export type UserState = z.infer; -export type AccountStateRes = { ok: boolean, state: AccountState }; +export type UserStateRes = { ok: boolean, state: UserState }; -export enum HoldersAccountState { +export enum HoldersUserState { NeedEnrollment = 'need-enrollment', NeedPhone = 'need-phone', NoRef = 'no-ref', @@ -23,15 +23,15 @@ export enum HoldersAccountState { Ok = 'ok', } -export const accountStateCodec = z.union([ +export const userStateCodec = z.union([ z.object({ state: z.union([ - z.literal(HoldersAccountState.NeedPhone), - z.literal(HoldersAccountState.NoRef), + z.literal(HoldersUserState.NeedPhone), + z.literal(HoldersUserState.NoRef), ]), }), z.object({ - state: z.literal(HoldersAccountState.NeedKyc), + state: z.literal(HoldersUserState.NeedKyc), kycStatus: z.union([ z.null(), z.object({ @@ -53,9 +53,9 @@ export const accountStateCodec = z.union([ }), z.object({ state: z.union([ - z.literal(HoldersAccountState.Ok), - z.literal(HoldersAccountState.NeedEmail), - z.literal(HoldersAccountState.NeedPhone), + z.literal(HoldersUserState.Ok), + z.literal(HoldersUserState.NeedEmail), + z.literal(HoldersUserState.NeedPhone), ]), notificationSettings: z.object({ enabled: z.boolean(), @@ -64,12 +64,12 @@ export const accountStateCodec = z.union([ }), ]); -export const accountStateResCodec = z.object({ +export const userStateResCodec = z.object({ ok: z.boolean(), - state: accountStateCodec, + state: userStateCodec, }); -export async function fetchAccountState(token: string, isTestnet: boolean): Promise { +export async function fetchUserState(token: string, isTestnet: boolean): Promise { const endpoint = isTestnet ? holdersEndpointStage : holdersEndpointProd; const res = await axios.post(`https://${endpoint}/account/state`, { token }); @@ -81,5 +81,5 @@ export async function fetchAccountState(token: string, isTestnet: boolean): Prom throw Error('Failed to fetch account state'); } - return res.data.state as AccountState; + return res.data.state as UserState; } \ No newline at end of file diff --git a/app/engine/api/holders/fetchAccountToken.ts b/app/engine/api/holders/fetchUserToken.ts similarity index 87% rename from app/engine/api/holders/fetchAccountToken.ts rename to app/engine/api/holders/fetchUserToken.ts index a1cf12afc..270a45bbb 100644 --- a/app/engine/api/holders/fetchAccountToken.ts +++ b/app/engine/api/holders/fetchUserToken.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { holdersEndpoint } from './fetchAccountState'; +import { holdersEndpoint } from './fetchUserState'; import { z } from 'zod'; const tonconnectV2Config = z.object({ @@ -57,12 +57,13 @@ const keys = z.union([tonXKey, tonXLiteKey, tonconnectV2Key]); export type AccountKeyParam = z.infer; -export async function fetchAccountToken(key: AccountKeyParam, isTestnet: boolean): Promise { +export async function fetchUserToken(key: AccountKeyParam, isTestnet: boolean, inviteId?: string): Promise { const endpoint = holdersEndpoint(isTestnet); const requestParams = { stack: 'ton', network: isTestnet ? 'ton-testnet' : 'ton-mainnet', - key: key + key: key, + inviteId }; const res = await axios.post( @@ -71,7 +72,7 @@ export async function fetchAccountToken(key: AccountKeyParam, isTestnet: boolean ); if (!res.data.ok) { - throw Error('Failed to fetch card token'); + throw Error('Failed to fetch user token'); } return res.data.token as string; } \ No newline at end of file diff --git a/app/engine/effects/onHoldersEnroll.ts b/app/engine/effects/onHoldersEnroll.ts index 4ae8045ac..3113ae4c9 100644 --- a/app/engine/effects/onHoldersEnroll.ts +++ b/app/engine/effects/onHoldersEnroll.ts @@ -2,7 +2,7 @@ import { Address } from "@ton/ton"; import { queryClient } from "../clients"; import { Queries } from "../queries"; import { getHoldersToken } from "../hooks/holders/useHoldersAccountStatus"; -import { HoldersAccountState, fetchAccountState } from "../api/holders/fetchAccountState"; +import { HoldersUserState, fetchUserState } from "../api/holders/fetchUserState"; import { fetchAccountsPublic, fetchAccountsList } from "../api/holders/fetchAccounts"; import { updateProvisioningCredentials } from "../holders/updateProvisioningCredentials"; @@ -12,17 +12,17 @@ export async function onHoldersEnroll(account: string, isTestnet: boolean) { const token = getHoldersToken(address); if (!token) { - return { state: HoldersAccountState.NeedEnrollment } as { state: HoldersAccountState.NeedEnrollment }; // This looks amazingly stupid + return { state: HoldersUserState.NeedEnrollment } as { state: HoldersUserState.NeedEnrollment }; // This looks amazingly stupid } - const fetched = await fetchAccountState(token, isTestnet); + const fetched = await fetchUserState(token, isTestnet); if (!fetched) { - return { state: HoldersAccountState.NeedEnrollment } as { state: HoldersAccountState.NeedEnrollment }; + return { state: HoldersUserState.NeedEnrollment } as { state: HoldersUserState.NeedEnrollment }; } const status = { ...fetched, token }; queryClient.setQueryData(Queries.Holders(address).Status(), () => status); - if (status.state === HoldersAccountState.Ok) { + if (status.state === HoldersUserState.Ok) { let accounts; let prepaidCards; let type = 'public'; diff --git a/app/engine/holders/watchHoldersAccountUpdates.ts b/app/engine/holders/watchHoldersAccountUpdates.ts index 1383bf4e8..fba7be9e0 100644 --- a/app/engine/holders/watchHoldersAccountUpdates.ts +++ b/app/engine/holders/watchHoldersAccountUpdates.ts @@ -1,5 +1,5 @@ import { createLogger } from '../../utils/log'; -import { holdersEndpoint } from '../api/holders/fetchAccountState'; +import { holdersEndpoint } from '../api/holders/fetchUserState'; let index = 0; diff --git a/app/engine/holdersWatcher.ts b/app/engine/holdersWatcher.ts index 8ebe6d45b..b815646d4 100644 --- a/app/engine/holdersWatcher.ts +++ b/app/engine/holdersWatcher.ts @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { useHoldersAccountStatus } from "./hooks/holders/useHoldersAccountStatus"; import { useSelectedAccount } from "./hooks/appstate/useSelectedAccount"; -import { HoldersAccountState } from "./api/holders/fetchAccountState"; +import { HoldersUserState } from "./api/holders/fetchUserState"; import { useNetwork } from "./hooks/network/useNetwork"; import { watchHoldersAccountUpdates } from './holders/watchHoldersAccountUpdates'; import { queryClient } from "./clients"; @@ -14,8 +14,8 @@ export function useHoldersWatcher() { const status = useHoldersAccountStatus(account?.address.toString({ testOnly: isTestnet }) ?? ''); const cards = useHoldersAccounts(account?.address.toString({ testOnly: isTestnet }) ?? ''); - useEffect(() => { - if (status?.data?.state !== HoldersAccountState.Ok) { + useEffect(() => { + if (status?.data?.state !== HoldersUserState.Ok) { return; } diff --git a/app/engine/hooks/holders/useCardTransactions.ts b/app/engine/hooks/holders/useCardTransactions.ts index e6cd9c6a1..e7e6eee2c 100644 --- a/app/engine/hooks/holders/useCardTransactions.ts +++ b/app/engine/hooks/holders/useCardTransactions.ts @@ -2,7 +2,7 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { CardNotification, fetchCardsTransactions } from "../../api/holders/fetchCardsTransactions"; import { Queries } from "../../queries"; import { useHoldersAccountStatus } from "./useHoldersAccountStatus"; -import { HoldersAccountState } from "../../api/holders/fetchAccountState"; +import { HoldersUserState } from "../../api/holders/fetchUserState"; export function useCardTransactions(address: string, id: string) { let status = useHoldersAccountStatus(address).data; @@ -23,7 +23,7 @@ export function useCardTransactions(address: string, id: string) { }; }, queryFn: async (ctx) => { - if (!!status && status.state !== HoldersAccountState.NeedEnrollment) { + if (!!status && status.state !== HoldersUserState.NeedEnrollment) { const cardRes = await fetchCardsTransactions(status.token, id, 40, ctx.pageParam?.lastCursor, 'desc'); if (!!cardRes) { return cardRes; diff --git a/app/engine/hooks/holders/useClearHolders.ts b/app/engine/hooks/holders/useClearHolders.ts index a5a674c7f..15fe88925 100644 --- a/app/engine/hooks/holders/useClearHolders.ts +++ b/app/engine/hooks/holders/useClearHolders.ts @@ -1,4 +1,4 @@ -import { holdersUrl } from "../../api/holders/fetchAccountState"; +import { holdersUrl } from "../../api/holders/fetchUserState"; import { useDomainKeys } from "../dapps/useDomainKeys"; import { deleteHoldersToken } from "./useHoldersAccountStatus"; import { extractDomain } from "../../utils/extractDomain"; diff --git a/app/engine/hooks/holders/useHasHoldersProducts.ts b/app/engine/hooks/holders/useHasHoldersProducts.ts index e4e312900..d4c48777f 100644 --- a/app/engine/hooks/holders/useHasHoldersProducts.ts +++ b/app/engine/hooks/holders/useHasHoldersProducts.ts @@ -4,7 +4,7 @@ import { Queries } from "../../queries"; import { queryClient } from "../../clients"; import { getQueryData } from "../../utils/getQueryData"; import { HoldersAccountStatus } from "./useHoldersAccountStatus"; -import { HoldersAccountState } from "../../api/holders/fetchAccountState"; +import { HoldersUserState } from "../../api/holders/fetchUserState"; import { HoldersAccounts } from "./useHoldersAccounts"; function hasAccounts(accs: HoldersAccounts | undefined) { @@ -24,8 +24,8 @@ export function getHasHoldersProducts(address: string) { const token = ( !!status && - status.state !== HoldersAccountState.NoRef && - status.state !== HoldersAccountState.NeedEnrollment + status.state !== HoldersUserState.NoRef && + status.state !== HoldersUserState.NeedEnrollment ) ? status.token : null; const accounts = getQueryData(queryCache, Queries.Holders(address).Cards(!!token ? 'private' : 'public')); diff --git a/app/engine/hooks/holders/useHoldersAccountStatus.ts b/app/engine/hooks/holders/useHoldersAccountStatus.ts index cfe841fbb..aeeb04ff6 100644 --- a/app/engine/hooks/holders/useHoldersAccountStatus.ts +++ b/app/engine/hooks/holders/useHoldersAccountStatus.ts @@ -4,13 +4,13 @@ import { Address } from "@ton/core"; import { useMemo } from "react"; import { useNetwork } from "../network/useNetwork"; import { storage } from "../../../storage/storage"; -import { HoldersAccountState, accountStateCodec, fetchAccountState } from "../../api/holders/fetchAccountState"; +import { HoldersUserState, userStateCodec, fetchUserState } from "../../api/holders/fetchUserState"; import { z } from 'zod'; import { removeProvisioningCredentials } from "../../holders/updateProvisioningCredentials"; const holdersAccountStatus = z.union([ - z.object({ state: z.literal(HoldersAccountState.NeedEnrollment) }), - z.intersection(z.object({ token: z.string() }), accountStateCodec), + z.object({ state: z.literal(HoldersUserState.NeedEnrollment) }), + z.intersection(z.object({ token: z.string() }), userStateCodec), ]); export type HoldersAccountStatus = z.infer; @@ -63,13 +63,13 @@ export function useHoldersAccountStatus(address: string | Address) { const token = getHoldersToken(addr); if (!token) { - return { state: HoldersAccountState.NeedEnrollment } as HoldersAccountStatus; // This looks amazingly stupid + return { state: HoldersUserState.NeedEnrollment } as HoldersAccountStatus; // This looks amazingly stupid } - const fetched = await fetchAccountState(token, isTestnet); + const fetched = await fetchUserState(token, isTestnet); if (!fetched) { - return { state: HoldersAccountState.NeedEnrollment } as HoldersAccountStatus; + return { state: HoldersUserState.NeedEnrollment } as HoldersAccountStatus; } return { ...fetched, token } as HoldersAccountStatus; diff --git a/app/engine/hooks/holders/useHoldersAccounts.ts b/app/engine/hooks/holders/useHoldersAccounts.ts index f8507d2ef..6a15f01bb 100644 --- a/app/engine/hooks/holders/useHoldersAccounts.ts +++ b/app/engine/hooks/holders/useHoldersAccounts.ts @@ -5,7 +5,7 @@ import { useQuery } from "@tanstack/react-query"; import { Queries } from "../../queries"; import { GeneralHoldersAccount, PrePaidHoldersCard, fetchAccountsList, fetchAccountsPublic } from "../../api/holders/fetchAccounts"; import { useHoldersAccountStatus } from "./useHoldersAccountStatus"; -import { HoldersAccountState } from "../../api/holders/fetchAccountState"; +import { HoldersUserState } from "../../api/holders/fetchUserState"; import { updateProvisioningCredentials } from "../../holders/updateProvisioningCredentials"; export type HoldersAccounts = { @@ -27,8 +27,8 @@ export function useHoldersAccounts(address: string | Address) { const token = ( !!status && - status.state !== HoldersAccountState.NoRef && - status.state !== HoldersAccountState.NeedEnrollment + status.state !== HoldersUserState.NoRef && + status.state !== HoldersUserState.NeedEnrollment ) ? status.token : null; let query = useQuery({ diff --git a/app/engine/hooks/holders/useHoldersEnroll.ts b/app/engine/hooks/holders/useHoldersEnroll.ts index 955eeec62..59e5db53d 100644 --- a/app/engine/hooks/holders/useHoldersEnroll.ts +++ b/app/engine/hooks/holders/useHoldersEnroll.ts @@ -1,11 +1,11 @@ import { Address, beginCell, storeStateInit } from "@ton/core"; import { AuthParams, AuthWalletKeysType } from "../../../components/secure/AuthWalletKeys"; -import { fetchAccountToken } from "../../api/holders/fetchAccountToken"; +import { fetchUserToken } from "../../api/holders/fetchUserToken"; import { contractFromPublicKey } from "../../contractFromPublicKey"; import { onHoldersEnroll } from "../../effects/onHoldersEnroll"; import { WalletKeys } from "../../../storage/walletKeys"; import { ConnectReplyBuilder } from "../../tonconnect/ConnectReplyBuilder"; -import { holdersUrl } from "../../api/holders/fetchAccountState"; +import { holdersUrl } from "../../api/holders/fetchUserState"; import { getAppManifest } from "../../getters/getAppManifest"; import { AppManifest } from "../../api/fetchManifest"; import { ConnectItemReply, TonProofItemReplySuccess } from "@tonconnect/protocol"; @@ -24,7 +24,8 @@ export type HoldersEnrollParams = { }, domain: string, authContext: AuthWalletKeysType, - authStyle?: AuthParams | undefined + authStyle?: AuthParams | undefined, + inviteId?: string } export enum HoldersEnrollErrorType { @@ -41,7 +42,7 @@ export enum HoldersEnrollErrorType { export type HoldersEnrollResult = { type: 'error', error: HoldersEnrollErrorType } | { type: 'success' }; -export function useHoldersEnroll({ acc, authContext, authStyle }: HoldersEnrollParams) { +export function useHoldersEnroll({ acc, authContext, authStyle, inviteId }: HoldersEnrollParams) { const { isTestnet } = useNetwork(); const saveAppConnection = useSaveAppConnection(); const connectApp = useConnectApp(); @@ -59,6 +60,15 @@ export function useHoldersEnroll({ acc, authContext, authStyle }: HoldersEnrollP const connections = app ? connectAppConnections(extensionKey(app.url)) : []; const isInjected = connections.find((item) => item.type === TonConnectBridgeType.Injected); + if (inviteId) { + + // + // Reset holders token with every invite attempt + // + + deleteHoldersToken(acc.address.toString({ testOnly: isTestnet })) + } + // // Check holders token value // @@ -149,7 +159,7 @@ export function useHoldersEnroll({ acc, authContext, authStyle }: HoldersEnrollP return { type: 'error', error: HoldersEnrollErrorType.NoProof }; } - let token = await fetchAccountToken({ + let token = await fetchUserToken({ kind: 'tonconnect-v2', wallet: 'tonhub', config: { @@ -163,7 +173,7 @@ export function useHoldersEnroll({ acc, authContext, authStyle }: HoldersEnrollP walletStateInit: stateInitStr } } - }, isTestnet); + }, isTestnet, inviteId); setHoldersToken(acc.address.toString({ testOnly: isTestnet }), token); } catch { diff --git a/app/engine/hooks/holders/useIsHoldersWhitelisted.ts b/app/engine/hooks/holders/useIsHoldersInvited.ts similarity index 63% rename from app/engine/hooks/holders/useIsHoldersWhitelisted.ts rename to app/engine/hooks/holders/useIsHoldersInvited.ts index 313c76e86..c4a10805b 100644 --- a/app/engine/hooks/holders/useIsHoldersWhitelisted.ts +++ b/app/engine/hooks/holders/useIsHoldersInvited.ts @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import { Address } from "@ton/core"; import { Queries } from "../../queries"; import { useMemo } from "react"; -import { fetchAddressWhitelistCheck } from "../../api/holders/fetchAddressWhitelistCheck"; +import { fetchAddressInviteCheck } from "../../api/holders/fetchAddressInviteCheck"; -export function useIsHoldersWhitelisted(address: string | Address, isTestnet: boolean) { +export function useIsHoldersInvited(address: string | Address, isTestnet: boolean) { const addressString = useMemo(() => { if (address instanceof Address) { return address.toString({ testOnly: isTestnet }); @@ -13,11 +13,11 @@ export function useIsHoldersWhitelisted(address: string | Address, isTestnet: bo }, [address, isTestnet]); const query = useQuery({ - queryKey: Queries.Holders(addressString).WhiteList(), + queryKey: Queries.Holders(addressString).Invite(), refetchOnMount: true, staleTime: 1000 * 60 * 5, // 5 minutes queryFn: async (key) => { - return await fetchAddressWhitelistCheck(addressString, isTestnet); + return await fetchAddressInviteCheck(addressString, isTestnet); } }); diff --git a/app/engine/queries.ts b/app/engine/queries.ts index 2deeab8b0..72ce084f1 100644 --- a/app/engine/queries.ts +++ b/app/engine/queries.ts @@ -24,7 +24,7 @@ export const Queries = { Status: () => ['holders', address, 'status'], Cards: (mode: 'private' | 'public') => ['holders', address, 'cards', mode], Notifications: (id: string) => ['holders', address, 'events', id], - WhiteList: () => ['holders', address, 'whitelist'], + Invite: () => ['holders', address, 'invite'], }), ContractMetadata: (address: string) => (['contractMetadata', address]), diff --git a/app/fragments/SettingsFragment.tsx b/app/fragments/SettingsFragment.tsx index 00823afd4..08266de57 100644 --- a/app/fragments/SettingsFragment.tsx +++ b/app/fragments/SettingsFragment.tsx @@ -20,7 +20,7 @@ import { useFocusEffect, useRoute } from '@react-navigation/native'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useLedgerTransport } from './ledger/components/TransportContext'; import { Typography } from '../components/styles'; -import { HoldersAccountState, holdersUrl as resolveHoldersUrl } from '../engine/api/holders/fetchAccountState'; +import { HoldersUserState, holdersUrl as resolveHoldersUrl } from '../engine/api/holders/fetchUserState'; import IcSecurity from '@assets/settings/ic-security.svg'; import IcSpam from '@assets/settings/ic-spam.svg'; @@ -122,7 +122,7 @@ export const SettingsFragment = fragment(() => { const queryCache = queryClient.getQueryCache(); const address = seleted!.address.toString({ testOnly: network.isTestnet }); const status = getQueryData(queryCache, Queries.Holders(address).Status()); - const token = status?.state === HoldersAccountState.Ok ? status.token : getHoldersToken(address); + const token = status?.state === HoldersUserState.Ok ? status.token : getHoldersToken(address); const accountsStatus = getQueryData(queryCache, Queries.Holders(address).Cards(!!token ? 'private' : 'public')); const initialState = { diff --git a/app/fragments/holders/HoldersAppFragment.tsx b/app/fragments/holders/HoldersAppFragment.tsx index e94f2849d..e11dbb2f4 100644 --- a/app/fragments/holders/HoldersAppFragment.tsx +++ b/app/fragments/holders/HoldersAppFragment.tsx @@ -6,16 +6,25 @@ import { useParams } from '../../utils/useParams'; import { t } from '../../i18n/t'; import { useEffect, useMemo } from 'react'; import { useHoldersAccountStatus, useHoldersAccounts, useNetwork, useSelectedAccount, useTheme } from '../../engine/hooks'; -import { holdersUrl } from '../../engine/api/holders/fetchAccountState'; +import { holdersUrl } from '../../engine/api/holders/fetchUserState'; import { StatusBar, setStatusBarStyle } from 'expo-status-bar'; import { onHoldersInvalidate } from '../../engine/effects/onHoldersInvalidate'; import { useFocusEffect } from '@react-navigation/native'; +export enum HoldersAppParamsType { + Account = 'account', + Prepaid = 'prepaid', + Create = 'create', + Invite = 'invite', + Transactions = 'transactions' +} + export type HoldersAppParams = - | { type: 'account', id: string } - | { type: 'prepaid', id: string } - | { type: 'create' } - | { type: 'transactions', query: { [key: string]: string | undefined } }; + | { type: HoldersAppParamsType.Account, id: string } + | { type: HoldersAppParamsType.Prepaid, id: string } + | { type: HoldersAppParamsType.Create } + | { type: HoldersAppParamsType.Invite } + | { type: HoldersAppParamsType.Transactions, query: { [key: string]: string | undefined } }; export const HoldersAppFragment = fragment(() => { const theme = useTheme(); diff --git a/app/fragments/holders/HoldersLandingFragment.tsx b/app/fragments/holders/HoldersLandingFragment.tsx index 9598deb1b..85dbc6473 100644 --- a/app/fragments/holders/HoldersLandingFragment.tsx +++ b/app/fragments/holders/HoldersLandingFragment.tsx @@ -6,7 +6,7 @@ import { useTypedNavigation } from '../../utils/useTypedNavigation'; import { t } from '../../i18n/t'; import { extractDomain } from '../../engine/utils/extractDomain'; import { useParams } from '../../utils/useParams'; -import { HoldersAppParams } from './HoldersAppFragment'; +import { HoldersAppParams, HoldersAppParamsType } from './HoldersAppFragment'; import { getLocales } from 'react-native-localize'; import { fragment } from '../../fragment'; import { useKeysAuth } from '../../components/secure/AuthWalletKeys'; @@ -33,10 +33,10 @@ export const HoldersLandingFragment = fragment(() => { const safeArea = useSafeAreaInsets(); const [currency,] = usePrimaryCurrency(); - const { endpoint, onEnrollType } = useParams<{ endpoint: string, onEnrollType: HoldersAppParams }>(); + const { endpoint, onEnrollType, inviteId } = useParams<{ endpoint: string, onEnrollType: HoldersAppParams, inviteId?: string }>(); const domain = extractDomain(endpoint); - const enroll = useHoldersEnroll({ acc, domain, authContext, authStyle: { paddingTop: 32 } }); + const enroll = useHoldersEnroll({ acc, domain, authContext, inviteId, authStyle: { paddingTop: 32 } }); const lang = getLocales()[0].languageCode; // Anim @@ -198,7 +198,7 @@ export const HoldersLandingFragment = fragment(() => { lockScroll: true }} webviewDebuggingEnabled={isTestnet} - loader={(p) => } + loader={(p) => } /> { ); }); -export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'account' | 'create' | 'prepaid' }) => { +export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: HoldersAppParamsType }) => { const theme = useTheme(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); @@ -253,11 +253,11 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a }, []); const placeholder = useMemo(() => { - if (type === 'account') { + if (type === HoldersAppParamsType.Account) { return ; } - if (type === 'prepaid') { + if (type === HoldersAppParamsType.Prepaid) { return ; } @@ -274,7 +274,7 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a }, animatedStyles, Platform.select({ - ios: { paddingTop: (type === 'account' || type === 'prepaid') ? 0 : safeArea.top }, + ios: { paddingTop: (type === HoldersAppParamsType.Account || type === HoldersAppParamsType.Prepaid) ? 0 : safeArea.top }, android: { paddingTop: 0 } }), ]} @@ -328,16 +328,19 @@ export const HoldersAppComponent = memo(( let route = ''; switch (props.variant.type) { - case 'create': + case HoldersAppParamsType.Invite: + route = '/onboarding/invite'; + break; + case HoldersAppParamsType.Create: route = '/create'; break; - case 'account': + case HoldersAppParamsType.Account: route = `/account/${props.variant.id}`; break; - case 'prepaid': + case HoldersAppParamsType.Prepaid: route = `/card-prepaid/${props.variant.id}`; break; - case 'transactions': + case HoldersAppParamsType.Transactions: route = `/transactions`; for (const [key, value] of Object.entries(props.variant.query)) { if (!!value) { @@ -414,7 +417,7 @@ export const HoldersAppComponent = memo(( kycStatus: status.state === 'need-kyc' ? status.kycStatus : null, suspended: (status as { suspended: boolean | undefined }).suspended === true, }, - token: status.state === HoldersAccountState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), + token: status.state === HoldersUserState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), } } : {}, @@ -491,7 +494,10 @@ export const HoldersAppComponent = memo(( webviewDebuggingEnabled={isTestnet} loader={(p) => ( )} diff --git a/app/fragments/secure/components/TransferSingleView.tsx b/app/fragments/secure/components/TransferSingleView.tsx index b72dc4cc0..e52bbc94a 100644 --- a/app/fragments/secure/components/TransferSingleView.tsx +++ b/app/fragments/secure/components/TransferSingleView.tsx @@ -14,7 +14,7 @@ import { Address, fromNano, toNano } from "@ton/core"; import { WalletSettings } from "../../../engine/state/walletSettings"; import { useAppState, useNetwork, useBounceableWalletFormat, usePrice, useSelectedAccount, useTheme, useWalletsSettings, useVerifyJetton } from "../../../engine/hooks"; import { AddressComponent } from "../../../components/address/AddressComponent"; -import { holdersUrl as resolveHoldersUrl } from "../../../engine/api/holders/fetchAccountState"; +import { holdersUrl as resolveHoldersUrl } from "../../../engine/api/holders/fetchUserState"; import { useLedgerTransport } from "../../ledger/components/TransportContext"; import { Jetton, StoredOperation } from "../../../engine/types"; import { AboutIconButton } from "../../../components/AboutIconButton"; diff --git a/app/fragments/wallet/ProductsFragment.tsx b/app/fragments/wallet/ProductsFragment.tsx index 63728e1be..1a484c809 100644 --- a/app/fragments/wallet/ProductsFragment.tsx +++ b/app/fragments/wallet/ProductsFragment.tsx @@ -3,14 +3,15 @@ import { useCallback, useMemo } from "react"; import { fragment } from "../../fragment"; import { useTypedNavigation } from "../../utils/useTypedNavigation"; import { useHoldersAccountStatus, useIsConnectAppReady, useNetwork, useSelectedAccount, useStakingApy, useTheme } from "../../engine/hooks"; -import { HoldersAccountState, holdersUrl as resolveHoldersUrl } from "../../engine/api/holders/fetchAccountState"; +import { HoldersUserState, holdersUrl as resolveHoldersUrl } from "../../engine/api/holders/fetchUserState"; import { ScreenHeader } from "../../components/ScreenHeader"; import { t } from "../../i18n/t"; import { ProductBanner } from "../../components/products/ProductBanner"; import { StatusBar } from "expo-status-bar"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { useIsHoldersWhitelisted } from "../../engine/hooks/holders/useIsHoldersWhitelisted"; +import { useIsHoldersInvited } from "../../engine/hooks/holders/useIsHoldersInvited"; import { Typography } from "../../components/styles"; +import { HoldersAppParamsType } from "../holders/HoldersAppFragment"; export const ProductsFragment = fragment(() => { const navigation = useTypedNavigation(); @@ -22,7 +23,7 @@ export const ProductsFragment = fragment(() => { const status = useHoldersAccountStatus(selected!.address).data; const holdersUrl = resolveHoldersUrl(network.isTestnet); const isHoldersReady = useIsConnectAppReady(holdersUrl); - const isHoldersWhitelisted = useIsHoldersWhitelisted(selected!.address, network.isTestnet); + const isHoldersInvited = useIsHoldersInvited(selected!.address, network.isTestnet); const apyWithFee = useMemo(() => { if (!!apy) { @@ -31,7 +32,7 @@ export const ProductsFragment = fragment(() => { }, [apy]); const needsEnrolment = useMemo(() => { - if (status?.state === HoldersAccountState.NeedEnrollment) { + if (status?.state === HoldersUserState.NeedEnrollment) { return true; } return false; @@ -42,11 +43,11 @@ export const ProductsFragment = fragment(() => { navigation.goBack(); if (needsEnrolment || !isHoldersReady) { - navigation.navigateHoldersLanding({ endpoint: holdersUrl, onEnrollType: { type: 'create' } }, network.isTestnet); + navigation.navigateHoldersLanding({ endpoint: holdersUrl, onEnrollType: { type: HoldersAppParamsType.Create } }, network.isTestnet); return; } - navigation.navigateHolders({ type: 'create' }, network.isTestnet); + navigation.navigateHolders({ type: HoldersAppParamsType.Create }, network.isTestnet); }, [needsEnrolment, isHoldersReady, network.isTestnet]); return ( @@ -81,7 +82,7 @@ export const ProductsFragment = fragment(() => { {t('products.addNew')} - {isHoldersWhitelisted && ( + {isHoldersInvited && ( { illustrationStyle={{ backgroundColor: theme.elevation }} /> )} - + { navigation.goBack(); diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index 5935c3849..1226a66eb 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -32,9 +32,9 @@ import { extractDomain } from './engine/utils/extractDomain'; import { Linking } from 'react-native'; import { openWithInApp } from './utils/openWithInApp'; import { getHoldersToken, HoldersAccountStatus } from './engine/hooks/holders/useHoldersAccountStatus'; -import { HoldersAccountState, holdersUrl } from './engine/api/holders/fetchAccountState'; +import { HoldersUserState, holdersUrl } from './engine/api/holders/fetchUserState'; import { getIsConnectAppReady } from './engine/hooks/dapps/useIsConnectAppReady'; -import { HoldersAppParams } from './fragments/holders/HoldersAppFragment'; +import { HoldersAppParams, HoldersAppParamsType } from './fragments/holders/HoldersAppFragment'; const infoBackoff = createBackoff({ maxFailureCount: 10 }); @@ -473,7 +473,7 @@ function getNeedsEnrollment(url: string, address: string, isTestnet: boolean, qu return true; } - if (status.state === HoldersAccountState.NeedEnrollment) { + if (status.state === HoldersUserState.NeedEnrollment) { return true; } @@ -499,7 +499,7 @@ function resolveAndNavigateToHolders(params: { const transactionId = query['transactionId']; const holdersNavParams: HoldersAppParams = { - type: 'transactions', + type: HoldersAppParamsType.Transactions, query: { transactionId } } @@ -552,6 +552,18 @@ function resolveAndNavigateToHolders(params: { } } +function resolveHoldersInviteLink(params: { + navigation: TypedNavigation, + isTestnet: boolean, + inviteId: string +}){ + const { navigation, isTestnet, inviteId } = params + + const endpoint = holdersUrl(isTestnet); + + navigation.navigateHoldersLanding({endpoint, onEnrollType: { type: HoldersAppParamsType.Invite }, inviteId }, isTestnet); +} + export function useLinkNavigator( isTestnet: boolean, toastProps?: { duration?: ToastDuration, marginBottom?: number }, @@ -693,6 +705,18 @@ export function useLinkNavigator( isTestnet, queryClient }); + break + } + case 'holders-invite': { + if (!selected) { + return; + } + + resolveHoldersInviteLink({ + navigation, + isTestnet, + inviteId: resolved.inviteId + }) } } diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index 141634735..ecb236b69 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -68,6 +68,9 @@ export type ResolvedUrl = { } | { type: 'holders-transactions', query: { [key: string]: string | undefined } +} | { + type: 'holders-invite', + inviteId: string } export function isUrl(str: string): boolean { @@ -89,6 +92,16 @@ function resolveHoldersUrl(url: Url>): Resolv } } + const isInvite = url.pathname.startsWith('/holders/invite'); + const inviteId = url.pathname.split('holders/invite/')[1] + + if (isInvite && inviteId) { + return { + type: 'holders-invite', + inviteId: inviteId + } + } + return { type: 'error', error: ResolveUrlError.InvalidHoldersPath }; } diff --git a/app/utils/useTypedNavigation.ts b/app/utils/useTypedNavigation.ts index 7d8dc612e..9d69728d9 100644 --- a/app/utils/useTypedNavigation.ts +++ b/app/utils/useTypedNavigation.ts @@ -167,16 +167,16 @@ export class TypedNavigation { this.navigateAndReplaceAll('LedgerApp'); } - navigateHoldersLanding({ endpoint, onEnrollType }: { endpoint: string, onEnrollType: HoldersAppParams }, isTestnet: boolean) { + navigateHoldersLanding({ endpoint, onEnrollType, inviteId }: { endpoint: string, onEnrollType: HoldersAppParams, inviteId?: string }, isTestnet: boolean) { if (shouldTurnAuthOn(isTestnet)) { const callback = (success: boolean) => { if (success) { // navigate only if auth is set up - this.navigate('HoldersLanding', { endpoint, onEnrollType }) + this.navigate('HoldersLanding', { endpoint, onEnrollType, inviteId }) } } this.navigateMandatoryAuthSetup({ callback }); } else { - this.navigate('HoldersLanding', { endpoint, onEnrollType }); + this.navigate('HoldersLanding', { endpoint, onEnrollType, inviteId }); } } From f783f6d13b9360a21320e1d5ac3fe2ebf94390af Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Thu, 5 Sep 2024 08:22:38 +0200 Subject: [PATCH 14/37] fix: mark club pools as known --- app/secure/KnownWallets.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/secure/KnownWallets.ts b/app/secure/KnownWallets.ts index 0f58019e6..df72f51da 100644 --- a/app/secure/KnownWallets.ts +++ b/app/secure/KnownWallets.ts @@ -32,6 +32,8 @@ const Img_venera = require('@assets/known/Img_venera.jpeg'); const Img_Team_1 = require('@assets/known/ic_team_1.png'); const Img_Team_2 = require('@assets/known/ic_team_2.png'); +const Img_Club_1 = require('@assets/ic_club_cosmos.png'); +const Img_Club_2 = require('@assets/ic_club_robot.png'); const Img_ePN_1 = require('@assets/known/ic_epn_1.png'); const Img_ePN_2 = require('@assets/known/ic_epn_2.png'); const Img_Lockups_1 = require('@assets/known/ic_lockups_1.png'); @@ -171,6 +173,24 @@ const knownWalletsMainnet = { ic: Img_Team_2, requireMemo: true }, + [Address.parse('EQDFvnxuyA2ogNPOoEj1lu968U4PP8_FzJfrOWUsi_o1CLUB').toString()]: { + name: 'Club 1', + colors: { + primary: '#65C6FF', + secondary: '#DEEFFC' + }, + ic: Img_Club_1, + requireMemo: true + }, + [Address.parse('EQA_cc5tIQ4haNbMVFUD1d0bNRt17S7wgWEqfP_xEaTACLUB').toString()]: { + name: 'Club 2', + colors: { + primary: '#65C6FF', + secondary: '#DEEFFC' + }, + ic: Img_Club_2, + requireMemo: true + }, [Address.parse('EQBeNwQShukLyOWjKWZ0Oxoe5U3ET-ApQIWYeC4VLZ4tmeTm').toString()]: { name: 'Whales Pool Withdraw 1', colors: { From 35a6a5dd72275c9588abf22e5083186b6bdcfa0a Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Thu, 5 Sep 2024 09:47:00 +0200 Subject: [PATCH 15/37] add: new poor connection loader --- .../holders/components/AccountPlaceholder.tsx | 167 +++++------------- .../components/HoldersAppComponent.tsx | 48 +++-- app/i18n/i18n_en.ts | 3 +- app/i18n/i18n_ru.ts | 3 +- app/i18n/schema.ts | 1 + 5 files changed, 84 insertions(+), 138 deletions(-) diff --git a/app/fragments/holders/components/AccountPlaceholder.tsx b/app/fragments/holders/components/AccountPlaceholder.tsx index 6f0a8da35..312212b4d 100644 --- a/app/fragments/holders/components/AccountPlaceholder.tsx +++ b/app/fragments/holders/components/AccountPlaceholder.tsx @@ -1,103 +1,13 @@ import { memo, useEffect } from "react"; import { ThemeType } from "../../../engine/state/theme"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import Animated, { Easing, Extrapolate, Extrapolation, FadeInDown, FadeInUp, interpolate, useAnimatedStyle, useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated"; -import { Platform, View, StyleSheet, TextInput, Dimensions } from "react-native"; +import Animated, { Easing, Extrapolation, FadeInDown, interpolate, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from "react-native-reanimated"; +import { Platform, View, Text } from "react-native"; import { } from "react-native"; -import { - Blur, - Canvas, - Group, - Paint, - Path, - RoundedRect, - Skia, - SweepGradient, - vec, -} from "@shopify/react-native-skia"; import { RoundButton } from "../../../components/RoundButton"; import { t } from "../../../i18n/t"; - -const GlowingBorderView = memo(({ width, height, glowSize, blurRadius, theme, borderRadius }: { - theme: ThemeType - width: number, - height: number, - glowSize: number, - blurRadius: number, - borderRadius?: number -}) => { - // const GLOW_COLOR = "#1976edFF"; - const GLOW_BG_COLOR = "#1976ed00"; // Should be the same color as GLOW_COLOR but fully transparent - // const { height: screenHeight, width: screenWidth } = Dimensions.get("window"); - const rotation = useSharedValue(0); - - const screenWidth = 132; - const screenHeight = 36; - const centerX = width / 2; - const centerY = height / 2; - const centerVec = vec(centerX, centerY); - - useEffect(() => { - rotation.value = withRepeat( - withTiming(2, { - duration: 4000, - easing: Easing.linear, - }), - -1, - false, - ); - }, []); - - const animatedRotation = useDerivedValue(() => { - return [{ rotate: Math.PI * rotation.value }]; - }, [rotation]); - - const GlowGradient = () => { - return ( - - - - ); - }; - - return ( - - - {/* Blurred Glow */} - - - - - - {/* Outline */} - - - {/* Box overlay */} - - - - ); -}); +import { Image } from "expo-image"; +import { Typography } from "../../../components/styles"; export const AccountPlaceholder = memo(({ theme, @@ -119,7 +29,7 @@ export const AccountPlaceholder = memo(({ duration: 500, easing: Easing.bezier(0.25, 0.1, 0.25, 1) }), - -1, + 10, true, ); }, []); @@ -218,30 +128,49 @@ export const AccountPlaceholder = memo(({ }} /> - - {onReload && ( - - - - )} - {onSupport && ( - - - - )} - + + {(onReload || onSupport) && ( + + + + {t('products.holders.loadingLongerTitle')} + + + {t('products.holders.loadingLonger')} + + + {onReload && ( + + + + )} + {onSupport && ( + + + + )} + + + )} ); }); diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index b1f1f0f4f..5b5d02964 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Linking, Platform, View } from 'react-native'; +import { Linking, Platform, Pressable, View } from 'react-native'; import { ShouldStartLoadRequest } from 'react-native-webview/lib/WebViewTypes'; import { extractDomain } from '../../../engine/utils/extractDomain'; import { useTypedNavigation } from '../../../utils/useTypedNavigation'; @@ -29,7 +29,7 @@ import { openWithInApp } from '../../../utils/openWithInApp'; import { t } from '../../../i18n/t'; import { useActionSheet } from '@expo/react-native-action-sheet'; import { AccountPlaceholder } from './AccountPlaceholder'; -import { ToastDuration, useToaster } from '../../../components/toast/ToastProvider'; +import { Image } from "expo-image"; export function normalizePath(path: string) { return path.replaceAll('.', '_'); @@ -162,7 +162,6 @@ export const HoldersLoader = memo(({ onSupport?: () => void }) => { const theme = useTheme(); - const toaster = useToaster(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); const [showClose, setShowClose] = useState(false); @@ -173,11 +172,8 @@ export const HoldersLoader = memo(({ }); const longLoadingTimerRef = useRef(null); - - const start = useMemo(() => { - return Date.now(); - }, []); - const trackLoadingtime = useCallback(() => { + const start = useMemo(() => Date.now(), []); + const trackLoadingTime = useCallback(() => { trackEvent(MixpanelEvent.HoldersLoadingTime, { type, duration: Date.now() - start }); }, []); @@ -185,7 +181,7 @@ export const HoldersLoader = memo(({ if (loaded) { longLoadingTimerRef.current && clearTimeout(longLoadingTimerRef.current); opacity.value = withTiming(0, { duration: 350, easing: Easing.inOut(Easing.ease) }); - trackLoadingtime(); + trackLoadingTime(); } else { setShowClose(false); opacity.value = 1; @@ -195,7 +191,7 @@ export const HoldersLoader = memo(({ useEffect(() => { const showCloseTimer = setTimeout(() => { setShowClose(true); - }, 5000); + }, 7000); if (longLoadingTimerRef.current) { clearTimeout(longLoadingTimerRef.current); @@ -203,12 +199,7 @@ export const HoldersLoader = memo(({ longLoadingTimerRef.current = setTimeout(() => { trackEvent(MixpanelEvent.holdersLongLoadingTime, { type, duration: 12000 }); - toaster.show({ - type: 'error', - message: t('products.holders.loadingLonger'), - duration: ToastDuration.DEFAULT - }); - }, 12000); + }, 10000); return () => { longLoadingTimerRef.current && clearTimeout(longLoadingTimerRef.current); @@ -254,7 +245,29 @@ export const HoldersLoader = memo(({ {placeholder} {!loaded && ( [ + { + opacity: pressed ? 0.5 : 1, + backgroundColor: '#1c1c1e', + borderRadius: 32, + height: 32, width: 32, + justifyContent: 'center', alignItems: 'center', + }, + ]} + onPress={navigation.goBack} + > + + : undefined} style={[ { position: 'absolute', top: 32, left: 16, right: 0 }, Platform.select({ @@ -510,6 +523,7 @@ export const HoldersAppComponent = memo(( {...p} onReload={onReaload} onSupport={onSupport} + loaded={false} /> )} /> diff --git a/app/i18n/i18n_en.ts b/app/i18n/i18n_en.ts index f9feb144c..37fb78074 100644 --- a/app/i18n/i18n_en.ts +++ b/app/i18n/i18n_en.ts @@ -501,7 +501,8 @@ const schema: PrepareSchema = { }, holders: { title: 'Bank account', - loadingLonger: 'Loading takes longer than usual, try reloading or contact support', + loadingLongerTitle: 'Connection problems', + loadingLonger: 'Check your internet connection and reload page. If the issue persists please contact support', accounts: { title: 'Payment accounts', prepaidTitle: 'Prepaid cards', diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index c2a447349..0e6314397 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -501,7 +501,8 @@ const schema: PrepareSchema = { }, "holders": { "title": "Банковский счет", - "loadingLonger": "Загрузка занимает больше времени, чем обычно, попробуйте еще раз или обратитесь в службу поддержки", + "loadingLongerTitle": "Проблемы c подключением", + "loadingLonger": "Проверьте подключение к интернету и перезагрузите страницу. Если проблема сохраняется, обратитесь в службу поддержки", "accounts": { "title": "Счета", "prepaidTitle": 'Prepaid карты', diff --git a/app/i18n/schema.ts b/app/i18n/schema.ts index b38b63c7f..ff24786a3 100644 --- a/app/i18n/schema.ts +++ b/app/i18n/schema.ts @@ -503,6 +503,7 @@ export type LocalizationSchema = { }, holders: { title: string, + loadingLongerTitle: string, loadingLonger: string, accounts: { title: string, From 7111c0a4d449ef40dfcab8e3c6375d0913ad1ee9 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Thu, 5 Sep 2024 09:48:12 +0200 Subject: [PATCH 16/37] add: icons --- assets/ic-bad-connection.png | Bin 0 -> 1662 bytes assets/ic-bad-connection@2x.png | Bin 0 -> 3242 bytes assets/ic-bad-connection@3x.png | Bin 0 -> 4975 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/ic-bad-connection.png create mode 100644 assets/ic-bad-connection@2x.png create mode 100644 assets/ic-bad-connection@3x.png diff --git a/assets/ic-bad-connection.png b/assets/ic-bad-connection.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fb93747a57ecc0655688bef9784d4e86e7551a GIT binary patch literal 1662 zcmV-^27&pBP)D<8F08TJgb}38-ss;!v%n2YT08Rj$AkHeuKour|Vi&;)@D0!roZZwI ztLME&0%OZ|OWm^DQ~gy@*v6LBKd)bR|F12;;cz${4u`|xa5xj>np+TBk>~WJ0jq>U+exEETsgCAwe5yuhT^z!D6fym8gz*y#kAw zxu{onLG|-(umS_{ajvd{nG3#%DRc2}P@_!FB7PLWO1Fm!CH8xI?n4)Wt8N+9&o@Db zj@hPHuIaV**1CU>DN?wo968-3Y*3f@K%@AEm`>NN(0OX$A5xb^rr-DU{JLDi{pQbp zhyp20)HouKx)hBREtP1@d&TJ7X^IM+WP^ThP|x5r(W1xK+iQLk1z=QEj>_jH9MGtJ zC~O`~Vu2}wk4hB53niSGj9w$6@?i7a!vRHQgQF7jLzPleBkG|+i;SizakWI1ky)lL z-oqj09FyYano_ZtyxO6W_KCWAk%ESpGAu3y$rHB^Ims+44?ra5qj+17f0uCkDCa)O z0XHMc-9PE-1xKbTA*MrBcnB_Tg0sagrG!ntqw*o8NadA^j7~cQojTlZZa)vmD({~t zDqC-K)Bpv{CFQC|;G7mto27>e`+{7J2WqB(L&&% z{1vT9-HCG=(*K#^cHLhd_t`%wsum*DC^y!qkso0&|C6VCJkHY{DJpgO-!Z~^%X|^M zwih^Ad`4R}FUf1r5OwHSw?P9e?#skq8w!r*r!{{+?QcV(%C&k~c6VLps6$b5zVwW< zEJF5UQq%;F;8G9A)bKrMGD_H?oJu4774!sUF;B`}EbpxQ$9V-Uid~x2@6`Q9Zvo4c z+WvqBJg2?n9zDQ2QR5t1LtZ?uX{(rnm=ZTl*jG_yJt+U66nX;w_F8SUJlrqOCGz_#3ewT1i+S5Vn4sc~$hY0ZOUGUnPUN3gEA z{d(~4ae5TGmnx~KstC!G@WDLIQJwZ#_7b-0{?I-F5>8+;A-h>cRZ^KkO4ODa+Jnf* zrB%=e(^b?2_rYi3~G|?87BaDRgm4&4x9yhvZQ#%ot>t%``VfW29QMoWO`YQ_i zQgZ3@9j5`;%lNz)AGO8=WsJ>JCkQLO$yt?L_`H06qzh|QP{!zrYJ{*-&|QOTY+3?m z8o-zlrn7ND8RKjJL|9q&_0ZB0v@|N6&~cFizK-VwZ1T0mdlUR3s=wBMH~>LNn8JEd8TMsG2~{sbIPax!hr{7;IA)IZqrWU7E@EY#Mt`eC9k_o=g7x=Nu~yVc zC7MZJW|$>biYhmm2|P}EpY&ucOb1rpLxnC%X`iepdD``)BCt|a>h@|I*Mz9SLhKY( zeeZykri!hiJ}AA>^K$P!EfPC(s!-hkEOLG6p=hiWl}GeR1v~z`&MxU9vHMuf#vH$=8 literal 0 HcmV?d00001 diff --git a/assets/ic-bad-connection@2x.png b/assets/ic-bad-connection@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d3cdca1ae9a6aeb970b0239687419f1fb773251 GIT binary patch literal 3242 zcmaJ@X*3iJ7iN%s-(H05OSFhAi5ZNUAq-<=&t60Jb*xz%yD?0~*ru_DPnMKyg)EaP zV=08h&=86!vV8jee&@UA+OsjnJfRa=yxO>oCIci@`M-k3e22}aT z8V3RYxKjdjKS#|E%A0tc)U?$BEx*KvLXVIh(Mfw#waa&lDKSB8D1b6D+z{ zH}VAXcY41y$g}TjQ3w0JXC>%Gt?DllhQ6Iuz>7A#( z^=RIjT1}->v&Oc+N(Z-9H;@+=Zok@d5v@na3QLE6TGT#vCR`_rAd>KJ@^@f1>pgMc zrbBegQs`$IhIc&$+Ok22duu?~ZA00TxT)R`?l{GMw1O<=a84oHporkcYHRhHN9$9& zlQ_5gLPSf_G@kN0Ry(=7YO1s9X`|Xycy{AGajbZ-L3!lk{!bz8&Dqf~YNgLuY3w`a z#6_u>0ROTGEzIP>bQcB@@H|)Vr+ODHr`TwZKH}J9Z|oq^oQ1@W>}&Sdip$wWFm(?* zVioE!wg6&R-=%_v)N_`iL$P+}Jxc1VRJC*7@cYX-MvBPUS-M;=^{L&PP^^^6uMjphuJO)nE|LuI;k9FEIi|2y)%yOr%_xt7`C- zlPx4mpKU9%T>HkjIZ%5~gA9Ov0%;*$LZ@rv2PwNMx>1g{%PC|(oy%o9(HDXBYbU&Y zj@bx*15`BO#jPr-IqMZ-*ki7{`|~YNqbCk5{lW{LPk6vC`fpti5v3{ z@D{?O<5xILDVc)7ov=(*`$Gdv5+7NJvWNz10VLGV^1<-~7EG8KTO8tOQyVhd#=9 zK#9)pbRrzjNgF==c|RPhxY`CH(?3d6^*|<5_*C%%A>T%)gBQL8GY(`Su*H%ZX3$1j z?PYFW-}h~k3pC@arYO9=i^29#7eX*)xBksPcX%qGK z`E{`wlbB-JAxCnN=zGYfUbWGv%(Ap(dsd4+smK*5=%r3UGbZ3t?-DaK#>St^tcNBX z(3L+uQ<);9)i)V%L|(!wt(1})59cz}6DjDEqtyDq8jGive7-77c6FnciWB5tye$ZM z2`BwDW9NZq_E#!gse=xVmmGZWtIxfX7xpY;X)fxz)O=5jM`pk~y+gxq9b>;${zml4jDPSH~QgT>sZ=9N!nsE5q}>N0j2kFbnz>XFiZy9KWo z4$I5{RFSe>@^Cl)aXn`&d=euBm`r8c9seLG>_S9hY?I_gl-J4%_#r=X(nvkw*rExD zM@Ov;)P_yz=$C(Wb$w=-5H8GmUEux5YaOD0Gv;W+!1{{s;3xFi_KkM>@gi@$dPGdB z>#Mz3<@G^g=?%W8cVM*UtAVJOC0k+TmD0WuqBw-Lg!qa^rSWXNy9;SaEn)tAj9T_$HT22u~a7*!Ap^_vQfUh}-sj@6j8hH|pOjXr2t^HA2ZQA8fT+G28 zfaI`+$IxHY!xhT&&?Y-NOlI*b`P>1Mr?iWKjcv`Bgd!Q{P)a3;3k_F?gW}rm1XOGj z%W*#!pmnMcC7)e@FhLnrDsPKBSq4|AQ1>!fEr$!P0I2DYq+QVq0e=!o_-a1im^*SY z5yqw!^Lhht41yV3qqL?P0vv|9$pxdYOwOnei*Uo)Pe@hdIy!Y%4_@vno6(G=TO~>6^NT}WG5wkC+xrt5 z{+xxTnW&%+Y+&NiPf92fw5Jj#P~avezW;Z5b9jgybs$Rxka|y{;oi@RKuBvzb!>{oSH;GaLMGXUMBDl* pzT5`yo5x(KQUAX9|72>Cdl;}-_?b$Xi|D@%FtDXbqmf(Ee*r>R0M!5h literal 0 HcmV?d00001 diff --git a/assets/ic-bad-connection@3x.png b/assets/ic-bad-connection@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed9f9a6a6518dbb09db9a920b520a5f3dc23957 GIT binary patch literal 4975 zcma)Ai8CA8_YZ1mtzA&6LeYidRW}rAtF|gBRa=p^s%WYyY6%j8@-4-)B~@EJMO#bk zTOy&V+Qe2$tl=THAeIn`B!2n_yqVvfIrq%DbMD-kdp~EnpEq}HEhG;q9s&RWl2(?t z?DsYAkBEuv&mounx%=wiOG}qP06KzsY9BeUxb~MX>1uz5ZvO-Kr10p6GMwwqKYHaq781E8^6*cft5~i-m?-8A zSKpiG( zHT($$^tn~dVs);^wQ5PCFl8TsXV%Pd2XcWa={6`QpGD(WJF320_pF#0Z3PIdKv(+s zzbsMhmUvE9ZPEMI?72w!@1@7@>c&z{?R0uS-V8MCKC>oLz`AQ74H+X!*O)EvE_Fq4 z&+nOZ$rOkRnl7MPWx`bciY3G)oP60D+a><=`_~D``MCk$(xtJ6?V6+vmRi+iM)>rb zeCnA8b{_|jsjoKg@QVfnynp4qq5wO+qd%)L?>UrZkU#nfP+m6dUXevZxGp->8Vcub zJJG&OXi%QJt$(hbxfE$g)?+s|s(6=1CgP&soMIkjZmUo^m@ap~iS3V>e+H)$NN z#Cx0H$6bjk%I55j`CmGnLOr!wl_1a=3nRb_iv^f}xfzUnY_ZnS`uov43)rT28+W`q zl>wtGVP?W>-m(1mF|0R9>LB{O;op~z|1T!-dBR%VeL##3ARghN!n8*-5;u%`y)HJO z)Y6c-12JAPI#KofYldQ>8E^21;BqPpeGf`2G^WTmKqnhEykhZL?l~uh6*Bfz>-lZ3 z`C+9x?;1blF#_%Q&>hI=u_-sxdeg;S(W9Jzpd3k3P#IR|o#G1kDl|e6dO`SIQ9nE+ zZFx~;iNGiYv`F(l#P<3blmQyFZtqzd5~R8C6C83S7~U+oiql(T^>=C!@Z!dgEMB|D zAc^3AS*MvMuUm?RAgY_1+@5$U8QpT2m;WIdFGj2>(6yKeIXiPf$ZRL5l`+E2dER(s zaNcc!+^wrih6j&d$B9nho1x_|6Q`Ml4klv``PQ4<9;LUwk3jlX9^3M>HH4Azj|rod zpH0Mx2vCQBZ7h|uqPPtnZH66;ar<;`7x^r5uO`^|n!RV84uc${9uhFNF49=Az<~bT z`&6M3>GvQEF^~uS$+QwPQgT3Z^N~ij&nU9x?$!`x9kwA^ArqTg6+Gfg-oRWJbM5t{sPU^G@rYM%<@3 zxQ2}#wlIjqO-Fxs^H|iq!tCsifH-!j%?DQh`tjsGad?cwNoF=QM}y9hc*5*Uivf|- z+AT>v-epMU&G~@2U5&ROHwlN4l)^T~h{%^$wxcfUD`(SSzA`$pg}WeL z+H)hGj!D|VcRmZ{`Sv0vPcTl}fs-5tNpvFB4K6bbz)tYVrq|!wnMz&WznGL6zt6+B zZbGO+EW@cJJ!8ooLLBdGEE=y#6Z(a8a}@M71qz-@QEx>-=<`6OGu?Hxo!1VZHu|I; zDORDSbe$Mms0o3d8FgP^RCXbD8&FxJc4-$rEn5m~Sh$Y;=0 zXR=O~m-5@qKzq0f5>DmF-wWd?&e&ncl%f8%3OmN@7uM`*OCUMU7Pd1}yL3Bdss1A7kSxjF>@(iX6x>^+dC_-3EVx-%(h5O;ES4G#$v$uug^qL<+#Y% zsgz@3(n27y-Ix;av^HlMS=B!|u$FR+8y-*POB)@+V;)he(eXo)Mv{9iF_G9lh(b|F z{`SaLJ7lBEtJh#))mAxL6*~TnAWbK)$2^ACjQtCfJj%t!5r&Osf~Q1z+Ztb!al;Pz zXs*A1Jqnl5qKh6I0YC?HM9?loGi&v_WvgEvI?p{ro<_7(Ht6SnOZT(vF=e z{gK~yoau`_5{?esa4?oXRIZI!y$kuC_q8KuH@4@PP}he+`FQi4%@Z$@n_G$+(D#;f z2TUj+X1mP8LnOz&HZUW-eBmG2a@^|HWMf?s1r?QFL~RasyM)S*K9A)Mjr!JP=IESX z`U(57US819eu{L@jXwXl@`dkj-nT{|dW+gMgc!ynlSJ9g=ovZ@4qfmwZtn3PD-w7A zd{}Fq0&H*^IT(U7zhzm{K|*9J>ga5I!c-Xl{etu}caymtogjOAOAOgdI!9t-$p?AA zU78>|Gv)2!klF3}%bF1PxCiM|V?>l!?Wei`1xA?#iy?tmc-G#1CkC=T>Ea?PFHx;m zxL%1Bp-QyW1ia1cu>wuZ4uipw$S&aHIK>KT`2gBl*FAT8k(B#{o7UyX; zEqmJV3`jG_&0}fmF@4290G?ICpC6DZb}Rq4KfY1>)mZA!MyIK6%XupJubuj9I@>(* z!*VwKbnhjJ6+&)zp8UO3RUfqXFL|sBP|!Ws!^q|ydt_eYcYTDDR_;Ya-+jc!?-n7L zIbr#+U|WN1a4A8A+9O|!qtuSmeW27M@6dh%Ns9edn%6;l14fw$AP{h6^NGAehDA@^ zlL$3o>f4}po7ZhXw(#$)%+-1Ej&d!z?Rrm~TuHZ7h}$pG)&yfft)M>X;t0$4{g7g< z;jCBLi5GW-W5TJzRFU#pAciSF>qI9DzifIu_})>##~^1xXsSl?fK+%tbYf6d*E62_ zcKs&)Aii!X*#1o%FCF1>FwATD*c@e4gTq}_hp(9Kc;{kn--MZms(Kti$^`ojg2t>g zuC74LFUL5IbcFu!F8#+XUogkZLsPt^sZ-qFWNm2LnS4i)UpA7+RF*L0RvwuE<34!antO2JAf zDnV(Fr*z3XlkLfzvtMCe#6$!i3RgJ)V+qZ=>_EMqeAz1mvjH5f0(OY5n}z9A6zCRD z$kE^+j1BLx-B-%HOdc(2lDgkCIGudvRx#@}eY(FN;teAtq8i#xt<*%mH_J!xbA*iw zHAT556Tz$D3#W(rab?b@vq{k6iJYO@__}S@N;FBb=kdJ7O^q-=FlbvRh6Bju$leLM?rsLD$Cky0VSy67a929FSW_18_;;~d+ z5KKJ!!UA1)*tbeRxn1tLb)*S@AwK(RoPZr8^8uY;9_;95`U%A9EK`mB{ zCW9Qn_j)Ss7iI6ViuGwCD&A}Y-m!)iZo2l2p`92)ktUG*x((QP*6DDc!d=xnbm7CC zQ)}`inUyQDA?FBZf@`xvyLS7=M2EqPL{Fc3_!&6s-8-cwUStJb>Pr1$h-MQaXb#R|~uAimsL?ALb1<7H^S|4b{_`!(b+fN_ZjaBV>RH#yeW{j;bxRk zQaEX|*DViTt;1HGJ>o68l_QN%Dg%?OFOHcAs}(^<@l+ zH9eKy;KK_Tk)nmpwY`?QA~pockVUN2f2-4wBB{YulLH8NU9v@q2|yf|xdtWsD^0_f z9lM?BM1m5ivLEuTT+%prB7?u1>$lkQu3C%KnWzh%2r`x0wQ&tOx)bp4 zMTwX<_)6WYkBC_aq&UK*`mU7?TPAd7aCFyI1R}b?By*>Lz?&+oo@I-R-A8C(-#YG_kFa;}Z&Gl4lv>%4fD zy~4{-Sl3?rK9(N%^jL%|D?wI`$3b4vbICl{z%db_h9b2K z5$J-uQtd#cV|9Rb%2M5!pYu?st~HMwALMUur|!S+oH`{fa`oML?rC?*Ey?^CynXeD?V6144Bh4A%;*{?qahY9GRHjjclwy4T{Wrg}a}(6}M`pCA9<9{!guMYsFAp!7rYYqtjY1S3&m5$L@WztM^MBoOVjvA!7MuOaXC zAy2HOGofZ}Ka1v(i@6tOAp=RLMzUc)OuXmW@qOV-C6KhtPpViN@)5D3bW)~=$dV{v z^|kp|7kY7v9c~fTaZvKQCFbb|q*#kLHBUW0O@4mNsk@2Z?0>PMZsUI<{H<6)nDVxK z;;NvM#e%W_%QXD&){rM@Ow`7Vzr9O?K+O#}Lm(T*DA{l+)=1O9Dcze=cMX&Nn1#DTzC{q^>^_&Uz$s>%sZHK zYvE*YXxeblfOb~W+=(m8`fvKKowxCp+xhBtrBGAIQvtCUiZy zUXI%jei{9EG&QJcnSB1%ug|SkvD6^Rk#q&i6YTmwhJQWQeb=W%&&IBnB~&tNnAjIS z6=8dYS@Jn)c7TLS>I-jmb@y8-Ir7a2Z!u*=3hyn`-mCM}Xmqf85Gu%!b`1l!7+V=CI$pb7s!>R% zWj%jcPz3q&yyEBg37(>+t<49lo&4=u*U-999o)Fj4DcrKe}5q8YwB1FVwIa5r~nck zIo%Jrya3#qFJuQAs4eqPF;Uf;*9ob`5vj@o{|&hRL?YnyT%?qs@myE*0rnp!D>K_$ J6*nJ7{~sKT3JL%K literal 0 HcmV?d00001 From dcd5c68b37a29b3d7ded63440ec6aedb384da622 Mon Sep 17 00:00:00 2001 From: Garrethta Date: Thu, 5 Sep 2024 12:25:54 +0400 Subject: [PATCH 17/37] chore: imports --- app/components/products/ProductsComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/products/ProductsComponent.tsx b/app/components/products/ProductsComponent.tsx index 424e62ca8..6032e4239 100644 --- a/app/components/products/ProductsComponent.tsx +++ b/app/components/products/ProductsComponent.tsx @@ -23,9 +23,9 @@ import { AddressFormatUpdate } from "./AddressFormatUpdate" import { TonProductComponent } from "./TonProductComponent" import { SpecialJettonProduct } from "./SpecialJettonProduct" import { useIsHoldersInvited } from "../../engine/hooks/holders/useIsHoldersInvited" +import { HoldersAppParamsType } from "../../fragments/holders/HoldersAppFragment" import OldWalletIcon from '@assets/ic_old_wallet.svg'; -import { HoldersAppParamsType } from "../../fragments/holders/HoldersAppFragment" export const ProductsComponent = memo(({ selected }: { selected: SelectedAccount }) => { const theme = useTheme(); From 8694f0b2e83d3cf9222d4c93def990c27a4e321d Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 6 Sep 2024 09:53:02 +0200 Subject: [PATCH 18/37] add: adding request interceptors with user agent --- app/engine/clients.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/engine/clients.ts b/app/engine/clients.ts index edf61f8b5..ffc950e82 100644 --- a/app/engine/clients.ts +++ b/app/engine/clients.ts @@ -1,10 +1,23 @@ import { TonClient4 } from '@ton/ton'; import { QueryClient } from '@tanstack/react-query'; +import { Platform } from 'react-native'; +import * as Application from 'expo-application'; + + +const requestInterceptorMainnet = (config: any) => { + config.headers['User-Agent'] = `Tonhub/${Application.nativeApplicationVersion} ${Platform.OS}`; + return config; +}; + +const requestInterceptorTestnet = (config: any) => { + config.headers['User-Agent'] = `Tonhub/${Application.nativeApplicationVersion} ${Platform.OS} (testnet)`; + return config; +}; export const clients = { ton: { - testnet: new TonClient4({ endpoint: 'https://testnet-v4.tonhubapi.com', timeout: 5000 }), - mainnet: new TonClient4({ endpoint: 'https://mainnet-v4.tonhubapi.com', timeout: 5000 }), + testnet: new TonClient4({ endpoint: 'https://testnet-v4.tonhubapi.com', timeout: 5000, requestInterceptor: requestInterceptorTestnet }), + mainnet: new TonClient4({ endpoint: 'https://mainnet-v4.tonhubapi.com', timeout: 5000, requestInterceptor: requestInterceptorMainnet }), } } From bc33a70b8444e962b3832e6a27109e15f6ba0758 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 6 Sep 2024 09:56:39 +0200 Subject: [PATCH 19/37] add: new OKX address --- app/secure/KnownWallets.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/secure/KnownWallets.ts b/app/secure/KnownWallets.ts index 49db4e74b..8713b2a9b 100644 --- a/app/secure/KnownWallets.ts +++ b/app/secure/KnownWallets.ts @@ -489,6 +489,11 @@ const knownWalletsMainnet = { ic: Img_OKX, requireMemo: true }, + [Address.parse('EQCbm3Od4lJ65y0hCD9RmNcQxyiEU7RzPlfsrbLhiayCnNuU').toString()]: { + name: 'OKX', + ic: Img_OKX, + requireMemo: true + }, [Address.parse('EQCFTsRSHv1SrUO88ZiOTETr35omrRj6Uav9toX8OzSKXGkS').toString()]: { name: 'OKX', ic: Img_OKX, From 222b5acb7fd8abcd11c68f42bc1b6b92f06364d7 Mon Sep 17 00:00:00 2001 From: Garrethta Date: Fri, 6 Sep 2024 14:51:13 +0400 Subject: [PATCH 20/37] fix: network field in check invite api request --- app/engine/api/holders/fetchAddressInviteCheck.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/engine/api/holders/fetchAddressInviteCheck.ts b/app/engine/api/holders/fetchAddressInviteCheck.ts index 99f5f422b..e591571d3 100644 --- a/app/engine/api/holders/fetchAddressInviteCheck.ts +++ b/app/engine/api/holders/fetchAddressInviteCheck.ts @@ -11,7 +11,10 @@ export async function fetchAddressInviteCheck(address: string, isTestnet: boolea const endpoint = holdersEndpoint(isTestnet); const formattedAddress = Address.parse(address).toString({ testOnly: isTestnet }); - const res = await axios.post(`https://${endpoint}/v2/invite/wallet/check`, { wallet: formattedAddress }); + const res = await axios.post(`https://${endpoint}/v2/invite/wallet/check`, { + wallet: formattedAddress, + network: isTestnet ? 'ton-testnet' : 'ton-mainnet' + }); const parsed = inviteCheckCodec.safeParse(res.data); From 7c2d029fcf5f72bc70e9356d3026a43bb82bdeec Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Fri, 6 Sep 2024 17:47:32 +0200 Subject: [PATCH 21/37] fix: rm dev prop --- app/fragments/holders/components/HoldersAppComponent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 5b5d02964..ac08ff432 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -523,7 +523,6 @@ export const HoldersAppComponent = memo(( {...p} onReload={onReaload} onSupport={onSupport} - loaded={false} /> )} /> From 1649c2c391909bf5446e6bc491ab7c373639784d Mon Sep 17 00:00:00 2001 From: Garrethta Date: Mon, 9 Sep 2024 01:06:59 +0400 Subject: [PATCH 22/37] wip: temporary invite api route fallback --- .../api/holders/fetchAddressInviteCheck.ts | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/app/engine/api/holders/fetchAddressInviteCheck.ts b/app/engine/api/holders/fetchAddressInviteCheck.ts index e591571d3..a7d075a0a 100644 --- a/app/engine/api/holders/fetchAddressInviteCheck.ts +++ b/app/engine/api/holders/fetchAddressInviteCheck.ts @@ -4,24 +4,57 @@ import { z } from "zod"; import { Address } from "@ton/core"; const inviteCheckCodec = z.object({ - allowed: z.boolean(), + allowed: z.boolean(), }); -export async function fetchAddressInviteCheck(address: string, isTestnet: boolean) { - const endpoint = holdersEndpoint(isTestnet); - const formattedAddress = Address.parse(address).toString({ testOnly: isTestnet }); +export async function fetchAddressInviteCheck( + address: string, + isTestnet: boolean +) { + const endpoint = holdersEndpoint(isTestnet); + const formattedAddress = Address.parse(address).toString({ + testOnly: isTestnet, + }); - const res = await axios.post(`https://${endpoint}/v2/invite/wallet/check`, { - wallet: formattedAddress, - network: isTestnet ? 'ton-testnet' : 'ton-mainnet' + try { + let res = await axios.post(`https://${endpoint}/v2/invite/wallet/check`, { + wallet: formattedAddress, + network: isTestnet ? "ton-testnet" : "ton-mainnet", }); + if (res.status >= 400) { + res = await axios.post(`https://${endpoint}/v2/whitelist/wallet/check`, { + wallet: formattedAddress, + }); + } + const parsed = inviteCheckCodec.safeParse(res.data); if (!parsed.success) { - console.warn('Failed to parse invite check response', parsed.error); - return false; + console.warn("Failed to parse invite check response", parsed.error); + return false; } return parsed.data.allowed; -} \ No newline at end of file + } catch (error) { + if (axios.isAxiosError(error)) { + if (error.response!.status >= 400) { + const res = await axios.post( + `https://${endpoint}/v2/whitelist/wallet/check`, + { + wallet: formattedAddress, + } + ); + + const parsed = inviteCheckCodec.safeParse(res.data); + + if (!parsed.success) { + console.warn("Failed to parse invite check response", parsed.error); + return false; + } + + return parsed.data.allowed; + } + } + } +} From bf1078fb15904fb5e189ac74cd23afc62e63856c Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 10:54:11 +0200 Subject: [PATCH 23/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index 6e738cca6..b4425b8dd 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -27,6 +27,16 @@ brew install yarn # Install dependencies you manage with CocoaPods. echo "===== Generating node_modules =====" yarn + +# Ensure the correct version of rexml is installed +echo "===== Installing correct version of rexml =====" +gem install rexml -v '~> 3.2.4' + +# Ensure the correct version of rexml is activated +echo "===== Activating correct version of rexml =====" +bundle exec gem uninstall rexml -v '3.3.6' || true +bundle exec gem install rexml -v '3.2.4' + echo "===== Installing pods =====" pod install # the sed command from RN cant find the file... so we have to run it ourselves From 5be01110dbb7a4b23211b1724e679bf493a9f9f3 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 11:16:58 +0200 Subject: [PATCH 24/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index b4425b8dd..4aebe4db6 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -30,12 +30,12 @@ yarn # Ensure the correct version of rexml is installed echo "===== Installing correct version of rexml =====" -gem install rexml -v '~> 3.2.4' +sudo gem install rexml -v '~> 3.2.4' # Ensure the correct version of rexml is activated echo "===== Activating correct version of rexml =====" -bundle exec gem uninstall rexml -v '3.3.6' || true -bundle exec gem install rexml -v '3.2.4' +sudo gem uninstall rexml -v '3.3.6' || true +sudo gem install rexml -v '3.2.4' echo "===== Installing pods =====" pod install From d47505934d2e421ad7c5303653de37e2aecaf991 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 11:35:23 +0200 Subject: [PATCH 25/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index 4aebe4db6..c0c307f67 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -28,14 +28,24 @@ brew install yarn echo "===== Generating node_modules =====" yarn +# rbenv to manage Ruby versions and gem installations +echo "===== Installing rbenv =====" +brew install rbenv +eval "$(rbenv init -)" +echo "===== Installing 3.0.0 =====" +rbenv install 3.0.0 +rbenv global 3.0.0 +echo "===== Installing bundler =====" +gem install bundler + # Ensure the correct version of rexml is installed echo "===== Installing correct version of rexml =====" -sudo gem install rexml -v '~> 3.2.4' +gem install rexml -v '~> 3.2.4' # Ensure the correct version of rexml is activated echo "===== Activating correct version of rexml =====" -sudo gem uninstall rexml -v '3.3.6' || true -sudo gem install rexml -v '3.2.4' +gem uninstall rexml -v '3.3.6' || true +gem install rexml -v '3.2.4' echo "===== Installing pods =====" pod install From 2d6d694c80168a78d24a35e5c2d0615da3b6b2d2 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 11:54:05 +0200 Subject: [PATCH 26/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index c0c307f67..f0cc29bc2 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -4,6 +4,11 @@ set -e export HOMEBREW_NO_INSTALL_CLEANUP=TRUE export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE + +# Uninstall previous cocoapods +echo "===== Uninstalling prev cocoapods =====" +brew uninstall --ignore-dependencies cocoapods || true + # brew install cocoapods # CocoaPods 1.15.0 is unstable, so we have to use 1.14.3 as prev stable # brew doesn't have version pinning for cocoapods, so we have to install it manually from the commit From fdbdcb46bf1ce1dcdfb8b66013669f274f4a8af4 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 12:20:54 +0200 Subject: [PATCH 27/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index f0cc29bc2..b2813f1f4 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -53,6 +53,7 @@ gem uninstall rexml -v '3.3.6' || true gem install rexml -v '3.2.4' echo "===== Installing pods =====" -pod install +bundle exec pod install + # the sed command from RN cant find the file... so we have to run it ourselves sed -i -e $'s/ && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)//' /Volumes/workspace/repository/ios/Pods/RCT-Folly/folly/portability/Time.h \ No newline at end of file From e47c991f85c8838a92c1cf4356ae66cd06ea1171 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 12:44:42 +0200 Subject: [PATCH 28/37] wip: fixing rexml versioning --- ios/ci_scripts/ci_post_clone.sh | 38 ++++++--------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/ios/ci_scripts/ci_post_clone.sh b/ios/ci_scripts/ci_post_clone.sh index b2813f1f4..bfee41934 100644 --- a/ios/ci_scripts/ci_post_clone.sh +++ b/ios/ci_scripts/ci_post_clone.sh @@ -4,20 +4,17 @@ set -e export HOMEBREW_NO_INSTALL_CLEANUP=TRUE export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE - -# Uninstall previous cocoapods -echo "===== Uninstalling prev cocoapods =====" -brew uninstall --ignore-dependencies cocoapods || true - # brew install cocoapods # CocoaPods 1.15.0 is unstable, so we have to use 1.14.3 as prev stable # brew doesn't have version pinning for cocoapods, so we have to install it manually from the commit echo "===== Uninstalling prev cocoapods =====" brew uninstall --ignore-dependencies cocoapods || true -echo "===== Downloading formula =====" -curl https://raw.githubusercontent.com/Homebrew/homebrew-core/1364b74ebeedb2eab300d62c99e12f2a6f344277/Formula/c/cocoapods.rb > cocoapods.rb -echo "===== Installing formula =====" -brew install cocoapods.rb +# echo "===== Downloading formula =====" +# curl https://raw.githubusercontent.com/Homebrew/homebrew-core/1364b74ebeedb2eab300d62c99e12f2a6f344277/Formula/c/cocoapods.rb > cocoapods.rb +# echo "===== Installing formula =====" +# brew install cocoapods.rb +echo "===== Installing cocoapods=====" +brew install cocoapods echo "===== Installing node =====" # have to add node yourself @@ -32,28 +29,7 @@ brew install yarn # Install dependencies you manage with CocoaPods. echo "===== Generating node_modules =====" yarn - -# rbenv to manage Ruby versions and gem installations -echo "===== Installing rbenv =====" -brew install rbenv -eval "$(rbenv init -)" -echo "===== Installing 3.0.0 =====" -rbenv install 3.0.0 -rbenv global 3.0.0 -echo "===== Installing bundler =====" -gem install bundler - -# Ensure the correct version of rexml is installed -echo "===== Installing correct version of rexml =====" -gem install rexml -v '~> 3.2.4' - -# Ensure the correct version of rexml is activated -echo "===== Activating correct version of rexml =====" -gem uninstall rexml -v '3.3.6' || true -gem install rexml -v '3.2.4' - echo "===== Installing pods =====" -bundle exec pod install - +pod install # the sed command from RN cant find the file... so we have to run it ourselves sed -i -e $'s/ && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)//' /Volumes/workspace/repository/ios/Pods/RCT-Folly/folly/portability/Time.h \ No newline at end of file From 5de903550bc26e7417f3b9e6c2cbac4cca0b3bea Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 13:51:10 +0200 Subject: [PATCH 29/37] v2.3.13 --- VERSION_CODE | 2 +- ios/wallet/Info.plist | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION_CODE b/VERSION_CODE index 4c009fb2f..bea0d09c4 100644 --- a/VERSION_CODE +++ b/VERSION_CODE @@ -1 +1 @@ -206 \ No newline at end of file +207 \ No newline at end of file diff --git a/ios/wallet/Info.plist b/ios/wallet/Info.plist index 9f1def4a8..565dbc4f1 100644 --- a/ios/wallet/Info.plist +++ b/ios/wallet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 2.3.12 + 2.3.13 CFBundleSignature ???? CFBundleURLTypes @@ -41,7 +41,7 @@ CFBundleVersion - 206 + 207 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/package.json b/package.json index efda4eca8..bfbfac837 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wallet", - "version": "2.3.12", + "version": "2.3.13", "scripts": { "start": "expo start --dev-client", "android": "expo run:android", From 82f9e3b7faa3395a252a4293903da5af98b95b92 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 15:10:52 +0200 Subject: [PATCH 30/37] Merge branch 'develop' into feat/hub-1350 # Conflicts: # app/fragments/holders/HoldersAppFragment.tsx # app/fragments/holders/components/HoldersAppComponent.tsx # app/useLinkNavigator.ts # app/utils/resolveUrl.ts --- app/fragments/holders/HoldersAppFragment.tsx | 20 ++++++--- .../components/HoldersAppComponent.tsx | 32 +++++++------- app/useLinkNavigator.ts | 43 +++++++++++++------ app/utils/resolveUrl.ts | 13 ++++++ 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/app/fragments/holders/HoldersAppFragment.tsx b/app/fragments/holders/HoldersAppFragment.tsx index f05d3ef61..e11dbb2f4 100644 --- a/app/fragments/holders/HoldersAppFragment.tsx +++ b/app/fragments/holders/HoldersAppFragment.tsx @@ -6,17 +6,25 @@ import { useParams } from '../../utils/useParams'; import { t } from '../../i18n/t'; import { useEffect, useMemo } from 'react'; import { useHoldersAccountStatus, useHoldersAccounts, useNetwork, useSelectedAccount, useTheme } from '../../engine/hooks'; -import { holdersUrl } from '../../engine/api/holders/fetchAccountState'; +import { holdersUrl } from '../../engine/api/holders/fetchUserState'; import { StatusBar, setStatusBarStyle } from 'expo-status-bar'; import { onHoldersInvalidate } from '../../engine/effects/onHoldersInvalidate'; import { useFocusEffect } from '@react-navigation/native'; +export enum HoldersAppParamsType { + Account = 'account', + Prepaid = 'prepaid', + Create = 'create', + Invite = 'invite', + Transactions = 'transactions' +} + export type HoldersAppParams = - | { type: 'account', id: string } - | { type: 'prepaid', id: string } - | { type: 'create' } - | { type: 'transactions', query: { [key: string]: string | undefined } } - | { type: 'path', path: string }; + | { type: HoldersAppParamsType.Account, id: string } + | { type: HoldersAppParamsType.Prepaid, id: string } + | { type: HoldersAppParamsType.Create } + | { type: HoldersAppParamsType.Invite } + | { type: HoldersAppParamsType.Transactions, query: { [key: string]: string | undefined } }; export const HoldersAppFragment = fragment(() => { const theme = useTheme(); diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 0aedc1781..969f0388f 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -10,14 +10,14 @@ import { getLocales } from 'react-native-localize'; import { useLinkNavigator } from '../../../useLinkNavigator'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { HoldersAppParams } from '../HoldersAppFragment'; +import { HoldersAppParams, HoldersAppParamsType } from '../HoldersAppFragment'; import Animated, { Easing, Extrapolation, interpolate, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; import { useDAppBridge, usePrimaryCurrency } from '../../../engine/hooks'; import { useTheme } from '../../../engine/hooks'; import { useNetwork } from '../../../engine/hooks'; import { useSelectedAccount } from '../../../engine/hooks'; import { getCurrentAddress } from '../../../storage/appState'; -import { HoldersAccountState, holdersUrl } from '../../../engine/api/holders/fetchAccountState'; +import { HoldersUserState, holdersUrl } from '../../../engine/api/holders/fetchUserState'; import { HoldersAccountStatus, getHoldersToken } from '../../../engine/hooks/holders/useHoldersAccountStatus'; import { ScreenHeader } from '../../../components/ScreenHeader'; import { onHoldersInvalidate } from '../../../engine/effects/onHoldersInvalidate'; @@ -226,7 +226,7 @@ export const HoldersPlaceholder = memo(() => { ); }); -export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'account' | 'create' | 'prepaid' }) => { +export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: HoldersAppParamsType }) => { const theme = useTheme(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); @@ -253,11 +253,11 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a }, []); const placeholder = useMemo(() => { - if (type === 'account') { + if (type === HoldersAppParamsType.Account) { return ; } - if (type === 'prepaid') { + if (type === HoldersAppParamsType.Prepaid) { return ; } @@ -274,7 +274,7 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: 'a }, animatedStyles, Platform.select({ - ios: { paddingTop: (type === 'account' || type === 'prepaid') ? 0 : safeArea.top }, + ios: { paddingTop: (type === HoldersAppParamsType.Account || type === HoldersAppParamsType.Prepaid) ? 0 : safeArea.top }, android: { paddingTop: 0 } }), ]} @@ -328,16 +328,19 @@ export const HoldersAppComponent = memo(( let route = ''; switch (props.variant.type) { - case 'create': + case HoldersAppParamsType.Invite: + route = '/onboarding/invite'; + break; + case HoldersAppParamsType.Create: route = '/create'; break; - case 'account': + case HoldersAppParamsType.Account: route = `/account/${props.variant.id}`; break; - case 'prepaid': + case HoldersAppParamsType.Prepaid: route = `/card-prepaid/${props.variant.id}`; break; - case 'transactions': + case HoldersAppParamsType.Transactions: route = `/transactions`; for (const [key, value] of Object.entries(props.variant.query)) { if (!!value) { @@ -417,7 +420,7 @@ export const HoldersAppComponent = memo(( kycStatus: status.state === 'need-kyc' ? status.kycStatus : null, suspended: (status as { suspended: boolean | undefined }).suspended === true, }, - token: status.state === HoldersAccountState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), + token: status.state === HoldersUserState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), } } : {}, @@ -494,10 +497,9 @@ export const HoldersAppComponent = memo(( webviewDebuggingEnabled={isTestnet} loader={(p) => ( diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index 06dfd6db9..111aa9f77 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -32,9 +32,9 @@ import { extractDomain } from './engine/utils/extractDomain'; import { Linking } from 'react-native'; import { openWithInApp } from './utils/openWithInApp'; import { getHoldersToken, HoldersAccountStatus } from './engine/hooks/holders/useHoldersAccountStatus'; -import { HoldersAccountState, holdersUrl } from './engine/api/holders/fetchAccountState'; +import { HoldersUserState, holdersUrl } from './engine/api/holders/fetchUserState'; import { getIsConnectAppReady } from './engine/hooks/dapps/useIsConnectAppReady'; -import { HoldersAppParams } from './fragments/holders/HoldersAppFragment'; +import { HoldersAppParams, HoldersAppParamsType } from './fragments/holders/HoldersAppFragment'; const infoBackoff = createBackoff({ maxFailureCount: 10 }); @@ -473,7 +473,7 @@ function getNeedsEnrollment(url: string, address: string, isTestnet: boolean, qu return true; } - if (status.state === HoldersAccountState.NeedEnrollment) { + if (status.state === HoldersUserState.NeedEnrollment) { return true; } @@ -508,15 +508,10 @@ function resolveAndNavigateToHolders(params: { const isSelectedAddress = addresses.find((a) => Address.parse(a).equals(selected.address)); const transactionId = query['transactionId']; - const holdersNavParams: HoldersAppParams = type === 'holders-transactions' - ? { - type: 'transactions', - query: { transactionId } - } - : { - type: 'path', - path: params.path - } + const holdersNavParams: HoldersAppParams = { + type: HoldersAppParamsType.Transactions, + query: { transactionId } + } const url = holdersUrl(isTestnet); @@ -567,6 +562,18 @@ function resolveAndNavigateToHolders(params: { } } +function resolveHoldersInviteLink(params: { + navigation: TypedNavigation, + isTestnet: boolean, + inviteId: string +}){ + const { navigation, isTestnet, inviteId } = params + + const endpoint = holdersUrl(isTestnet); + + navigation.navigateHoldersLanding({endpoint, onEnrollType: { type: HoldersAppParamsType.Invite }, inviteId }, isTestnet); +} + export function useLinkNavigator( isTestnet: boolean, toastProps?: { duration?: ToastDuration, marginBottom?: number }, @@ -728,6 +735,18 @@ export function useLinkNavigator( }); break; } + case 'holders-invite': { + if (!selected) { + return; + } + + resolveHoldersInviteLink({ + navigation, + isTestnet, + inviteId: resolved.inviteId + }) + break; + } } }, [selected, updateAppState]); diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index 6686dfa8e..d5bec2cb6 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -72,6 +72,9 @@ export type ResolvedUrl = { type: 'holders-path', path: string, query: { [key: string]: string | undefined } +} | { + type: 'holders-invite', + inviteId: string } export function isUrl(str: string): boolean { @@ -99,6 +102,16 @@ function resolveHoldersUrl(url: Url>): Resolv } } + const isInvite = url.pathname.startsWith('/holders/invite'); + const inviteId = url.pathname.split('holders/invite/')[1] + + if (isInvite && inviteId) { + return { + type: 'holders-invite', + inviteId: inviteId + } + } + return { type: 'error', error: ResolveUrlError.InvalidHoldersPath }; } From d2bf86afa9f132535a7b371ccd0d4a1eda801082 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 15:12:36 +0200 Subject: [PATCH 31/37] Merge branch 'develop' into feat/hub-1350 # Conflicts: # app/useLinkNavigator.ts # app/utils/resolveUrl.ts --- app/useLinkNavigator.ts | 3 +-- app/utils/resolveUrl.ts | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index 111aa9f77..18234bc07 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -733,7 +733,7 @@ export function useLinkNavigator( isTestnet, queryClient }); - break; + break } case 'holders-invite': { if (!selected) { @@ -745,7 +745,6 @@ export function useLinkNavigator( isTestnet, inviteId: resolved.inviteId }) - break; } } diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index d5bec2cb6..97fa33e60 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -68,10 +68,6 @@ export type ResolvedUrl = { } | { type: 'holders-transactions', query: { [key: string]: string | undefined } -} | { - type: 'holders-path', - path: string, - query: { [key: string]: string | undefined } } | { type: 'holders-invite', inviteId: string @@ -105,7 +101,7 @@ function resolveHoldersUrl(url: Url>): Resolv const isInvite = url.pathname.startsWith('/holders/invite'); const inviteId = url.pathname.split('holders/invite/')[1] - if (isInvite && inviteId) { + if (isInvite && inviteId) { return { type: 'holders-invite', inviteId: inviteId From 55cd435b7bdd4de903383a99ae49c617d741cc79 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 15:29:12 +0200 Subject: [PATCH 32/37] Merge branch 'develop' into feat/hub-1350 # Conflicts: # app/fragments/holders/HoldersAppFragment.tsx # app/fragments/holders/components/HoldersAppComponent.tsx # app/useLinkNavigator.ts # app/utils/resolveUrl.ts --- app/fragments/holders/HoldersAppFragment.tsx | 4 +++- .../holders/components/HoldersAppComponent.tsx | 7 ++++--- app/useLinkNavigator.ts | 9 +++++++-- app/utils/resolveUrl.ts | 14 ++++---------- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/fragments/holders/HoldersAppFragment.tsx b/app/fragments/holders/HoldersAppFragment.tsx index e11dbb2f4..5072465a0 100644 --- a/app/fragments/holders/HoldersAppFragment.tsx +++ b/app/fragments/holders/HoldersAppFragment.tsx @@ -16,7 +16,8 @@ export enum HoldersAppParamsType { Prepaid = 'prepaid', Create = 'create', Invite = 'invite', - Transactions = 'transactions' + Transactions = 'transactions', + Path = 'path' } export type HoldersAppParams = @@ -24,6 +25,7 @@ export type HoldersAppParams = | { type: HoldersAppParamsType.Prepaid, id: string } | { type: HoldersAppParamsType.Create } | { type: HoldersAppParamsType.Invite } + | { type: HoldersAppParamsType.Path, path: string } | { type: HoldersAppParamsType.Transactions, query: { [key: string]: string | undefined } }; export const HoldersAppFragment = fragment(() => { diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 969f0388f..df9cf8c1a 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -497,9 +497,10 @@ export const HoldersAppComponent = memo(( webviewDebuggingEnabled={isTestnet} loader={(p) => ( diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index 18234bc07..cff6d366e 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -508,9 +508,13 @@ function resolveAndNavigateToHolders(params: { const isSelectedAddress = addresses.find((a) => Address.parse(a).equals(selected.address)); const transactionId = query['transactionId']; - const holdersNavParams: HoldersAppParams = { + const holdersNavParams: HoldersAppParams = type === 'holders-transactions' + ? { type: HoldersAppParamsType.Transactions, query: { transactionId } + } : { + type: HoldersAppParamsType.Path, + path: params.path } const url = holdersUrl(isTestnet); @@ -733,7 +737,7 @@ export function useLinkNavigator( isTestnet, queryClient }); - break + break; } case 'holders-invite': { if (!selected) { @@ -745,6 +749,7 @@ export function useLinkNavigator( isTestnet, inviteId: resolved.inviteId }) + break; } } diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index edd917ca9..ba1c6b1d9 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -68,6 +68,10 @@ export type ResolvedUrl = { } | { type: 'holders-transactions', query: { [key: string]: string | undefined } +} | { + type: 'holders-path', + path: string, + query: { [key: string]: string | undefined } } | { type: 'holders-invite', inviteId: string @@ -108,16 +112,6 @@ function resolveHoldersUrl(url: Url>): Resolv } } - const isInvite = url.pathname.startsWith('/holders/invite'); - const inviteId = url.pathname.split('holders/invite/')[1] - - if (isInvite && inviteId) { - return { - type: 'holders-invite', - inviteId: inviteId - } - } - return { type: 'error', error: ResolveUrlError.InvalidHoldersPath }; } From 24ee00867b392c26157ac9e48a8866e7ffb20bfd Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 16:16:32 +0200 Subject: [PATCH 33/37] fix: fixing imports, resolving after merge tsc errors --- app/components/webview/DAppWebView.tsx | 2 +- app/fragments/holders/HoldersAppFragment.tsx | 2 +- .../components/HoldersAppComponent.tsx | 7 ++- app/useLinkNavigator.ts | 44 +++++++++---------- app/utils/resolveUrl.ts | 5 ++- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/app/components/webview/DAppWebView.tsx b/app/components/webview/DAppWebView.tsx index 2396962fe..dd0dd2fc0 100644 --- a/app/components/webview/DAppWebView.tsx +++ b/app/components/webview/DAppWebView.tsx @@ -26,7 +26,7 @@ import WalletService, { addCardRequestSchema } from "../../modules/WalletService import { getHoldersToken } from "../../engine/hooks/holders/useHoldersAccountStatus"; import { getCurrentAddress } from "../../storage/appState"; import { WebViewSourceUri } from "react-native-webview/lib/WebViewTypes"; -import { holdersUrl } from "../../engine/api/holders/fetchAccountState"; +import { holdersUrl } from "../../engine/api/holders/fetchUserState"; export type DAppWebViewProps = WebViewProps & { useMainButton?: boolean; diff --git a/app/fragments/holders/HoldersAppFragment.tsx b/app/fragments/holders/HoldersAppFragment.tsx index 5072465a0..f2bfd597e 100644 --- a/app/fragments/holders/HoldersAppFragment.tsx +++ b/app/fragments/holders/HoldersAppFragment.tsx @@ -25,7 +25,7 @@ export type HoldersAppParams = | { type: HoldersAppParamsType.Prepaid, id: string } | { type: HoldersAppParamsType.Create } | { type: HoldersAppParamsType.Invite } - | { type: HoldersAppParamsType.Path, path: string } + | { type: HoldersAppParamsType.Path, path: string, query: { [key: string]: string | undefined } } | { type: HoldersAppParamsType.Transactions, query: { [key: string]: string | undefined } }; export const HoldersAppFragment = fragment(() => { diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index df9cf8c1a..7b13ce0b4 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -348,8 +348,13 @@ export const HoldersAppComponent = memo(( } } break; - case 'path': + case HoldersAppParamsType.Path: route = `/${props.variant.path ?? ''}`; + for (const [key, value] of Object.entries(props.variant.query)) { + if (!!value) { + queryParams.append(key, value); + } + } break; } diff --git a/app/useLinkNavigator.ts b/app/useLinkNavigator.ts index cff6d366e..11b13d25e 100644 --- a/app/useLinkNavigator.ts +++ b/app/useLinkNavigator.ts @@ -480,24 +480,19 @@ function getNeedsEnrollment(url: string, address: string, isTestnet: boolean, qu return false; } -function resolveAndNavigateToHolders(params: { - type: 'holders-transactions', +type HolderResloveParams = { query: { [key: string]: string | undefined }, navigation: TypedNavigation, selected: SelectedAccount, updateAppState: (value: AppState, isTestnet: boolean) => void, isTestnet: boolean, queryClient: QueryClient -} | { - type: 'holders-path', - path: string, - query: { [key: string]: string | undefined }, - navigation: TypedNavigation, - selected: SelectedAccount, - updateAppState: (value: AppState, isTestnet: boolean) => void, - isTestnet: boolean, - queryClient: QueryClient -}) { +} + +type HoldersTransactionResolveParams = HolderResloveParams & { type: 'holders-transactions' } +type HoldersPathResolveParams = HolderResloveParams & { type: 'holders-path', path: string } + +function resolveAndNavigateToHolders(params: HoldersTransactionResolveParams | HoldersPathResolveParams) { const { type, query, navigation, selected, updateAppState, queryClient, isTestnet } = params const addresses = query['addresses']?.split(','); @@ -508,14 +503,15 @@ function resolveAndNavigateToHolders(params: { const isSelectedAddress = addresses.find((a) => Address.parse(a).equals(selected.address)); const transactionId = query['transactionId']; - const holdersNavParams: HoldersAppParams = type === 'holders-transactions' - ? { - type: HoldersAppParamsType.Transactions, - query: { transactionId } - } : { - type: HoldersAppParamsType.Path, - path: params.path - } + const holdersNavParams: HoldersAppParams = type === 'holders-transactions' + ? { + type: HoldersAppParamsType.Transactions, + query: { transactionId } + } : { + type: HoldersAppParamsType.Path, + path: params.path, + query + } const url = holdersUrl(isTestnet); @@ -570,12 +566,12 @@ function resolveHoldersInviteLink(params: { navigation: TypedNavigation, isTestnet: boolean, inviteId: string -}){ +}) { const { navigation, isTestnet, inviteId } = params const endpoint = holdersUrl(isTestnet); - navigation.navigateHoldersLanding({endpoint, onEnrollType: { type: HoldersAppParamsType.Invite }, inviteId }, isTestnet); + navigation.navigateHoldersLanding({ endpoint, onEnrollType: { type: HoldersAppParamsType.Invite }, inviteId }, isTestnet); } export function useLinkNavigator( @@ -746,8 +742,8 @@ export function useLinkNavigator( resolveHoldersInviteLink({ navigation, - isTestnet, - inviteId: resolved.inviteId + isTestnet, + inviteId: resolved.inviteId }) break; } diff --git a/app/utils/resolveUrl.ts b/app/utils/resolveUrl.ts index ba1c6b1d9..a60fa2fb1 100644 --- a/app/utils/resolveUrl.ts +++ b/app/utils/resolveUrl.ts @@ -95,9 +95,12 @@ function resolveHoldersUrl(url: Url>): Resolv query: url.query } } else if (!!url.query && url.query.path) { + const path = decodeURIComponent(url.query.path); + delete url.query.path; + return { type: 'holders-path', - path: decodeURIComponent(url.query.path), + path, query: url.query } } From a9d0961451e3e394670d6a9761d5fd61ebe98d44 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Mon, 9 Sep 2024 16:20:27 +0200 Subject: [PATCH 34/37] Merge branch 'develop' into feat/hub-1362 # Conflicts: # app/fragments/holders/HoldersLandingFragment.tsx # app/fragments/holders/components/HoldersAppComponent.tsx --- .../holders/HoldersLandingFragment.tsx | 15 ++--- .../components/HoldersAppComponent.tsx | 60 +++++++++---------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/app/fragments/holders/HoldersLandingFragment.tsx b/app/fragments/holders/HoldersLandingFragment.tsx index 201321868..c3f3d1e99 100644 --- a/app/fragments/holders/HoldersLandingFragment.tsx +++ b/app/fragments/holders/HoldersLandingFragment.tsx @@ -6,7 +6,7 @@ import { useTypedNavigation } from '../../utils/useTypedNavigation'; import { t } from '../../i18n/t'; import { extractDomain } from '../../engine/utils/extractDomain'; import { useParams } from '../../utils/useParams'; -import { HoldersAppParams } from './HoldersAppFragment'; +import { HoldersAppParams, HoldersAppParamsType } from './HoldersAppFragment'; import { getLocales } from 'react-native-localize'; import { fragment } from '../../fragment'; import { useKeysAuth } from '../../components/secure/AuthWalletKeys'; @@ -35,10 +35,10 @@ export const HoldersLandingFragment = fragment(() => { const safeArea = useSafeAreaInsets(); const [currency,] = usePrimaryCurrency(); - const { endpoint, onEnrollType } = useParams<{ endpoint: string, onEnrollType: HoldersAppParams }>(); + const { endpoint, onEnrollType, inviteId } = useParams<{ endpoint: string, onEnrollType: HoldersAppParams, inviteId?: string }>(); const domain = extractDomain(endpoint); - const enroll = useHoldersEnroll({ acc, domain, authContext, authStyle: { paddingTop: 32 } }); + const enroll = useHoldersEnroll({ acc, domain, authContext, inviteId, authStyle: { paddingTop: 32 } }); const lang = getLocales()[0].languageCode; // Anim @@ -235,14 +235,7 @@ export const HoldersLandingFragment = fragment(() => { lockScroll: true }} webviewDebuggingEnabled={isTestnet} - loader={(p) => ( - - )} + loader={(p) => } /> { ); }); -export const HoldersLoader = memo(({ - loaded, - type, - onReload: onReaload, - onSupport -}: { - loaded: boolean, - type: 'account' | 'create' | 'prepaid', - onReload?: () => void, - onSupport?: () => void -}) => { +export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: HoldersAppParamsType }) => { const theme = useTheme(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); @@ -208,18 +198,11 @@ export const HoldersLoader = memo(({ }, []); const placeholder = useMemo(() => { - if (type === 'account') { - return ( - - ); + if (type === HoldersAppParamsType.Account) { + return ; } - if (type === 'prepaid') { + if (type === HoldersAppParamsType.Prepaid) { return ; } @@ -236,7 +219,7 @@ export const HoldersLoader = memo(({ }, animatedStyles, Platform.select({ - ios: { paddingTop: (type === 'account' || type === 'prepaid') ? 0 : safeArea.top }, + ios: { paddingTop: (type === HoldersAppParamsType.Account || type === HoldersAppParamsType.Prepaid) ? 0 : safeArea.top }, android: { paddingTop: 0 } }), ]} @@ -313,16 +296,19 @@ export const HoldersAppComponent = memo(( let route = ''; switch (props.variant.type) { - case 'create': + case HoldersAppParamsType.Invite: + route = '/onboarding/invite'; + break; + case HoldersAppParamsType.Create: route = '/create'; break; - case 'account': + case HoldersAppParamsType.Account: route = `/account/${props.variant.id}`; break; - case 'prepaid': + case HoldersAppParamsType.Prepaid: route = `/card-prepaid/${props.variant.id}`; break; - case 'transactions': + case HoldersAppParamsType.Transactions: route = `/transactions`; for (const [key, value] of Object.entries(props.variant.query)) { if (!!value) { @@ -330,6 +316,14 @@ export const HoldersAppComponent = memo(( } } break; + case HoldersAppParamsType.Path: + route = `/${props.variant.path ?? ''}`; + for (const [key, value] of Object.entries(props.variant.query)) { + if (!!value) { + queryParams.append(key, value); + } + } + break; } const url = `${props.endpoint}${route}?${queryParams.toString()}`; @@ -399,7 +393,7 @@ export const HoldersAppComponent = memo(( kycStatus: status.state === 'need-kyc' ? status.kycStatus : null, suspended: (status as { suspended: boolean | undefined }).suspended === true, }, - token: status.state === HoldersAccountState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), + token: status.state === HoldersUserState.Ok ? status.token : getHoldersToken(acc.address.toString({ testOnly: isTestnet })), } } : {}, @@ -519,7 +513,11 @@ export const HoldersAppComponent = memo(( webviewDebuggingEnabled={isTestnet} loader={(p) => ( Date: Mon, 9 Sep 2024 16:29:54 +0200 Subject: [PATCH 35/37] fix: fixing props passing down --- app/Navigation.tsx | 2 +- .../holders/HoldersLandingFragment.tsx | 7 +++++- .../components/HoldersAppComponent.tsx | 25 ++++++++++++++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/Navigation.tsx b/app/Navigation.tsx index cab1322f3..56f5e9ddf 100644 --- a/app/Navigation.tsx +++ b/app/Navigation.tsx @@ -97,7 +97,7 @@ import { TonconnectWatcher } from './components/TonconnectWatcher'; import { SessionWatcher } from './components/SessionWatcher'; import { MandatoryAuthSetupFragment } from './fragments/secure/MandatoryAuthSetupFragment'; import { WebViewPreloader } from './components/WebViewPreloader'; -import { holdersUrl } from './engine/api/holders/fetchAccountState'; +import { holdersUrl } from './engine/api/holders/fetchUserState'; const Stack = createNativeStackNavigator(); Stack.Navigator.displayName = 'MainStack'; diff --git a/app/fragments/holders/HoldersLandingFragment.tsx b/app/fragments/holders/HoldersLandingFragment.tsx index c3f3d1e99..9d65a9024 100644 --- a/app/fragments/holders/HoldersLandingFragment.tsx +++ b/app/fragments/holders/HoldersLandingFragment.tsx @@ -235,7 +235,12 @@ export const HoldersLandingFragment = fragment(() => { lockScroll: true }} webviewDebuggingEnabled={isTestnet} - loader={(p) => } + loader={(p) => } /> { ); }); -export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: HoldersAppParamsType }) => { +export const HoldersLoader = memo(({ + loaded, + type, + onReload, + onSupport +}: { + loaded: boolean, + type: HoldersAppParamsType, + onReload?: () => void, + onSupport?: () => void +}) => { const theme = useTheme(); const navigation = useTypedNavigation(); const safeArea = useSafeAreaInsets(); @@ -199,7 +209,14 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: Ho const placeholder = useMemo(() => { if (type === HoldersAppParamsType.Account) { - return ; + return ( + + ); } if (type === HoldersAppParamsType.Prepaid) { @@ -233,7 +250,7 @@ export const HoldersLoader = memo(({ loaded, type }: { loaded: boolean, type: Ho style={({ pressed }) => [ { opacity: pressed ? 0.5 : 1, - backgroundColor: '#1c1c1e', + backgroundColor: type === HoldersAppParamsType.Account ? '#1c1c1e' : theme.surfaceOnBg, borderRadius: 32, height: 32, width: 32, justifyContent: 'center', alignItems: 'center', From 796ee920bdd4fca0989a7faba2182558629d5a70 Mon Sep 17 00:00:00 2001 From: vzhovnitsky Date: Tue, 10 Sep 2024 16:43:18 +0200 Subject: [PATCH 36/37] fix: typo --- app/i18n/i18n_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/i18n/i18n_ru.ts b/app/i18n/i18n_ru.ts index 59335aaa6..4d271d3ce 100644 --- a/app/i18n/i18n_ru.ts +++ b/app/i18n/i18n_ru.ts @@ -145,7 +145,7 @@ const schema: PrepareSchema = { "subtitle": "Отправляйте на этот адрес только токены блокчейна TON. Другие активы будут потеряны навсегда", "share": { "title": "Мой Tonhub адрес", - "error": "Не удалось поделиыться адресом, попробуйте еще раз или обратитесь в службу поддержки" + "error": "Не удалось поделиться адресом, попробуйте еще раз или обратитесь в службу поддержки" } }, "transfer": { From 79be6789b1aad37ee924574491747fc84c4695bc Mon Sep 17 00:00:00 2001 From: Garrethta Date: Tue, 10 Sep 2024 19:15:37 +0400 Subject: [PATCH 37/37] fix: invite routing --- app/fragments/holders/components/HoldersAppComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fragments/holders/components/HoldersAppComponent.tsx b/app/fragments/holders/components/HoldersAppComponent.tsx index 58ef77eff..6f6715d7e 100644 --- a/app/fragments/holders/components/HoldersAppComponent.tsx +++ b/app/fragments/holders/components/HoldersAppComponent.tsx @@ -329,7 +329,7 @@ export const HoldersAppComponent = memo(( let route = ''; switch (props.variant.type) { case HoldersAppParamsType.Invite: - route = '/onboarding/invite'; + route = '/'; break; case HoldersAppParamsType.Create: route = '/create';