diff --git a/package.json b/package.json index d34a7c2d5e8..ced97bca6cc 100644 --- a/package.json +++ b/package.json @@ -137,11 +137,11 @@ "@leather.io/bitcoin": "0.10.0", "@leather.io/constants": "0.8.2", "@leather.io/crypto": "1.1.0", - "@leather.io/models": "0.10.2", - "@leather.io/query": "2.1.0", + "@leather.io/models": "0.11.0", + "@leather.io/query": "2.4.0", "@leather.io/tokens": "0.7.0", - "@leather.io/ui": "1.9.0", - "@leather.io/utils": "0.11.0", + "@leather.io/ui": "1.10.0", + "@leather.io/utils": "0.11.1", "@ledgerhq/hw-transport-webusb": "6.27.19", "@noble/hashes": "1.4.0", "@noble/secp256k1": "2.1.0", @@ -228,7 +228,7 @@ "react-qr-code": "2.0.12", "react-redux": "9.1.0", "react-router-dom": "6.23.1", - "react-virtuoso": "4.7.1", + "react-virtuoso": "4.9.0", "redux-persist": "6.0.0", "remark-gfm": "4.0.0", "rxjs": "7.8.1", @@ -252,7 +252,7 @@ "@leather.io/eslint-config": "0.6.1", "@leather.io/panda-preset": "0.3.4", "@leather.io/prettier-config": "0.5.0", - "@leather.io/rpc": "2.1.1", + "@leather.io/rpc": "2.1.2", "@ls-lint/ls-lint": "2.2.3", "@mdx-js/loader": "3.0.0", "@pandacss/dev": "0.40.1", @@ -356,6 +356,7 @@ }, "resolutions": { "braces": "3.0.3", + "fast-xml-parser": "4.4.1", "nanoid": "3.3.4", "socket.io-parser": "4.2.4", "wrap-ansi": "7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3adc85064f2..6dc7ea8ee51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ settings: overrides: braces: 3.0.3 + fast-xml-parser: 4.4.1 nanoid: 3.3.4 socket.io-parser: 4.2.4 wrap-ansi: 7.0.0 @@ -42,20 +43,20 @@ importers: specifier: 1.1.0 version: 1.1.0 '@leather.io/models': - specifier: 0.10.2 - version: 0.10.2 + specifier: 0.11.0 + version: 0.11.0 '@leather.io/query': - specifier: 2.1.0 - version: 2.1.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1) + specifier: 2.4.0 + version: 2.4.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1) '@leather.io/tokens': specifier: 0.7.0 version: 0.7.0 '@leather.io/ui': - specifier: 1.9.0 - version: 1.9.0(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@swc/core@1.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.0)(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5) + specifier: 1.10.0 + version: 1.10.0(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@swc/core@1.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.0)(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5) '@leather.io/utils': - specifier: 0.11.0 - version: 0.11.0 + specifier: 0.11.1 + version: 0.11.1 '@ledgerhq/hw-transport-webusb': specifier: 6.27.19 version: 6.27.19 @@ -315,8 +316,8 @@ importers: specifier: 6.23.1 version: 6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-virtuoso: - specifier: 4.7.1 - version: 4.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 4.9.0 + version: 4.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) redux-persist: specifier: 6.0.0 version: 6.0.0(react@18.3.1)(redux@5.0.1) @@ -382,8 +383,8 @@ importers: specifier: 0.5.0 version: 0.5.0(@vue/compiler-sfc@3.4.32) '@leather.io/rpc': - specifier: 2.1.1 - version: 2.1.1 + specifier: 2.1.2 + version: 2.1.2 '@ls-lint/ls-lint': specifier: 2.2.3 version: 2.2.3 @@ -2600,41 +2601,56 @@ packages: '@leather.io/bitcoin@0.10.0': resolution: {integrity: sha512-Po5MBZzOBCQ9cc27BXkwzpJ2hPUsD4rVtjHZ7jDX3a3Q/v3jGu1jBgyxTglhIpPU/M6ja+a3eXw0XfOW2oKFVQ==} + '@leather.io/bitcoin@0.10.1': + resolution: {integrity: sha512-rZdilqmVcsMYGG/H+bRT6DNvL65Ivme+W6Ihli+nBRw09gNcqB7z8CD+H1is0QFP84o6mxvKAUbZyucrIJB/Gg==} + '@leather.io/constants@0.8.2': resolution: {integrity: sha512-W4Q8e4H7scLlhnA2UFWFsnQYGu4NPGQmmIuq5N13wvOhzZD9iebrgN9I/VtE1GJwiQmBYdzhJ45EmEAM90zFhA==} '@leather.io/crypto@1.1.0': resolution: {integrity: sha512-iI5skLZN745rdeqishDnoMKxtVdT4rCrHUVox4TOywhqTzCw1aOmMpXXkVIen7fNnVrIjuDP0Ek4LBgzhIiDrw==} + '@leather.io/crypto@1.1.1': + resolution: {integrity: sha512-5hAJk4tdwvHBcUEC6loozFmLnuaxYumcR8eyvS5jZrC8CMSWsCKvk0OHMrAI30AuO4BRhiOR+nQfVK3ws4JGmg==} + '@leather.io/eslint-config@0.6.1': resolution: {integrity: sha512-NLvT7wpDR02jFZdp9g08ja5mRxdz/xCcB/tmXWJL8uDt2l+ebjHqaq45dZroAPm+EciLd0HU3jFfsSA7Txgh9w==} '@leather.io/models@0.10.2': resolution: {integrity: sha512-N4KTT0jApIZphx96/abD0xwA+4A23k+hmPWsf98N2LKxG5ZzYmuzoB5VUlw7hGdwpZFbDW625ALQjjPMPoyG8A==} + '@leather.io/models@0.11.0': + resolution: {integrity: sha512-iKhEno6aVhFKvsnsMKbHQYXIqEGo4TOkJcPs/4yHq2EZmPEMFk8FMxUNZZKcKnVSxedInjzFaFhYF/sz+tTxjQ==} + '@leather.io/panda-preset@0.3.4': resolution: {integrity: sha512-q+ri1ObAPe0GsYWKZe/5AWjSwH1lEgRnBn5NIVE7OJUt8eXa1vQuXMeXFLmY+UU7dhVYSY148RU+SNiRl1DfSw==} '@leather.io/prettier-config@0.5.0': resolution: {integrity: sha512-Pul+4MAyBKnQvqgcKJLbZl4DHnS4kCJzSuaYFW6cfHdre7BFn/iY6Er/Dvm9F8g7VMtkdYu68jEYxQ1Xc7A0KQ==} - '@leather.io/query@2.1.0': - resolution: {integrity: sha512-iMM47shEtq4dVx/MlwQInv+oJk9s9dAXDiO3j56QT3HBnOzguStrpgEO5eXvsnASx6xQwguh5xheA4S/DBwhYg==} + '@leather.io/query@2.4.0': + resolution: {integrity: sha512-y6wxEOQmrXRAwx4OIAH64gX3fY0nIkbnWs5OIgC1i6OAhyWjo4F0NkMsFbG7i6Fo5Sgn5hQearWp+Jk5oZALRg==} peerDependencies: react: '*' '@leather.io/rpc@2.1.1': resolution: {integrity: sha512-rAoPxiooffpbF9mtpSleA8Y5O9WZS6VEvGFYDLQPkt3JxAEm0+cVykDYypJWbrnMAqKNL3FVSSnR2TDtsZ8GiA==} + '@leather.io/rpc@2.1.2': + resolution: {integrity: sha512-Smv00bOQF3Suju3peJWAywpFjTurLg+GZgKi7uymKHfkXhnrUmOxYkXKE1azac8E4MardseZIP7jjGU/hgmFnQ==} + '@leather.io/tokens@0.7.0': resolution: {integrity: sha512-RLF8enOE+t/KdcTjjyQUf6rMEyxLb/PPMYZgvoT/FWoQgnjYluhG6zc1HROu7wM8XEBF4kESABIM6UYKtkAs1Q==} - '@leather.io/ui@1.9.0': - resolution: {integrity: sha512-KNfjqsULmj9S697oh4eGkmGBNiEDXl3vH9Ghddv+RcN2IwuWA8ulNbp8C268HyI/PWhBdm3sbM2LMRXpf9qDSA==} + '@leather.io/ui@1.10.0': + resolution: {integrity: sha512-xi3V9wSno2oXu05ilNdUz83dsGRFY1Ty+6NMhL1KBky4SKHx7QLGeqeB4YHdlEe7Cp8EN09PH4JeNVxdAhza0A==} '@leather.io/utils@0.11.0': resolution: {integrity: sha512-/i1auGETc6WlPCdukbkMKtMUsliDQwUZ/YNJ8noCjXkZTGKvecvH8VXt+nY3lnqUSAigCfSTMk/c8CGLFUJkxQ==} + '@leather.io/utils@0.11.1': + resolution: {integrity: sha512-uRwWihcI0U/QUCheAr9TwKa9NvUb7HB5vtIpiCpYKDeGmjer4lvvIcPvyPxm5G0/ccB/umtqni9nK5EtwJJ6vA==} + '@ledgerhq/devices@8.4.0': resolution: {integrity: sha512-TUrMlWZJ+5AFp2lWMw4rGQoU+WtjIqlFX5SzQDL9phaUHrt4TFierAGHsaj5+tUHudhD4JhIaLI2cn1NOyq5NQ==} @@ -8536,8 +8552,8 @@ packages: peerDependencies: expo: '*' - expo-constants@16.0.1: - resolution: {integrity: sha512-s6aTHtglp926EsugWtxN7KnpSsE9FCEjb7CgEjQQ78Gpu4btj4wB+IXot2tlqNwqv+x7xFe5veoPGfJDGF/kVg==} + expo-constants@16.0.2: + resolution: {integrity: sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ==} peerDependencies: expo: '*' @@ -8618,8 +8634,8 @@ packages: fast-uri@3.0.1: resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} - fast-xml-parser@4.4.0: - resolution: {integrity: sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==} + fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true fastest-levenshtein@1.0.16: @@ -12699,8 +12715,8 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react-virtuoso@4.7.1: - resolution: {integrity: sha512-V1JIZLEwgX7R+YNkbY8dq6NcnIGKGWXe4mnMJJPsA2L4qeFKst0LY3mDk6sBCJyKRbMzYFxTZWyTT4QsA1JvVQ==} + react-virtuoso@4.9.0: + resolution: {integrity: sha512-MiiSGKqvYPfAK3FUe852n2L3M5IXMKP0pUgYQ/UTk90A/l2UNQOvaEUvAZp+0ytL0kOCNk8i8/J8FMKvIq7kqg==} engines: {node: '>=10'} peerDependencies: react: '>=16 || >=17 || >= 18' @@ -17225,6 +17241,28 @@ snapshots: transitivePeerDependencies: - encoding + '@leather.io/bitcoin@0.10.1(encoding@0.1.13)': + dependencies: + '@bitcoinerlab/secp256k1': 1.0.2 + '@leather.io/constants': 0.8.2 + '@leather.io/crypto': 1.1.1 + '@leather.io/models': 0.11.0 + '@leather.io/utils': 0.11.1 + '@noble/hashes': 1.4.0 + '@noble/secp256k1': 2.1.0 + '@scure/base': 1.1.6 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + '@scure/btc-signer': 1.3.2 + '@stacks/common': 6.13.0 + '@stacks/transactions': 6.15.0(encoding@0.1.13) + bip32: 4.0.0 + bitcoinjs-lib: 6.1.5 + ecpair: 2.1.0 + varuint-bitcoin: 1.1.2 + transitivePeerDependencies: + - encoding + '@leather.io/constants@0.8.2': {} '@leather.io/crypto@1.1.0': @@ -17233,6 +17271,12 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 + '@leather.io/crypto@1.1.1': + dependencies: + '@leather.io/utils': 0.11.1 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + '@leather.io/eslint-config@0.6.1(typescript@5.4.5)': dependencies: '@typescript-eslint/eslint-plugin': 6.9.0(@typescript-eslint/parser@6.9.0(eslint@8.56.0)(typescript@5.4.5))(eslint@8.56.0)(typescript@5.4.5) @@ -17248,6 +17292,12 @@ snapshots: '@stacks/stacks-blockchain-api-types': 7.8.2 bignumber.js: 9.1.2 + '@leather.io/models@0.11.0': + dependencies: + '@stacks/stacks-blockchain-api-types': 7.8.2 + bignumber.js: 9.1.2 + zod: 3.23.6 + '@leather.io/panda-preset@0.3.4(jsdom@22.1.0)(typescript@5.4.5)': dependencies: '@pandacss/dev': 0.40.1(jsdom@22.1.0)(typescript@5.4.5) @@ -17263,15 +17313,15 @@ snapshots: - '@vue/compiler-sfc' - supports-color - '@leather.io/query@2.1.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1)': + '@leather.io/query@2.4.0(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react@18.3.1)': dependencies: '@fungible-systems/zone-file': 2.0.0 '@hirosystems/token-metadata-api-client': 1.2.0(encoding@0.1.13) - '@leather.io/bitcoin': 0.10.0(encoding@0.1.13) + '@leather.io/bitcoin': 0.10.1(encoding@0.1.13) '@leather.io/constants': 0.8.2 - '@leather.io/models': 0.10.2 - '@leather.io/rpc': 2.1.1 - '@leather.io/utils': 0.11.0 + '@leather.io/models': 0.11.0 + '@leather.io/rpc': 2.1.2 + '@leather.io/utils': 0.11.1 '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 '@scure/bip32': 1.4.0 @@ -17301,13 +17351,18 @@ snapshots: '@leather.io/models': 0.10.2 zod: 3.23.6 + '@leather.io/rpc@2.1.2': + dependencies: + '@leather.io/models': 0.11.0 + zod: 3.23.6 + '@leather.io/tokens@0.7.0': {} - '@leather.io/ui@1.9.0(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@swc/core@1.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.0)(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5)': + '@leather.io/ui@1.10.0(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(@swc/core@1.7.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(postcss@8.4.38)(ts-node@10.9.2(@swc/core@1.7.0)(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5)': dependencies: '@expo/vector-icons': 14.0.0 '@leather.io/tokens': 0.7.0 - '@leather.io/utils': 0.11.0 + '@leather.io/utils': 0.11.1 '@radix-ui/react-accessible-icon': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-accordion': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-avatar': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -17325,7 +17380,7 @@ snapshots: '@storybook/react': 7.6.15(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) expo: 51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13) expo-asset: 10.0.6(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) - expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) + expo-constants: 16.0.2(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) expo-font: 12.0.5(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) expo-splash-screen: 0.27.4(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) metro-cache: 0.80.5 @@ -17358,6 +17413,13 @@ snapshots: '@leather.io/rpc': 2.1.1 bignumber.js: 9.1.2 + '@leather.io/utils@0.11.1': + dependencies: + '@leather.io/constants': 0.8.2 + '@leather.io/models': 0.11.0 + '@leather.io/rpc': 2.1.2 + bignumber.js: 9.1.2 + '@ledgerhq/devices@8.4.0': dependencies: '@ledgerhq/errors': 6.17.0 @@ -19129,7 +19191,7 @@ snapshots: chalk: 4.1.2 execa: 5.1.1 fast-glob: 3.3.2 - fast-xml-parser: 4.4.0 + fast-xml-parser: 4.4.1 logkitty: 0.7.1 transitivePeerDependencies: - encoding @@ -19140,7 +19202,7 @@ snapshots: chalk: 4.1.2 execa: 5.1.1 fast-glob: 3.3.2 - fast-xml-parser: 4.4.0 + fast-xml-parser: 4.4.1 ora: 5.4.1 transitivePeerDependencies: - encoding @@ -25573,15 +25635,16 @@ snapshots: dependencies: '@react-native/assets-registry': 0.74.85 expo: 51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13) - expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) + expo-constants: 16.0.2(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-constants@16.0.1(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)): + expo-constants@16.0.2(expo@51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13)): dependencies: '@expo/config': 9.0.3 + '@expo/env': 0.3.0 expo: 51.0.8(@babel/core@7.24.9)(@babel/preset-env@7.24.8(@babel/core@7.24.9))(encoding@0.1.13) transitivePeerDependencies: - supports-color @@ -25721,7 +25784,7 @@ snapshots: fast-uri@3.0.1: {} - fast-xml-parser@4.4.0: + fast-xml-parser@4.4.1: dependencies: strnum: 1.0.5 @@ -30591,7 +30654,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-virtuoso@4.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-virtuoso@4.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) diff --git a/src/app/common/app-analytics.ts b/src/app/common/app-analytics.ts index ac4a8a69065..cfd5468c78c 100644 --- a/src/app/common/app-analytics.ts +++ b/src/app/common/app-analytics.ts @@ -1,14 +1,21 @@ import { useEffect } from 'react'; +import { z } from 'zod'; + import { HIRO_API_BASE_URL_MAINNET, HIRO_API_BASE_URL_TESTNET } from '@leather.io/models'; import { IS_TEST_ENV, SEGMENT_WRITE_KEY } from '@shared/environment'; -import { decorateAnalyticsEventsWithContext, initAnalytics } from '@shared/utils/analytics'; +import { + analytics, + decorateAnalyticsEventsWithContext, + initAnalytics, +} from '@shared/utils/analytics'; import { store } from '@app/store'; import { selectWalletType } from '@app/store/common/wallet-type.selectors'; import { selectCurrentNetwork } from '@app/store/networks/networks.selectors'; +import { useOnMount } from './hooks/use-on-mount'; import { flow, origin } from './initial-search-params'; const defaultStaticAnalyticContext = { @@ -57,3 +64,32 @@ decorateAnalyticsEventsWithContext(() => ({ ...defaultStaticAnalyticContext, ...getDerivedStateAnalyticsContext(), })); + +const analyticsQueueItemSchema = z.object({ + eventName: z.string(), + properties: z.record(z.unknown()).optional(), +}); + +const analyicsQueueSchema = z.array(analyticsQueueItemSchema); + +const analyticsEventKey = 'backgroundAnalyticsRequests'; + +export function useHandleQueuedBackgroundAnalytics() { + useOnMount(() => { + async function handleQueuedAnalytics() { + const queuedEventsStore = await chrome.storage.local.get(analyticsEventKey); + + try { + const events = analyicsQueueSchema.parse(queuedEventsStore[analyticsEventKey] ?? []); + if (!events.length) return; + await chrome.storage.local.remove(analyticsEventKey); + await Promise.all( + events.map(({ eventName, properties }) => analytics.track(eventName, properties)) + ); + } catch (e) { + void analytics.track('background_analytics_schema_fail'); + } + } + void handleQueuedAnalytics(); + }); +} diff --git a/src/app/common/hooks/auth/use-has-keys.tsx b/src/app/common/hooks/auth/use-has-keys.tsx new file mode 100644 index 00000000000..b19b8c18025 --- /dev/null +++ b/src/app/common/hooks/auth/use-has-keys.tsx @@ -0,0 +1,13 @@ +import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors'; +import { useCurrentKeyDetails } from '@app/store/software-keys/software-key.selectors'; + +export function useHasKeys() { + const hasSoftwareKeys = !!useCurrentKeyDetails(); + const hasLedgerKeys = useHasLedgerKeys(); + + return { + hasSoftwareKeys, + hasLedgerKeys, + hasKeys: hasSoftwareKeys || hasLedgerKeys, + }; +} diff --git a/src/app/common/hooks/use-key-actions.ts b/src/app/common/hooks/use-key-actions.ts index c52aa2c1afa..5b67c98b926 100644 --- a/src/app/common/hooks/use-key-actions.ts +++ b/src/app/common/hooks/use-key-actions.ts @@ -5,6 +5,8 @@ import { generateSecretKey } from '@stacks/wallet-sdk'; import { useBitcoinClient } from '@leather.io/query'; import { logger } from '@shared/logger'; +import { InternalMethods } from '@shared/message-types'; +import { sendMessage } from '@shared/messages'; import { clearChromeStorage } from '@shared/storage/redux-pesist'; import { analytics } from '@shared/utils/analytics'; @@ -46,8 +48,9 @@ export function useKeyActions() { return dispatch(keyActions.unlockWalletAction(password)); }, - switchAccount(index: number) { - return dispatch(stxChainActions.switchAccount(index)); + switchAccount(accountIndex: number) { + sendMessage({ method: InternalMethods.AccountChanged, payload: { accountIndex } }); + return dispatch(stxChainActions.switchAccount(accountIndex)); }, async createNewAccount() { diff --git a/src/app/common/hooks/use-media-query.ts b/src/app/common/hooks/use-media-query.ts index 1d9249726cc..a8e856ddb95 100644 --- a/src/app/common/hooks/use-media-query.ts +++ b/src/app/common/hooks/use-media-query.ts @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { BreakpointToken, token } from 'leather-styles/tokens'; function useMediaQuery(query: string) { - const [matches, setMatches] = useState(false); + const [matches, setMatches] = useState(() => window.matchMedia(query).matches); useEffect(() => { const media = window.matchMedia(query); diff --git a/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx b/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx index 946a40edfef..3d84c8a4f92 100644 --- a/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx +++ b/src/app/components/broadcast-error-dialog/broadcast-error-dialog.tsx @@ -4,10 +4,9 @@ import GenericError from '@assets/images/generic-error.png'; import { Flex, styled } from 'leather-styles/jsx'; import get from 'lodash.get'; -import { Button, Dialog } from '@leather.io/ui'; +import { Button, Dialog, DialogHeader } from '@leather.io/ui'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; +import { Footer } from '@app/components/layout'; export function BroadcastErrorDialog() { const navigate = useNavigate(); diff --git a/src/app/ui/layout/card/card-content.tsx b/src/app/components/layout/card/card-content.tsx similarity index 100% rename from src/app/ui/layout/card/card-content.tsx rename to src/app/components/layout/card/card-content.tsx diff --git a/src/app/ui/layout/card/card.stories.tsx b/src/app/components/layout/card/card.stories.tsx similarity index 100% rename from src/app/ui/layout/card/card.stories.tsx rename to src/app/components/layout/card/card.stories.tsx diff --git a/src/app/ui/layout/card/card.tsx b/src/app/components/layout/card/card.tsx similarity index 100% rename from src/app/ui/layout/card/card.tsx rename to src/app/components/layout/card/card.tsx diff --git a/src/app/ui/components/containers/footers/available-balance.tsx b/src/app/components/layout/footer/available-balance.tsx similarity index 100% rename from src/app/ui/components/containers/footers/available-balance.tsx rename to src/app/components/layout/footer/available-balance.tsx diff --git a/src/app/ui/components/containers/footers/footer.stories.tsx b/src/app/components/layout/footer/footer.stories.tsx similarity index 100% rename from src/app/ui/components/containers/footers/footer.stories.tsx rename to src/app/components/layout/footer/footer.stories.tsx diff --git a/src/app/ui/components/containers/footers/footer.tsx b/src/app/components/layout/footer/footer.tsx similarity index 100% rename from src/app/ui/components/containers/footers/footer.tsx rename to src/app/components/layout/footer/footer.tsx diff --git a/src/app/ui/components/containers/headers/components/header-action-button.tsx b/src/app/components/layout/headers/header-action-button.tsx similarity index 100% rename from src/app/ui/components/containers/headers/components/header-action-button.tsx rename to src/app/components/layout/headers/header-action-button.tsx diff --git a/src/app/components/layout/headers/header-grid.tsx b/src/app/components/layout/headers/header-grid.tsx new file mode 100644 index 00000000000..e48f7fed6df --- /dev/null +++ b/src/app/components/layout/headers/header-grid.tsx @@ -0,0 +1,43 @@ +import { ChainID } from '@stacks/transactions'; +import { Flex, Grid, GridItem, type GridProps, HStack } from 'leather-styles/jsx'; + +import { NetworkModeBadge } from '@leather.io/ui'; + +import type { HasChildren } from '@app/common/has-children'; +import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; + +interface HeaderGridProps extends GridProps { + leftCol: React.ReactNode; + centerCol?: React.ReactNode; + rightCol: React.ReactNode; +} +export function HeaderGrid({ leftCol, centerCol, rightCol, ...props }: HeaderGridProps) { + return ( + + + {leftCol} + + {centerCol && {centerCol}} + {rightCol} + + ); +} + +export function HeaderGridRightCol({ children }: HasChildren) { + const { chain, name: chainName } = useCurrentNetworkState(); + return ( + + + {children} + + ); +} diff --git a/src/app/components/layout/headers/header.tsx b/src/app/components/layout/headers/header.tsx new file mode 100644 index 00000000000..90641b4f901 --- /dev/null +++ b/src/app/components/layout/headers/header.tsx @@ -0,0 +1,19 @@ +import { type BoxProps, styled } from 'leather-styles/jsx'; + +import type { HasChildren } from '@app/common/has-children'; + +export function Header({ children, ...props }: HasChildren & BoxProps) { + return ( + + {children} + + ); +} diff --git a/src/app/components/layout/headers/logo-box.tsx b/src/app/components/layout/headers/logo-box.tsx new file mode 100644 index 00000000000..cc33a7d529b --- /dev/null +++ b/src/app/components/layout/headers/logo-box.tsx @@ -0,0 +1,34 @@ +import { useLocation, useNavigate } from 'react-router-dom'; + +import { OnboardingSelectors } from '@tests/selectors/onboarding.selectors'; +import { Box } from 'leather-styles/jsx'; + +import { Logo } from '@leather.io/ui'; + +import { RouteUrls } from '@shared/route-urls'; + +export function LogoBox({ isSessionLocked }: { isSessionLocked?: boolean | undefined }) { + const navigate = useNavigate(); + const location = useLocation(); + + const shouldShowLogo = + location.pathname === RouteUrls.Home || location.pathname === RouteUrls.Activity; + + const hideBelow = shouldShowLogo ? undefined : isSessionLocked ? undefined : 'sm'; + const hideFrom = shouldShowLogo ? undefined : isSessionLocked ? 'sm' : undefined; + + return ( + + navigate(RouteUrls.Home)} + /> + + ); +} diff --git a/src/app/components/layout/index.ts b/src/app/components/layout/index.ts new file mode 100644 index 00000000000..9da37b9db05 --- /dev/null +++ b/src/app/components/layout/index.ts @@ -0,0 +1,9 @@ +export { Page } from './page/page.layout'; +export { Footer } from './footer/footer'; +export { Card } from './card/card'; +export { CardContent } from './card/card-content'; +export { AvailableBalance } from './footer/available-balance'; + +export { Content } from './layouts/content.layout'; +export { TwoColumnLayout } from './layouts/two-column.layout'; +export { ContainerLayout } from './layouts/container.layout'; diff --git a/src/app/components/layout/layouts/container.layout.tsx b/src/app/components/layout/layouts/container.layout.tsx new file mode 100644 index 00000000000..78b8ca731bb --- /dev/null +++ b/src/app/components/layout/layouts/container.layout.tsx @@ -0,0 +1,11 @@ +import { Flex } from 'leather-styles/jsx'; + +import type { HasChildren } from '@app/common/has-children'; + +export function ContainerLayout({ children }: HasChildren) { + return ( + + {children} + + ); +} diff --git a/src/app/components/layout/layouts/content.layout.tsx b/src/app/components/layout/layouts/content.layout.tsx new file mode 100644 index 00000000000..80ce69e9175 --- /dev/null +++ b/src/app/components/layout/layouts/content.layout.tsx @@ -0,0 +1,11 @@ +import { Flex } from 'leather-styles/jsx'; + +import type { HasChildren } from '@app/common/has-children'; + +export function Content({ children }: HasChildren) { + return ( + + {children} + + ); +} diff --git a/src/app/components/layout/layouts/switch-account.layout.tsx b/src/app/components/layout/layouts/switch-account.layout.tsx new file mode 100644 index 00000000000..2bfbff341f7 --- /dev/null +++ b/src/app/components/layout/layouts/switch-account.layout.tsx @@ -0,0 +1,9 @@ +import { Outlet, useOutletContext } from 'react-router-dom'; + +import { SwitchAccountOutletContext } from '@shared/switch-account'; + +export function SwitchAccountLayout() { + const { isShowingSwitchAccount, setIsShowingSwitchAccount } = + useOutletContext(); + return ; +} diff --git a/src/app/ui/pages/two-column.layout.stories.tsx b/src/app/components/layout/layouts/two-column.layout.stories.tsx similarity index 100% rename from src/app/ui/pages/two-column.layout.stories.tsx rename to src/app/components/layout/layouts/two-column.layout.stories.tsx diff --git a/src/app/ui/pages/two-column.layout.tsx b/src/app/components/layout/layouts/two-column.layout.tsx similarity index 91% rename from src/app/ui/pages/two-column.layout.tsx rename to src/app/components/layout/layouts/two-column.layout.tsx index fa47e5e236b..98dd8ad7288 100644 --- a/src/app/ui/pages/two-column.layout.tsx +++ b/src/app/components/layout/layouts/two-column.layout.tsx @@ -1,10 +1,12 @@ +import { ReactNode } from 'react'; + import { Box, Flex, Stack, styled } from 'leather-styles/jsx'; interface TwoColumnLayoutProps { - title: React.JSX.Element; - content: React.JSX.Element; - action?: React.JSX.Element; - children: React.JSX.Element; + title: ReactNode; + content: ReactNode; + action?: ReactNode; + children: ReactNode; wideChild?: boolean; } diff --git a/src/app/ui/layout/page/page.layout.stories.tsx b/src/app/components/layout/page/page.layout.stories.tsx similarity index 83% rename from src/app/ui/layout/page/page.layout.stories.tsx rename to src/app/components/layout/page/page.layout.stories.tsx index 8b972aa73d1..3034f56b11b 100644 --- a/src/app/ui/layout/page/page.layout.stories.tsx +++ b/src/app/components/layout/page/page.layout.stories.tsx @@ -1,6 +1,6 @@ import type { Meta } from '@storybook/react'; -import { Card } from '@app/ui/layout/card/card.stories'; +import { Card } from '@app/components/layout/card/card.stories'; import { Page as Component } from './page.layout'; diff --git a/src/app/ui/layout/page/page.layout.tsx b/src/app/components/layout/page/page.layout.tsx similarity index 100% rename from src/app/ui/layout/page/page.layout.tsx rename to src/app/components/layout/page/page.layout.tsx diff --git a/src/app/components/request-password.tsx b/src/app/components/request-password.tsx index 7993607fc71..080aeefab16 100644 --- a/src/app/components/request-password.tsx +++ b/src/app/components/request-password.tsx @@ -10,9 +10,7 @@ import { analytics } from '@shared/utils/analytics'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { buildEnterKeyEvent } from '@app/common/hooks/use-modifier-key'; import { WaitingMessages, useWaitingMessage } from '@app/common/hooks/use-waiting-message'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { Page } from '@app/ui/layout/page/page.layout'; +import { Card, Footer, Page } from '@app/components/layout'; import { ErrorLabel } from './error-label'; diff --git a/src/app/features/add-network/add-network-form.tsx b/src/app/features/add-network/add-network-form.tsx index 0372ea738e4..913831f1cef 100644 --- a/src/app/features/add-network/add-network-form.tsx +++ b/src/app/features/add-network/add-network-form.tsx @@ -39,7 +39,8 @@ const networks: { ]; export function AddNetworkForm() { - const { handleChange, setFieldValue, values } = useFormikContext(); + const { handleChange, setFieldValue, values, initialValues } = + useFormikContext(); const setStacksUrl = useCallback( (value: string) => { @@ -92,7 +93,7 @@ export function AddNetworkForm() { Bitcoin API { void setFieldValue('bitcoinNetwork', value); }} diff --git a/src/app/features/add-network/add-network.tsx b/src/app/features/add-network/add-network.tsx index f3a23a4e31c..7328728f12e 100644 --- a/src/app/features/add-network/add-network.tsx +++ b/src/app/features/add-network/add-network.tsx @@ -5,8 +5,8 @@ import { Stack, styled } from 'leather-styles/jsx'; import { Button } from '@leather.io/ui'; import { ErrorLabel } from '@app/components/error-label'; -import { Card } from '@app/ui/layout/card/card'; -import { Page } from '@app/ui/layout/page/page.layout'; +import { Card, Content, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { AddNetworkForm } from './add-network-form'; import { useAddNetwork } from './use-add-network'; @@ -15,49 +15,58 @@ export function AddNetwork() { const { error, initialFormValues, loading, onSubmit } = useAddNetwork(); return ( - - - {() => ( - -
- - - Use this form to add a new instance of the{' '} - + + + + + {() => ( + + + - Stacks Blockchain API - {' '} - or{' '} - - Bitcoin Blockchain API - - . Make sure you review and trust the host before you add it. - - - {error ? ( - {error} - ) : null} - - -
-
- )} -
-
+ + Use this form to add a new instance of the{' '} + + Stacks Blockchain API + {' '} + or{' '} + + Bitcoin Blockchain API + + . Make sure you review and trust the host before you add it. + + + {error ? ( + {error} + ) : null} + + + + + )} + + + + ); } diff --git a/src/app/features/add-network/use-add-network.tsx b/src/app/features/add-network/use-add-network.tsx index e0aba75008f..0d20529ab81 100644 --- a/src/app/features/add-network/use-add-network.tsx +++ b/src/app/features/add-network/use-add-network.tsx @@ -1,9 +1,14 @@ import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { ChainID } from '@stacks/transactions'; -import type { BitcoinNetworkModes, DefaultNetworkConfigurations } from '@leather.io/models'; +import { + type BitcoinNetworkModes, + type DefaultNetworkConfigurations, + type NetworkConfiguration, + networkConfigurationSchema, +} from '@leather.io/models'; import { RouteUrls } from '@shared/route-urls'; import { isValidUrl } from '@shared/utils/validate-url'; @@ -39,16 +44,45 @@ const initialFormValues: AddNetworkFormValues = { bitcoinNetwork: 'mainnet', }; +function useInitialValues() { + const { state } = useLocation(); + + if (!state) { + return initialFormValues; + } + + const network = state.network as NetworkConfiguration | undefined; + + if (!network) { + return initialFormValues; + } + + const isProperStateProvided = networkConfigurationSchema.safeParse(network).success; + + if (!isProperStateProvided) { + return initialFormValues; + } + + return { + key: network.id, + name: network.name, + stacksUrl: network.chain.stacks.url, + bitcoinUrl: network.chain.bitcoin.bitcoinUrl, + bitcoinNetwork: network.chain.bitcoin.bitcoinNetwork, + }; +} + export function useAddNetwork() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const navigate = useNavigate(); const network = useCurrentStacksNetworkState(); const networksActions = useNetworksActions(); + const initialValues = useInitialValues(); return { error, - initialFormValues, + initialFormValues: initialValues, loading, onSubmit: async (values: AddNetworkFormValues) => { const { name, stacksUrl, bitcoinUrl, key, bitcoinNetwork } = values; @@ -113,10 +147,16 @@ export function useAddNetwork() { isSubnet && (parentNetworkId === PeerNetworkID.Mainnet || parentNetworkId === PeerNetworkID.Testnet); + function removeEditedNetwork() { + if (initialValues.key) { + networksActions.removeNetwork(initialValues.key); + } + } // Currently, only subnets of mainnet and testnet are supported in the wallet if (isFirstLevelSubnet) { const parentChainId = parentNetworkId === PeerNetworkID.Mainnet ? ChainID.Mainnet : ChainID.Testnet; + removeEditedNetwork(); networksActions.addNetwork({ id: key as DefaultNetworkConfigurations, name: name, @@ -128,6 +168,7 @@ export function useAddNetwork() { }); navigate(RouteUrls.Home); } else if (chainId === ChainID.Mainnet || chainId === ChainID.Testnet) { + removeEditedNetwork(); networksActions.addNetwork({ id: key as DefaultNetworkConfigurations, name: name, diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx index 3a8933f4dfd..81a3ebcf260 100644 --- a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx +++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx @@ -25,7 +25,7 @@ export function Sip10TokenAssetList({ {tokens.map(token => ( closeWindow()); useOnSignOut(() => closeWindow()); useRestoreFormState(); useInitalizeAnalytics(); + useHandleQueuedBackgroundAnalytics(); + useOnChangeAccount(index => dispatch(stxChainSlice.actions.switchAccount(index))); useEffect(() => void analytics.page('view', `${pathname}`), [pathname]); - const variant = getPageVariant(pathname); - - const displayHeader = !isLandingPage(pathname) && !isNoHeaderPopup(pathname); - const isSessionLocked = getIsSessionLocked(pathname); - - // TODO: Refactor? This is very hard to manage with dynamic routes. Temporarily - // added a fix to catch the swap route: '/swap/:base/:quote?' - function getOnGoBackLocation(pathname: RouteUrls) { - if (pathname.includes('/swap')) return navigate(RouteUrls.Home); - switch (pathname) { - case RouteUrls.Fund.replace(':currency', 'STX'): - case RouteUrls.Fund.replace(':currency', 'BTC'): - case RouteUrls.SendCryptoAssetForm.replace(':symbol', 'stx'): - case RouteUrls.SendCryptoAssetForm.replace(':symbol', 'btc'): - return navigate(RouteUrls.Home); - case RouteUrls.SendStxConfirmation: - return navigate(RouteUrls.SendCryptoAssetForm.replace(':symbol', 'stx')); - case RouteUrls.SendBtcConfirmation: - return navigate(RouteUrls.SendCryptoAssetForm.replace(':symbol', 'btc')); - default: - return navigate(-1); - } - } - if (!hasStateRehydrated) return ; - const showLogoSm = variant === 'home' || isSessionLocked || isKnownPopupRoute(pathname); - const hideSettings = - isKnownPopupRoute(pathname) || isSummaryPage(pathname) || variant === 'onboarding'; - - const isLogoClickable = variant !== 'home' && !isRpcRoute(pathname); return ( <> {isShowingSwitchAccount && ( @@ -102,76 +48,8 @@ export function Container() { onClose={() => setIsShowingSwitchAccount(false)} /> )} - - getOnGoBackLocation(pathname) : undefined} - onClose={isSummaryPage(pathname) ? () => navigate(RouteUrls.Home) : undefined} - settingsMenu={ - hideSettings ? null : ( - - } - toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} - /> - ) - } - networkBadge={ - - } - title={getTitleFromUrl(pathname)} - logo={ - !hideLogo(pathname) && ( - - navigate(RouteUrls.Home) : undefined} - /> - - ) - } - account={ - showAccountInfo(pathname) && ( - - setIsShowingSwitchAccount(!isShowingSwitchAccount) - } - /> - } - > - - - ) - } - totalBalance={ - showBalanceInfo(pathname) && ( - - ) - } - /> - ) : null - } - > + diff --git a/src/app/features/container/headers/main.header.tsx b/src/app/features/container/headers/main.header.tsx new file mode 100644 index 00000000000..181f54031c7 --- /dev/null +++ b/src/app/features/container/headers/main.header.tsx @@ -0,0 +1,53 @@ +import { useNavigate, useOutletContext } from 'react-router-dom'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; + +import { ArrowLeftIcon, HamburgerIcon } from '@leather.io/ui'; + +import { SwitchAccountOutletContext } from '@shared/switch-account'; + +import { Header } from '@app/components/layout/headers/header'; +import { HeaderActionButton } from '@app/components/layout/headers/header-action-button'; +import { HeaderGrid, HeaderGridRightCol } from '@app/components/layout/headers/header-grid'; +import { LogoBox } from '@app/components/layout/headers/logo-box'; +import { Settings } from '@app/features/settings/settings'; + +interface MainHeaderProps { + hideBackButton?: boolean; + hideLogo?: boolean; +} + +export function MainHeader({ hideBackButton = false, hideLogo = false }: MainHeaderProps) { + const { isShowingSwitchAccount, setIsShowingSwitchAccount } = + useOutletContext(); + const navigate = useNavigate(); + return ( + <> +
+ + {!hideBackButton && ( + } + onAction={() => navigate(-1)} + dataTestId={SharedComponentsSelectors.HeaderBackBtn} + /> + )} + {!hideLogo && } + + } + rightCol={ + + } + toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} + /> + + } + /> +
+ + ); +} diff --git a/src/app/features/container/headers/page.header.tsx b/src/app/features/container/headers/page.header.tsx new file mode 100644 index 00000000000..acce74e4244 --- /dev/null +++ b/src/app/features/container/headers/page.header.tsx @@ -0,0 +1,83 @@ +import { useNavigate, useOutletContext } from 'react-router-dom'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; +import { styled } from 'leather-styles/jsx'; + +import { ArrowLeftIcon, CloseIcon, HamburgerIcon } from '@leather.io/ui'; + +import { RouteUrls } from '@shared/route-urls'; +import { SwitchAccountOutletContext } from '@shared/switch-account'; + +import { Header } from '@app/components/layout/headers/header'; +import { HeaderActionButton } from '@app/components/layout/headers/header-action-button'; +import { HeaderGrid, HeaderGridRightCol } from '@app/components/layout/headers/header-grid'; +import { LogoBox } from '@app/components/layout/headers/logo-box'; +import { Settings } from '@app/features/settings/settings'; + +interface PageHeaderProps { + title?: string; + isSummaryPage?: boolean; + isSessionLocked?: boolean; + isSettingsVisibleOnSm?: boolean; + onBackLocation?: RouteUrls; + onClose?(): void; +} + +export function PageHeader({ + title, + isSummaryPage, + isSessionLocked, + isSettingsVisibleOnSm, + onBackLocation, +}: PageHeaderProps) { + const { isShowingSwitchAccount, setIsShowingSwitchAccount } = + useOutletContext(); + const navigate = useNavigate(); + + // pages with nested dialogs specify onBackLocation to prevent navigate(-1) re-opening the dialog + const onGoBack = onBackLocation ? () => navigate(onBackLocation) : () => navigate(-1); + const canGoBack = !isSummaryPage && !isSessionLocked; + + return ( + <> +
+ + {canGoBack && ( + } + onAction={onGoBack} + dataTestId={SharedComponentsSelectors.HeaderBackBtn} + /> + )} + + + } + centerCol={title && {title}} + rightCol={ + + {isSummaryPage ? ( + } + dataTestId={SharedComponentsSelectors.HeaderCloseBtn} + onAction={() => navigate(RouteUrls.Home)} + /> + ) : ( + + + } + toggleSwitchAccount={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} + /> + + )} + + } + /> +
+ + ); +} diff --git a/src/app/features/container/headers/popup.header.tsx b/src/app/features/container/headers/popup.header.tsx new file mode 100644 index 00000000000..8bea87db5c7 --- /dev/null +++ b/src/app/features/container/headers/popup.header.tsx @@ -0,0 +1,54 @@ +import { useOutletContext } from 'react-router-dom'; + +import { Box } from 'leather-styles/jsx'; + +import { Flag, Logo } from '@leather.io/ui'; + +import { SwitchAccountOutletContext } from '@shared/switch-account'; + +import { Header } from '@app/components/layout/headers/header'; +import { HeaderGrid, HeaderGridRightCol } from '@app/components/layout/headers/header-grid'; +import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar'; +import { CurrentAccountName } from '@app/features/current-account/current-account-name'; +import { TotalBalance } from '@app/features/total-balance/total-balance'; + +interface PopupHeaderProps { + showSwitchAccount?: boolean; + balance?: 'all' | 'stx'; +} + +export function PopupHeader({ showSwitchAccount, balance }: PopupHeaderProps) { + const { isShowingSwitchAccount, setIsShowingSwitchAccount } = + useOutletContext(); + + return ( +
+ + {showSwitchAccount ? ( + } + onClick={() => setIsShowingSwitchAccount(!isShowingSwitchAccount)} + cursor="pointer" + > + + + ) : ( + + + + )} + + } + rightCol={ + + {balance && } + + } + /> +
+ ); +} diff --git a/src/app/features/container/utils/get-popup-header.ts b/src/app/features/container/utils/get-popup-header.ts deleted file mode 100644 index 52d71c014eb..00000000000 --- a/src/app/features/container/utils/get-popup-header.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { RouteUrls } from '@shared/route-urls'; - -export function isRpcRoute(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.PsbtRequest: - case RouteUrls.SignatureRequest: - case RouteUrls.RpcStacksSignature: - case RouteUrls.RpcSignBip322Message: - case RouteUrls.RpcStacksSignature: - case RouteUrls.RpcSignPsbt: - case RouteUrls.RpcSignPsbtSummary: - case RouteUrls.RpcSendTransfer: - case RouteUrls.RpcSendTransferChooseFee: - case RouteUrls.RpcSendTransferConfirmation: - case RouteUrls.RpcSendTransferSummary: - return true; - default: - return false; - } -} - -export function showAccountInfo(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.PsbtRequest: - case RouteUrls.TransactionRequest: - case RouteUrls.ProfileUpdateRequest: - case RouteUrls.RpcSendTransfer: - case RouteUrls.RpcSignPsbt: - case RouteUrls.RpcSignBip322Message: - case RouteUrls.SignatureRequest: - case RouteUrls.RpcStacksSignature: - return true; - default: - return false; - } -} - -export function showBalanceInfo(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.ProfileUpdateRequest: - case RouteUrls.RpcSendTransfer: - case RouteUrls.PsbtRequest: - case RouteUrls.RpcSignBip322Message: - case RouteUrls.RpcStacksSignature: - case RouteUrls.TransactionRequest: - return true; - default: - return false; - } -} - -export function getDisplayAddresssBalanceOf(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.ProfileUpdateRequest: - case RouteUrls.RpcSendTransfer: - case RouteUrls.PsbtRequest: - case RouteUrls.RpcSignBip322Message: - return 'all'; - case RouteUrls.RpcStacksSignature: - case RouteUrls.TransactionRequest: - default: - return 'stx'; - } -} - -export function isKnownPopupRoute(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.TransactionRequest: - case RouteUrls.ProfileUpdateRequest: - case RouteUrls.PsbtRequest: - case RouteUrls.SignatureRequest: - case RouteUrls.RpcGetAddresses: - case RouteUrls.RpcSendTransfer: - case RouteUrls.SignatureRequest: - case RouteUrls.RpcSignBip322Message: - case RouteUrls.RpcStacksSignature: - case RouteUrls.RpcSignPsbt: - case RouteUrls.RpcSignPsbtSummary: - case RouteUrls.RpcSendTransfer: - case RouteUrls.RpcSendTransferChooseFee: - case RouteUrls.RpcSendTransferConfirmation: - case RouteUrls.RpcSendTransferSummary: - return true; - default: - return false; - } -} diff --git a/src/app/features/container/utils/get-title-from-url.ts b/src/app/features/container/utils/get-title-from-url.ts deleted file mode 100644 index c23fa79d6dc..00000000000 --- a/src/app/features/container/utils/get-title-from-url.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { RouteUrls } from '@shared/route-urls'; - -export function getTitleFromUrl(pathname: RouteUrls) { - if (pathname.match(RouteUrls.SendCryptoAsset)) { - // don't show send on first step of send flow or popuop transfer - if (pathname === RouteUrls.SendCryptoAsset) return undefined; - if (pathname === RouteUrls.RpcSendTransfer) return undefined; - return 'Send'; - } - - switch (pathname) { - case RouteUrls.AddNetwork: - return 'Add a network'; - case RouteUrls.SendBrc20ChooseFee: - return 'Choose fee'; - case RouteUrls.SendBrc20Confirmation: - case RouteUrls.SwapReview: - case RouteUrls.SendBrc20Confirmation: - case '/send/btc/confirm': - return 'Review'; - case RouteUrls.Swap: - return 'Swap'; - case RouteUrls.SentStxTxSummary: - case RouteUrls.SentBtcTxSummary: - return 'Sent'; - case RouteUrls.SentBrc20Summary: - return 'Creating transfer inscription'; - case RouteUrls.SendBrc20Confirmation: - default: - return undefined; - } -} diff --git a/src/app/features/container/utils/route-helpers.ts b/src/app/features/container/utils/route-helpers.ts deleted file mode 100644 index 7e7f66f31fa..00000000000 --- a/src/app/features/container/utils/route-helpers.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { RouteUrls } from '@shared/route-urls'; - -import { isKnownPopupRoute } from './get-popup-header'; - -function isHomePage(pathname: RouteUrls) { - return ( - pathname === RouteUrls.Home || - pathname.match(RouteUrls.Activity) || - pathname.match(RouteUrls.Receive) || - pathname.match(RouteUrls.SendOrdinalInscription) - ); -} - -export function isLandingPage(pathname: RouteUrls) { - return pathname.match(RouteUrls.Onboarding); // need to match get-started/ledger -} - -function isOnboardingPage(pathname: RouteUrls) { - return ( - pathname === RouteUrls.BackUpSecretKey || - pathname === RouteUrls.SetPassword || - pathname === RouteUrls.SignIn || - pathname === RouteUrls.ViewSecretKey - ); -} - -function isFundPage(pathname: RouteUrls) { - return ( - pathname === RouteUrls.Fund.replace(':currency', 'STX') || - pathname === RouteUrls.Fund.replace(':currency', 'BTC') - ); -} - -export function getPageVariant(pathname: RouteUrls) { - if (isFundPage(pathname)) return 'fund'; - if (isHomePage(pathname)) return 'home'; - if (isOnboardingPage(pathname)) return 'onboarding'; - return 'page'; -} - -export function getIsSessionLocked(pathname: RouteUrls) { - return pathname === RouteUrls.Unlock; -} - -export function isSummaryPage(pathname: RouteUrls) { - /* TODO refactor the summary routes to make this cleaner - we need to block going back from summary pages catching the dynamic routes: - SentBtcTxSummary = '/sent/btc/:txId', - SentStxTxSummary = '/sent/stx/:txId', - SentBrc20Summary = '/send/brc20/:ticker/summary', - RpcSignPsbtSummary = '/sign-psbt/summary', - RpcSendTransferSummary = '/send-transfer/summary', - */ - return pathname.match('/sent/stx/') || pathname.match('/sent/btc/' || pathname.match('summary')); -} - -export function canGoBack(pathname: RouteUrls) { - if (getIsSessionLocked(pathname) || isKnownPopupRoute(pathname) || isSummaryPage(pathname)) { - return false; - } - return true; -} - -export function hideLogo(pathname: RouteUrls) { - return pathname === RouteUrls.RpcGetAddresses || pathname === RouteUrls.ViewSecretKey; -} - -export function isNoHeaderPopup(pathname: RouteUrls) { - return pathname === RouteUrls.RpcGetAddresses || pathname === RouteUrls.ChooseAccount; -} - -export function hideSettingsOnSm(pathname: RouteUrls) { - switch (pathname) { - case RouteUrls.SendCryptoAsset: - case RouteUrls.FundChooseCurrency: - return true; - default: - return false; - } -} diff --git a/src/app/features/current-account/current-account-avatar.tsx b/src/app/features/current-account/current-account-avatar.tsx index 0187c47fd33..0f312db8f2e 100644 --- a/src/app/features/current-account/current-account-avatar.tsx +++ b/src/app/features/current-account/current-account-avatar.tsx @@ -1,5 +1,3 @@ -import { memo } from 'react'; - import { CircleProps } from 'leather-styles/jsx'; import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; @@ -9,17 +7,12 @@ import { AccountAvatar } from '@app/ui/components/account/account-avatar/account interface CurrentAccountAvatar extends CircleProps { toggleSwitchAccount(): void; } -export const CurrentAccountAvatar = memo(({ toggleSwitchAccount }: CurrentAccountAvatar) => { +export function CurrentAccountAvatar() { const stacksAccount = useCurrentStacksAccount(); const { data: name = 'Account' } = useCurrentAccountDisplayName(); - if (!stacksAccount) return null; + if (!stacksAccount) return ; return ( - toggleSwitchAccount()} - publicKey={stacksAccount.stxPublicKey} - /> + ); -}); +} diff --git a/src/app/features/current-account/current-account-name.tsx b/src/app/features/current-account/current-account-name.tsx index abf7ade63c6..beb04152bc7 100644 --- a/src/app/features/current-account/current-account-name.tsx +++ b/src/app/features/current-account/current-account-name.tsx @@ -6,7 +6,6 @@ import { Box, BoxProps, styled } from 'leather-styles/jsx'; import { HasChildren } from '@app/common/has-children'; import { useCurrentAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { truncateString } from '@app/common/utils'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; function AccountNameTitle({ children, ...props }: HasChildren & BoxProps) { @@ -20,9 +19,7 @@ function AccountNameTitle({ children, ...props }: HasChildren & BoxProps) { } const AccountNameSuspense = memo((props: BoxProps) => { - const currentAccount = useCurrentStacksAccount(); const { data: name = 'Account' } = useCurrentAccountDisplayName(); - if (!currentAccount || typeof currentAccount.index === 'undefined') return null; // FIXME: The name is truncated here with JS but we could just use CSS to do this const nameCharLimit = 18; const isLong = name.length > nameCharLimit; diff --git a/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx b/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx index 35389fa1f3d..86fa318bc18 100644 --- a/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx +++ b/src/app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog.tsx @@ -4,13 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { useFormikContext } from 'formik'; import { Stack, styled } from 'leather-styles/jsx'; -import { Dialog, Link } from '@leather.io/ui'; +import { Dialog, DialogHeader, Link } from '@leather.io/ui'; import { StacksSendFormValues, StacksTransactionFormValues } from '@shared/models/form.model'; import { useOnMount } from '@app/common/hooks/use-on-mount'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { EditNonceForm } from './components/edit-nonce-form'; diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx index 8a382bca309..bdbf8e02d22 100644 --- a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx @@ -5,7 +5,7 @@ import { Formik } from 'formik'; import { Flex, Stack } from 'leather-styles/jsx'; import type { BitcoinTx } from '@leather.io/models'; -import { Caption, Dialog, Spinner } from '@leather.io/ui'; +import { Caption, Dialog, DialogHeader, Spinner } from '@leather.io/ui'; import { btcToSat, createMoney, formatMoney } from '@leather.io/utils'; import { RouteUrls } from '@shared/route-urls'; @@ -14,10 +14,9 @@ import { useLocationStateWithCache } from '@app/common/hooks/use-location-state' import { getBitcoinTxValue } from '@app/common/transactions/bitcoin/utils'; import { BitcoinCustomFeeInput } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee-input'; import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; +import { Footer } from '@app/components/layout'; import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { IncreaseFeeActions } from './components/increase-fee-actions'; import { useBtcIncreaseFee } from './hooks/use-btc-increase-fee'; diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx index 00730e1e7c4..fc4aef13c2d 100644 --- a/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx +++ b/src/app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog.tsx @@ -12,7 +12,7 @@ import { useStacksRawTransaction, useStxAvailableUnlockedBalance, } from '@leather.io/query'; -import { Caption, Dialog, Spinner } from '@leather.io/ui'; +import { Caption, Dialog, DialogHeader, Spinner } from '@leather.io/ui'; import { microStxToStx, stxToMicroStx } from '@leather.io/utils'; import { RouteUrls } from '@shared/route-urls'; @@ -21,14 +21,13 @@ import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh- import { stacksValue } from '@app/common/stacks-utils'; import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; +import { Footer } from '@app/components/layout'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; import { useToast } from '@app/features/toasts/use-toast'; import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { IncreaseFeeActions } from './components/increase-fee-actions'; import { IncreaseFeeField } from './components/increase-fee-field'; diff --git a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx index 4f1724f28e7..42442e647e6 100644 --- a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx +++ b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx @@ -3,25 +3,19 @@ import { Virtuoso } from 'react-virtuoso'; import { Box } from 'leather-styles/jsx'; -import { Button, Dialog } from '@leather.io/ui'; +import { Button, Dialog, DialogHeader } from '@leather.io/ui'; import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; import { useWalletType } from '@app/common/use-wallet-type'; +import { Footer } from '@app/components/layout'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { VirtuosoWrapper } from '@app/ui/components/virtuoso'; import { AccountListUnavailable } from './components/account-list-unavailable'; import { SwitchAccountListItem } from './components/switch-account-list-item'; -export interface SwitchAccountOutletContext { - isShowingSwitchAccount: boolean; - setIsShowingSwitchAccount(isShowing: boolean): void; -} - interface SwitchAccountDialogProps { isShowing: boolean; onClose(): void; @@ -76,7 +70,7 @@ export const SwitchAccountDialog = memo(({ isShowing, onClose }: SwitchAccountDi initialTopMostItemIndex={whenWallet({ ledger: 0, software: currentAccountIndex })} totalCount={accountNum} itemContent={index => ( - + - + {addressHoverLabel ? : null} diff --git a/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx b/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx index 9d12fe9a9c8..7f58a02c03c 100644 --- a/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx +++ b/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx @@ -46,7 +46,7 @@ export function PsbtAddressTotalItem({ label={hasCopied ? 'Copied!' : hoverLabel} side="bottom" > - + {hoverLabel ? : null} diff --git a/src/app/features/psbt-signer/components/psbt-request-actions.tsx b/src/app/features/psbt-signer/components/psbt-request-actions.tsx index 6b8746b8b7f..d940cc6d347 100644 --- a/src/app/features/psbt-signer/components/psbt-request-actions.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-actions.tsx @@ -1,6 +1,6 @@ import { Button } from '@leather.io/ui'; -import { Footer } from '@app/ui/components/containers/footers/footer'; +import { Footer } from '@app/components/layout'; interface PsbtRequestActionsProps { isLoading?: boolean; diff --git a/src/app/features/psbt-signer/psbt-signer.tsx b/src/app/features/psbt-signer/psbt-signer.tsx index 50073036c2d..ee13153be63 100644 --- a/src/app/features/psbt-signer/psbt-signer.tsx +++ b/src/app/features/psbt-signer/psbt-signer.tsx @@ -11,13 +11,12 @@ import { RouteUrls } from '@shared/route-urls'; import { closeWindow } from '@shared/utils'; import { SignPsbtArgs } from '@app/common/psbt/requests'; +import { Card, CardContent, Footer } from '@app/components/layout'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; import { useBreakOnNonCompliantEntity } from '@app/query/common/compliance-checker/compliance-checker.query'; import { useOnOriginTabClose } from '@app/routes/hooks/use-on-tab-closed'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentAccountTaprootIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import * as Psbt from './components'; import { usePsbtDetails } from './hooks/use-psbt-details'; @@ -93,6 +92,7 @@ export function PsbtSigner(props: PsbtSignerProps) { return ( + diff --git a/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx b/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx index d72d6a97620..8b05860953c 100644 --- a/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx +++ b/src/app/features/retrieve-taproot-to-native-segwit/components/retrieve-taproot-to-native-segwit.layout.tsx @@ -1,10 +1,8 @@ import { Flex, styled } from 'leather-styles/jsx'; -import { BtcAvatarIcon, Button, Callout, Dialog } from '@leather.io/ui'; +import { BtcAvatarIcon, Button, Callout, Dialog, DialogHeader } from '@leather.io/ui'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; -import { Card } from '@app/ui/layout/card/card'; +import { Card, Footer } from '@app/components/layout'; interface RetrieveTaprootToNativeSegwitLayoutProps { isBroadcasting: boolean; diff --git a/src/app/features/settings/network/components/network-list-item-menu.tsx b/src/app/features/settings/network/components/network-list-item-menu.tsx new file mode 100644 index 00000000000..f5f1c74ed1e --- /dev/null +++ b/src/app/features/settings/network/components/network-list-item-menu.tsx @@ -0,0 +1,58 @@ +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; +import { css } from 'leather-styles/css'; +import { HStack, styled } from 'leather-styles/jsx'; + +import { DotsVerticalIcon, DropdownMenu, PenIcon, TrashIcon } from '@leather.io/ui'; + +interface Props { + onEditNetwork(): void; + onClickDeleteNetwork(): void; +} + +export function NetworkItemMenu({ onClickDeleteNetwork, onEditNetwork }: Props) { + return ( + + + + + + + + { + e.stopPropagation(); + onEditNetwork(); + }} + > + + + Edit + + + { + e.stopPropagation(); + onClickDeleteNetwork(); + }} + > + + + Delete + + + + + + + ); +} diff --git a/src/app/features/settings/network/components/network-list-item.layout.tsx b/src/app/features/settings/network/components/network-list-item.layout.tsx index b0b4afef823..b0aea3769be 100644 --- a/src/app/features/settings/network/components/network-list-item.layout.tsx +++ b/src/app/features/settings/network/components/network-list-item.layout.tsx @@ -1,11 +1,13 @@ import { NetworkSelectors } from '@tests/selectors/network.selectors'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Flex, Stack, styled } from 'leather-styles/jsx'; +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; import type { NetworkConfiguration } from '@leather.io/models'; -import { Button, CheckmarkIcon, CloudOffIcon, TrashIcon } from '@leather.io/ui'; +import { Button, CheckmarkIcon, CloudOffIcon } from '@leather.io/ui'; -import { getUrlHostname } from '@app/common/utils'; +import { getUrlHostname, truncateString } from '@app/common/utils'; + +import { NetworkItemMenu } from './network-list-item-menu'; interface NetworkListItemLayoutProps { networkId: string; @@ -14,8 +16,10 @@ interface NetworkListItemLayoutProps { isCustom: boolean; network: NetworkConfiguration; onSelectNetwork(): void; + onEditNetwork(): void; onRemoveNetwork(id: string): void; } + export function NetworkListItemLayout({ networkId, isOnline, @@ -24,6 +28,7 @@ export function NetworkListItemLayout({ isCustom, onRemoveNetwork, onSelectNetwork, + onEditNetwork, }: NetworkListItemLayoutProps) { const unselectable = !isOnline || isActive; return ( @@ -49,31 +54,28 @@ export function NetworkListItemLayout({ > - - {network.name} - + + {truncateString(network.name, 20)} + {isActive && ( + + )} + + {getUrlHostname(network.chain.stacks.url)} - {!isOnline ? ( - - ) : isActive ? ( - - ) : null} + {!isOnline ? : null} + {isOnline && isCustom && ( + onRemoveNetwork(network.id)} + onEditNetwork={onEditNetwork} + /> + )} - {isCustom && ( - - )} ); diff --git a/src/app/features/settings/network/network-list-item.tsx b/src/app/features/settings/network/network-list-item.tsx index 6134f12b195..3a399d1a4de 100644 --- a/src/app/features/settings/network/network-list-item.tsx +++ b/src/app/features/settings/network/network-list-item.tsx @@ -10,12 +10,14 @@ interface NetworkListItemProps { isCustom: boolean; onNetworkSelected(networkId: string): void; onRemoveNetwork(networkId: string): void; + onEditNetwork(): void; } export function NetworkListItem({ networkId, onNetworkSelected, onRemoveNetwork, isCustom, + onEditNetwork, }: NetworkListItemProps) { const currentNetworkId = useCurrentNetworkId(); const networks = useNetworks(); @@ -31,6 +33,7 @@ export function NetworkListItem({ networkId={networkId} isCustom={isCustom} onSelectNetwork={() => onNetworkSelected(networkId)} + onEditNetwork={onEditNetwork} onRemoveNetwork={onRemoveNetwork} /> ); diff --git a/src/app/features/settings/network/network.tsx b/src/app/features/settings/network/network.tsx index a79ef308ab0..7a3ff0fa32b 100644 --- a/src/app/features/settings/network/network.tsx +++ b/src/app/features/settings/network/network.tsx @@ -3,16 +3,15 @@ import { useNavigate } from 'react-router-dom'; import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { WalletDefaultNetworkConfigurationIds } from '@leather.io/models'; -import { Button, Dialog } from '@leather.io/ui'; +import { Button, Dialog, DialogHeader } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; +import { Footer } from '@app/components/layout'; import { NetworkListItem } from '@app/features/settings/network/network-list-item'; import { useCurrentNetworkState, useNetworksActions } from '@app/store/networks/networks.hooks'; import { useNetworks } from '@app/store/networks/networks.selectors'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; const defaultNetworkIds = Object.values(WalletDefaultNetworkConfigurationIds) as string[]; @@ -23,7 +22,6 @@ interface NetworkDialogProps { export function NetworkDialog({ onClose }: NetworkDialogProps) { const navigate = useNavigate(); const networks = useNetworks(); - const networksActions = useNetworksActions(); const currentNetwork = useCurrentNetworkState(); @@ -75,6 +73,14 @@ export function NetworkDialog({ onClose }: NetworkDialogProps) { if (id === currentNetwork.id) networksActions.changeNetwork('mainnet'); removeNetwork(id); }} + onEditNetwork={() => { + onClose(); + navigate(RouteUrls.AddNetwork, { + state: { + network: networks[id], + }, + }); + }} /> ))} diff --git a/src/app/features/settings/settings.tsx b/src/app/features/settings/settings.tsx index 84f8a3e345b..1dc8804d7fc 100644 --- a/src/app/features/settings/settings.tsx +++ b/src/app/features/settings/settings.tsx @@ -24,17 +24,18 @@ import { import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; +import { useHasKeys } from '@app/common/hooks/auth/use-has-keys'; import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { useModifierKey } from '@app/common/hooks/use-modifier-key'; import { useWalletType } from '@app/common/use-wallet-type'; +import { truncateString } from '@app/common/utils'; import { openInNewTab, openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; import { AppVersion } from '@app/components/app-version'; import { Divider } from '@app/components/layout/divider'; import { NetworkDialog } from '@app/features/settings/network/network'; import { SignOut } from '@app/features/settings/sign-out/sign-out-confirm'; import { ThemeDialog } from '@app/features/settings/theme/theme-dialog'; -import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useHasLedgerKeys, useLedgerDeviceTargetId } from '@app/store/ledger/ledger.selectors'; +import { useLedgerDeviceTargetId } from '@app/store/ledger/ledger.selectors'; import { useCurrentNetworkId } from '@app/store/networks/networks.selectors'; import { openFeedbackDialog } from '../feedback-button/feedback-button'; @@ -50,7 +51,9 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) const [showSignOut, setShowSignOut] = useState(false); const [showChangeTheme, setShowChangeTheme] = useState(false); const [showChangeNetwork, setShowChangeNetwork] = useState(false); - const hasGeneratedWallet = !!useCurrentStacksAccount(); + + const { hasKeys, hasLedgerKeys } = useHasKeys(); + const { lockWallet } = useKeyActions(); const currentNetworkId = useCurrentNetworkId(); @@ -61,7 +64,6 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) const location = useLocation(); - const isLedger = useHasLedgerKeys(); const { isPressed: showAdvancedMenuOptions } = useModifierKey('alt', 120); return ( @@ -80,12 +82,12 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) })} > - {isLedger && targetId && ( + {hasLedgerKeys && targetId && ( )} - {hasGeneratedWallet && ( + {hasKeys && ( )} - {hasGeneratedWallet && walletType === 'software' && ( + {hasKeys && walletType === 'software' && ( navigate(RouteUrls.ViewSecretKey)} @@ -130,7 +132,7 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) Change network - {currentNetworkId} + {truncateString(currentNetworkId.toString(), 15)} @@ -175,7 +177,7 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) {showAdvancedMenuOptions && } - {hasGeneratedWallet && walletType === 'software' && ( + {hasKeys && walletType === 'software' && ( { void analytics.track('lock_session'); @@ -190,14 +192,16 @@ export function Settings({ triggerButton, toggleSwitchAccount }: SettingsProps) )} - setShowSignOut(!showSignOut)} - data-testid={SettingsSelectors.SignOutListItem} - > - } textStyle="label.02"> - Sign out - - + {hasKeys && ( + setShowSignOut(!showSignOut)} + data-testid={SettingsSelectors.SignOutListItem} + > + } textStyle="label.02"> + Sign out + + + )} diff --git a/src/app/features/settings/sign-out/sign-out.tsx b/src/app/features/settings/sign-out/sign-out.tsx index 04e83ee6e6b..54a3f0811a3 100644 --- a/src/app/features/settings/sign-out/sign-out.tsx +++ b/src/app/features/settings/sign-out/sign-out.tsx @@ -2,11 +2,10 @@ import { SettingsSelectors } from '@tests/selectors/settings.selectors'; import { useFormik } from 'formik'; import { Flex, HStack, styled } from 'leather-styles/jsx'; -import { Button, Callout, Dialog } from '@leather.io/ui'; +import { Button, Callout, Dialog, DialogHeader } from '@leather.io/ui'; import { useWalletType } from '@app/common/use-wallet-type'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; +import { Footer } from '@app/components/layout'; interface SignOutDialogProps { isShowing: boolean; diff --git a/src/app/features/settings/theme/theme-dialog.tsx b/src/app/features/settings/theme/theme-dialog.tsx index 96cd5117739..9ad35a84ad1 100644 --- a/src/app/features/settings/theme/theme-dialog.tsx +++ b/src/app/features/settings/theme/theme-dialog.tsx @@ -1,11 +1,10 @@ import { useCallback } from 'react'; -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { analytics } from '@shared/utils/analytics'; import { UserSelectedTheme, themeLabelMap, useThemeSwitcher } from '@app/common/theme-provider'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { ThemeListItem } from './theme-list-item'; diff --git a/src/app/features/stacks-high-fee-warning/stacks-high-fee-dialog.tsx b/src/app/features/stacks-high-fee-warning/stacks-high-fee-dialog.tsx index 9ebcd5850e7..4079cf6630b 100644 --- a/src/app/features/stacks-high-fee-warning/stacks-high-fee-dialog.tsx +++ b/src/app/features/stacks-high-fee-warning/stacks-high-fee-dialog.tsx @@ -2,13 +2,12 @@ import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors'; import { useFormikContext } from 'formik'; import { HStack, Stack } from 'leather-styles/jsx'; -import { Button, Caption, Dialog, ErrorIcon, Link, Title } from '@leather.io/ui'; +import { Button, Caption, Dialog, DialogHeader, ErrorIcon, Link, Title } from '@leather.io/ui'; import { StacksSendFormValues } from '@shared/models/form.model'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; +import { Footer } from '@app/components/layout'; import { useStacksHighFeeWarningContext } from './stacks-high-fee-warning-container'; diff --git a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts index 13c3801ced7..c9441d95680 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts +++ b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts @@ -4,7 +4,7 @@ import { TransactionTypes } from '@stacks/connect'; import BigNumber from 'bignumber.js'; import { useFormikContext } from 'formik'; -import { useGetContractInterfaceQuery, useStxAvailableUnlockedBalance } from '@leather.io/query'; +import { useGetContractInterfaceQuery, useStxCryptoAssetBalance } from '@leather.io/query'; import { stxToMicroStx } from '@leather.io/utils'; import { StacksTransactionFormValues } from '@shared/models/form.model'; @@ -27,11 +27,16 @@ export function useTransactionError() { const { values } = useFormikContext(); const currentAccount = useCurrentStacksAccount(); - const availableUnlockedBalance = useStxAvailableUnlockedBalance(currentAccount?.address ?? ''); + const { data, isLoading: isLoadingStxBalance } = useStxCryptoAssetBalance( + currentAccount?.address ?? '' + ); + const availableUnlockedBalance = data?.unlockedBalance; return useMemo(() => { if (!origin) return TransactionErrorReason.ExpiredRequest; + if (isLoadingStxBalance) return; + if (!transactionRequest || !availableUnlockedBalance || !currentAccount) { return TransactionErrorReason.Generic; } @@ -63,6 +68,7 @@ export function useTransactionError() { } return; }, [ + isLoadingStxBalance, origin, transactionRequest, availableUnlockedBalance, diff --git a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx index ce6e5a6b2a1..0db9930b038 100644 --- a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx +++ b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx @@ -10,7 +10,7 @@ import { FeeTypes } from '@leather.io/models'; import { useCalculateStacksTxFees, useNextNonce, - useStxAvailableUnlockedBalance, + useStxCryptoAssetBalance, } from '@leather.io/query'; import { Link } from '@leather.io/ui'; import { stxToMicroStx } from '@leather.io/utils'; @@ -58,9 +58,12 @@ export function StacksTransactionSigner({ const { data: stxFees } = useCalculateStacksTxFees(stacksTransaction); const stxAddress = useCurrentStacksAccountAddress(); - const availableUnlockedBalance = useStxAvailableUnlockedBalance(stxAddress); + const { data, status: balanceQueryStatus } = useStxCryptoAssetBalance(stxAddress); + const availableUnlockedBalance = data?.availableUnlockedBalance; const navigate = useNavigate(); - const { data: nextNonce } = useNextNonce(stxAddress); + const { data: nextNonce, status: nonceQueryStatus } = useNextNonce(stxAddress); + const canSubmit = balanceQueryStatus === 'success' && nonceQueryStatus === 'success'; + const { search } = useLocation(); useOnMount(() => { @@ -134,7 +137,7 @@ export function StacksTransactionSigner({ )} - + diff --git a/src/app/features/stacks-transaction-request/submit-action.tsx b/src/app/features/stacks-transaction-request/submit-action.tsx index e42728c219a..df4810b0077 100644 --- a/src/app/features/stacks-transaction-request/submit-action.tsx +++ b/src/app/features/stacks-transaction-request/submit-action.tsx @@ -9,7 +9,11 @@ import { useTransactionError } from '@app/features/stacks-transaction-request/ho import { useStacksHighFeeWarningContext } from '../stacks-high-fee-warning/stacks-high-fee-warning-container'; -export function StacksTxSubmitAction() { +interface Props { + canSubmit: boolean; +} + +export function StacksTxSubmitAction({ canSubmit }: Props) { const { handleSubmit, values, validateForm, isSubmitting } = useFormikContext(); @@ -17,7 +21,7 @@ export function StacksTxSubmitAction() { const error = useTransactionError(); - const isDisabled = !!error || Number(values.fee) < 0; + const isDisabled = !!error || Number(values.fee) < 0 || !canSubmit; async function onConfirmTransaction() { const formErrors = await validateForm(); diff --git a/src/app/features/container/total-balance.tsx b/src/app/features/total-balance/total-balance.tsx similarity index 94% rename from src/app/features/container/total-balance.tsx rename to src/app/features/total-balance/total-balance.tsx index 7070924fd0b..644d0507ddf 100644 --- a/src/app/features/container/total-balance.tsx +++ b/src/app/features/total-balance/total-balance.tsx @@ -33,7 +33,7 @@ function TotalBalanceSuspense({ displayAddresssBalanceOf }: TotalBalanceProps) { return ( - {account && displayAddresssBalanceOf === 'stx' && } + {displayAddresssBalanceOf === 'stx' && } {isBitcoinEnabled && displayAddresssBalanceOf === 'all' && } diff --git a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx index d67188eab31..8dbbf6e9e45 100644 --- a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx +++ b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx @@ -5,18 +5,17 @@ import { Stack, styled } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; +import { Card, Content, Page } from '@app/components/layout'; import { BitcoinNativeSegwitAccountLoader } from '@app/components/loaders/bitcoin-account-loader'; import { BtcAssetItemBalanceLoader } from '@app/components/loaders/btc-balance-loader'; import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; import { StxAssetItemBalanceLoader } from '@app/components/loaders/stx-balance-loader'; import { BtcCryptoAssetItem } from '@app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item'; import { StxCryptoAssetItem } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item'; -import { Card } from '@app/ui/layout/card/card'; -import { Page } from '@app/ui/layout/page/page.layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; export function ChooseCryptoAssetToFund() { const navigate = useNavigate(); - const navigateToFund = useCallback( (symbol: string) => navigate(RouteUrls.Fund.replace(':currency', symbol)), [navigate] @@ -24,46 +23,49 @@ export function ChooseCryptoAssetToFund() { return ( <> - - - choose asset
to fund - - } - > - - - {signer => ( - - {(balance, isLoading) => ( - navigateToFund('BTC')} - /> - )} - - )} - + + + + + choose asset
to fund + + } + > + + + {signer => ( + + {(balance, isLoading) => ( + navigateToFund('BTC')} + /> + )} + + )} + - - {account => ( - - {(balance, isLoading) => ( - navigateToFund('STX')} - /> - )} - - )} - - -
-
- + + {account => ( + + {(balance, isLoading) => ( + navigateToFund('STX')} + /> + )} + + )} + +
+
+
+ + ); } diff --git a/src/app/pages/fund/fund.tsx b/src/app/pages/fund/fund.tsx index 506bf98b4e4..9a895a7bcff 100644 --- a/src/app/pages/fund/fund.tsx +++ b/src/app/pages/fund/fund.tsx @@ -9,7 +9,9 @@ import type { import { RouteUrls } from '@shared/route-urls'; +import { Content } from '@app/components/layout'; import { FullPageLoadingSpinner } from '@app/components/loading-spinner'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -51,10 +53,13 @@ export function FundPage() { return ( <> - - - - + + + + + + + ); } diff --git a/src/app/pages/home/home.tsx b/src/app/pages/home/home.tsx index 0e323f01bd3..0f55c4bdf58 100644 --- a/src/app/pages/home/home.tsx +++ b/src/app/pages/home/home.tsx @@ -1,13 +1,16 @@ import { Route, useNavigate, useOutletContext } from 'react-router-dom'; +import { HomePageSelectors } from '@tests/selectors/home.selectors'; +import { Box, Stack } from 'leather-styles/jsx'; + import { RouteUrls } from '@shared/route-urls'; +import { SwitchAccountOutletContext } from '@shared/switch-account'; import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; import { useTotalBalance } from '@app/common/hooks/balance/use-total-balance'; import { useOnMount } from '@app/common/hooks/use-on-mount'; import { ActivityList } from '@app/features/activity-list/activity-list'; -import { SwitchAccountOutletContext } from '@app/features/dialogs/switch-account-dialog/switch-account-dialog'; import { FeedbackButton } from '@app/features/feedback-button/feedback-button'; import { Assets } from '@app/pages/home/components/assets'; import { homePageModalRoutes } from '@app/routes/app-routes'; @@ -16,7 +19,6 @@ import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { AccountCard } from '@app/ui/components/account/account.card'; -import { HomeLayout } from '@app/ui/pages/home.layout'; import { AccountActions } from './components/account-actions'; import { HomeTabs } from './components/home-tabs'; @@ -45,8 +47,19 @@ export function Home() { }); return ( - + - } - > + @@ -68,6 +80,6 @@ export function Home() { {homePageModalRoutes} - + ); } diff --git a/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx b/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx index 7ee4ff0de05..e44a75f6a75 100644 --- a/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx +++ b/src/app/pages/onboarding/back-up-secret-key/back-up-secret-key.tsx @@ -7,9 +7,10 @@ import { EyeSlashIcon, KeyIcon, LockIcon } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; +import { Content, TwoColumnLayout } from '@app/components/layout'; +import { MainHeader } from '@app/features/container/headers/main.header'; import { SecretKey } from '@app/features/secret-key-displayer/secret-key-displayer'; import { useDefaultWalletSecretKey } from '@app/store/in-memory-key/in-memory-key.selectors'; -import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export const BackUpSecretKeyPage = memo(() => { const secretKey = useDefaultWalletSecretKey(); @@ -22,38 +23,43 @@ export const BackUpSecretKeyPage = memo(() => { if (!secretKey) return null; return ( - Back up your Secret Key} - content={ - <> - You'll need it to access your wallet on a new device, or this one if you lose your - password — so back it up somewhere safe! - - } - action={ - - - - - Your Secret Key gives
access to your wallet -
-
- - - - Never share your
Secret Key with anyone -
-
- - - - Store it somewhere
100% private and secure -
-
-
- } - > - -
+ <> + + + + You'll need it to access your wallet on a new device, or this one if you lose your + password — so back it up somewhere safe! + + } + action={ + + + + + Your Secret Key gives
access to your wallet +
+
+ + + + Never share your
Secret Key with anyone +
+
+ + + + Store it somewhere
100% private and secure +
+
+
+ } + > + +
+
+ ); }); diff --git a/src/app/pages/onboarding/set-password/set-password.tsx b/src/app/pages/onboarding/set-password/set-password.tsx index b2b8fa4dffe..7777413e261 100644 --- a/src/app/pages/onboarding/set-password/set-password.tsx +++ b/src/app/pages/onboarding/set-password/set-password.tsx @@ -19,9 +19,10 @@ import { blankPasswordValidation, validatePassword, } from '@app/common/validation/validate-password'; +import { Content, TwoColumnLayout } from '@app/components/layout'; +import { MainHeader } from '@app/features/container/headers/main.header'; import { OnboardingGate } from '@app/routes/onboarding-gate'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; import { PasswordField } from './components/password-field'; @@ -109,46 +110,51 @@ function SetPasswordPage() { }), }); return ( - - {({ dirty, isSubmitting, isValid }) => ( -
- - Set a
- password - - } - content={ - <> - Your password protects your Secret Key on this device only. To access your wallet on - another device, you'll need just your Secret Key. - - } - > - <> - - - -
-
- )} -
+ <> + + + + + + )} + + + ); } diff --git a/src/app/pages/onboarding/sign-in/sign-in.tsx b/src/app/pages/onboarding/sign-in/sign-in.tsx index 6f20c5a85ec..510f4ac8975 100644 --- a/src/app/pages/onboarding/sign-in/sign-in.tsx +++ b/src/app/pages/onboarding/sign-in/sign-in.tsx @@ -3,8 +3,9 @@ import { useEffect, useState } from 'react'; import { Link } from '@leather.io/ui'; import { createNullArrayOfLength } from '@leather.io/utils'; +import { Content, TwoColumnLayout } from '@app/components/layout'; +import { MainHeader } from '@app/features/container/headers/main.header'; import { MnemonicForm } from '@app/pages/onboarding/sign-in/mnemonic-form'; -import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export function SignIn() { const [twentyFourWordMode, setTwentyFourWordMode] = useState(true); @@ -18,29 +19,34 @@ export function SignIn() { }, [twentyFourWordMode]); return ( - - Sign in
with your
Secret Key - - } - content={<>Speed things up by pasting your entire Secret Key in one go.} - action={ - setTwentyFourWordMode(!twentyFourWordMode)} - textStyle="label.03" - width="fit-content" - variant="text" + <> + + + + Sign in
with your
Secret Key + + } + content="Speed things up by pasting your entire Secret Key in one go." + action={ + setTwentyFourWordMode(!twentyFourWordMode)} + textStyle="label.03" + width="fit-content" + variant="text" + > + {twentyFourWordMode ? 'Have a 12-word Secret Key?' : 'Use 24 word Secret Key'} + + } > - {twentyFourWordMode ? 'Have a 12-word Secret Key?' : 'Use 24 word Secret Key'} - - } - > - -
+ +
+ + ); } diff --git a/src/app/ui/pages/welcome.layout.tsx b/src/app/pages/onboarding/welcome/welcome.layout.tsx similarity index 100% rename from src/app/ui/pages/welcome.layout.tsx rename to src/app/pages/onboarding/welcome/welcome.layout.tsx diff --git a/src/app/pages/onboarding/welcome/welcome.tsx b/src/app/pages/onboarding/welcome/welcome.tsx index 5b1b4c98de5..bdeca0af3bf 100644 --- a/src/app/pages/onboarding/welcome/welcome.tsx +++ b/src/app/pages/onboarding/welcome/welcome.tsx @@ -9,7 +9,8 @@ import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state' import { useKeyActions } from '@app/common/hooks/use-key-actions'; import { doesBrowserSupportWebUsbApi, isPopupMode, whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { WelcomeLayout } from '@app/ui/pages/welcome.layout'; + +import { WelcomeLayout } from './welcome.layout'; export function WelcomePage() { const navigate = useNavigate(); diff --git a/src/app/pages/receive/components/receive-tokens.layout.tsx b/src/app/pages/receive/components/receive-tokens.layout.tsx index d476f84da4a..cbf96db96e4 100644 --- a/src/app/pages/receive/components/receive-tokens.layout.tsx +++ b/src/app/pages/receive/components/receive-tokens.layout.tsx @@ -5,12 +5,11 @@ import { SharedComponentsSelectors } from '@tests/selectors/shared-component.sel import { Box, Flex, styled } from 'leather-styles/jsx'; import { token } from 'leather-styles/tokens'; -import { AddressDisplayer, Button, Dialog } from '@leather.io/ui'; +import { AddressDisplayer, Button, Dialog, DialogHeader } from '@leather.io/ui'; import { useLocationState } from '@app/common/hooks/use-location-state'; +import { Footer } from '@app/components/layout'; import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background-location-redirect'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Header } from '@app/ui/components/containers/headers/header'; interface ReceiveTokensLayoutProps { address: string; @@ -29,14 +28,14 @@ export function ReceiveTokensLayout(props: ReceiveTokensLayoutProps) { return ( Receive
{title} } - onGoBack={() => navigate(backgroundLocation ?? '..')} + onClose={() => navigate(backgroundLocation ?? '..')} /> } isShowing diff --git a/src/app/pages/receive/receive-dialog.tsx b/src/app/pages/receive/receive-dialog.tsx index 57ef07fcd89..71608c4a27b 100644 --- a/src/app/pages/receive/receive-dialog.tsx +++ b/src/app/pages/receive/receive-dialog.tsx @@ -4,7 +4,7 @@ import { HomePageSelectors } from '@tests/selectors/home.selectors'; import { Box } from 'leather-styles/jsx'; import get from 'lodash.get'; -import { Dialog, Tabs } from '@leather.io/ui'; +import { Dialog, DialogHeader, Tabs } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; @@ -14,7 +14,6 @@ import { useBackgroundLocationRedirect } from '@app/routes/hooks/use-background- import { useZeroIndexTaprootAddress } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { Header } from '@app/ui/components/containers/headers/header'; import { ReceiveCollectibles } from './components/receive-collectibles'; import { ReceiveTokens } from './components/receive-tokens'; @@ -86,10 +85,10 @@ export function ReceiveDialog({ type = 'full' }: ReceiveDialogProps) { return ( navigate(backgroundLocation ?? '..')} + onClose={() => navigate(backgroundLocation ?? '..')} /> } onClose={() => navigate(backgroundLocation ?? '..')} diff --git a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx b/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx index 4b2b8429401..fba9ead95c6 100644 --- a/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx +++ b/src/app/pages/rpc-get-addresses/components/get-addresses.layout.tsx @@ -8,9 +8,10 @@ interface GetAddressesLayoutProps { requester: string; onUserApproveGetAddresses(): void; } -export function GetAddressesLayout(props: GetAddressesLayoutProps) { - const { requester, onUserApproveGetAddresses } = props; - +export function GetAddressesLayout({ + requester, + onUserApproveGetAddresses, +}: GetAddressesLayoutProps) { return ( - {children} - - ); -} diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx index 5d377e4a7ba..4fe359d610e 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer-container.tsx @@ -1,11 +1,14 @@ import { useState } from 'react'; import { Outlet, useOutletContext } from 'react-router-dom'; +import { Flex } from 'leather-styles/jsx'; + import type { BtcFeeType } from '@leather.io/models'; import { closeWindow } from '@shared/utils'; -import { RpcSendTransferContainerLayout } from './components/rpc-send-transfer-container.layout'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; + import { useRpcSendTransfer } from './use-rpc-send-transfer'; interface RpcSendTransferContextState { @@ -27,8 +30,11 @@ export function RpcSendTransferContainer() { } return ( - - - + <> + + + + + ); } diff --git a/src/app/pages/rpc-send-transfer/rpc-send-transfer-summary.tsx b/src/app/pages/rpc-send-transfer/rpc-send-transfer-summary.tsx index c53db7c3196..b39b14f6cc0 100644 --- a/src/app/pages/rpc-send-transfer/rpc-send-transfer-summary.tsx +++ b/src/app/pages/rpc-send-transfer/rpc-send-transfer-summary.tsx @@ -17,8 +17,8 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; +import { Card } from '@app/components/layout'; import { useToast } from '@app/features/toasts/use-toast'; -import { Card } from '@app/ui/layout/card/card'; export function RpcSendTransferSummary() { const { state } = useLocation(); diff --git a/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx b/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx index f9baaa93a53..245d75bb1f3 100644 --- a/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx +++ b/src/app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message.tsx @@ -7,6 +7,7 @@ import { closeWindow } from '@shared/utils'; import { Disclaimer } from '@app/components/disclaimer'; import { NoFeesWarningRow } from '@app/components/no-fees-warning-row'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; import { MessagePreviewBox } from '@app/features/message-signer/message-preview-box'; import { MessageSigningRequestLayout } from '@app/features/message-signer/message-signing-request.layout'; import { AccountGate } from '@app/routes/account-gate'; @@ -58,6 +59,7 @@ function RpcSignBip322Message() { return ( <> + + <> + + + ); } diff --git a/src/app/pages/send/broadcast-error/broadcast-error.tsx b/src/app/pages/send/broadcast-error/broadcast-error.tsx index 2536c2cc68b..1b1afa1ba6d 100644 --- a/src/app/pages/send/broadcast-error/broadcast-error.tsx +++ b/src/app/pages/send/broadcast-error/broadcast-error.tsx @@ -2,13 +2,12 @@ import { useLocation, useNavigate } from 'react-router-dom'; import get from 'lodash.get'; -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { BroadcastErrorLayout } from './components/broadcast-error.layout'; diff --git a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx index 3942c3452c1..47f13bf7808 100644 --- a/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx +++ b/src/app/pages/send/choose-crypto-asset/choose-crypto-asset.tsx @@ -4,9 +4,10 @@ import { Box, styled } from 'leather-styles/jsx'; import { RouteUrls } from '@shared/route-urls'; +import { Card, Content, Page } from '@app/components/layout'; import { AssetList } from '@app/features/asset-list/asset-list'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useConfigBitcoinSendEnabled } from '@app/query/common/remote-config/remote-config.query'; -import { Card } from '@app/ui/layout/card/card'; export function ChooseCryptoAsset() { const navigate = useNavigate(); @@ -21,16 +22,23 @@ export function ChooseCryptoAsset() { } return ( - - choose asset
to send - - } - > - - - -
+ <> + + + + + choose asset
to send + + } + > + + + +
+
+
+ ); } diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-choose-fee.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-choose-fee.tsx index 3f44a7b911f..5f7d5c9a6e9 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-choose-fee.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-choose-fee.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Outlet, useNavigate } from 'react-router-dom'; import type { BtcFeeType } from '@leather.io/models'; -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { createMoney } from '@leather.io/utils'; import { RouteUrls } from '@shared/route-urls'; @@ -14,7 +14,6 @@ import { import { LoadingSpinner } from '@app/components/loading-spinner'; import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { useSendInscriptionState } from './components/send-inscription-container'; import { useSendInscriptionFeesList } from './hooks/use-send-inscription-fees-list'; diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx index 339f28d5c5e..c57b848b57e 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-form.tsx @@ -3,15 +3,14 @@ import { useNavigate } from 'react-router-dom'; import { Form, Formik } from 'formik'; import { Box, Flex } from 'leather-styles/jsx'; -import { Button, Dialog, OrdinalAvatarIcon } from '@leather.io/ui'; +import { Button, Dialog, DialogHeader, OrdinalAvatarIcon } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { ErrorLabel } from '@app/components/error-label'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; import { InscriptionPreviewCard } from '@app/components/inscription-preview-card/inscription-preview-card'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; +import { Footer } from '@app/components/layout'; import { RecipientAddressTypeField } from '../send-crypto-asset-form/components/recipient-address-type-field'; import { CollectibleAsset } from './components/collectible-asset'; diff --git a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx index 810c6b5c418..b1e0ad03a44 100644 --- a/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx +++ b/src/app/pages/send/ordinal-inscription/send-inscription-review.tsx @@ -5,7 +5,7 @@ import { Box, Flex, Stack } from 'leather-styles/jsx'; import get from 'lodash.get'; import { useBitcoinBroadcastTransaction } from '@leather.io/query'; -import { Button, Dialog } from '@leather.io/ui'; +import { Button, Dialog, DialogHeader } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; @@ -13,12 +13,10 @@ import { analytics } from '@shared/utils/analytics'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; import { InfoCardRow, InfoCardSeparator } from '@app/components/info-card/info-card'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; +import { Card, Footer } from '@app/components/layout'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useAppDispatch } from '@app/store'; import { inscriptionSent } from '@app/store/ordinals/ordinals.slice'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; -import { Card } from '@app/ui/layout/card/card'; import { InscriptionPreviewCard } from '../../../components/inscription-preview-card/inscription-preview-card'; import { useSendInscriptionState } from './components/send-inscription-container'; diff --git a/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx b/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx index 8cbcff5c829..3dc7341b58a 100644 --- a/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx +++ b/src/app/pages/send/ordinal-inscription/sent-inscription-summary.tsx @@ -4,7 +4,7 @@ import { Box, Flex, HStack, Stack } from 'leather-styles/jsx'; import get from 'lodash.get'; import type { Blockchains, Inscription } from '@leather.io/models'; -import { CheckmarkIcon, CopyIcon, Dialog, ExternalLinkIcon } from '@leather.io/ui'; +import { CheckmarkIcon, CopyIcon, Dialog, DialogHeader, ExternalLinkIcon } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; import { analytics } from '@shared/utils/analytics'; @@ -14,10 +14,8 @@ import { copyToClipboard } from '@app/common/utils/copy-to-clipboard'; import { FormAddressDisplayer } from '@app/components/address-displayer/form-address-displayer'; import { InfoCardBtn, InfoCardRow, InfoCardSeparator } from '@app/components/info-card/info-card'; import { InscriptionPreview } from '@app/components/inscription-preview-card/components/inscription-preview'; +import { Card, Footer } from '@app/components/layout'; import { useToast } from '@app/features/toasts/use-toast'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; -import { Card } from '@app/ui/layout/card/card'; import { InscriptionPreviewCard } from '../../../components/inscription-preview-card/inscription-preview-card'; diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx index 3af89fdde84..8b2b093ea0f 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-dialog/recipient-accounts-dialog.tsx @@ -4,11 +4,10 @@ import { Virtuoso } from 'react-virtuoso'; import { Box } from 'leather-styles/jsx'; -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { DialogHeader } from '@app/ui/components/containers/headers/dialog-header'; import { VirtuosoWrapper } from '@app/ui/components/virtuoso'; import { AccountListItem } from './account-list-item'; diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx index 3c43058f2aa..fd4b702e5a6 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-choose-fee.tsx @@ -18,9 +18,11 @@ import { OnChooseFeeArgs, } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; +import { Content, Page } from '@app/components/layout'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useToast } from '@app/features/toasts/use-toast'; import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; import { useSignBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; @@ -124,42 +126,52 @@ export function BrcChooseFee() { } } - return isLoadingOrder ? ( - - - - ) : ( + return ( <> - setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - selectedFeeType={selectedFeeType} - /> - } - isLoading={isLoading} - isSendingMax={false} - onChooseFee={previewTransaction} - onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} - onValidateBitcoinSpend={onValidateBitcoinFeeSpend} - recommendedFeeRate={recommendedFeeRate} - recipients={recipients} - showError={showInsufficientBalanceError} - maxRecommendedFeeRate={feesList[0]?.feeRate} - /> - + + + + {isLoadingOrder && ( + + + + )} + {!isLoadingOrder && ( + <> + setSelectedFeeType(value)} + onValidateBitcoinSpend={onValidateBitcoinFeeSpend} + selectedFeeType={selectedFeeType} + /> + } + isLoading={isLoading} + isSendingMax={false} + onChooseFee={previewTransaction} + onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} + onValidateBitcoinSpend={onValidateBitcoinFeeSpend} + recommendedFeeRate={recommendedFeeRate} + recipients={recipients} + showError={showInsufficientBalanceError} + maxRecommendedFeeRate={feesList[0]?.feeRate} + /> + + + )} + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx index 9eb2af315e1..fbbd719607c 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form-confirmation.tsx @@ -17,11 +17,10 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; +import { Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; import { useBrc20Transfers } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { useSendFormNavigate } from '../../hooks/use-send-form-navigate'; @@ -106,39 +105,46 @@ export function Brc20SendFormConfirmation() { } return ( - - - - } - > - - - - - - - - - - - - - + <> + + + + + + + } + > + + + + + + + + + + + + + + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx index bc3ffd79965..edc1e5317d5 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/brc20/brc20-send-form.tsx @@ -10,10 +10,8 @@ import { Brc20AvatarIcon, Button, Callout, Link } from '@leather.io/ui'; import { convertAmountToBaseUnit, formatMoney } from '@leather.io/utils'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; +import { AvailableBalance, Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { AmountField } from '../../components/amount-field'; import { SelectedAssetField } from '../../components/selected-asset-field'; @@ -38,80 +36,91 @@ export function Brc20SendForm() { useBrc20SendForm({ balance, ticker, holderAddress }); return ( - - - {props => { - onFormStateChange(props.values); - return ( -
- - + + + } > - Continue - - - - } - > - - - } - autoComplete="off" - switchableAmount={ - marketData ? ( - + + } + autoComplete="off" + switchableAmount={ + marketData ? ( + + ) : undefined + } /> - ) : undefined - } - /> - } name={ticker} symbol={ticker} /> - - -
  • 1. Create transfer inscription with amount to send
  • -
  • 2. Send transfer inscription to recipient of choice
  • -
    - { - openInNewTab( - 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' - ); - }} - textStyle="body.02" - > - Learn more - -
    -
    -
    + } + name={ticker} + symbol={ticker} + /> + + +
  • 1. Create transfer inscription with amount to send
  • +
  • 2. Send transfer inscription to recipient of choice
  • +
    + { + openInNewTab( + 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' + ); + }} + textStyle="body.02" + > + Learn more + +
    + + - - - ); - }} -
    -
    + + + ); + }} + +
    + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx index 7a08c2c58ed..768f93630cc 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-choose-fee.tsx @@ -8,8 +8,10 @@ import { BitcoinSendFormValues } from '@shared/models/form.model'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; import { BitcoinFeesList } from '@app/components/bitcoin-fees-list/bitcoin-fees-list'; import { useBitcoinFeesList } from '@app/components/bitcoin-fees-list/use-bitcoin-fees-list'; +import { Content, Page } from '@app/components/layout'; import { BitcoinChooseFee } from '@app/features/bitcoin-choose-fee/bitcoin-choose-fee'; import { useValidateBitcoinSpend } from '@app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useSendBitcoinAssetContextState } from '../../family/bitcoin/components/send-bitcoin-asset-container'; import { useBtcChooseFee } from './use-btc-choose-fee'; @@ -49,30 +51,35 @@ export function BtcChooseFee() { return ( <> - + + + setSelectedFeeType(value)} + onValidateBitcoinSpend={onValidateBitcoinAmountSpend} + selectedFeeType={selectedFeeType} + /> + } isLoading={isLoading} + isSendingMax={isSendingMax} onChooseFee={previewTransaction} - onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} onValidateBitcoinSpend={onValidateBitcoinAmountSpend} - selectedFeeType={selectedFeeType} + onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} + recipients={recipients} + recommendedFeeRate={recommendedFeeRate} + showError={showInsufficientBalanceError} + maxRecommendedFeeRate={feesList[0]?.feeRate} /> - } - isLoading={isLoading} - isSendingMax={isSendingMax} - onChooseFee={previewTransaction} - onValidateBitcoinSpend={onValidateBitcoinAmountSpend} - onSetSelectedFeeType={(value: BtcFeeType | null) => setSelectedFeeType(value)} - recipients={recipients} - recommendedFeeRate={recommendedFeeRate} - showError={showInsufficientBalanceError} - maxRecommendedFeeRate={feesList[0]?.feeRate} - /> - + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx index b54a4b4b730..5ee31ef7d0f 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form-confirmation.tsx @@ -34,10 +34,9 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; +import { Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { useSendFormNavigate } from '../../hooks/use-send-form-navigate'; @@ -134,50 +133,57 @@ export function BtcSendFormConfirmation() { } return ( - - + + } > - Confirm and send transaction - - - } - > - - - - - } - data-testid={SendCryptoAssetSelectors.ConfirmationDetailsRecipient} - /> - - - - - {arrivesIn && } - - - + + + + + } + data-testid={SendCryptoAssetSelectors.ConfirmationDetailsRecipient} + /> + + + + + {arrivesIn && } + + + + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx index d5559567d73..02fcffb548a 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx @@ -9,10 +9,8 @@ import { useCryptoCurrencyMarketDataMeanAverage } from '@leather.io/query'; import { BtcAvatarIcon, Button, Callout, Link } from '@leather.io/ui'; import { formatMoney } from '@leather.io/utils'; -import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; +import { AvailableBalance, Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { AmountField } from '../../components/amount-field'; import { SelectedAssetField } from '../../components/selected-asset-field'; @@ -28,7 +26,6 @@ const symbol: CryptoCurrencies = 'BTC'; export function BtcSendForm() { const routeState = useSendFormRouteState(); const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC'); - const { balance, calcMaxSpend, @@ -43,84 +40,95 @@ export function BtcSendForm() { } = useBtcSendForm(); return ( - - - {props => { - onFormStateChange(props.values); - const sendMaxCalculation = calcMaxSpend(props.values.recipient, utxos); + <> + + + + + + {props => { + onFormStateChange(props.values); + const sendMaxCalculation = calcMaxSpend(props.values.recipient, utxos); - return ( -
    - - + + + } > - Continue - - - - } - > - - - } - onSetIsSendingMax={onSetIsSendingMax} - isSendingMax={isSendingMax} - switchableAmount={ - - } - /> - } name="Bitcoin" symbol={symbol} /> - - {currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && ( - - This is a Bitcoin testnet transaction. - - Get testnet BTC here ↗ - - - )} - - - + + + } + onSetIsSendingMax={onSetIsSendingMax} + isSendingMax={isSendingMax} + switchableAmount={ + + } + /> + } + name="Bitcoin" + symbol={symbol} + /> + + {currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && ( + + This is a Bitcoin testnet transaction. + + Get testnet BTC here ↗ + + + )} + + + - {/* This is for testing purposes only, to make sure the form is ready to be submitted. */} - {calcMaxSpend(props.values.recipient, utxos).spendableBitcoin.toNumber() > 0 ? ( - - ) : null} - - ); - }} -
    -
    + {/* This is for testing purposes only, to make sure the form is ready to be submitted. */} + {calcMaxSpend(props.values.recipient, utxos).spendableBitcoin.toNumber() > 0 ? ( + + ) : null} + + ); + }} +
    +
    + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx index 08d933cfc1b..5b0bcd0b5d5 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/send-form-confirmation.tsx @@ -9,9 +9,7 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; +import { Card, CardContent, Footer } from '@app/components/layout'; interface SendFormConfirmationProps { recipient: string; diff --git a/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form.tsx index e594deb3f67..da2a81f0a63 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form.tsx @@ -5,6 +5,8 @@ import { useAlexCurrencyPriceAsMarketData, useSip10Token } from '@leather.io/que import { RouteUrls } from '@shared/route-urls'; +import { Content } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useToast } from '@app/features/toasts/use-toast'; import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -41,14 +43,19 @@ function Sip10TokenSendFormLoader({ children }: Sip10TokenSendFormLoaderProps) { export function Sip10TokenSendForm() { return ( - - {token => ( - - )} - + <> + + + + {token => ( + + )} + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-common-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-common-send-form.tsx index fa2dc988213..bc505838b84 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-common-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-common-send-form.tsx @@ -14,13 +14,10 @@ import { StacksSendFormValues } from '@shared/models/form.model'; import { RouteUrls } from '@shared/route-urls'; import { FeesRow } from '@app/components/fees-row/fees-row'; +import { AvailableBalance, Card, CardContent, Footer, Page } from '@app/components/layout'; import { NonceSetter } from '@app/components/nonce-setter'; import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values'; import { HighFeeDialog } from '@app/features/stacks-high-fee-warning/stacks-high-fee-dialog'; -import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { MemoField } from '../../components/memo-field'; import { StacksRecipientField } from '../../family/stacks/components/stacks-recipient-field'; @@ -52,58 +49,60 @@ export function StacksCommonSendForm({ const navigate = useNavigate(); const { onFormStateChange } = useUpdatePersistedSendFormValues(); return ( - - - {props => { - onFormStateChange(props.values); - return ( - <> - -
    - - + + + } + > + + {amountField} + {selectedAssetField} + + + + + + navigate(RouteUrls.EditNonce)} > - Continue - - - - } - > - - {amountField} - {selectedAssetField} - - - - - - navigate(RouteUrls.EditNonce)} - > - Edit nonce - - - - - - - - ); - }} -
    -
    + Edit nonce + + + + + + + + ); + }} + +
    + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx index c7756aa7fe1..8455c3e6a59 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx @@ -7,6 +7,8 @@ import type { CryptoCurrencies } from '@leather.io/models'; import { InfoCircleIcon } from '@leather.io/ui'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; +import { Content, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; import { useStacksTransactionSummary } from '@app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; @@ -23,6 +25,7 @@ function useStacksSendFormConfirmationState() { export function StacksSendFormConfirmation() { const { tx, decimals, showFeeChangeWarning } = useStacksSendFormConfirmationState(); + const { symbol = 'STX' } = useParams(); const { stacksBroadcastTransaction, isBroadcasting } = useStacksBroadcastTransaction({ @@ -65,23 +68,28 @@ export function StacksSendFormConfirmation() { return ( <> - - stacksBroadcastTransaction(stacksDeserializedTransaction)} - /> + + + + + stacksBroadcastTransaction(stacksDeserializedTransaction)} + /> + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx index 665e702683b..2b80e54abd5 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stx/stx-send-form.tsx @@ -2,6 +2,9 @@ import type { CryptoCurrencies } from '@leather.io/models'; import { useCryptoCurrencyMarketDataMeanAverage } from '@leather.io/query'; import { StxAvatarIcon } from '@leather.io/ui'; +import { Content } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; + import { AmountField } from '../../components/amount-field'; import { SelectedAssetField } from '../../components/selected-asset-field'; import { SendFiatValue } from '../../components/send-fiat-value'; @@ -13,7 +16,6 @@ const symbol = 'STX' satisfies CryptoCurrencies; export function StxSendForm() { const stxMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol); - const { availableUnlockedBalance, initialValues, @@ -41,14 +43,19 @@ export function StxSendForm() { ); return ( - + <> + + + + + ); } diff --git a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx index 1867d49c15b..0c990810cf5 100644 --- a/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx +++ b/src/app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes.tsx @@ -1,5 +1,5 @@ import { Suspense } from 'react'; -import { Outlet, Route } from 'react-router-dom'; +import { Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; @@ -11,7 +11,6 @@ import { ledgerStacksTxSigningRoutes } from '@app/features/ledger/flows/stacks-t import { StacksHighFeeWarningContainer } from '@app/features/stacks-high-fee-warning/stacks-high-fee-warning-container'; import { SendBtcDisabled } from '@app/pages/send/choose-crypto-asset/send-btc-disabled'; import { AccountGate } from '@app/routes/account-gate'; -import { Page } from '@app/ui/layout/page/page.layout'; import { BroadcastError } from '../broadcast-error/broadcast-error'; import { ChooseCryptoAsset } from '../choose-crypto-asset/choose-crypto-asset'; @@ -43,13 +42,7 @@ const broadcastErrorDialogRoute = ( ); export const sendCryptoAssetFormRoutes = ( - - - - } - > + } /> + }> - - } - label="Pending BRC-20 transfers" - onClick={onClickLink} - /> - - - } - > - - + <> + + + + + + } + label="Pending BRC-20 transfers" + onClick={onClickLink} + /> + + + } + > + + - + - - - - - You'll need to send the transfer inscription to your recipient of choice from the - home screen once its status changes to "Ready to send" - - { - openInNewTab('https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens'); - }} - > - Learn more - - - - + + + + + You'll need to send the transfer inscription to your recipient of choice from + the home screen once its status changes to "Ready to send" + + { + openInNewTab( + 'https://leather.gitbook.io/guides/bitcoin/sending-brc-20-tokens' + ); + }} + > + Learn more + + + + - - - + + + - - - - - + + + + + + + + ); } diff --git a/src/app/pages/send/sent-summary/btc-sent-summary.tsx b/src/app/pages/send/sent-summary/btc-sent-summary.tsx index 2df406c9659..eb533b6a9b0 100644 --- a/src/app/pages/send/sent-summary/btc-sent-summary.tsx +++ b/src/app/pages/send/sent-summary/btc-sent-summary.tsx @@ -15,10 +15,9 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; +import { Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useToast } from '@app/features/toasts/use-toast'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { TxDone } from '../send-crypto-asset-form/components/tx-done'; @@ -55,36 +54,47 @@ export function BtcSentSummary() { } return ( - - - } label="View details" onClick={onClickLink} /> - } label="Copy ID" onClick={onClickCopy} /> - - - } - > - - - + <> + + + + + + } + label="View details" + onClick={onClickLink} + /> + } label="Copy ID" onClick={onClickCopy} /> + + + } + > + + + - - } /> - - + + } /> + + - - - {arrivesIn && } - - - + + + {arrivesIn && } + + + + + + ); } diff --git a/src/app/pages/send/sent-summary/stx-sent-summary.tsx b/src/app/pages/send/sent-summary/stx-sent-summary.tsx index 91096a6be51..21cf965be63 100644 --- a/src/app/pages/send/sent-summary/stx-sent-summary.tsx +++ b/src/app/pages/send/sent-summary/stx-sent-summary.tsx @@ -15,16 +15,14 @@ import { InfoCardRow, InfoCardSeparator, } from '@app/components/info-card/info-card'; +import { Card, CardContent, Content, Footer, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useToast } from '@app/features/toasts/use-toast'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { TxDone } from '../send-crypto-asset-form/components/tx-done'; export function StxSentSummary() { const { state } = useLocation(); - const toast = useToast(); const { @@ -55,37 +53,48 @@ export function StxSentSummary() { } return ( - - - } label="View details" onClick={onClickLink} /> - } label="Copy ID" onClick={onClickCopy} /> - - - } - > - - + <> + + + + + + } + label="View details" + onClick={onClickLink} + /> + } label="Copy ID" onClick={onClickCopy} /> + + + } + > + + - + - - } /> - - + + } /> + + - - - - - - + + + + + + + + + ); } diff --git a/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx b/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx index 1214ea51515..63191dcb3c0 100644 --- a/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx +++ b/src/app/pages/sign-stacks-message-request/sign-stacks-message-request.tsx @@ -1,5 +1,6 @@ import { isSignableMessageType } from '@shared/signature/signature-types'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; import { StacksMessageSigning } from '@app/features/stacks-message-signer/stacks-message-signing'; import { useSignStacksMessageRequest, @@ -18,14 +19,18 @@ export function SignStacksMessageRequest() { if (!payload) return null; return ( - + <> + {/* TODO check this route for '/signature' as it seems STX specific but we don't show balance */} + + + ); } diff --git a/src/app/pages/swap/alex-swap-container.tsx b/src/app/pages/swap/alex-swap-container.tsx index 5bacfe5d6bd..638df0b5695 100644 --- a/src/app/pages/swap/alex-swap-container.tsx +++ b/src/app/pages/swap/alex-swap-container.tsx @@ -20,10 +20,11 @@ import { alex } from '@shared/utils/alex-sdk'; import { migratePositiveAssetBalancesToTop } from '@app/common/asset-utils'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; +import { Content, Page } from '@app/components/layout'; +import { PageHeader } from '@app/features/container/headers/page.header'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useGenerateStacksContractCallUnsignedTx } from '@app/store/transactions/contract-call.hooks'; import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; -import { Page } from '@app/ui/layout/page/page.layout'; import { SwapForm } from './components/swap-form'; import { generateSwapRoutes } from './generate-swap-routes'; @@ -172,11 +173,15 @@ function AlexSwapContainer() { return ( - - - - - + {/* Swap uses routed dialogs to choose assets so needs onBackLocation to go Home */} + + + + + + + + ); } diff --git a/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-base.tsx b/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-base.tsx index 8dd31a13773..5733b032f5b 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-base.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-base.tsx @@ -1,9 +1,7 @@ -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; -import { Header } from '@app/ui/components/containers/headers/header'; - import { useSwapNavigate } from '../../hooks/use-swap-navigate'; import { useSwapContext } from '../../swap.context'; import { SwapAssetList } from './components/swap-asset-list'; @@ -17,14 +15,14 @@ export function SwapAssetDialogBase() { isShowing onClose={() => navigate(RouteUrls.Swap)} header={ -
    Choose asset
    to swap } - variant="bigTitle" - onGoBack={() => navigate(RouteUrls.Swap)} + variant="large" + onClose={() => navigate(RouteUrls.Swap)} /> } > diff --git a/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-quote.tsx b/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-quote.tsx index 81ce10f70b3..c387b560347 100644 --- a/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-quote.tsx +++ b/src/app/pages/swap/components/swap-asset-dialog/swap-asset-dialog-quote.tsx @@ -1,9 +1,7 @@ -import { Dialog } from '@leather.io/ui'; +import { Dialog, DialogHeader } from '@leather.io/ui'; import { RouteUrls } from '@shared/route-urls'; -import { Header } from '@app/ui/components/containers/headers/header'; - import { useSwapNavigate } from '../../hooks/use-swap-navigate'; import { useSwapContext } from '../../swap.context'; import { SwapAssetList } from './components/swap-asset-list'; @@ -17,14 +15,14 @@ export function SwapAssetDialogQuote() { isShowing onClose={() => navigate(RouteUrls.Swap)} header={ -
    Choose asset
    to receive } - variant="bigTitle" - onGoBack={() => navigate(RouteUrls.Swap)} + variant="large" + onClose={() => navigate(RouteUrls.Swap)} /> } > diff --git a/src/app/pages/swap/components/swap-review.tsx b/src/app/pages/swap/components/swap-review.tsx index c7f8675f6d3..3a3bbebc2ea 100644 --- a/src/app/pages/swap/components/swap-review.tsx +++ b/src/app/pages/swap/components/swap-review.tsx @@ -5,9 +5,7 @@ import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { Button } from '@leather.io/ui'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; +import { Card, CardContent, Footer } from '@app/components/layout'; import { useSwapContext } from '../swap.context'; import { SwapAssetsPair } from './swap-assets-pair/swap-assets-pair'; diff --git a/src/app/pages/swap/swap.tsx b/src/app/pages/swap/swap.tsx index 260777648e1..a9cc1eadcf5 100644 --- a/src/app/pages/swap/swap.tsx +++ b/src/app/pages/swap/swap.tsx @@ -7,10 +7,8 @@ import { useFormikContext } from 'formik'; import { Button } from '@leather.io/ui'; import { isUndefined } from '@leather.io/utils'; +import { Card, CardContent, Footer } from '@app/components/layout'; import { LoadingSpinner } from '@app/components/loading-spinner'; -import { Footer } from '@app/ui/components/containers/footers/footer'; -import { Card } from '@app/ui/layout/card/card'; -import { CardContent } from '@app/ui/layout/card/card-content'; import { SwapAssetSelectBase } from './components/swap-asset-select/swap-asset-select-base'; import { SwapAssetSelectQuote } from './components/swap-asset-select/swap-asset-select-quote'; diff --git a/src/app/pages/transaction-request/transaction-request.tsx b/src/app/pages/transaction-request/transaction-request.tsx index 9734f21849c..a549c5e79a1 100644 --- a/src/app/pages/transaction-request/transaction-request.tsx +++ b/src/app/pages/transaction-request/transaction-request.tsx @@ -10,7 +10,7 @@ import { FeeTypes } from '@leather.io/models'; import { useCalculateStacksTxFees, useNextNonce, - useStxAvailableUnlockedBalance, + useStxCryptoAssetBalance, } from '@leather.io/query'; import { Link } from '@leather.io/ui'; @@ -23,6 +23,7 @@ import { useOnMount } from '@app/common/hooks/use-on-mount'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; import { nonceValidator } from '@app/common/validation/nonce-validators'; import { NonceSetter } from '@app/components/nonce-setter'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; import { RequestingTabClosedWarningMessage } from '@app/features/errors/requesting-tab-closed-error-msg'; import { HighFeeDialog } from '@app/features/stacks-high-fee-warning/stacks-high-fee-dialog'; import { ContractCallDetails } from '@app/features/stacks-transaction-request/contract-call-details/contract-call-details'; @@ -50,8 +51,12 @@ function TransactionRequestBase() { const generateUnsignedTx = useGenerateUnsignedStacksTransaction(); const stxAddress = useCurrentStacksAccountAddress(); - const availableUnlockedBalance = useStxAvailableUnlockedBalance(stxAddress); - const { data: nextNonce } = useNextNonce(stxAddress); + const { data, status: balanceQueryStatus } = useStxCryptoAssetBalance(stxAddress); + const availableUnlockedBalance = data?.availableUnlockedBalance; + + const { data: nextNonce, status: nonceQueryStatus } = useNextNonce(stxAddress); + const canSubmit = balanceQueryStatus === 'success' && nonceQueryStatus === 'success'; + const navigate = useNavigate(); const { stacksBroadcastTransaction } = useStacksBroadcastTransaction({ token: 'STX' }); @@ -94,46 +99,53 @@ function TransactionRequestBase() { }; return ( - - + + - {() => ( - <> - - - - - - {transactionRequest.txType === 'contract_call' && } - {transactionRequest.txType === 'token_transfer' && } - {transactionRequest.txType === 'smart_contract' && } - - - - navigate(RouteUrls.EditNonce)}> - Edit nonce - - - - - - - - )} - - + + {() => ( + <> + + + + + + {transactionRequest.txType === 'contract_call' && } + {transactionRequest.txType === 'token_transfer' && } + {transactionRequest.txType === 'smart_contract' && } + + + + navigate(RouteUrls.EditNonce)} + > + Edit nonce + + + + + + + + )} + + + ); } diff --git a/src/app/pages/unlock.tsx b/src/app/pages/unlock.tsx index da4ad908c1a..ead840ed975 100644 --- a/src/app/pages/unlock.tsx +++ b/src/app/pages/unlock.tsx @@ -1,18 +1,22 @@ import { Outlet, useNavigate } from 'react-router-dom'; +import { Content } from '@app/components/layout'; import { RequestPassword } from '@app/components/request-password'; +import { PageHeader } from '@app/features/container/headers/page.header'; export function Unlock() { const navigate = useNavigate(); - // Here we want to return to the previous route. The user could land on any // page when the wallet is locked, so we can't assume as single route. const returnToPreviousRoute = () => navigate(-1); return ( <> - - + + + + + ); } diff --git a/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx b/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx index a8955e1f6d8..0b62e7f958a 100644 --- a/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx +++ b/src/app/pages/update-profile-request/components/update-profile-request.layout.tsx @@ -1,5 +1,7 @@ import { Stack } from 'leather-styles/jsx'; +import { PopupHeader } from '@app/features/container/headers/popup.header'; + import { PageTop } from './page-top'; interface ProfileUpdateRequestLayoutProps { @@ -7,14 +9,17 @@ interface ProfileUpdateRequestLayoutProps { } export function ProfileUpdateRequestLayout({ children }: ProfileUpdateRequestLayoutProps) { return ( - - - {children} - + <> + + + + {children} + + ); } diff --git a/src/app/pages/view-secret-key/view-secret-key.tsx b/src/app/pages/view-secret-key/view-secret-key.tsx index 1e6a6eb74d4..82a5a97a0fd 100644 --- a/src/app/pages/view-secret-key/view-secret-key.tsx +++ b/src/app/pages/view-secret-key/view-secret-key.tsx @@ -3,10 +3,11 @@ import { Outlet } from 'react-router-dom'; import { analytics } from '@shared/utils/analytics'; +import { Content, TwoColumnLayout } from '@app/components/layout'; import { RequestPassword } from '@app/components/request-password'; +import { MainHeader } from '@app/features/container/headers/main.header'; import { SecretKey } from '@app/features/secret-key-displayer/secret-key-displayer'; import { useDefaultWalletSecretKey } from '@app/store/in-memory-key/in-memory-key.selectors'; -import { TwoColumnLayout } from '@app/ui/pages/two-column.layout'; export function ViewSecretKey() { const defaultWalletSecretKey = useDefaultWalletSecretKey(); @@ -18,30 +19,38 @@ export function ViewSecretKey() { if (showSecretKey) { return ( - - Your
    Secret Key - - } - content={ - <> - These 24 words are your Secret Key. They create your account, and you sign in on - different devices with them. Make sure to save these somewhere safe. -
    - If you lose these words, you lose your account. - - } - > - -
    + <> + + + + Your
    Secret Key + + } + content={ + <> + These 24 words are your Secret Key. They create your account, and you sign in on + different devices with them. Make sure to save these somewhere safe. +
    + If you lose these words, you lose your account. + + } + > + +
    +
    + ); } return ( <> - setShowSecretKey(true)} /> - + + + setShowSecretKey(true)} /> + + ); } diff --git a/src/app/routes/app-routes.tsx b/src/app/routes/app-routes.tsx index 4f3158f4249..6392f92875c 100644 --- a/src/app/routes/app-routes.tsx +++ b/src/app/routes/app-routes.tsx @@ -10,10 +10,12 @@ import * as Sentry from '@sentry/react'; import { RouteUrls } from '@shared/route-urls'; +import { Content } from '@app/components/layout/layouts/content.layout'; +import { SwitchAccountLayout } from '@app/components/layout/layouts/switch-account.layout'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { AddNetwork } from '@app/features/add-network/add-network'; import { Container } from '@app/features/container/container'; -import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; +import { MainHeader } from '@app/features/container/headers/main.header'; import { IncreaseBtcFeeDialog } from '@app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog'; import { IncreaseStxFeeDialog } from '@app/features/dialogs/increase-fee-dialog/increase-stx-fee-dialog'; import { leatherIntroDialogRoutes } from '@app/features/dialogs/leather-intro-dialog/leather-intro-dialog'; @@ -36,7 +38,6 @@ import { WelcomePage } from '@app/pages/onboarding/welcome/welcome'; import { ReceiveBtcModal } from '@app/pages/receive/receive-btc'; import { ReceiveStxModal } from '@app/pages/receive/receive-stx'; import { RequestError } from '@app/pages/request-error/request-error'; -import { RpcSignStacksTransaction } from '@app/pages/rpc-sign-stacks-transaction/rpc-sign-stacks-transaction'; import { BroadcastError } from '@app/pages/send/broadcast-error/broadcast-error'; import { sendOrdinalRoutes } from '@app/pages/send/ordinal-inscription/ordinal-routes'; import { sendCryptoAssetFormRoutes } from '@app/pages/send/send-crypto-asset-form/send-crypto-asset-form.routes'; @@ -79,15 +80,44 @@ function useAppRoutes() { }> }> - - + <> + + + + + } > - {homePageModalRoutes} + + + + } + > + {homePageModalRoutes} + + + } + /> + }> + {ledgerStacksTxSigningRoutes} + + } + /> + }> + {ledgerBitcoinTxSigningRoutes} + + + {ledgerStacksTxSigningRoutes} + {/* Page Routes */} + + + } + /> + + + + + } + > + } /> + } /> + + + + + } + > + } /> + + + {sendCryptoAssetFormRoutes} + + }> + {leatherIntroDialogRoutes} + + } /> + + + + } + /> + + {alexSwapRoutes} + + {/* OnBoarding Routes */} + - + } /> + + {/* Popup Routes */} + {/* ChooseAccount is a popup as shown only in popup when decodedAuthRequest in set-password */} {ledgerJwtSigningRoutes} - - - - - } - > - } /> - } /> - - - - - } - > - } /> - - - {sendCryptoAssetFormRoutes} - - - - - } - /> - }> - {leatherIntroDialogRoutes} - - {legacyRequestRoutes} {rpcRequestRoutes} - } /> - - - - } - /> - - - - - } - > - } /> - - - { - const { RpcSignBip322MessageRoute } = await import( - '@app/pages/rpc-sign-bip322-message/rpc-sign-bip322-message' - ); - return { Component: RpcSignBip322MessageRoute }; - }} - > - {ledgerBitcoinTxSigningRoutes} - - - {alexSwapRoutes} - - {/* Catch-all route redirects to onboarding */} - } /> + + {/* Catch-all route redirects to onboarding */} + } /> ) ); diff --git a/src/app/routes/hooks/use-on-change-account.ts b/src/app/routes/hooks/use-on-change-account.ts new file mode 100644 index 00000000000..c1d3c58c1a0 --- /dev/null +++ b/src/app/routes/hooks/use-on-change-account.ts @@ -0,0 +1,13 @@ +import { InternalMethods } from '@shared/message-types'; +import type { BackgroundMessages } from '@shared/messages'; + +import { useOnMount } from '@app/common/hooks/use-on-mount'; + +export function useOnChangeAccount(handler: (accountIndex: number) => void) { + useOnMount(() => { + chrome.runtime.onMessage.addListener((message: BackgroundMessages, _sender, sendResponse) => { + if (message?.method === InternalMethods.AccountChanged) handler(message.payload.accountIndex); + sendResponse(); + }); + }); +} diff --git a/src/app/routes/rpc-routes.tsx b/src/app/routes/rpc-routes.tsx index 8893db05c47..0f2a1f98c36 100644 --- a/src/app/routes/rpc-routes.tsx +++ b/src/app/routes/rpc-routes.tsx @@ -3,6 +3,7 @@ import { Route } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; +import { EditNonceDialog } from '@app/features/dialogs/edit-nonce-dialog/edit-nonce-dialog'; import { ledgerBitcoinTxSigningRoutes } from '@app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container'; import { ledgerStacksMessageSigningRoutes } from '@app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg.routes'; import { RpcGetAddresses } from '@app/pages/rpc-get-addresses/rpc-get-addresses'; @@ -10,6 +11,7 @@ import { rpcSendTransferRoutes } from '@app/pages/rpc-send-transfer/rpc-send-tra import { RpcSignPsbt } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt'; import { RpcSignPsbtSummary } from '@app/pages/rpc-sign-psbt/rpc-sign-psbt-summary'; import { RpcStacksMessageSigning } from '@app/pages/rpc-sign-stacks-message/rpc-sign-stacks-message'; +import { RpcSignStacksTransaction } from '@app/pages/rpc-sign-stacks-transaction/rpc-sign-stacks-transaction'; import { AccountGate } from '@app/routes/account-gate'; import { SuspenseLoadingSpinner } from './app-routes'; @@ -66,5 +68,16 @@ export const rpcRequestRoutes = ( > {ledgerStacksMessageSigningRoutes} + + + + + } + > + } /> + ); diff --git a/src/app/ui/components/containers/container.layout.tsx b/src/app/ui/components/containers/container.layout.tsx deleted file mode 100644 index 6167da4552d..00000000000 --- a/src/app/ui/components/containers/container.layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Flex } from 'leather-styles/jsx'; - -interface ContainerLayoutProps { - children: React.JSX.Element | React.JSX.Element[]; - header: React.JSX.Element | null; -} -export function ContainerLayout({ children, header }: ContainerLayoutProps) { - return ( - - {header} - - {children} - - - ); -} diff --git a/src/app/ui/components/containers/headers/components/big-title-header.tsx b/src/app/ui/components/containers/headers/components/big-title-header.tsx deleted file mode 100644 index b64910a12b9..00000000000 --- a/src/app/ui/components/containers/headers/components/big-title-header.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode } from 'react'; - -import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; -import { Flex, styled } from 'leather-styles/jsx'; - -import { CloseIcon } from '@leather.io/ui'; - -import { HeaderActionButton } from './header-action-button'; - -interface BigTitleHeaderProps { - onClose?(): void; - title?: ReactNode; -} - -export function BigTitleHeader({ onClose, title }: BigTitleHeaderProps) { - return ( - - {title} - {onClose && ( - - } - dataTestId={SharedComponentsSelectors.HeaderCloseBtn} - onAction={onClose} - /> - - )} - - ); -} diff --git a/src/app/ui/components/containers/headers/dialog-header.tsx b/src/app/ui/components/containers/headers/dialog-header.tsx deleted file mode 100644 index 40d0f58093a..00000000000 --- a/src/app/ui/components/containers/headers/dialog-header.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { ReactNode } from 'react'; - -import { Flex, styled } from 'leather-styles/jsx'; - -import { CloseIcon, IconButton } from '@leather.io/ui'; - -interface DialogHeaderProps { - onClose?(): void; - title?: ReactNode; -} - -export function DialogHeader({ onClose, title }: DialogHeaderProps) { - return ( - - {title && ( - - {title} - - )} - {onClose && } onClick={onClose} position="absolute" />} - - ); -} diff --git a/src/app/ui/components/containers/headers/header.stories.tsx b/src/app/ui/components/containers/headers/header.stories.tsx deleted file mode 100644 index d6b48b0b883..00000000000 --- a/src/app/ui/components/containers/headers/header.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta } from '@storybook/react'; - -import { Header as Component, HeaderProps } from './header'; - -const meta: Meta = { - component: Component, - tags: ['autodocs'], - title: 'Containers/Header', - args: { - variant: 'home', - }, -}; - -export default meta; - -export function Header(args: HeaderProps) { - return null} onGoBack={() => null} />; -} - -export function PageHeader(args: HeaderProps) { - return null} />; -} diff --git a/src/app/ui/components/containers/headers/header.tsx b/src/app/ui/components/containers/headers/header.tsx deleted file mode 100644 index 285d5e58d36..00000000000 --- a/src/app/ui/components/containers/headers/header.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { ReactNode } from 'react'; - -import { SharedComponentsSelectors } from '@tests/selectors/shared-component.selectors'; -import { Flex, Grid, GridItem, HStack, styled } from 'leather-styles/jsx'; - -import { ArrowLeftIcon, CloseIcon } from '@leather.io/ui'; - -import { BigTitleHeader } from './components/big-title-header'; -import { HeaderActionButton } from './components/header-action-button'; - -type HeaderVariants = 'page' | 'home' | 'onboarding' | 'bigTitle' | 'fund'; - -export interface HeaderProps { - variant: HeaderVariants; - onClose?(): void; - onGoBack?(): void; - title?: ReactNode; - account?: ReactNode; - totalBalance?: ReactNode; - settingsMenu?: ReactNode; - networkBadge?: ReactNode; - logo?: ReactNode; -} - -export function Header({ - variant, - onClose, - onGoBack, - account, - totalBalance, - settingsMenu, - networkBadge, - title, - logo, -}: HeaderProps) { - const logoItem = onGoBack || logo || account; - - return ( - - - - {logoItem && ( - - {variant !== 'home' && onGoBack ? ( - } - onAction={onGoBack} - dataTestId={SharedComponentsSelectors.HeaderBackBtn} - /> - ) : undefined} - {account ? account : logo} - - )} - - - {title && {title}} - - - - {networkBadge} - {totalBalance && totalBalance} - {settingsMenu} - {variant !== 'bigTitle' && onClose && ( - } - dataTestId={SharedComponentsSelectors.HeaderCloseBtn} - onAction={onClose} - /> - )} - - - - {variant === 'bigTitle' && } - - ); -} diff --git a/src/app/ui/components/virtuoso.tsx b/src/app/ui/components/virtuoso.tsx index b87aebbc45f..aa79cfd6d84 100644 --- a/src/app/ui/components/virtuoso.tsx +++ b/src/app/ui/components/virtuoso.tsx @@ -20,7 +20,7 @@ export function VirtuosoWrapper({ children, hasFooter, isPopup }: VirtuosoWrappe const [key, setKey] = useState(0); const isAtLeastMd = useViewportMinWidth('md'); const virtualHeight = isAtLeastMd ? '70vh' : '100vh'; - const headerHeight = isPopup ? 230 : 80; + const headerHeight = isPopup ? 230 : 60; const footerHeight = hasFooter ? 95 : 0; const heightOffset = headerHeight + footerHeight; const height = vhToPixels(virtualHeight) - heightOffset; @@ -33,9 +33,10 @@ export function VirtuosoWrapper({ children, hasFooter, isPopup }: VirtuosoWrappe return ( diff --git a/src/app/ui/pages/home.layout.stories.tsx b/src/app/ui/pages/home.layout.stories.tsx deleted file mode 100644 index bd972f72553..00000000000 --- a/src/app/ui/pages/home.layout.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { Meta } from '@storybook/react'; -import { Box, Flex, Stack } from 'leather-styles/jsx'; - -import { ArrowDownIcon, ArrowUpIcon, IconButton, PlusIcon, SwapIcon, Tabs } from '@leather.io/ui'; - -import { RouteUrls } from '@shared/route-urls'; - -import { HomeLayout as Component } from './home.layout'; - -const meta: Meta = { - component: Component, - tags: ['autodocs'], - title: 'Pages/Home', -}; - -export default meta; - -export function HomeLayout() { - return ( - - } label="Send" /> - } label="Receive" /> - } label="Buy" /> - } label="Swap" /> - - } - > - - - - - Assets - - - Activity - - - - - - - ); -} diff --git a/src/app/ui/pages/home.layout.tsx b/src/app/ui/pages/home.layout.tsx deleted file mode 100644 index ee06606f5e8..00000000000 --- a/src/app/ui/pages/home.layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { ReactNode } from 'react'; - -import { HomePageSelectors } from '@tests/selectors/home.selectors'; -import { Box, Stack } from 'leather-styles/jsx'; - -interface HomeLayoutProps { - children: ReactNode; - accountCard: ReactNode; -} - -export function HomeLayout({ children, accountCard }: HomeLayoutProps) { - return ( - - - {accountCard} - - {children} - - ); -} diff --git a/src/background/background-analytics.ts b/src/background/background-analytics.ts new file mode 100644 index 00000000000..8a5f9ba0dad --- /dev/null +++ b/src/background/background-analytics.ts @@ -0,0 +1,19 @@ +// Segment/Mixpanel libraries are not compatible with extension background +// scripts. This function adds analytics requests to chrome.storage.local so +// that, when opened, an extension frame (that does support analyics) can read +// and fire the requests. +const queueStore = 'backgroundAnalyticsRequests'; + +export async function queueAnalyticsRequest( + eventName: string, + properties: Record = {} +) { + const currentQueue = await chrome.storage.local.get(queueStore); + const queue = currentQueue[queueStore] ?? []; + return chrome.storage.local.set({ + [queueStore]: [ + ...queue, + { eventName, properties: { ...properties, backgroundQueuedMessage: true } }, + ], + }); +} diff --git a/src/background/messaging/rpc-message-handler.ts b/src/background/messaging/rpc-message-handler.ts index 4efb3119b29..f863fbef2a8 100644 --- a/src/background/messaging/rpc-message-handler.ts +++ b/src/background/messaging/rpc-message-handler.ts @@ -2,6 +2,7 @@ import { RpcErrorCode } from '@btckit/types'; import { WalletRequests, makeRpcErrorResponse } from '@shared/rpc/rpc-methods'; +import { queueAnalyticsRequest } from '@background/background-analytics'; import { rpcSignStacksTransaction } from '@background/messaging/rpc-methods/sign-stacks-transaction'; import { getTabIdFromPort } from './messaging-utils'; @@ -63,3 +64,18 @@ export async function rpcMessageHandler(message: WalletRequests, port: chrome.ru break; } } + +interface TrackRpcRequestSuccess { + endpoint: WalletRequests['method']; +} +export async function trackRpcRequestSuccess(args: TrackRpcRequestSuccess) { + return queueAnalyticsRequest('rpc_request_successful', { ...args }); +} + +interface TrackRpcRequestError { + endpoint: WalletRequests['method']; + error: string; +} +export async function trackRpcRequestError(args: TrackRpcRequestError) { + return queueAnalyticsRequest('rpc_request_error', { ...args }); +} diff --git a/src/background/messaging/rpc-methods/get-addresses.ts b/src/background/messaging/rpc-methods/get-addresses.ts index 396478563a2..0582ee9dce3 100644 --- a/src/background/messaging/rpc-methods/get-addresses.ts +++ b/src/background/messaging/rpc-methods/get-addresses.ts @@ -8,10 +8,13 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestSuccess } from '../rpc-message-handler'; export async function rpcGetAddresses(message: GetAddressesRequest, port: chrome.runtime.Port) { const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [['requestId', message.id]]); const { id } = await triggerRequestWindowOpen(RouteUrls.RpcGetAddresses, urlParams); + void trackRpcRequestSuccess({ endpoint: message.method }); + listenForPopupClose({ tabId, id, diff --git a/src/background/messaging/rpc-methods/send-transfer.ts b/src/background/messaging/rpc-methods/send-transfer.ts index ad48b3fd787..973cf733465 100644 --- a/src/background/messaging/rpc-methods/send-transfer.ts +++ b/src/background/messaging/rpc-methods/send-transfer.ts @@ -21,12 +21,15 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestError, trackRpcRequestSuccess } from '../rpc-message-handler'; export async function rpcSendTransfer( message: RpcRequest<'sendTransfer', RpcSendTransferParams | SendTransferRequestParams>, port: chrome.runtime.Port ) { if (isUndefined(message.params)) { + void trackRpcRequestError({ endpoint: 'sendTransfer', error: 'Undefined parameters' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('sendTransfer', { @@ -43,6 +46,8 @@ export async function rpcSendTransfer( : (message.params as RpcSendTransferParams); if (!validateRpcSendTransferParams(params)) { + void trackRpcRequestError({ endpoint: 'sendTransfer', error: 'Invalid parameters' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('sendTransfer', { @@ -56,6 +61,8 @@ export async function rpcSendTransfer( return; } + void trackRpcRequestSuccess({ endpoint: message.method }); + const recipients: [string, string][] = params.recipients.map(({ address }) => [ 'recipient', address, diff --git a/src/background/messaging/rpc-methods/sign-message.ts b/src/background/messaging/rpc-methods/sign-message.ts index b475c62a8cf..e795c7ab135 100644 --- a/src/background/messaging/rpc-methods/sign-message.ts +++ b/src/background/messaging/rpc-methods/sign-message.ts @@ -18,9 +18,11 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestError, trackRpcRequestSuccess } from '../rpc-message-handler'; export async function rpcSignMessage(message: SignMessageRequest, port: chrome.runtime.Port) { if (isUndefined(message.params)) { + void trackRpcRequestError({ endpoint: 'signMessage', error: 'Undefined parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('signMessage', { @@ -32,6 +34,8 @@ export async function rpcSignMessage(message: SignMessageRequest, port: chrome.r } if (!validateRpcSignMessageParams(message.params)) { + void trackRpcRequestError({ endpoint: 'signMessage', error: 'Invalid parameters' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('signMessage', { @@ -49,6 +53,8 @@ export async function rpcSignMessage(message: SignMessageRequest, port: chrome.r (message.params as any).paymentType ?? 'p2wpkh'; if (!isSupportedMessageSigningPaymentType(paymentType)) { + void trackRpcRequestError({ endpoint: 'signMessage', error: 'Unsupported payment type' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('signMessage', { @@ -63,6 +69,8 @@ export async function rpcSignMessage(message: SignMessageRequest, port: chrome.r return; } + void trackRpcRequestSuccess({ endpoint: message.method }); + const requestParams: RequestParams = [ ['message', message.params.message], ['network', (message.params as any).network ?? 'mainnet'], diff --git a/src/background/messaging/rpc-methods/sign-psbt.ts b/src/background/messaging/rpc-methods/sign-psbt.ts index 4b57dc92cfc..51567ef8502 100644 --- a/src/background/messaging/rpc-methods/sign-psbt.ts +++ b/src/background/messaging/rpc-methods/sign-psbt.ts @@ -19,6 +19,7 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestError, trackRpcRequestSuccess } from '../rpc-message-handler'; function validatePsbt(hex: string) { try { @@ -31,9 +32,10 @@ function validatePsbt(hex: string) { export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime.Port) { if (isUndefined(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Undefined parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), - makeRpcErrorResponse('signPsbt', { + makeRpcErrorResponse(message.method, { id: message.id, error: { code: RpcErrorCode.INVALID_REQUEST, message: 'Parameters undefined' }, }) @@ -42,9 +44,10 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime } if (!validateRpcSignPsbtParams(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Invalid parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), - makeRpcErrorResponse('signPsbt', { + makeRpcErrorResponse(message.method, { id: message.id, error: { code: RpcErrorCode.INVALID_PARAMS, @@ -56,6 +59,8 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime } if (!validatePsbt(message.params.hex)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Invalid PSBT' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('signPsbt', { @@ -88,6 +93,8 @@ export async function rpcSignPsbt(message: SignPsbtRequest, port: chrome.runtime requestParams.push(['signAtIndex', index.toString()]) ); + void trackRpcRequestSuccess({ endpoint: message.method }); + const { urlParams, tabId } = makeSearchParamsWithDefaults(port, requestParams); const { id } = await triggerRequestWindowOpen(RouteUrls.RpcSignPsbt, urlParams); diff --git a/src/background/messaging/rpc-methods/sign-stacks-message.ts b/src/background/messaging/rpc-methods/sign-stacks-message.ts index ba01a3779de..d9eede1020c 100644 --- a/src/background/messaging/rpc-methods/sign-stacks-message.ts +++ b/src/background/messaging/rpc-methods/sign-stacks-message.ts @@ -17,12 +17,14 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestError, trackRpcRequestSuccess } from '../rpc-message-handler'; export async function rpcSignStacksMessage( message: SignStacksMessageRequest, port: chrome.runtime.Port ) { if (isUndefined(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Undefined parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('stx_signMessage', { @@ -34,6 +36,7 @@ export async function rpcSignStacksMessage( } if (!validateRpcSignStacksMessageParams(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Invalid parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('stx_signMessage', { @@ -47,6 +50,8 @@ export async function rpcSignStacksMessage( return; } + void trackRpcRequestSuccess({ endpoint: message.method }); + const requestParams: RequestParams = [ ['message', message.params.message], ['messageType', message.params.messageType], diff --git a/src/background/messaging/rpc-methods/sign-stacks-transaction.ts b/src/background/messaging/rpc-methods/sign-stacks-transaction.ts index bdc66e1552d..c408382a80d 100644 --- a/src/background/messaging/rpc-methods/sign-stacks-transaction.ts +++ b/src/background/messaging/rpc-methods/sign-stacks-transaction.ts @@ -36,6 +36,7 @@ import { makeSearchParamsWithDefaults, triggerRequestWindowOpen, } from '../messaging-utils'; +import { trackRpcRequestError, trackRpcRequestSuccess } from '../rpc-message-handler'; const MEMO_DESERIALIZATION_STUB = '\u0000'; @@ -114,6 +115,7 @@ export async function rpcSignStacksTransaction( port: chrome.runtime.Port ) { if (isUndefined(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Undefined parameters' }); chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('stx_signTransaction', { @@ -125,6 +127,8 @@ export async function rpcSignStacksTransaction( } if (!validateRpcSignStacksTransactionParams(message.params)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Invalid parameters' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('stx_signTransaction', { @@ -139,6 +143,8 @@ export async function rpcSignStacksTransaction( } if (!validateStacksTransaction(message.params.txHex!)) { + void trackRpcRequestError({ endpoint: message.method, error: 'Invalid Stacks transaction' }); + chrome.tabs.sendMessage( getTabIdFromPort(port), makeRpcErrorResponse('stx_signTransaction', { @@ -160,6 +166,8 @@ export async function rpcSignStacksTransaction( const isMultisig = hashMode === AddressHashMode.SerializeP2SH || hashMode === AddressHashMode.SerializeP2WSH; + void trackRpcRequestSuccess({ endpoint: message.method }); + const requestParams = [ ['txHex', message.params.txHex], ['requestId', message.id], diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 65504545716..d82ad2f07e1 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -2,7 +2,7 @@ export const gaiaUrl = 'https://hub.blockstack.org'; export const ZERO_INDEX = 0; -export const GITHUB_ORG = 'leather-wallet'; +export const GITHUB_ORG = 'leather-io'; export const GITHUB_REPO = 'extension'; export const HIRO_EXPLORER_URL = 'https://explorer.hiro.so'; diff --git a/src/shared/message-types.ts b/src/shared/message-types.ts index c72f326e9ab..d59379e500b 100644 --- a/src/shared/message-types.ts +++ b/src/shared/message-types.ts @@ -28,6 +28,7 @@ export enum ExternalMethods { export enum InternalMethods { RequestDerivedStxAccounts = 'RequestDerivedStxAccounts', OriginatingTabClosed = 'OriginatingTabClosed', + AccountChanged = 'AccountChanged', } export type ExtensionMethods = ExternalMethods | InternalMethods; diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 4ffb8c78cd2..90418eb7306 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -13,7 +13,9 @@ type OriginatingTabClosed = BackgroundMessage< { tabId: number } >; -export type BackgroundMessages = OriginatingTabClosed; +type AccountChanged = BackgroundMessage; + +export type BackgroundMessages = OriginatingTabClosed | AccountChanged; export function sendMessage(message: BackgroundMessages) { return chrome.runtime.sendMessage(message); diff --git a/src/shared/switch-account.ts b/src/shared/switch-account.ts new file mode 100644 index 00000000000..2ca026ac282 --- /dev/null +++ b/src/shared/switch-account.ts @@ -0,0 +1,4 @@ +export interface SwitchAccountOutletContext { + isShowingSwitchAccount: boolean; + setIsShowingSwitchAccount(isShowing: boolean): void; +} diff --git a/test-app/src/components/debugger.tsx b/test-app/src/components/debugger.tsx index 0399d981727..54eae6b145a 100644 --- a/test-app/src/components/debugger.tsx +++ b/test-app/src/components/debugger.tsx @@ -131,13 +131,17 @@ export const Debugger = () => { sponsored = false ) => { clearState(); + const contractAddress = network.isMainnet() + ? 'SPY0682ZM7VGPMVGQP99Z05J3QWMVV83RA6N42SA' + : 'STS8CKF63P16J28AYF7PXW9E5AACH0NZNRV74CM7'; + const args = [ uintCV(1234), intCV(-234), bufferCV(Buffer.from('hello, world')), stringAsciiCV('hey-ascii'), stringUtf8CV('hey-utf8'), - standardPrincipalCV('STS8CKF63P16J28AYF7PXW9E5AACH0NZNRV74CM7'), + standardPrincipalCV(contractAddress), trueCV(), ]; const postConditions = [ @@ -150,7 +154,7 @@ export const Debugger = () => { console.log('creating allow mode contract call'); await doContractCall({ network, - contractAddress: 'STS8CKF63P16J28AYF7PXW9E5AACH0NZNRV74CM7', + contractAddress, contractName: 'faker', functionName: 'rawr', functionArgs: args, diff --git a/tests/fixtures/fixtures.ts b/tests/fixtures/fixtures.ts index f775b73fed9..197f8b8cb36 100644 --- a/tests/fixtures/fixtures.ts +++ b/tests/fixtures/fixtures.ts @@ -45,6 +45,7 @@ export const test = base.extend({ }); await use(context); await context.close(); + await context.browser()?.close(); }, extensionId: async ({ context }, use) => { let [background] = context.serviceWorkers(); diff --git a/tests/mocks/mock-apis.ts b/tests/mocks/mock-apis.ts index 8954790995a..26ced78e51f 100644 --- a/tests/mocks/mock-apis.ts +++ b/tests/mocks/mock-apis.ts @@ -2,7 +2,7 @@ import { Page } from '@playwright/test'; import { json } from '@tests/utils'; import { mockStacksFeeRequests } from './mock-stacks-fees'; -import { mockMainnetTestAccountBlockstreamRequests } from './mock-utxos'; +import { mockMainnetTestAccountBitcoinRequests } from './mock-utxos'; export async function setupMockApis(page: Page) { await Promise.all([ @@ -10,7 +10,7 @@ export async function setupMockApis(page: Page) { page.route(/github/, route => route.fulfill(json({}))), page.route('https://api.hiro.so/', route => route.fulfill()), page.route('https://api.testnet.hiro.so/', route => route.fulfill()), - mockMainnetTestAccountBlockstreamRequests(page), + mockMainnetTestAccountBitcoinRequests(page), mockStacksFeeRequests(page), ]); } diff --git a/tests/mocks/mock-utxos.ts b/tests/mocks/mock-utxos.ts index a74a8d3361f..afbe5bafac0 100644 --- a/tests/mocks/mock-utxos.ts +++ b/tests/mocks/mock-utxos.ts @@ -113,15 +113,15 @@ const mockMainnetNsTransactionsTestAccount = [ }, ]; -export async function mockMainnetTestAccountBlockstreamRequests(page: Page) { +export async function mockMainnetTestAccountBitcoinRequests(page: Page) { await Promise.all([ - page.route('**/blockstream.info/api/address/**/utxo', route => + page.route('**/leather.mempool.space/api/address/**/utxo', route => route.fulfill({ json: [], }) ), page.route( - `**/blockstream.info/api/address/${TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS}/txs`, + `**/leather.mempool.space/api/address/${TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS}/txs`, route => route.fulfill({ json: mockMainnetNsTransactionsTestAccount, diff --git a/tests/page-object-models/send.page.ts b/tests/page-object-models/send.page.ts index d89e993482e..70f05055e91 100644 --- a/tests/page-object-models/send.page.ts +++ b/tests/page-object-models/send.page.ts @@ -105,4 +105,8 @@ export class SendPage { async clickInfoCardButton() { await this.infoCardButton.click(); } + + async waitForFeeRow() { + await this.page.getByTestId(SharedComponentsSelectors.FeeRow).waitFor({ state: 'attached' }); + } } diff --git a/tests/specs/send/send-stx.spec.ts b/tests/specs/send/send-stx.spec.ts index 79cb1911fcd..cc83ccc146d 100644 --- a/tests/specs/send/send-stx.spec.ts +++ b/tests/specs/send/send-stx.spec.ts @@ -110,6 +110,10 @@ test.describe('send stx: tests on testnet', () => { }); test.describe('send form validation', () => { + test.beforeEach(async ({ sendPage }) => { + await sendPage.waitForFeeRow(); + }); + test('that the amount must be a number', async ({ sendPage }) => { await sendPage.amountInput.fill('aaaaaa'); await sendPage.amountInput.blur();