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}
+
+
+
+
+ )}
+
+
+
+ >
);
}
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 }) => (
-
- )}
-
+ <>
+
+
+ >
+
+
+ )}
+
+
+ >
);
}
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 (
-
-
- );
- }}
-
-
+
+
+ );
+ }}
+
+
+
+
+ >
);
}
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
+
+
+ }
>
- 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 (
-
-
- props.handleSubmit()}
- type="submit"
+ return (
+
+
+ props.handleSubmit()}
+ type="submit"
+ >
+ Continue
+
+
+
+ }
>
- 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 (
- <>
-
-
-
- props.handleSubmit()}
- type="submit"
+
+
+
+ {props => {
+ onFormStateChange(props.values);
+ return (
+ <>
+
+
+
+ props.handleSubmit()}
+ type="submit"
+ >
+ Continue
+
+
+
+ }
+ >
+
+ {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();