diff --git a/changelogs/3.0.25.txt b/changelogs/3.0.25.txt new file mode 100644 index 0000000..619f4cd --- /dev/null +++ b/changelogs/3.0.25.txt @@ -0,0 +1 @@ +Bug fixes and performance improvements diff --git a/package-lock.json b/package-lock.json index 991c722..9967d49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mytonwallet", - "version": "3.0.24", + "version": "3.0.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mytonwallet", - "version": "3.0.24", + "version": "3.0.25", "license": "GPL-3.0-or-later", "dependencies": { "@awesome-cordova-plugins/core": "6.8.0", diff --git a/package.json b/package.json index 5fb3472..d9759ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mytonwallet", - "version": "3.0.24", + "version": "3.0.25", "description": "The most feature-rich web wallet and browser extension for TON – with support of multi-accounts, tokens (jettons), NFT, TON DNS, TON Sites, TON Proxy, and TON Magic.", "main": "index.js", "scripts": { diff --git a/public/version.txt b/public/version.txt index 03a04fc..de2e889 100644 --- a/public/version.txt +++ b/public/version.txt @@ -1 +1 @@ -3.0.24 +3.0.25 diff --git a/src/api/chains/ton/auth.ts b/src/api/chains/ton/auth.ts index 6d4b2e3..99a14e0 100644 --- a/src/api/chains/ton/auth.ts +++ b/src/api/chains/ton/auth.ts @@ -3,7 +3,11 @@ import * as bip39 from 'bip39'; import nacl from 'tweetnacl'; import type { - ApiLedgerAccount, ApiNetwork, ApiTonAccount, ApiTonWallet, + ApiAccountWithMnemonic, + ApiLedgerAccount, + ApiNetwork, + ApiTonAccount, + ApiTonWallet, } from '../../types'; import type { ApiTonWalletVersion } from './types'; import type { TonWallet } from './util/tonCore'; @@ -14,7 +18,7 @@ import isMnemonicPrivateKey from '../../../util/isMnemonicPrivateKey'; import { logDebugError } from '../../../util/logs'; import { toBase64Address } from './util/tonCore'; import { fetchStoredAccount, getNewAccountId, setAccountValue } from '../../common/accounts'; -import { fetchMnemonic, validateBip39Mnemonic } from '../../common/mnemonic'; +import { getMnemonic } from '../../common/mnemonic'; import { bytesToHex, hexToBytes } from '../../common/utils'; import { TON_BIP39_PATH } from './constants'; import { buildWallet, pickBestWallet, publicKeyToAddress } from './wallet'; @@ -31,15 +35,9 @@ export function privateKeyHexToKeyPair(privateKeyHex: string) { return nacl.sign.keyPair.fromSeed(hexToBytes(privateKeyHex)); } -export function mnemonicToKeyPair(mnemonic: string[]) { - return validateBip39Mnemonic(mnemonic) - ? bip39MnemonicToKeyPair(mnemonic) - : tonWebMnemonic.mnemonicToKeyPair(mnemonic); -} - -export async function fetchPrivateKey(accountId: string, password: string) { +export async function fetchPrivateKey(accountId: string, password: string, account?: ApiAccountWithMnemonic) { try { - const { secretKey: privateKey } = await fetchKeyPair(accountId, password) || {}; + const { secretKey: privateKey } = await fetchKeyPair(accountId, password, account) || {}; return privateKey; } catch (err) { @@ -50,14 +48,21 @@ export async function fetchPrivateKey(accountId: string, password: string) { } } -export async function fetchKeyPair(accountId: string, password: string) { +export async function fetchKeyPair(accountId: string, password: string, account?: ApiAccountWithMnemonic) { try { - const mnemonic = await fetchMnemonic(accountId, password); + account = account ?? await fetchStoredAccount(accountId); + const mnemonic = await getMnemonic(accountId, password, account); if (!mnemonic) { return undefined; } - return isMnemonicPrivateKey(mnemonic) ? privateKeyHexToKeyPair(mnemonic[0]) : await mnemonicToKeyPair(mnemonic); + if (isMnemonicPrivateKey(mnemonic)) { + return privateKeyHexToKeyPair(mnemonic[0]); + } else if (account.type === 'bip39') { + return bip39MnemonicToKeyPair(mnemonic); + } else { + return await tonWebMnemonic.mnemonicToKeyPair(mnemonic); + } } catch (err) { logDebugError('fetchKeyPair', err); diff --git a/src/api/chains/ton/index.ts b/src/api/chains/ton/index.ts index fd61660..c225310 100644 --- a/src/api/chains/ton/index.ts +++ b/src/api/chains/ton/index.ts @@ -1,7 +1,6 @@ export { generateMnemonic, rawSign, - mnemonicToKeyPair, validateMnemonic, fetchPrivateKey, getWalletFromBip39Mnemonic, diff --git a/src/api/chains/ton/transactions.ts b/src/api/chains/ton/transactions.ts index 0a3b962..5e86d0a 100644 --- a/src/api/chains/ton/transactions.ts +++ b/src/api/chains/ton/transactions.ts @@ -5,6 +5,7 @@ import { import type { DieselStatus } from '../../../global/types'; import type { CheckTransactionDraftOptions } from '../../methods/types'; import type { + ApiAccountWithMnemonic, ApiActivity, ApiAnyDisplayError, ApiNetwork, @@ -56,7 +57,7 @@ import { resolveTokenWalletAddress, toBase64Address, } from './util/tonCore'; -import { fetchStoredTonWallet } from '../../common/accounts'; +import { fetchStoredAccount, fetchStoredTonWallet } from '../../common/accounts'; import { callBackendGet } from '../../common/backend'; import { updateTransactionMetadata } from '../../common/helpers'; import { getTokenByAddress, getTokenBySlug } from '../../common/tokens'; @@ -357,12 +358,10 @@ export async function submitTransfer(options: ApiSubmitTransferOptions): Promise const { network } = parseAccountId(accountId); try { - const [wallet, { address: fromAddress, isInitialized }, keyPair] = await Promise.all([ - getTonWallet(accountId), - fetchStoredTonWallet(accountId), - fetchKeyPair(accountId, password), - ]); - const { publicKey, secretKey } = keyPair!; + const account = await fetchStoredAccount(accountId); + const { address: fromAddress, isInitialized } = account.ton; + const wallet = await getTonWallet(accountId, account.ton); + const { publicKey, secretKey } = (await fetchKeyPair(accountId, password, account))!; let encryptedComment: string | undefined; @@ -904,11 +903,10 @@ export async function submitMultiTransfer({ const { network } = parseAccountId(accountId); try { - const [wallet, { address: fromAddress, isInitialized }, privateKey] = await Promise.all([ - getTonWallet(accountId), - fetchStoredTonWallet(accountId), - fetchPrivateKey(accountId, password), - ]); + const account = await fetchStoredAccount(accountId); + const { address: fromAddress, isInitialized } = account.ton; + const wallet = await getTonWallet(accountId, account.ton); + const privateKey = await fetchPrivateKey(accountId, password, account); let totalAmount = 0n; messages.forEach((message) => { diff --git a/src/api/chains/ton/wallet.ts b/src/api/chains/ton/wallet.ts index 6901166..33edbc0 100644 --- a/src/api/chains/ton/wallet.ts +++ b/src/api/chains/ton/wallet.ts @@ -1,6 +1,6 @@ import { beginCell, storeStateInit } from '@ton/core'; -import type { ApiNetwork, ApiWalletInfo } from '../../types'; +import type { ApiNetwork, ApiTonWallet, ApiWalletInfo } from '../../types'; import type { ApiTonWalletVersion, ContractInfo } from './types'; import type { TonWallet } from './util/tonCore'; @@ -230,9 +230,9 @@ export function pickWalletByAddress(network: ApiNetwork, publicKey: Uint8Array, return allWallets.find((w) => w.address === address)!; } -export async function getTonWallet(accountId: string) { +export async function getTonWallet(accountId: string, tonWallet?: ApiTonWallet) { const { network } = parseAccountId(accountId); - const { publicKey, version } = await fetchStoredTonWallet(accountId); + const { publicKey, version } = tonWallet ?? await fetchStoredTonWallet(accountId); const publicKeyBytes = hexToBytes(publicKey); return buildWallet(network, publicKeyBytes, version); diff --git a/src/api/chains/tron/transfer.ts b/src/api/chains/tron/transfer.ts index e0f1027..56c3b6e 100644 --- a/src/api/chains/tron/transfer.ts +++ b/src/api/chains/tron/transfer.ts @@ -5,13 +5,14 @@ import type { ContractParamter, Transaction } from 'tronweb/lib/commonjs/types'; import type { ApiSubmitTransferOptions, CheckTransactionDraftOptions } from '../../methods/types'; import type { ApiCheckTransactionDraftResult } from '../ton/types'; +import type { ApiAccountWithMnemonic } from '../../types'; import { ApiTransactionDraftError, ApiTransactionError } from '../../types'; import { parseAccountId } from '../../../util/account'; import { logDebugError } from '../../../util/logs'; import { getChainParameters, getTronClient } from './util/tronweb'; -import { fetchStoredTronWallet } from '../../common/accounts'; -import { fetchMnemonic } from '../../common/mnemonic'; +import { fetchStoredAccount, fetchStoredTronWallet } from '../../common/accounts'; +import { getMnemonic } from '../../common/mnemonic'; import { handleServerError } from '../../errors'; import { getWalletBalance } from './wallet'; import type { ApiSubmitTransferTronResult } from './types'; @@ -91,7 +92,8 @@ export async function submitTransfer(options: ApiSubmitTransferOptions): Promise try { const tronWeb = getTronClient(network); - const { address } = await fetchStoredTronWallet(accountId); + const account = await fetchStoredAccount(accountId); + const { address } = account.ton; const trxBalance = await getWalletBalance(network, address); const trxAmount = tokenAddress ? fee : fee + amount; @@ -101,7 +103,7 @@ export async function submitTransfer(options: ApiSubmitTransferOptions): Promise return { error: ApiTransactionError.InsufficientBalance }; } - const mnemonic = await fetchMnemonic(accountId, password); + const mnemonic = await getMnemonic(accountId, password, account); const privateKey = tronWeb.fromMnemonic(mnemonic!.join(' ')).privateKey.slice(2); if (tokenAddress) { diff --git a/src/api/common/accounts.ts b/src/api/common/accounts.ts index 7c5c858..38e37f9 100644 --- a/src/api/common/accounts.ts +++ b/src/api/common/accounts.ts @@ -1,6 +1,7 @@ import type { StorageKey } from '../storages/types'; import type { ApiAccountAny, + ApiAccountWithMnemonic, ApiAccountWithTon, ApiAccountWithTron, ApiBip39Account, @@ -39,10 +40,10 @@ export async function getAccountIds(): Promise { return Object.keys(await storage.getItem('accounts') || {}); } -export async function getAccountIdWithMnemonic() { +export async function getAccountWithMnemonic() { const byId = await fetchStoredAccounts(); - return Object.entries(byId).find(([, { type }]) => type !== 'ledger')?.[0]; + return Object.entries(byId).find(([, { type }]) => type !== 'ledger') as [string, ApiAccountWithMnemonic] | undefined; } export async function getNewAccountId(network: ApiNetwork) { diff --git a/src/api/common/mnemonic.ts b/src/api/common/mnemonic.ts index 83caee5..f0a4fa4 100644 --- a/src/api/common/mnemonic.ts +++ b/src/api/common/mnemonic.ts @@ -2,7 +2,7 @@ import * as bip39 from 'bip39'; import { type ApiAccountWithMnemonic, ApiCommonError } from '../types'; -import { fetchStoredAccount, updateStoredAccount } from './accounts'; +import { updateStoredAccount } from './accounts'; const PBKDF2_IMPORT_KEY_ARGS = [ { name: 'PBKDF2' }, @@ -118,9 +118,9 @@ async function decryptMnemonicLegacy(encrypted: string, password: string) { return plaintext.split(','); } -export async function fetchMnemonic(accountId: string, password: string) { +export async function getMnemonic(accountId: string, password: string, account: ApiAccountWithMnemonic) { try { - const { mnemonicEncrypted } = (await fetchStoredAccount(accountId)); + const { mnemonicEncrypted } = account; const mnemonic = await decryptMnemonic(mnemonicEncrypted, password); if (!mnemonicEncrypted.includes(':')) { diff --git a/src/api/methods/wallet.ts b/src/api/methods/wallet.ts index 69d5f5b..f0b4e86 100644 --- a/src/api/methods/wallet.ts +++ b/src/api/methods/wallet.ts @@ -1,21 +1,23 @@ import * as tonWebMnemonic from 'tonweb-mnemonic'; -import type { ApiChain, ApiNetwork } from '../types'; +import type { ApiAccountWithMnemonic, ApiChain, ApiNetwork } from '../types'; import { parseAccountId } from '../../util/account'; import chains from '../chains'; import { + fetchStoredAccount, fetchStoredAddress, fetchStoredTonWallet, - getAccountIdWithMnemonic, + getAccountWithMnemonic, } from '../common/accounts'; import * as dappPromises from '../common/dappPromises'; -import { fetchMnemonic } from '../common/mnemonic'; +import { getMnemonic } from '../common/mnemonic'; const ton = chains.ton; -export function getMnemonic(accountId: string, password: string) { - return fetchMnemonic(accountId, password); +export async function fetchMnemonic(accountId: string, password: string) { + const account = await fetchStoredAccount(accountId); + return getMnemonic(accountId, password, account); } export function getMnemonicWordList() { @@ -23,12 +25,12 @@ export function getMnemonicWordList() { } export async function verifyPassword(password: string) { - const accountId = await getAccountIdWithMnemonic(); - if (!accountId) { + const [accountId, account] = (await getAccountWithMnemonic()) ?? []; + if (!accountId || !account) { throw new Error('The user is not authorized in the wallet'); } - return Boolean(await fetchMnemonic(accountId, password)); + return Boolean(await getMnemonic(accountId, password, account)); } export function confirmDappRequest(promiseId: string, data: any) { diff --git a/src/assets/lottie/icon_clock_dark.tgs b/src/assets/lottie/icon_clock_dark.tgs index 90623de..ed6e37b 100644 Binary files a/src/assets/lottie/icon_clock_dark.tgs and b/src/assets/lottie/icon_clock_dark.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_blue.tgs b/src/assets/lottie/icon_clock_dark_blue.tgs index e10b911..0e9751d 100644 Binary files a/src/assets/lottie/icon_clock_dark_blue.tgs and b/src/assets/lottie/icon_clock_dark_blue.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_gray.tgs b/src/assets/lottie/icon_clock_dark_gray.tgs index 1839acf..507f0ec 100644 Binary files a/src/assets/lottie/icon_clock_dark_gray.tgs and b/src/assets/lottie/icon_clock_dark_gray.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_gray_white.tgs b/src/assets/lottie/icon_clock_dark_gray_white.tgs index 3cbe454..9d056f7 100644 Binary files a/src/assets/lottie/icon_clock_dark_gray_white.tgs and b/src/assets/lottie/icon_clock_dark_gray_white.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_green.tgs b/src/assets/lottie/icon_clock_dark_green.tgs index a977dcd..f0717de 100644 Binary files a/src/assets/lottie/icon_clock_dark_green.tgs and b/src/assets/lottie/icon_clock_dark_green.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_purple.tgs b/src/assets/lottie/icon_clock_dark_purple.tgs index 38902e0..cbac8f8 100644 Binary files a/src/assets/lottie/icon_clock_dark_purple.tgs and b/src/assets/lottie/icon_clock_dark_purple.tgs differ diff --git a/src/assets/lottie/icon_clock_dark_purple_white.tgs b/src/assets/lottie/icon_clock_dark_purple_white.tgs index 5a342f1..c366696 100644 Binary files a/src/assets/lottie/icon_clock_dark_purple_white.tgs and b/src/assets/lottie/icon_clock_dark_purple_white.tgs differ diff --git a/src/assets/lottie/icon_clock_light.tgs b/src/assets/lottie/icon_clock_light.tgs index 9cd520e..3b99dfa 100644 Binary files a/src/assets/lottie/icon_clock_light.tgs and b/src/assets/lottie/icon_clock_light.tgs differ diff --git a/src/assets/lottie/icon_clock_light_blue.tgs b/src/assets/lottie/icon_clock_light_blue.tgs index ad8e334..d4d37ec 100644 Binary files a/src/assets/lottie/icon_clock_light_blue.tgs and b/src/assets/lottie/icon_clock_light_blue.tgs differ diff --git a/src/assets/lottie/icon_clock_light_gray.tgs b/src/assets/lottie/icon_clock_light_gray.tgs index 93a2f50..4c95e2c 100644 Binary files a/src/assets/lottie/icon_clock_light_gray.tgs and b/src/assets/lottie/icon_clock_light_gray.tgs differ diff --git a/src/assets/lottie/icon_clock_light_gray_white.tgs b/src/assets/lottie/icon_clock_light_gray_white.tgs index 8f082a7..161f3f2 100644 Binary files a/src/assets/lottie/icon_clock_light_gray_white.tgs and b/src/assets/lottie/icon_clock_light_gray_white.tgs differ diff --git a/src/assets/lottie/icon_clock_light_green.tgs b/src/assets/lottie/icon_clock_light_green.tgs index d658394..77af3f4 100644 Binary files a/src/assets/lottie/icon_clock_light_green.tgs and b/src/assets/lottie/icon_clock_light_green.tgs differ diff --git a/src/assets/lottie/icon_clock_light_purple.tgs b/src/assets/lottie/icon_clock_light_purple.tgs index 3b1c58b..33de643 100644 Binary files a/src/assets/lottie/icon_clock_light_purple.tgs and b/src/assets/lottie/icon_clock_light_purple.tgs differ diff --git a/src/assets/lottie/icon_clock_light_purple_white.tgs b/src/assets/lottie/icon_clock_light_purple_white.tgs index 990307d..5babed4 100644 Binary files a/src/assets/lottie/icon_clock_light_purple_white.tgs and b/src/assets/lottie/icon_clock_light_purple_white.tgs differ diff --git a/src/components/auth/AuthBackupWalletModal.tsx b/src/components/auth/AuthBackupWalletModal.tsx index 741aef0..ae4d5da 100644 --- a/src/components/auth/AuthBackupWalletModal.tsx +++ b/src/components/auth/AuthBackupWalletModal.tsx @@ -109,6 +109,7 @@ function AuthBackupWalletModal({ hasCloseButton isOpen={isOpen} dialogClassName={styles.modalDialog} + forceFullNative={renderingKey !== BackupState.Accept} nativeBottomSheetKey="disclaimer" onClose={closeAuthBackupWalletModal} onCloseAnimationEnd={handleModalClose} diff --git a/src/components/main/modals/BackupModal.tsx b/src/components/main/modals/BackupModal.tsx index 73aeb40..d7fbbd8 100644 --- a/src/components/main/modals/BackupModal.tsx +++ b/src/components/main/modals/BackupModal.tsx @@ -69,7 +69,7 @@ function BackupModal({ const handlePasswordSubmit = useLastCallback(async (password: string) => { setIsLoading(true); - mnemonicRef.current = await callApi('getMnemonic', currentAccountId!, password); + mnemonicRef.current = await callApi('fetchMnemonic', currentAccountId!, password); if (!mnemonicRef.current) { setError('Wrong password, please try again.'); diff --git a/src/components/receive/ReceiveModal.module.scss b/src/components/receive/ReceiveModal.module.scss index b866ac6..52acd2f 100644 --- a/src/components/receive/ReceiveModal.module.scss +++ b/src/components/receive/ReceiveModal.module.scss @@ -10,8 +10,15 @@ } } +// `min-height: 100%` should be set for the Transition component to work, +// because on iOS the dialog doesn't have a height value, and the container uses `align-items: flex-start`. +.iosModalDialog { + min-height: 100%; +} + .contentWrapper { overflow: hidden; + flex-grow: 1; } .content { diff --git a/src/components/receive/ReceiveModal.tsx b/src/components/receive/ReceiveModal.tsx index 6f7e272..eb1419a 100644 --- a/src/components/receive/ReceiveModal.tsx +++ b/src/components/receive/ReceiveModal.tsx @@ -46,7 +46,7 @@ function ReceiveModal({ return ( diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 3f60bf9..1de074a 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -20,6 +20,7 @@ import freezeWhenClosed from '../../hooks/freezeWhenClosed'; import { useDelegatedBottomSheet } from '../../hooks/useDelegatedBottomSheet'; import { useDelegatingBottomSheet } from '../../hooks/useDelegatingBottomSheet'; import { useDeviceScreen } from '../../hooks/useDeviceScreen'; +import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useHideBrowser from '../../hooks/useHideBrowser'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLang from '../../hooks/useLang'; @@ -111,12 +112,12 @@ function Modal({ onBack: onClose, }); - useEffect(() => { - if (!IS_DELEGATED_BOTTOM_SHEET || !isCompact) return; - + useEffectWithPrevDeps(([prevIsOpen]) => { // Expand NBS to full size for a compact modal inside NBS - BottomSheet.toggleSelfFullSize({ isFullSize: !!isOpen }); - }, [isCompact, isOpen]); + if (IS_DELEGATED_BOTTOM_SHEET && isCompact && (prevIsOpen || isOpen)) { + BottomSheet.toggleSelfFullSize({ isFullSize: !!isOpen }); + } + }, [isOpen, isCompact]); useEffect( () => (isOpen ? captureKeyboardListeners({ diff --git a/src/components/ui/PinPad.module.scss b/src/components/ui/PinPad.module.scss index ac2923b..701349f 100644 --- a/src/components/ui/PinPad.module.scss +++ b/src/components/ui/PinPad.module.scss @@ -120,10 +120,6 @@ height: 5rem; - .minified > & { - height: 4.75rem; - } - font-size: 2rem; font-weight: 700; line-height: 2rem; @@ -131,6 +127,10 @@ transition: opacity 200ms; + .minified > & { + height: 4.75rem; + } + @media (max-height: 55rem) { height: 4rem; } diff --git a/src/giveaways/components/App.tsx b/src/giveaways/components/App.tsx index 4cb9790..cf809b7 100644 --- a/src/giveaways/components/App.tsx +++ b/src/giveaways/components/App.tsx @@ -1,12 +1,10 @@ import type { Wallet, WalletInfoRemote } from '@tonconnect/sdk'; import React, { - memo, - useCallback, - useEffect, useLayoutEffect, useState, + memo, useCallback, useEffect, useLayoutEffect, useState, } from '../../lib/teact/teact'; import type { JettonMetadata } from '../../api/chains/ton/types'; -import type { Giveaway } from '../utils/giveaway'; +import type { Giveaway, GiveawayWithTask } from '../utils/giveaway'; import buildClassName from '../../util/buildClassName'; import { resolveRender } from '../../util/renderPromise'; @@ -42,6 +40,15 @@ export type JettonMetadataInfo = JettonMetadata | { isTon: boolean }; const FETCH_REPEAT_MS = 3000; +enum PageKey { + NoGiveawayPageId = 0, + LoadingPageId = 1, + ConnectPageId = 2, + CaptchaPageId = 3, + CompleteTaskPageId = 4, + GiveawayInfoPageId = 5, +} + function App({ mtwWalletInfo }: OwnProps) { useLayoutEffect(() => { document.documentElement.classList.add('is-rendered'); @@ -54,7 +61,7 @@ function App({ mtwWalletInfo }: OwnProps) { const [participantStatus, setParticipantStatus] = useState(); const [wallet, setWallet] = useState(); const [tokenAddressData, setTokenAddressData] = useState(); - const [renderKey, setRenderKey] = useState(0); + const [renderKey, setRenderKey] = useState(PageKey.LoadingPageId); const loadGiveaway = useLoadGiveaway(setGiveaway, setTokenAddressData); const loadWallet = useLoadWallet(setWallet); @@ -72,8 +79,20 @@ function App({ mtwWalletInfo }: OwnProps) { useInterval(loadParticipantStatus, FETCH_REPEAT_MS); useEffect(() => { - setRenderKey((prevKey) => prevKey + 1); - }, [participantStatus, giveaway, wallet, tokenAddressData]); + if (!getGiveawayId()) { + setRenderKey(PageKey.NoGiveawayPageId); + } else if (!giveaway) { + setRenderKey(PageKey.LoadingPageId); + } else if (!wallet) { + setRenderKey(PageKey.ConnectPageId); + } else if (giveaway.status === GiveawayStatus.Active && participantStatus === ParticipantStatus.NotFound) { + setRenderKey(PageKey.CaptchaPageId); + } else if (isGiveawayWithTask(giveaway) && participantStatus === ParticipantStatus.AwaitingTask) { + setRenderKey(PageKey.CompleteTaskPageId); + } else if (participantStatus) { + setRenderKey(PageKey.GiveawayInfoPageId); + } + }, [participantStatus, giveaway, wallet]); const handleConnectClick = useCallback( () => handleTonConnectButtonClick(mtwWalletInfo), @@ -81,55 +100,52 @@ function App({ mtwWalletInfo }: OwnProps) { ); function renderPage() { - if (!getGiveawayId()) { - return
QueryParams has no giveawayId
; - } - - if (!giveaway) { - return
; - } - - if (!wallet) { - return ( - - ); - } - - if (giveaway.status === GiveawayStatus.Active && participantStatus === ParticipantStatus.NotFound) { - return ( - - ); - } - - if ((isGiveawayWithTask(giveaway) && participantStatus === ParticipantStatus.AwaitingTask)) { - return ( - - ); - } - - if (participantStatus) { - return ( - - ); + switch (renderKey) { + case PageKey.NoGiveawayPageId: + return
QueryParams has no giveawayId
; + + case PageKey.LoadingPageId: + return
; + + case PageKey.ConnectPageId: + return ( + + ); + + case PageKey.CaptchaPageId: + return ( + + ); + + case PageKey.CompleteTaskPageId: + return ( + + ); + + case PageKey.GiveawayInfoPageId: + return ( + + ); + + default: + return
; } - - return
; } return ( diff --git a/src/giveaways/index.scss b/src/giveaways/index.scss new file mode 100644 index 0000000..b770031 --- /dev/null +++ b/src/giveaways/index.scss @@ -0,0 +1,3 @@ +body, html { + transition: none; +} diff --git a/src/giveaways/index.tsx b/src/giveaways/index.tsx index 1230e09..ae28523 100644 --- a/src/giveaways/index.tsx +++ b/src/giveaways/index.tsx @@ -24,6 +24,7 @@ import { tonConnect } from './utils/tonConnect'; import App from './components/App'; import '../styles/index.scss'; +import './index.scss'; if (DEBUG) { // eslint-disable-next-line no-console diff --git a/src/giveaways/pages/CaptchaPage.tsx b/src/giveaways/pages/CaptchaPage.tsx index d5e1f82..c8964dd 100644 --- a/src/giveaways/pages/CaptchaPage.tsx +++ b/src/giveaways/pages/CaptchaPage.tsx @@ -1,11 +1,13 @@ import type { Wallet } from '@tonconnect/sdk'; -import React, { memo, useEffect } from '../../lib/teact/teact'; +import React, { memo } from '../../lib/teact/teact'; import type { SetGiveaway, SetParticipantStatus } from '../utils/giveaway'; import { GIVEAWAY_CAPTCHA_PUBLIC_KEY } from '../config'; import { checkinGiveaway } from '../utils/giveaway'; +import useEffectOnce from '../../hooks/useEffectOnce'; + import CommonPage from '../components/CommonPage'; import styles from './CaptchaPage.module.scss'; @@ -17,7 +19,7 @@ interface OwnProps { } function CaptchaPage({ wallet, setParticipantStatus, setGiveaway }: OwnProps) { - useEffect(() => { + useEffectOnce(() => { // eslint-disable-next-line func-names window.onloadTurnstileCallback = function () { // @ts-expect-error diff --git a/src/giveaways/pages/GiveawayInfoPage.tsx b/src/giveaways/pages/GiveawayInfoPage.tsx index 817b039..d4bfce8 100644 --- a/src/giveaways/pages/GiveawayInfoPage.tsx +++ b/src/giveaways/pages/GiveawayInfoPage.tsx @@ -1,10 +1,11 @@ -import { fromNano } from '@ton/core'; import type { Wallet } from '@tonconnect/sdk'; import React, { memo } from '../../lib/teact/teact'; import type { JettonMetadataInfo } from '../components/App'; import type { Giveaway } from '../utils/giveaway'; +import { toDecimal } from '../../util/decimals'; +import { DEFAULT_DECIMALS } from '../../api/chains/ton/constants'; import { GiveawayStatus, ParticipantStatus } from '../utils/giveaway'; import CommonPage from '../components/CommonPage'; @@ -39,6 +40,10 @@ function GiveawayInfoPage({ } function renderPaidPageContent() { + const decimals = jettonMetadata && 'decimals' in jettonMetadata + ? Number(jettonMetadata.decimals!) + : DEFAULT_DECIMALS; + return ( <> @@ -46,7 +51,7 @@ function GiveawayInfoPage({
Your Reward
diff --git a/webpack.config.ts b/webpack.config.ts index eb51625..0a9db2d 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -82,6 +82,7 @@ export default function createConfig( target: 'web', optimization: { + minimize: APP_ENV === 'production', usedExports: true, ...(APP_ENV === 'staging' && { chunkIds: 'named',