Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1955e03a35 |
@@ -30,6 +30,19 @@ module.exports = {
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
config.module.rules.unshift({
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: [
|
||||
{
|
||||
loader: 'yaml-loader',
|
||||
options: {
|
||||
asJSON: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/notification), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
isPermissionGranted: () => undefined,
|
||||
requestPermission: () => undefined,
|
||||
sendNotification: () => undefined,
|
||||
};
|
||||
Generated
+228
-90
@@ -14,12 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -469,12 +463,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.11.6"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4419e9adae9fd7e231b60d50467481bf8181ddeef6ed54683b23ae925c74c9c"
|
||||
checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -754,6 +747,17 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
@@ -1316,13 +1320,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.7.20"
|
||||
name = "dbus"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
|
||||
checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1471,6 +1476,12 @@ dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.9"
|
||||
@@ -1632,6 +1643,16 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
|
||||
dependencies = [
|
||||
"colored 1.9.3",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
@@ -2620,12 +2641,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ico"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804"
|
||||
checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"png 0.11.0",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2702,15 +2723,6 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inflate"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
@@ -2749,9 +2761,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.4"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -2793,9 +2805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
|
||||
checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
@@ -2914,6 +2926,15 @@ version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.14.0+1.5.0"
|
||||
@@ -2974,6 +2995,12 @@ dependencies = [
|
||||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -3003,6 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3034,6 +3062,19 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mac-notification-sys"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"dirs-next",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"time 0.3.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -3255,6 +3296,17 @@ dependencies = [
|
||||
"wasm-timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-rust"
|
||||
version = "4.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"mac-notification-sys",
|
||||
"tauri-winrt-notification",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
@@ -3276,17 +3328,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
@@ -3357,21 +3398,28 @@ dependencies = [
|
||||
name = "nym-connect"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
"chrono",
|
||||
"client-core",
|
||||
"config",
|
||||
"crypto",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"fern",
|
||||
"fix-path-env",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"logging",
|
||||
"nym-socks5-client",
|
||||
"pretty_env_logger",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tap",
|
||||
"task",
|
||||
"tauri",
|
||||
@@ -3385,6 +3433,7 @@ dependencies = [
|
||||
"topology",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3612,9 +3661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -4065,18 +4114,6 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"deflate",
|
||||
"inflate",
|
||||
"num-iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
@@ -4221,6 +4258,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
@@ -4473,9 +4519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4535,6 +4581,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-socks",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -4604,6 +4651,41 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "6.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "7.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2 0.10.6",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
@@ -5459,6 +5541,27 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
|
||||
dependencies = [
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
@@ -5531,9 +5634,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.14.0"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43336f5d1793543ba96e2a1e75f3a5c7dcd592743be06a0ab3a190f4fcb4b934"
|
||||
checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -5566,12 +5669,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"paste",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"raw-window-handle",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"x11-dl",
|
||||
@@ -5608,9 +5711,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.1.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbf22abd61d95ca9b2becd77f9db4c093892f73e8a07d21d8b0b2bf71a7bcea"
|
||||
checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
@@ -5628,6 +5731,7 @@ dependencies = [
|
||||
"http",
|
||||
"ignore",
|
||||
"minisign-verify",
|
||||
"notify-rust",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
@@ -5652,7 +5756,7 @@ dependencies = [
|
||||
"time 0.3.17",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@@ -5661,9 +5765,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0991fb306849897439dbd4a72e4cbed2413e2eb26cb4b3ba220b94edba8b4b88"
|
||||
checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5677,16 +5781,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "356fa253e40ae4d6ff02075011f2f2bb4066f5c9d8c1e16ca6912d7b75903ba6"
|
||||
checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
"plist",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
@@ -5697,15 +5801,15 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time 0.3.17",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6051fd6940ddb22af452340d03c66a3e2f5d72e0788d4081d91e31528ccdc4d"
|
||||
checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
@@ -5717,30 +5821,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.11.1"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49439a5ea47f474572b854972f42eda2e02a470be5ca9609cc83bb66945abe2"
|
||||
checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"http-range",
|
||||
"infer",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.11.1"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dce920995fd49907aa9bea7249ed1771454f11f7611924c920a1f75fb614d4"
|
||||
checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -5749,7 +5852,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@@ -5758,15 +5861,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8fdae6f29cef959809a3c3afef510c5b715a446a597ab8b791497585363f39"
|
||||
checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"glob",
|
||||
"heck 0.4.0",
|
||||
"html5ever",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchiki",
|
||||
"memchr",
|
||||
@@ -5783,6 +5887,17 @@ dependencies = [
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-winrt-notification"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b"
|
||||
dependencies = [
|
||||
"quick-xml",
|
||||
"strum",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
@@ -6055,6 +6170,18 @@ dependencies = [
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.10"
|
||||
@@ -6346,9 +6473,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.2"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
]
|
||||
@@ -6363,7 +6490,7 @@ dependencies = [
|
||||
"coconut-bandwidth-contract-common",
|
||||
"coconut-dkg-common",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"colored 2.0.0",
|
||||
"config",
|
||||
"contracts-common",
|
||||
"cosmrs",
|
||||
@@ -6634,9 +6761,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "0.18.0"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29952969fb5e10fe834a52eb29ad0814ccdfd8387159b0933edf1344a1c9cdcc"
|
||||
checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -7011,15 +7138,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.21.1"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff5c1352b4266fdf92c63479d2f58ab4cd29dc4e78fbc1b62011ed1227926945"
|
||||
checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"block",
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dunce",
|
||||
"gdk",
|
||||
"gio",
|
||||
"glib",
|
||||
@@ -7035,6 +7163,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.6",
|
||||
"soup2",
|
||||
"tao",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -7101,6 +7230,15 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
@@ -7124,9 +7262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d"
|
||||
checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"prewebpack:dev": "yarn --cwd .. build",
|
||||
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:dev:onlyThis": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
|
||||
"tauri:dev": "RUST_DEBUG=1 yarn tauri dev",
|
||||
"tauri:build": "yarn tauri build",
|
||||
@@ -30,7 +31,7 @@
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@tauri-apps/api": "^1.1.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"clsx": "^1.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
@@ -39,6 +40,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"yup": "^0.32.9"
|
||||
@@ -49,11 +51,12 @@
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/react": "^6.5.8",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/cli": "^1.1.0",
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
@@ -105,6 +108,7 @@
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body style="background: rgb(29, 33, 37);">
|
||||
<div id="root-growth"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>nym-connect</title>
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Wallet Logs</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root-log"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,33 +13,40 @@ rust-version = "1.58"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "^1.1.1", features = [] }
|
||||
tauri-build = { version = "^1.2.1", features = [] }
|
||||
|
||||
tauri-codegen = "^1.1.1"
|
||||
tauri-macros = "^1.1.1"
|
||||
tauri-codegen = "^1.2.1"
|
||||
tauri-macros = "^1.2.1"
|
||||
|
||||
[dependencies]
|
||||
|
||||
anyhow = "1.0"
|
||||
bip39 = "1.0"
|
||||
chrono = "0.4"
|
||||
dirs = "4.0"
|
||||
eyre = "0.6.5"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
fern = { version = "0.6.1", features = ["colored"] }
|
||||
itertools = "0.10.5"
|
||||
log = { version = "0.4", features = ["serde"] }
|
||||
pretty_env_logger = "0.4.0"
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
reqwest = { version = "0.11", features = ["json", "socks"] }
|
||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
tap = "1.0.1"
|
||||
tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tauri = { version = "^1.2.2", features = ["clipboard-write-text", "macos-private-api", "notification-all", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.21.2", features = ["sync", "time"] }
|
||||
url = "2.2"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
nym-socks5-client = { path = "../../clients/socks5" }
|
||||
task = { path = "../../common/task" }
|
||||
|
||||
@@ -44,6 +44,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new_with_port<S: Into<String>>(id: S, provider_mix_address: S, port: u16) -> Self {
|
||||
Config {
|
||||
socks5: Socks5Config::new(id, provider_mix_address).with_port(port),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_socks5(&self) -> &Socks5Config {
|
||||
&self.socks5
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ pub enum BackendError {
|
||||
source: tauri::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
TauriApiError {
|
||||
#[from]
|
||||
source: tauri::api::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
@@ -36,6 +41,11 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: ClientCoreError<fs_backend::Backend>,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ApiClientError {
|
||||
#[from]
|
||||
source: crate::operations::growth::api_client::ApiClientError,
|
||||
},
|
||||
|
||||
#[error("Could not send disconnect signal to the SOCKS5 client")]
|
||||
CoundNotSendDisconnectSignal,
|
||||
@@ -57,6 +67,8 @@ pub enum BackendError {
|
||||
CouldNotGetConfigFilename,
|
||||
#[error("Could not load existing gateway configuration")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
#[error("Unable to open a new window")]
|
||||
NewWindowError,
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use fern::colors::ColoredLevelConfig;
|
||||
use serde::Serialize;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use tauri::Manager;
|
||||
|
||||
pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> {
|
||||
let colors = ColoredLevelConfig::new();
|
||||
let base_config = fern::Dispatch::new()
|
||||
.level(global_level())
|
||||
.filter_lowlevel_external_components()
|
||||
.show_operations();
|
||||
|
||||
let stdout_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}][{}] {}",
|
||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
||||
record.target(),
|
||||
colors.color(record.level()),
|
||||
message,
|
||||
))
|
||||
})
|
||||
.chain(std::io::stdout());
|
||||
|
||||
let tauri_event_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}] {}",
|
||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
||||
record.target(),
|
||||
message,
|
||||
))
|
||||
})
|
||||
.chain(fern::Output::call(move |record| {
|
||||
let msg = LogMessage {
|
||||
message: record.args().to_string(),
|
||||
level: record.level().into(),
|
||||
};
|
||||
app_handle.emit_all("log://log", msg).unwrap();
|
||||
}));
|
||||
|
||||
base_config
|
||||
.chain(stdout_config)
|
||||
.chain(tauri_event_config)
|
||||
.apply()
|
||||
}
|
||||
|
||||
trait FernExt {
|
||||
fn show_operations(self) -> Self;
|
||||
fn filter_lowlevel_external_components(self) -> Self;
|
||||
}
|
||||
|
||||
impl FernExt for fern::Dispatch {
|
||||
fn show_operations(self) -> Self {
|
||||
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
|
||||
self.level_for("nym_connect::operations", log::LevelFilter::Trace)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_lowlevel_external_components(self) -> Self {
|
||||
self.level_for("hyper", log::LevelFilter::Warn)
|
||||
.level_for("tokio_reactor", log::LevelFilter::Warn)
|
||||
.level_for("reqwest", log::LevelFilter::Warn)
|
||||
.level_for("mio", log::LevelFilter::Warn)
|
||||
.level_for("want", log::LevelFilter::Warn)
|
||||
.level_for("sled", log::LevelFilter::Warn)
|
||||
.level_for("tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("rustls", log::LevelFilter::Warn)
|
||||
.level_for("tokio_util", log::LevelFilter::Warn)
|
||||
}
|
||||
}
|
||||
|
||||
fn global_level() -> log::LevelFilter {
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log::LevelFilter::from_str(&s).unwrap_or(log::LevelFilter::Info)
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct LogMessage {
|
||||
message: String,
|
||||
level: LogLevel,
|
||||
}
|
||||
|
||||
// Serialize to u16 instead of strings.
|
||||
#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
|
||||
#[repr(u16)]
|
||||
enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<log::Level> for LogLevel {
|
||||
fn from(level: log::Level) -> Self {
|
||||
match level {
|
||||
log::Level::Trace => LogLevel::Trace,
|
||||
log::Level::Debug => LogLevel::Debug,
|
||||
log::Level::Info => LogLevel::Info,
|
||||
log::Level::Warn => LogLevel::Warn,
|
||||
log::Level::Error => LogLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_common::defaults::setup_env;
|
||||
use logging::setup_logging;
|
||||
use tauri::Menu;
|
||||
use tauri::{Manager, Menu};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::menu::AddDefaultSubmenus;
|
||||
@@ -17,6 +16,7 @@ use crate::window::window_toggle;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod logging;
|
||||
mod menu;
|
||||
mod models;
|
||||
mod operations;
|
||||
@@ -25,7 +25,6 @@ mod tasks;
|
||||
mod window;
|
||||
|
||||
fn main() {
|
||||
setup_logging();
|
||||
setup_env(None);
|
||||
println!("Starting up...");
|
||||
|
||||
@@ -35,11 +34,13 @@ fn main() {
|
||||
log::warn!("Failed to fix PATH: {error}");
|
||||
}
|
||||
|
||||
let context = tauri::generate_context!();
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::new())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::config::get_config_file_location,
|
||||
crate::config::get_config_id,
|
||||
crate::operations::connection::status::get_connection_status,
|
||||
crate::operations::connection::connect::get_gateway,
|
||||
crate::operations::connection::connect::get_service_provider,
|
||||
crate::operations::connection::connect::set_gateway,
|
||||
@@ -49,10 +50,31 @@ fn main() {
|
||||
crate::operations::directory::get_services,
|
||||
crate::operations::export::export_keys,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
|
||||
crate::operations::growth::test_and_earn::growth_tne_take_part,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_draws,
|
||||
crate::operations::growth::test_and_earn::growth_tne_ping,
|
||||
crate::operations::growth::test_and_earn::growth_tne_submit_wallet_address,
|
||||
crate::operations::growth::test_and_earn::growth_tne_enter_draw,
|
||||
crate::operations::growth::test_and_earn::growth_tne_toggle_window,
|
||||
crate::operations::help::log::help_log_toggle_window,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.menu(Menu::os_default(&context.package_info().name).add_default_app_submenus())
|
||||
.on_menu_event(|event| {
|
||||
if event.menu_item_id() == menu::SHOW_LOG_WINDOW {
|
||||
let _r = crate::operations::help::log::help_log_toggle_window(
|
||||
event.window().app_handle(),
|
||||
);
|
||||
}
|
||||
if event.menu_item_id() == menu::CLEAR_STORAGE {
|
||||
let _r = crate::operations::help::storage::help_clear_storage(
|
||||
event.window().app_handle(),
|
||||
);
|
||||
}
|
||||
})
|
||||
.setup(|app| Ok(crate::logging::setup_logging(app.app_handle())?))
|
||||
.system_tray(create_tray_menu())
|
||||
.on_system_tray_event(tray_menu_event_handler)
|
||||
.run(tauri::generate_context!())
|
||||
.run(context)
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -1,40 +1,26 @@
|
||||
use crate::window_toggle;
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Menu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
SystemTrayMenuItem, Wry,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::{MenuItem, Submenu};
|
||||
|
||||
use crate::window_toggle;
|
||||
|
||||
pub const SHOW_LOG_WINDOW: &str = "show_log_window";
|
||||
pub const CLEAR_STORAGE: &str = "clear_storage";
|
||||
|
||||
pub trait AddDefaultSubmenus {
|
||||
fn add_default_app_submenu_if_macos(self) -> Self;
|
||||
fn add_default_app_submenus(self) -> Self;
|
||||
}
|
||||
|
||||
impl AddDefaultSubmenus for Menu {
|
||||
fn add_default_app_submenu_if_macos(self) -> Menu {
|
||||
#[cfg(target_os = "macos")]
|
||||
return self
|
||||
.add_submenu(Submenu::new(
|
||||
"File",
|
||||
Menu::new().add_native_item(MenuItem::Quit),
|
||||
))
|
||||
.add_submenu(Submenu::new(
|
||||
"Edit",
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::Copy)
|
||||
.add_native_item(MenuItem::Cut)
|
||||
.add_native_item(MenuItem::Paste)
|
||||
.add_native_item(MenuItem::SelectAll),
|
||||
))
|
||||
.add_submenu(Submenu::new(
|
||||
"Window",
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
.add_native_item(MenuItem::ShowAll),
|
||||
));
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return self;
|
||||
fn add_default_app_submenus(self) -> Self {
|
||||
let submenu = Submenu::new(
|
||||
"Help",
|
||||
Menu::new()
|
||||
.add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs"))
|
||||
.add_item(CustomMenuItem::new(CLEAR_STORAGE, "Clear all settings")),
|
||||
);
|
||||
self.add_submenu(submenu)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,21 +39,31 @@ impl fmt::Display for ConnectionStatusKind {
|
||||
pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed";
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct AppEventConnectionStatusChangedPayload {
|
||||
pub status: ConnectionStatusKind,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DirectoryService {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub items: Vec<DirectoryServiceProvider>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct HarbourMasterService {
|
||||
pub service_provider_client_id: String,
|
||||
pub gateway_identity_key: String,
|
||||
pub ip_address: String,
|
||||
pub last_successful_ping_utc: String,
|
||||
pub last_updated_utc: String,
|
||||
pub routing_score: f32,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DirectoryServiceProvider {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
@@ -63,3 +73,11 @@ pub struct DirectoryServiceProvider {
|
||||
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub gateway: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PagedResult<T> {
|
||||
pub page: u32,
|
||||
pub size: u32,
|
||||
pub total: i32,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_service_provider(
|
||||
service_provider: String,
|
||||
service_provider: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting service_provider: {service_provider}");
|
||||
log::trace!("Setting service_provider: {:?}", &service_provider);
|
||||
let mut guard = state.write().await;
|
||||
guard.set_service_provider(service_provider);
|
||||
Ok(())
|
||||
@@ -57,10 +57,10 @@ pub async fn get_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_gateway(
|
||||
gateway: String,
|
||||
gateway: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting gateway: {gateway}");
|
||||
log::trace!("Setting gateway: {:?}", &gateway);
|
||||
let mut guard = state.write().await;
|
||||
guard.set_gateway(gateway);
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod connect;
|
||||
pub mod disconnect;
|
||||
pub mod status;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::error::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::models::ConnectionStatusKind;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_connection_status(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ConnectionStatusKind> {
|
||||
let state = state.read().await;
|
||||
Ok(state.get_status())
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::DirectoryService;
|
||||
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
log::trace!("Fetching services");
|
||||
@@ -12,5 +16,36 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", res);
|
||||
Ok(res)
|
||||
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
let active_services = reqwest::get(HARBOUR_MASTER_URL)
|
||||
.await?
|
||||
.json::<PagedResult<HarbourMasterService>>()
|
||||
.await?;
|
||||
log::trace!("Active: {:#?}", active_services);
|
||||
|
||||
let mut filtered: Vec<DirectoryService> = vec![];
|
||||
|
||||
for service in &res {
|
||||
let items: _ = service
|
||||
.items
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|sp| {
|
||||
active_services
|
||||
.items
|
||||
.iter()
|
||||
.any(|active| active.service_provider_client_id == sp.address)
|
||||
})
|
||||
.collect_vec();
|
||||
log::trace!("service = {} has {} items", service.id, items.len());
|
||||
filtered.push(DirectoryService {
|
||||
id: service.id.clone(),
|
||||
description: service.description.clone(),
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(filtered)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,24 @@ use crate::{
|
||||
error::{BackendError, Result},
|
||||
state::State,
|
||||
};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use crypto::asymmetric::identity;
|
||||
|
||||
pub async fn get_identity_key(
|
||||
state: &tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Arc<identity::KeyPair>> {
|
||||
let config = {
|
||||
let state = state.read().await;
|
||||
state.load_socks5_config()?
|
||||
};
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
let key_manager = KeyManager::load_keys(&pathfinder)?;
|
||||
let identity_keypair = key_manager.identity_keypair();
|
||||
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
/// Export the gateway keys as a JSON string blob
|
||||
#[tauri::command]
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApiClientError {
|
||||
#[error("{source}")]
|
||||
Reqwest {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJson {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
Status(String),
|
||||
}
|
||||
|
||||
const API_BASE_URL: &str = "https://growth-api.nymtech.net";
|
||||
|
||||
// For development mode, switch to this
|
||||
// const API_BASE_URL: &str = "http://localhost:8000";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GrowthApiClient {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl GrowthApiClient {
|
||||
pub fn new(resource_base: &str) -> Self {
|
||||
let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string());
|
||||
GrowthApiClient {
|
||||
base_url: format!("{}{}", base_url, resource_base),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registrations() -> Registrations {
|
||||
Registrations::new(GrowthApiClient::new("/v1/tne"))
|
||||
}
|
||||
|
||||
pub fn daily_draws() -> DailyDraws {
|
||||
DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw"))
|
||||
}
|
||||
|
||||
pub(crate) async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, ApiClientError> {
|
||||
log::info!(">>> GET {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client.get(format!("{}{}", self.base_url, url)).send().await {
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn post<REQ: Serialize + ?Sized, RESP: DeserializeOwned>(
|
||||
&self,
|
||||
url: &str,
|
||||
body: &REQ,
|
||||
) -> Result<RESP, ApiClientError> {
|
||||
log::info!(">>> POST {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client
|
||||
.post(format!("{}{}", self.base_url, url))
|
||||
.json(body)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClientIdPartial {
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Registration {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Ping {
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
pub struct Registrations {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl Registrations {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
Registrations { client }
|
||||
}
|
||||
|
||||
pub async fn register(
|
||||
&self,
|
||||
registration: &ClientIdPartial,
|
||||
) -> Result<Registration, ApiClientError> {
|
||||
self.client.post("/register", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/unregister", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/status", ®istration).await
|
||||
}
|
||||
|
||||
pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/ping", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn health(&self) -> Result<(), ApiClientError> {
|
||||
self.client.get("/health").await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntryPartial {
|
||||
pub draw_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntry {
|
||||
pub id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawWithWordOfTheDay {
|
||||
pub id: String,
|
||||
pub start_utc: String,
|
||||
pub end_utc: String,
|
||||
pub word_of_the_day: Option<String>,
|
||||
pub last_modified: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClaimPartial {
|
||||
pub draw_id: String,
|
||||
pub registration_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
pub wallet_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Winner {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub winner_reg_id: String,
|
||||
pub winner_wallet_address: Option<String>,
|
||||
pub winner_claim_timestamp: Option<String>,
|
||||
}
|
||||
|
||||
pub struct DailyDraws {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl DailyDraws {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
DailyDraws { client }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/current").await
|
||||
}
|
||||
|
||||
pub async fn next(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/next").await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, draw_id: &str) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client
|
||||
.get(format!("/status/{}", draw_id).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn enter(&self, entry: &DrawEntryPartial) -> Result<DrawEntry, ApiClientError> {
|
||||
self.client.post("/enter", entry).await
|
||||
}
|
||||
|
||||
pub async fn entries(
|
||||
&self,
|
||||
client_id: &ClientIdPartial,
|
||||
) -> Result<Vec<DrawEntry>, ApiClientError> {
|
||||
self.client.post("/entries", client_id).await
|
||||
}
|
||||
|
||||
pub async fn claim(&self, claim: &ClaimPartial) -> Result<Winner, ApiClientError> {
|
||||
self.client.post("/claim", claim).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use rust_embed::RustEmbed;
|
||||
extern crate yaml_rust;
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../src/components/Growth/content/"]
|
||||
#[include = "*.yaml"]
|
||||
#[exclude = "*.mdx"]
|
||||
struct Asset;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationContent {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Notifications {
|
||||
pub you_are_in_draw: NotificationContent,
|
||||
pub take_part: NotificationContent,
|
||||
}
|
||||
|
||||
pub struct Content {}
|
||||
|
||||
const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR";
|
||||
|
||||
fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String {
|
||||
value.as_str().unwrap_or(RESOURCE_ERROR).to_string()
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn get_notifications() -> Notifications {
|
||||
let content = Asset::get("en.yaml").unwrap();
|
||||
let s = std::str::from_utf8(content.data.as_ref()).unwrap();
|
||||
let content = YamlLoader::load_from_str(s).unwrap();
|
||||
let content = &content[0];
|
||||
|
||||
Notifications {
|
||||
you_are_in_draw: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["body"],
|
||||
),
|
||||
},
|
||||
take_part: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["body"],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod assets;
|
||||
pub mod test_and_earn;
|
||||
pub mod api_client;
|
||||
@@ -0,0 +1,156 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::export::get_identity_key;
|
||||
use crate::operations::growth::api_client::{
|
||||
ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay,
|
||||
GrowthApiClient, Registration, Winner,
|
||||
};
|
||||
use crate::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::api::notification::Notification;
|
||||
use tauri::Manager;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
async fn get_client_id(
|
||||
state: &tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
let keypair = get_identity_key(state).await?;
|
||||
let client_id = keypair.public_key().to_base58_string();
|
||||
let client_id_signature = keypair
|
||||
.private_key()
|
||||
.sign(client_id.as_bytes())
|
||||
.to_base58_string();
|
||||
Ok(ClientIdPartial {
|
||||
client_id,
|
||||
client_id_signature,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_client_id(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
get_client_id(&state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_take_part(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Registration, BackendError> {
|
||||
let notifications = super::assets::Content::get_notifications();
|
||||
|
||||
let client_id = get_client_id(&state).await?;
|
||||
let registration = GrowthApiClient::registrations()
|
||||
.register(&client_id)
|
||||
.await?;
|
||||
|
||||
log::info!("<<< Test&Earn: registration details: {:?}", registration);
|
||||
|
||||
if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier)
|
||||
.title(notifications.take_part.title)
|
||||
.body(notifications.take_part.body)
|
||||
.show()
|
||||
{
|
||||
log::error!("Could not show notification. Error = {:?}", e);
|
||||
}
|
||||
|
||||
Ok(registration)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Draws {
|
||||
pub current: Option<DrawWithWordOfTheDay>,
|
||||
pub next: Option<DrawWithWordOfTheDay>,
|
||||
pub draws: Vec<DrawEntry>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result<Draws, BackendError> {
|
||||
let draws_api = GrowthApiClient::daily_draws();
|
||||
|
||||
let current = draws_api.current().await.ok();
|
||||
let next = draws_api.next().await.ok();
|
||||
let draws = draws_api.entries(&client_details).await?;
|
||||
|
||||
Ok(Draws {
|
||||
current,
|
||||
next,
|
||||
draws,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_enter_draw(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
) -> Result<DrawEntry, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.enter(&DrawEntryPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_submit_wallet_address(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
wallet_address: String,
|
||||
registration_id: String,
|
||||
) -> Result<Winner, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.claim(&ClaimPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
wallet_address,
|
||||
registration_id,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> {
|
||||
log::info!("Test&Earn is sending a ping...");
|
||||
Ok(GrowthApiClient::registrations()
|
||||
.ping(&client_details)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_toggle_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
window_title: Option<String>,
|
||||
) -> Result<(), BackendError> {
|
||||
if let Some(window) = app_handle.windows().get("growth") {
|
||||
log::info!("Closing growth window...");
|
||||
if let Err(e) = window.close() {
|
||||
log::error!("Unable to close growth window: {:?}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Creating growth window...");
|
||||
match tauri::WindowBuilder::new(
|
||||
&app_handle,
|
||||
"growth",
|
||||
tauri::WindowUrl::App("growth.html".into()),
|
||||
)
|
||||
.title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string()))
|
||||
.build()
|
||||
{
|
||||
Ok(window) => {
|
||||
if let Err(e) = window.set_focus() {
|
||||
log::error!("Unable to focus growth window: {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Unable to create growth window: {:?}", e);
|
||||
Err(BackendError::NewWindowError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::error::BackendError;
|
||||
use tauri::Manager;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
|
||||
if let Some(current_log_window) = app_handle.windows().get("log") {
|
||||
log::info!("Closing log window...");
|
||||
if let Err(e) = current_log_window.close() {
|
||||
log::error!("Unable to close log window: {:?}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Creating log window...");
|
||||
match tauri::WindowBuilder::new(&app_handle, "log", tauri::WindowUrl::App("log.html".into()))
|
||||
.title("Nym Connect Logs")
|
||||
.build()
|
||||
{
|
||||
Ok(window) => {
|
||||
if let Err(e) = window.set_focus() {
|
||||
log::error!("Unable to focus log window: {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Unable to create log window: {:?}", e);
|
||||
Err(BackendError::NewWindowError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod log;
|
||||
pub mod storage;
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::error::BackendError;
|
||||
use serde::Serialize;
|
||||
use tauri::Manager;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct ClearStorageEvent {
|
||||
kind: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
|
||||
log::info!("Sending event to clear local storage...");
|
||||
|
||||
let event = ClearStorageEvent {
|
||||
kind: "local_storage".to_string(),
|
||||
};
|
||||
app_handle.emit_all("help://clear-storage", event)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod export;
|
||||
pub mod growth;
|
||||
pub mod help;
|
||||
pub mod window;
|
||||
|
||||
@@ -64,16 +64,16 @@ impl State {
|
||||
&self.service_provider
|
||||
}
|
||||
|
||||
pub fn set_service_provider(&mut self, provider: String) {
|
||||
self.service_provider = Some(provider);
|
||||
pub fn set_service_provider(&mut self, provider: Option<String>) {
|
||||
self.service_provider = provider;
|
||||
}
|
||||
|
||||
pub fn get_gateway(&self) -> &Option<String> {
|
||||
&self.gateway
|
||||
}
|
||||
|
||||
pub fn set_gateway(&mut self, gateway: String) {
|
||||
self.gateway = Some(gateway);
|
||||
pub fn set_gateway(&mut self, gateway: Option<String>) {
|
||||
self.gateway = gateway;
|
||||
}
|
||||
|
||||
/// The effective config id is the static config id appended with the id of the gateway
|
||||
|
||||
@@ -59,13 +59,16 @@
|
||||
"startDragging": true,
|
||||
"close": true,
|
||||
"minimize": true
|
||||
},
|
||||
"notification": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "NymConnect",
|
||||
"width": 240,
|
||||
"height": 575,
|
||||
"height": 605,
|
||||
"resizable": false,
|
||||
"decorations": false,
|
||||
"transparent": true
|
||||
|
||||
+12
-1
@@ -1,15 +1,27 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { ConnectionStatusKind } from './types';
|
||||
import { useClientContext } from './context/main';
|
||||
import { DefaultLayout } from './layouts/DefaultLayout';
|
||||
import { ConnectedLayout } from './layouts/ConnectedLayout';
|
||||
import { HelpGuideLayout } from './layouts/HelpGuideLayout';
|
||||
import { useTauriEvents } from './utils';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
const [showInfoModal, setShowInfoModal] = React.useState(false);
|
||||
useTauriEvents('help://clear-storage', (_event) => {
|
||||
console.log('About to clear local storage...');
|
||||
// clear local storage
|
||||
try {
|
||||
forage.clear()();
|
||||
console.log('Local storage cleared');
|
||||
} catch (e) {
|
||||
console.error('Failed to clear local storage', e);
|
||||
}
|
||||
});
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const currentStatus = context.connectionStatus;
|
||||
@@ -49,7 +61,6 @@ export const App: React.FC = () => {
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={context.services}
|
||||
onServiceProviderChange={context.setServiceProvider}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const AppVersion = () => {
|
||||
const { appVersion } = useClientContext();
|
||||
|
||||
return (
|
||||
<Typography
|
||||
fontSize="small"
|
||||
textAlign="center"
|
||||
sx={{ color: 'grey.600', position: 'absolute', bottom: 10, width: '100%' }}
|
||||
>
|
||||
Version {appVersion}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { CustomTitleBar } from './CustomTitleBar';
|
||||
import { AppVersion } from './AppVersion';
|
||||
|
||||
export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
<Box
|
||||
@@ -12,10 +11,10 @@ export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
gridTemplateRows: '40px 1fr 30px',
|
||||
bgcolor: 'nym.background.dark',
|
||||
height: '100vh',
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
>
|
||||
<CustomTitleBar />
|
||||
<Box style={{ padding: '16px' }}>{children}</Box>
|
||||
<AppVersion />
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,13 @@ export const CopyToClipboard = ({
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async (text: string) => {
|
||||
const handleCopy = async (textToCopy: string) => {
|
||||
try {
|
||||
await clipboard.writeText(text);
|
||||
if (clipboard) {
|
||||
await clipboard.writeText(textToCopy);
|
||||
} else {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
}
|
||||
setCopied(true);
|
||||
} catch (e) {
|
||||
console.log(`failed to copy: ${e}`);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { FallbackProps } from 'react-error-boundary';
|
||||
import { Alert, AlertTitle, Button } from '@mui/material';
|
||||
|
||||
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
|
||||
<div>
|
||||
<Alert severity="error" data-testid="error-message">
|
||||
<AlertTitle>{error.name}</AlertTitle>
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Alert severity="error" data-testid="stack-trace">
|
||||
<AlertTitle>Stack trace</AlertTitle>
|
||||
{error.stack}
|
||||
</Alert>
|
||||
<Button onClick={resetErrorBoundary}>Back to safety</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Badge, Box, Button, Tooltip } from '@mui/material';
|
||||
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import Content from './content/en.yaml';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
|
||||
export const Wrapper: React.FC<{ disabled: boolean }> = ({ disabled, children }) => {
|
||||
if (disabled) {
|
||||
return (
|
||||
<Badge badgeContent="!" color="warning">
|
||||
<Tooltip arrow title={disabled ? Content.testAndEarn.mainWindow.button.popup.disconnected : undefined}>
|
||||
<div>{children}</div>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const TestAndEarnButtonArea: React.FC = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected;
|
||||
const pinger = React.useRef<NodeJS.Timer | null>();
|
||||
|
||||
const doPing = async () => {
|
||||
if (context.clientDetails) {
|
||||
try {
|
||||
await invoke('growth_tne_ping', { clientDetails: context.clientDetails });
|
||||
} catch (_e) {
|
||||
// console.error('Failed to ping: ', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!disabled) {
|
||||
// sleep a little until the SOCKS5 proxy connects
|
||||
setTimeout(() => {
|
||||
doPing();
|
||||
}, 1000 * 10);
|
||||
|
||||
// update every 15 mins
|
||||
pinger.current = setInterval(doPing, 1000 * 60 * 15);
|
||||
} else if (pinger.current) {
|
||||
clearInterval(pinger.current);
|
||||
pinger.current = null;
|
||||
}
|
||||
})();
|
||||
}, [disabled, context.clientDetails]);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!disabled) {
|
||||
await context.toggleGrowthWindow(Content.testAndEarn.popupWindow.title);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NymShipyardTheme>
|
||||
<Box justifyContent="center" display="grid">
|
||||
<Wrapper disabled={disabled}>
|
||||
<Button
|
||||
color={disabled ? 'secondary' : undefined}
|
||||
variant="contained"
|
||||
size="small"
|
||||
endIcon={<MonetizationOnIcon />}
|
||||
sx={{ width: '150px', mb: 4, opacity: disabled ? 0.4 : undefined }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{context.registration
|
||||
? Content.testAndEarn.mainWindow.button.text.entered
|
||||
: Content.testAndEarn.mainWindow.button.text.default}
|
||||
</Button>
|
||||
</Wrapper>
|
||||
</Box>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import {
|
||||
TestAndEarnCurrentDraw,
|
||||
TestAndEarnCurrentDrawEntered,
|
||||
TestAndEarnCurrentDrawFuture,
|
||||
} from './TestAndEarnCurrentDraw';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { DrawEntryStatus } from './context/types';
|
||||
import { testMarkdown } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Current Draw',
|
||||
component: TestAndEarnCurrentDraw,
|
||||
} as ComponentMeta<typeof TestAndEarnCurrentDraw>;
|
||||
|
||||
export const Valid = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDraw
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredMalformedDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: undefined,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: testMarkdown,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Future = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawFuture
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600 * 2))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,192 @@
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { Alert, AlertTitle, Box, Card, CardContent, CardMedia, Link, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import assetAnimation from './content/assets/matrix.webp';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntryStatus, DrawWithWordOfTheDay } from './context/types';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
export const TestAndEarnCurrentDrawFuture: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const startsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.start_utc), [draw?.start_utc]);
|
||||
const startsIn = React.useMemo(() => {
|
||||
if (draw && startsUtc) {
|
||||
return startsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [draw?.start_utc]);
|
||||
|
||||
if (!draw || !startsUtc) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card sx={{ mb: 2 }} elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{Content.testAndEarn.draw.next.header} {startsIn} ⏰
|
||||
</h3>
|
||||
<p>on {startsUtc.toLocaleString(DateTime.DATETIME_FULL)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEnter: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const handleClick = async () => {
|
||||
if (!draw) {
|
||||
setError('No draw selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setBusy(true);
|
||||
try {
|
||||
await context.enterDraw(draw.id);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Could not enter draw', message);
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" alignItems="center" py={3} px={2} mx={6} my={2}>
|
||||
<Typography mb={4}>Complete today’s task for the chance to earn 1000 NYMs.</Typography>
|
||||
<LoadingButton variant="contained" size="large" loading={busy} onClick={handleClick}>
|
||||
Start task ✨
|
||||
</LoadingButton>
|
||||
{error && (
|
||||
<Box mt={2}>
|
||||
<Alert variant="filled" severity="error">
|
||||
<AlertTitle>Oh no! Something went wrong.</AlertTitle>
|
||||
{error}
|
||||
</Alert>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEntered: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
if (!draw || !draw.entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!draw.word_of_the_day) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Something is wrong</AlertTitle>
|
||||
Someone configured the wrong instructions for the task, you will not be able to see it until this is fixed
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
sx={{ background: 'rgba(255,255,255,0.1)' }}
|
||||
py={4}
|
||||
mx={6}
|
||||
my={2}
|
||||
borderRadius={2}
|
||||
>
|
||||
<Box py={2} px={4} color="warning.light">
|
||||
<ReactMarkdown>{draw.word_of_the_day}</ReactMarkdown>
|
||||
</Box>
|
||||
|
||||
<Typography>{Content.testAndEarn.task.afterText}</Typography>
|
||||
<Typography mt={2} fontFamily="monospace" fontWeight="bold" color="warning.main">
|
||||
{draw.entry.id} <CopyToClipboard iconButton light text={draw.entry.id} />
|
||||
</Typography>
|
||||
|
||||
<Typography mt={2}>{Content.testAndEarn.task.beforeSocials}</Typography>
|
||||
<Typography mt={2} mx={1} textAlign="center">
|
||||
<Typography component="span" color="info.light" fontWeight="bold">
|
||||
Twitter
|
||||
</Typography>{' '}
|
||||
- remember to
|
||||
<Typography component="span" color="info.light">
|
||||
@nymproject
|
||||
</Typography>{' '}
|
||||
and use the hashtag{' '}
|
||||
<Typography component="span" color="info.light">
|
||||
#PrivacyLovesCompany
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography mt={2}>or</Typography>
|
||||
<Typography textAlign="center" fontWeight="bold">
|
||||
Nym{' '}
|
||||
<Link target="_blank" href="https://t.me/nymchan" color="info.light">
|
||||
Telegram channel
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDraw: React.FC<{
|
||||
draw?: DrawWithWordOfTheDay;
|
||||
sx?: SxProps;
|
||||
}> = ({ draw, sx }) => {
|
||||
const [trigger, setTrigger] = React.useState(DateTime.now().toISO());
|
||||
const endsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.end_utc), [draw?.end_utc]);
|
||||
const closesIn = React.useMemo(() => {
|
||||
if (draw && endsUtc) {
|
||||
return endsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [trigger, endsUtc]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => setTrigger(DateTime.now().toISO()), 1000 * 3600 * 15);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
if (draw && closesIn && endsUtc) {
|
||||
return (
|
||||
<Card sx={{ mb: 2, ...sx }} elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{"Today's task ends "}
|
||||
{closesIn}
|
||||
<Typography sx={{ opacity: 0.5 }}>
|
||||
{endsUtc.weekdayLong} {endsUtc.toLocaleString(DateTime.DATETIME_FULL)}
|
||||
</Typography>
|
||||
</h3>
|
||||
{!draw.entry && <TestAndEarnCurrentDrawEnter draw={draw} />}
|
||||
{draw.entry && <TestAndEarnCurrentDrawEntered draw={draw} />}
|
||||
</CardContent>
|
||||
<CardMedia component="img" height="150" image={assetAnimation} alt="lottery" />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
if (
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.winner ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.claimed ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.noWin
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!context.draws?.current) {
|
||||
return <TestAndEarnCurrentDrawFuture draw={context.draws?.next} />;
|
||||
}
|
||||
|
||||
return <TestAndEarnCurrentDraw sx={sx} draw={context.draws.current} />;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnDraws } from './TestAndEarnDraws';
|
||||
import { MockTestAndEarnProvider_RegisteredWithAllDraws } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Draws',
|
||||
component: TestAndEarnDraws,
|
||||
} as ComponentMeta<typeof TestAndEarnDraws>;
|
||||
|
||||
export const Draws = () => (
|
||||
<NymShipyardTheme>
|
||||
<MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
<TestAndEarnDraws />
|
||||
</MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Chip,
|
||||
Dialog,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus } from './context/types';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
const statusToText = (status: string): string => Content.testAndEarn.status.chip[status] || '-';
|
||||
|
||||
const statusToColor = (status: string): 'info' | 'success' | 'warning' | undefined => {
|
||||
switch (status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return 'info';
|
||||
case DrawEntryStatus.winner:
|
||||
return 'warning';
|
||||
case DrawEntryStatus.claimed:
|
||||
return 'success';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const StatusText: React.FC<{ entry: DrawEntry }> = ({ entry }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
|
||||
|
||||
const clear = () => {
|
||||
setShowWalletCapture(false);
|
||||
setError(undefined);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
const handleStartWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(true);
|
||||
};
|
||||
|
||||
const cancelEndWalletCapture = async () => {
|
||||
setBusy(false);
|
||||
setShowWalletCapture(false);
|
||||
};
|
||||
|
||||
const handleEndWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(false);
|
||||
|
||||
if (!context.walletAddress) {
|
||||
setError('Wallet address is not set');
|
||||
return;
|
||||
}
|
||||
if (!entry.draw_id) {
|
||||
setError('Task id is not set');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await context.claim(entry.draw_id, context.walletAddress);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Failed to submit claim');
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
|
||||
{error}
|
||||
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
|
||||
Try again!
|
||||
</Button>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (showWalletCapture) {
|
||||
return (
|
||||
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
|
||||
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
switch (entry.status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return <>{Content.testAndEarn.status.text.Pending}</>;
|
||||
case DrawEntryStatus.winner:
|
||||
return (
|
||||
<>
|
||||
{Content.testAndEarn.status.text.Winner}
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
disabled={busy}
|
||||
variant="contained"
|
||||
sx={{ ml: 2 }}
|
||||
size="small"
|
||||
onClick={handleStartWalletCapture}
|
||||
>
|
||||
{Content.testAndEarn.winner.claimButton.text}
|
||||
</LoadingButton>
|
||||
</>
|
||||
);
|
||||
case DrawEntryStatus.claimed:
|
||||
return <>{Content.testAndEarn.status.text.Claimed}</>;
|
||||
case DrawEntryStatus.noWin:
|
||||
return <>{Content.testAndEarn.status.text.NoWin}</>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnDraws: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const draws = React.useMemo<DrawEntry[]>(
|
||||
() =>
|
||||
(context.draws?.draws || []).map((item) => ({
|
||||
...item,
|
||||
timestamp: DateTime.fromISO(item.timestamp).toLocaleString(DateTime.DATETIME_FULL),
|
||||
})),
|
||||
[context.draws?.draws],
|
||||
);
|
||||
|
||||
if (!context.draws) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography mb={2}>Here is a history of the tasks you have completed:</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{draws.map((entry) => (
|
||||
<TableRow key={entry.draw_id}>
|
||||
<TableCell width="150px">{entry.timestamp}</TableCell>
|
||||
<TableCell width="150px">
|
||||
<Tooltip arrow title={`Task Id: ${entry.draw_id}`}>
|
||||
<Chip label={statusToText(entry.status)} color={statusToColor(entry.status)} />
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusText entry={entry} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{entry.id} <CopyToClipboard iconButton light text={entry.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnDrawsWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const drawCount = context.draws?.draws?.length || 0;
|
||||
if (drawCount < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TestAndEarnDraws sx={sx} />;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Enter wallet address',
|
||||
component: TestAndEarnEnterWalletAddress,
|
||||
} as ComponentMeta<typeof TestAndEarnEnterWalletAddress>;
|
||||
|
||||
export const Empty = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ErrorValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="this is a bad value" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ValidValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Paper, Stack, Button, Box } from '@mui/material';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
|
||||
export const TestAndEarnEnterWalletAddress: React.FC<{
|
||||
initialValue?: string;
|
||||
placeholder?: string;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
sx?: SxProps;
|
||||
}> = ({ initialValue, placeholder, onSubmit, sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [isAddressValid, setAddressIsValid] = React.useState(false);
|
||||
return (
|
||||
<Paper sx={{ py: 4, px: 2 }}>
|
||||
<Stack spacing={4}>
|
||||
<Box>
|
||||
<WalletAddressFormField
|
||||
label="Wallet address"
|
||||
initialValue={initialValue}
|
||||
placeholder={placeholder || 'Please enter your wallet address'}
|
||||
onChanged={context.setWalletAddress}
|
||||
onValidate={setAddressIsValid}
|
||||
sx={{ width: '80%' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button variant="contained" endIcon={<ArrowCircleRightIcon />} disabled={!isAddressValid} onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
|
||||
export const TestAndEarnError: React.FC<{ error?: string }> = ({ error = 'An error has occurred' }) => (
|
||||
<Box>
|
||||
<Box mb={4} fontWeight="bold">
|
||||
{error}
|
||||
</Box>
|
||||
<Button variant="outlined" color="secondary">
|
||||
Send us an error report
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,167 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Alert, Box } from '@mui/material';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnPopup, TestAndEarnPopupContent } from './TestAndEarnPopup';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { MockProvider } from '../../context/mocks/main';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import { TestAndEarnCurrentDraw } from './TestAndEarnCurrentDraw';
|
||||
import { TestAndEarnWinner } from './TestAndEarnWinner';
|
||||
import { TestAndEarnDraws } from './TestAndEarnDraws';
|
||||
import { TestAndEarnWinnerWalletAddress } from './TestAndEarnWinnerWalletAddress';
|
||||
import {
|
||||
MockTestAndEarnProvider_NotRegistered,
|
||||
MockTestAndEarnProvider_Registered,
|
||||
MockTestAndEarnProvider_RegisteredAndError,
|
||||
MockTestAndEarnProvider_RegisteredWithDraws,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntry,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent,
|
||||
} from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Content/Popup',
|
||||
component: TestAndEarnPopupContent,
|
||||
} as ComponentMeta<typeof TestAndEarnPopupContent>;
|
||||
|
||||
const MacOSWindow: React.FC<{ width?: string | number; height?: string | number; title?: string }> = ({
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
}) => (
|
||||
<Box sx={{ border: '1px solid #EEEEEE', width, height }}>
|
||||
<Box sx={{ background: '#EEEEEE', display: 'grid', gridTemplateColumns: 'auto auto', gridTemplateRows: 'auto' }}>
|
||||
<Box ml={1}>
|
||||
<svg width="52px" height="12px" viewBox="0 0 52 12" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Components" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="macOS" transform="translate(-600.000000, -220.000000)">
|
||||
<g id="Group" transform="translate(600.000000, 220.000000)" strokeWidth="0.5">
|
||||
<g id="Traffic-Lights">
|
||||
<circle id="Traffic-Light---Zoom" stroke="#1BAC2C" fill="#2ACB42" cx="46" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Minimise" stroke="#DFA023" fill="#FFC12F" cx="26" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Close" stroke="#E24640" fill="#FF6157" cx="6" cy="6" r="5.75" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: 'center',
|
||||
color: '#000000',
|
||||
opacity: 0.848675272,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{title || 'Window title'}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ overflowY: 'scroll', height: 'calc(100% - 25px)' }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const Wrapper: React.FC<{ text: React.ReactNode }> = ({ text }) => (
|
||||
<NymShipyardTheme>
|
||||
<Alert severity="info" sx={{ mb: 4 }}>
|
||||
{text}
|
||||
</Alert>
|
||||
<MacOSWindow width={700} height={600} title="Test&Earn">
|
||||
<TestAndEarnPopup />
|
||||
</MacOSWindow>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Stage0 = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_NotRegistered>
|
||||
<Wrapper text="The user sees this content when they have not joined Test&Earn." />
|
||||
</MockTestAndEarnProvider_NotRegistered>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage1EnterDraw = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
<Wrapper text="The user has signed up and can see the next draw and choose the enter." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage2GetTask = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
<Wrapper text="The user has entered a draw and can view the word of the day if they missed the popup notification." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3Winner = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
<Wrapper text="The user has won and can claim their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3NoPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
<Wrapper text="The user has not won. A winner has been announced." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage4EnterWalletAddress = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
<Wrapper text="The user is a winner, claims their prize and enters their wallet address." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage5ClaimedPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
<Wrapper text="The user is a winner and has claimed their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage6DrawsFinished = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
<Wrapper text="There are no more draws. The user can see their entries and prizes they have claimed." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Connecting = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connecting}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown while connecting." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Disconnected = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.disconnected}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown when not connected." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Error = () => (
|
||||
<MockProvider>
|
||||
<MockTestAndEarnProvider_RegisteredAndError>
|
||||
<Wrapper text="The user see this with details about errors. They can submit an error report." />
|
||||
</MockTestAndEarnProvider_RegisteredAndError>
|
||||
</MockProvider>
|
||||
);
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, LinearProgress, Stack, Typography } from '@mui/material';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import ErrorContent from './content/TestAndEarn/Error.mdx';
|
||||
import ContentStep0 from './content/TestAndEarn/Stage0_intro.mdx';
|
||||
import ContentNotAvailable from './content/TestAndEarnNotAvaialble.mdx';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { TestAndEarnWinnerWithState } from './TestAndEarnWinner';
|
||||
import { TestAndEarnCurrentDrawWithState } from './TestAndEarnCurrentDraw';
|
||||
import { TestAndEarnDrawsWithState } from './TestAndEarnDraws';
|
||||
|
||||
enum Stages {
|
||||
mustRegister = 'mustRegister',
|
||||
registered = 'registered',
|
||||
}
|
||||
|
||||
export const TestAndEarnPopupContent: React.FC<{
|
||||
stage?: string;
|
||||
connectionStatus?: ConnectionStatusKind;
|
||||
error?: string;
|
||||
}> = ({ connectionStatus, error, stage = Stages.mustRegister }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ErrorContent error={error} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentNotAvailable />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) {
|
||||
return (
|
||||
<Box p={4} justifyContent="center" alignItems="center" display="flex">
|
||||
<CircularProgress />
|
||||
<Typography ml={3}>Please wait...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case Stages.mustRegister:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentStep0 />
|
||||
</Box>
|
||||
);
|
||||
case Stages.registered:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<TestAndEarnWinnerWithState />
|
||||
<TestAndEarnCurrentDrawWithState />
|
||||
<TestAndEarnDrawsWithState />
|
||||
</Box>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>Waiting for task information...</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnPopup: React.FC = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
context.refresh();
|
||||
}
|
||||
}, [clientContext.connectionStatus]);
|
||||
|
||||
const stage = React.useMemo<Stages>(() => {
|
||||
if (context.registration) {
|
||||
return Stages.registered;
|
||||
}
|
||||
return Stages.mustRegister;
|
||||
}, [context.registration?.id]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(context.refresh, 1000 * 60 * 5);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
const message = 'Waiting for data to be transferred over the mixnet...';
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>{message}</Box>
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{context.loading && <LinearProgress />}
|
||||
{/* <Button onClick={context.refresh}>Refresh</Button> */}
|
||||
<TestAndEarnPopupContent connectionStatus={clientContext.connectionStatus} stage={stage} error={context.error} />
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Box, Checkbox, Link, Stack } from '@mui/material';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { SxProps } from '@mui/system';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { ClientId, Registration } from './context/types';
|
||||
|
||||
export const TestAndEarnTakePart: React.FC<{
|
||||
websiteLinkUrl: string;
|
||||
websiteLinkText: string;
|
||||
content: string;
|
||||
sx?: SxProps;
|
||||
}> = ({ content, websiteLinkText, websiteLinkUrl, sx }) => {
|
||||
const [agree, setAgree] = React.useState(false);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const context = useTestAndEarnContext();
|
||||
const handleNext = async () => {
|
||||
try {
|
||||
setBusy(true);
|
||||
if (context.clientDetails) {
|
||||
const registration: Registration = await invoke('growth_tne_take_part');
|
||||
console.log('Registration: ', { registration });
|
||||
await context.setAndStoreRegistration(registration);
|
||||
if (registration) {
|
||||
console.log('Registered...');
|
||||
} else {
|
||||
setError('Failed to get registration details');
|
||||
}
|
||||
} else {
|
||||
setError('Failed to get client details');
|
||||
}
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('An error occurred', message);
|
||||
setError(message);
|
||||
setBusy(false); // the busy state only resets on errors, for success stats, the context will navigate the window away
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" spacing={6} alignItems="center" sx={sx}>
|
||||
<Stack alignItems="center" direction="row">
|
||||
<Checkbox onChange={(_event, checked) => setAgree(checked)} />
|
||||
<Box color="primary.light" fontWeight="bold">
|
||||
{content}
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Link href={websiteLinkUrl} target="_blank" color="secondary" sx={{ opacity: 0.5 }}>
|
||||
{websiteLinkText}
|
||||
</Link>
|
||||
</Box>
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
disabled={!agree || busy}
|
||||
variant="contained"
|
||||
sx={{ justifySelf: 'end' }}
|
||||
endIcon={<ArrowCircleRightIcon />}
|
||||
onClick={handleNext}
|
||||
>
|
||||
Next
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
{error && (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Something went wrong</AlertTitle>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnWinner, TestAndEarnWinnerWithState } from './TestAndEarnWinner';
|
||||
import { MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Winner',
|
||||
component: TestAndEarnWinner,
|
||||
} as ComponentMeta<typeof TestAndEarnWinner>;
|
||||
|
||||
export const Winner = () => (
|
||||
<NymShipyardTheme>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
<TestAndEarnWinnerWithState />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Button, Card, CardContent, CardMedia, Dialog, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import winner from './content/assets/winner.webp';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus } from './context/types';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
export const TestAndEarnWinner: React.FC<{
|
||||
sx?: SxProps;
|
||||
entry?: DrawEntry;
|
||||
}> = ({ sx, entry }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
|
||||
|
||||
const clear = () => {
|
||||
setShowWalletCapture(false);
|
||||
setError(undefined);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
const handleStartWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(true);
|
||||
};
|
||||
|
||||
const cancelEndWalletCapture = async () => {
|
||||
setBusy(false);
|
||||
setShowWalletCapture(false);
|
||||
};
|
||||
|
||||
const handleEndWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(false);
|
||||
|
||||
if (!context.walletAddress) {
|
||||
setError('Wallet address is not set');
|
||||
return;
|
||||
}
|
||||
if (!entry?.draw_id) {
|
||||
setError('Draw id is not set');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await context.claim(entry.draw_id, context.walletAddress);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Failed to submit claim', entry.draw_id, context.walletAddress);
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showWalletCapture && (
|
||||
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
|
||||
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
|
||||
</Dialog>
|
||||
)}
|
||||
<Card sx={{ mb: 2 }}>
|
||||
<CardMedia component="img" height="165" image={winner} alt="winner" />
|
||||
<CardContent>
|
||||
<Typography color="warning.main" fontSize={20} fontWeight="bold">
|
||||
{Content.testAndEarn.winner.card.header}
|
||||
</Typography>
|
||||
<Typography mt={2}>
|
||||
{entry && (
|
||||
<>
|
||||
{Content.testAndEarn.winner.card.text} {entry.draw_id}.
|
||||
</>
|
||||
)}
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
variant="contained"
|
||||
sx={{ ml: 2, my: 2 }}
|
||||
size="small"
|
||||
onClick={handleStartWalletCapture}
|
||||
>
|
||||
{Content.testAndEarn.winner.claimButton.text}
|
||||
</LoadingButton>
|
||||
</Typography>
|
||||
{error && (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
|
||||
{error}
|
||||
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
|
||||
Try again!
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnWinnerWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
if (context.draws?.current?.entry?.status === DrawEntryStatus.winner) {
|
||||
return <TestAndEarnWinner sx={sx} entry={context.draws.current.entry} />;
|
||||
}
|
||||
|
||||
// when the user does not have any unclaimed prizes, don't render anything
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Card, CardContent, CardMedia, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import Content from './content/TestAndEarn/WinnerEntersWalletAddress.mdx';
|
||||
|
||||
export const TestAndEarnWinnerWalletAddress: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => (
|
||||
<Box>
|
||||
<Content />
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Alert, AlertTitle, Link } from '@mui/material';
|
||||
import { TestAndEarnError } from '../../TestAndEarnError';
|
||||
|
||||
<Alert severity="error" sx={{ mb: 4 }}>
|
||||
<AlertTitle>Oh no! Something went wrong</AlertTitle>
|
||||
|
||||
Sorry about that. Here is some more information about the error that occurred:
|
||||
|
||||
<TestAndEarnError/>
|
||||
|
||||
Any error reports that you send us will contain information about your client, the gateway you're using and your IP address.
|
||||
|
||||
We need this information to make Nym better for everyone.
|
||||
|
||||
</Alert>
|
||||
|
||||
If you'd like more information about Test&Earn please look on the <Link href="http://shipyard.nymtech.net/test-and-win">Shipyard website</Link>.
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Card, CardContent, Link, Typography } from '@mui/material';
|
||||
import { TestAndEarnTakePart } from '../../TestAndEarnTakePart';
|
||||
|
||||
### Test privacy & Earn tokens!
|
||||
|
||||
<Typography color="primary.light" component="span">
|
||||
Help us stress test the Nym privacy system and have the chance to earn 1000 NYMs per day!
|
||||
</Typography>
|
||||
|
||||
All you need to do is:
|
||||
|
||||
1. Make sure you're running NymConnect and it is connected.
|
||||
2. Note your reference number.
|
||||
3. NymConnect will ping you a task every day.
|
||||
|
||||
Complete the task, post it on Twitter <Typography color="primary.light" component="span">#PrivacyLovesCompany</Typography> or <Link target="_blank" href="https://t.me/nymchan">Telegram</Link> along with your reference number!
|
||||
|
||||
Thank you for being part of the Nym community and helping to build a flourishing and free digital society. #PrivacyLovesCompany and we love you!
|
||||
|
||||
<Card>
|
||||
<CardContent sx={{ py: 2 }}>
|
||||
<TestAndEarnTakePart content={"I want to take part"} websiteLinkText={"Terms and conditions"}
|
||||
websiteLinkUrl={"https://shipyard.nymtech.net/test-and-win"} sx={{ py: 2 }} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { TestAndEarnEnterWalletAddress } from '../../TestAndEarnEnterWalletAddress';
|
||||
|
||||
### 🎉 Congratulations! One more thing...
|
||||
|
||||
We need one more thing from you, and that is your wallet address:
|
||||
|
||||
<TestAndEarnEnterWalletAddress/>
|
||||
|
||||
Once you've submitting your wallet address over the mixnet, we will be in touch to arrange sending your tokens to you.
|
||||
@@ -0,0 +1,3 @@
|
||||
## 😕 Sorry, Test&Earn is only accessible while you are connected to the mixnet
|
||||
|
||||
Please connect to any service provider and try again once the connection has been established.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 498 KiB |
@@ -0,0 +1,43 @@
|
||||
testAndEarn:
|
||||
mainWindow:
|
||||
button:
|
||||
text:
|
||||
default: Join Test&Earn
|
||||
entered: Test&Earn
|
||||
claim: Claim your reward
|
||||
popup:
|
||||
disconnected: Test&Earn is only available when connected. Please connect to any service provider.
|
||||
help:
|
||||
url: https://shipyard.nymtech.net/test-and-win
|
||||
popupWindow:
|
||||
title: NymConnect Test&Earn 🌈
|
||||
notifications:
|
||||
takePart:
|
||||
title: Thanks for taking part in Ter&Earn
|
||||
body: Watch out for new tasks 👀 and take part to earn
|
||||
youAreInDraw:
|
||||
title: Thanks for completing the task ✨
|
||||
body: Post a message on Telegram, Discord or Twitter for a chance to to be selected 🤞 good luck
|
||||
task:
|
||||
afterText: Copy your reference number
|
||||
beforeSocials: "And include it in your post to:"
|
||||
draw:
|
||||
next:
|
||||
header: The next task starts
|
||||
winner:
|
||||
claimButton:
|
||||
text: Claim your reward!
|
||||
card:
|
||||
header: You are a top contributor!
|
||||
text: Congratulations, you have earned the reward for
|
||||
status:
|
||||
chip:
|
||||
Pending: Good luck 🤞
|
||||
Winner: Rewarded!
|
||||
Claimed: Claimed
|
||||
NoWin: No rewards
|
||||
text:
|
||||
Pending: Task completed. Good luck 🤞
|
||||
Winner: Well done 🎉
|
||||
Claimed: You have claimed the reward 💰
|
||||
NoWin: Sorry you were not a top contributor, better luck next time!
|
||||
@@ -0,0 +1,272 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { ClientId, DrawEntry, Draws, Registration } from './types';
|
||||
import { useClientContext } from '../../../context/main';
|
||||
import { ConnectionStatusKind } from '../../../types';
|
||||
|
||||
export type TTestAndEarnContext = {
|
||||
loadedOnce: boolean;
|
||||
loading: boolean;
|
||||
clientDetails?: ClientId;
|
||||
registration?: Registration;
|
||||
walletAddress?: string;
|
||||
draws?: Draws;
|
||||
isWinnerWithUnclaimedPrize?: boolean;
|
||||
isEnterWallet?: boolean;
|
||||
error?: string;
|
||||
setWalletAddress: (newWalletAddress: string) => void;
|
||||
clearStorage: () => Promise<void>;
|
||||
toggleGrowthWindow: (windowTitle?: string) => Promise<void>;
|
||||
setAndStoreClientId: (newClientId: ClientId) => Promise<void>;
|
||||
setAndStoreRegistration: (registration: Registration) => Promise<void>;
|
||||
enterDraw: (drawId: string) => Promise<DrawEntry>;
|
||||
claim: (drawId: string, walletAddress: string) => Promise<void>;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
const defaultValue: TTestAndEarnContext = {
|
||||
loadedOnce: false,
|
||||
loading: true,
|
||||
setWalletAddress: () => undefined,
|
||||
clearStorage: async () => undefined,
|
||||
toggleGrowthWindow: async () => undefined,
|
||||
setAndStoreRegistration: async () => undefined,
|
||||
setAndStoreClientId: async () => undefined,
|
||||
enterDraw: async () => ({} as DrawEntry),
|
||||
claim: async () => undefined,
|
||||
refresh: async () => undefined,
|
||||
};
|
||||
|
||||
export const TestAndEarnContext = createContext(defaultValue);
|
||||
|
||||
const CLIENT_ID_KEY = 'tne_client_id';
|
||||
const REGISTRATION_KEY = 'tne_registration';
|
||||
|
||||
export const TestAndEarnContextProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const clientContext = useClientContext();
|
||||
const [loadedOnce, setLoadedOnce] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [walletAddress, setWalletAddress] = useState<string>();
|
||||
const [registration, setRegistration] = useState<Registration>();
|
||||
const [clientDetails, setClientDetails] = useState<ClientId>();
|
||||
const [draws, setDraws] = useState<Draws>();
|
||||
|
||||
const setAndStoreClientId = async (newClientId: ClientId) => {
|
||||
await forage.setItem({ key: CLIENT_ID_KEY, value: newClientId } as any)();
|
||||
setClientDetails((prevState) => {
|
||||
if (
|
||||
prevState?.client_id !== newClientId.client_id ||
|
||||
prevState?.client_id_signature !== newClientId.client_id_signature
|
||||
) {
|
||||
console.log('Setting client details');
|
||||
return newClientId;
|
||||
}
|
||||
console.log('Skipping client details');
|
||||
return prevState;
|
||||
});
|
||||
};
|
||||
const loadClientDetails = async () => {
|
||||
const data: ClientId | undefined = await forage.getItem({ key: CLIENT_ID_KEY })();
|
||||
if (data) {
|
||||
try {
|
||||
setClientDetails((prevState) => {
|
||||
if (prevState?.client_id !== data.client_id || prevState?.client_id_signature !== data.client_id_signature) {
|
||||
console.log('Setting client details');
|
||||
return data;
|
||||
}
|
||||
console.log('Skipping client details');
|
||||
return prevState;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to get registration');
|
||||
}
|
||||
} else {
|
||||
const clientId: ClientId = await invoke('growth_tne_get_client_id');
|
||||
await setAndStoreClientId(clientId);
|
||||
}
|
||||
};
|
||||
|
||||
const loadRegistration = async () => {
|
||||
const data: Registration | undefined = await forage.getItem({ key: REGISTRATION_KEY })();
|
||||
if (data) {
|
||||
try {
|
||||
setRegistration((prevState) => {
|
||||
if (
|
||||
prevState?.timestamp !== data.timestamp ||
|
||||
prevState.client_id_signature !== data.client_id_signature ||
|
||||
prevState.id !== data.id
|
||||
) {
|
||||
console.log('Setting registration');
|
||||
return data;
|
||||
}
|
||||
console.log('Skipping registration');
|
||||
return prevState;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to get registration');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadDraws = React.useCallback(async () => {
|
||||
setLoading(true);
|
||||
let clientDetailsForDraws = clientDetails;
|
||||
try {
|
||||
if (!clientDetailsForDraws) {
|
||||
console.log('[loadDraws] client details not set, trying to get...');
|
||||
clientDetailsForDraws = await invoke('growth_tne_get_client_id');
|
||||
}
|
||||
|
||||
if (!clientDetailsForDraws) {
|
||||
console.log('[loadDraws] failed to get client details not set, skipping...');
|
||||
setLoading(false);
|
||||
setLoadedOnce(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const newDraws: Draws = await invoke('growth_tne_get_draws', { clientDetails: clientDetailsForDraws });
|
||||
console.log('[loadDraws] draws = ', newDraws);
|
||||
|
||||
// find the entered draw and keep a reference
|
||||
const entered = newDraws.draws.find((draw) => draw.draw_id === newDraws.current?.id);
|
||||
if (newDraws.current) {
|
||||
newDraws.current.entry = entered;
|
||||
}
|
||||
|
||||
console.log('[loadDraws] setting draws');
|
||||
setDraws(newDraws);
|
||||
} catch (e) {
|
||||
console.error('Could not get draws: ', e);
|
||||
}
|
||||
setLoading(false);
|
||||
setLoadedOnce(true);
|
||||
console.log('[loadDraws] done, loaded once');
|
||||
}, [clientDetails]);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadClientDetails().catch(console.error);
|
||||
loadRegistration().catch(console.error);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (registration && clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
setTimeout(() => {
|
||||
loadDraws().catch(console.error);
|
||||
}, 1000 * 3);
|
||||
}
|
||||
}, [registration?.id, registration?.timestamp, clientContext.connectionStatus]);
|
||||
|
||||
const refresh = React.useCallback(async () => {
|
||||
console.log('Refreshing...');
|
||||
|
||||
console.log('Loading client details...');
|
||||
await loadClientDetails();
|
||||
|
||||
console.log('Loading registration...');
|
||||
await loadRegistration();
|
||||
|
||||
console.log('Loading draws...');
|
||||
await loadDraws();
|
||||
|
||||
console.log('Refresh complete.');
|
||||
}, [clientDetails]);
|
||||
|
||||
const clearStorage = async () => {
|
||||
await forage.setItem({ key: REGISTRATION_KEY, value: undefined })();
|
||||
};
|
||||
|
||||
const toggleGrowthWindow = useCallback(async (windowTitle?: string) => {
|
||||
try {
|
||||
await invoke('growth_tne_toggle_window', { windowTitle });
|
||||
} catch (e) {
|
||||
console.error('Failed to toggle growth window', e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setAndStoreRegistration = async (newRegistration: Registration) => {
|
||||
await forage.setItem({ key: REGISTRATION_KEY, value: newRegistration } as any)();
|
||||
setRegistration(newRegistration);
|
||||
};
|
||||
|
||||
const enterDraw = async (drawId: string): Promise<DrawEntry> => {
|
||||
if (!clientDetails) {
|
||||
throw new Error('No client details set');
|
||||
}
|
||||
if (!draws) {
|
||||
throw new Error('No draws set');
|
||||
}
|
||||
|
||||
const existingEntry: DrawEntry | undefined = draws.draws.filter((d) => d.draw_id === drawId)[0];
|
||||
if (existingEntry) {
|
||||
throw new Error('Already entered into draw');
|
||||
}
|
||||
|
||||
const entry: DrawEntry = await invoke('growth_tne_enter_draw', { clientDetails, drawId });
|
||||
console.log('Entered draw', { entry });
|
||||
|
||||
await loadDraws();
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
const claim = async (drawId: string, newWalletAddress: string) => {
|
||||
if (!clientDetails) {
|
||||
throw new Error('No client details set');
|
||||
}
|
||||
if (!draws) {
|
||||
throw new Error('No draws set');
|
||||
}
|
||||
if (!registration) {
|
||||
throw new Error('No registration set');
|
||||
}
|
||||
|
||||
const registrationId = registration.id;
|
||||
|
||||
const args = {
|
||||
registrationId,
|
||||
clientDetails,
|
||||
drawId,
|
||||
walletAddress: newWalletAddress,
|
||||
};
|
||||
console.log({ args });
|
||||
await invoke('growth_tne_submit_wallet_address', args);
|
||||
|
||||
await loadDraws();
|
||||
};
|
||||
|
||||
const contextValue = useMemo<TTestAndEarnContext>(
|
||||
() => ({
|
||||
loadedOnce,
|
||||
loading,
|
||||
clientDetails,
|
||||
registration,
|
||||
walletAddress,
|
||||
draws,
|
||||
clearStorage,
|
||||
toggleGrowthWindow,
|
||||
setWalletAddress,
|
||||
setAndStoreClientId,
|
||||
setAndStoreRegistration,
|
||||
enterDraw,
|
||||
refresh,
|
||||
claim,
|
||||
}),
|
||||
[
|
||||
loadedOnce,
|
||||
loading,
|
||||
walletAddress,
|
||||
registration,
|
||||
refresh,
|
||||
draws,
|
||||
draws?.current?.last_modified,
|
||||
draws?.current?.entry,
|
||||
draws?.draws.length,
|
||||
clientDetails,
|
||||
],
|
||||
);
|
||||
return <TestAndEarnContext.Provider value={contextValue}>{children}</TestAndEarnContext.Provider>;
|
||||
};
|
||||
|
||||
export const useTestAndEarnContext = () => useContext(TestAndEarnContext);
|
||||
@@ -0,0 +1,262 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { TTestAndEarnContext, TestAndEarnContext } from '../TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus, DrawWithWordOfTheDay } from '../types';
|
||||
|
||||
const methodDefaults = {
|
||||
loadedOnce: true,
|
||||
loading: false,
|
||||
refresh: async () => undefined,
|
||||
setAndStoreClientId: async () => undefined,
|
||||
setAndStoreRegistration: async () => undefined,
|
||||
clearStorage: async () => undefined,
|
||||
toggleGrowthWindow: async () => undefined,
|
||||
setWalletAddress: async () => undefined,
|
||||
enterDraw: async () => ({} as DrawEntry),
|
||||
claim: async () => undefined,
|
||||
};
|
||||
|
||||
const mockValues_NotRegistered: TTestAndEarnContext = {
|
||||
...methodDefaults,
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_NotRegistered = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_NotRegistered}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
export const testMarkdown = `**Create a sentence including "Nym" and one or more of the following words** *(in any language)*:
|
||||
|
||||
- Privacy
|
||||
- Pleasure
|
||||
- Pineapple
|
||||
- Mix
|
||||
`;
|
||||
|
||||
const mockValues_Registered: TTestAndEarnContext = {
|
||||
...methodDefaults,
|
||||
registration: {
|
||||
id: '1234',
|
||||
client_id_signature: 'signature',
|
||||
client_id: '5678',
|
||||
timestamp: '2022-12-12T18:17:37.840Z',
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_Registered = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_Registered}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const allDraws: DrawEntry[] = [
|
||||
{
|
||||
draw_id: '1111',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'AAAA',
|
||||
status: DrawEntryStatus.pending,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.noWin,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.claimed,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.winner,
|
||||
},
|
||||
];
|
||||
|
||||
const draws: DrawEntry[] = [
|
||||
{
|
||||
draw_id: '1111',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'AAAA',
|
||||
status: DrawEntryStatus.pending,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.noWin,
|
||||
},
|
||||
];
|
||||
|
||||
const drawsWithWin: DrawEntry[] = [
|
||||
{
|
||||
draw_id: '1111',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'AAAA',
|
||||
status: DrawEntryStatus.winner,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.noWin,
|
||||
},
|
||||
];
|
||||
|
||||
const drawsWithClaim: DrawEntry[] = [
|
||||
{
|
||||
draw_id: '1111',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'AAAA',
|
||||
status: DrawEntryStatus.claimed,
|
||||
},
|
||||
{
|
||||
draw_id: '2222',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
id: 'BBBB',
|
||||
status: DrawEntryStatus.noWin,
|
||||
},
|
||||
];
|
||||
|
||||
const current: DrawWithWordOfTheDay = {
|
||||
id: '1111',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now().plus({ day: 1 }).minus({ second: 25 }).toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: testMarkdown,
|
||||
};
|
||||
|
||||
const mockValues_RegisteredWithAllDrawsAndEntry: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
current: {
|
||||
...current,
|
||||
},
|
||||
draws: allDraws,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithAllDraws = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithAllDrawsAndEntry}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsNoCurrent: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
draws: drawsWithClaim,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsNoCurrent}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDraws: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
current,
|
||||
draws,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDraws = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDraws}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsAndEntry: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
current: {
|
||||
...current,
|
||||
entry: mockValues_RegisteredWithDraws.draws!.draws[0],
|
||||
},
|
||||
draws,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntry = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntry}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsAndEntryAndWinner: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
current: {
|
||||
...current,
|
||||
entry: drawsWithWin[0],
|
||||
},
|
||||
draws: drawsWithWin,
|
||||
},
|
||||
isWinnerWithUnclaimedPrize: true,
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntryAndWinner}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsAndEntryAndNoWinner: TTestAndEarnContext = {
|
||||
...mockValues_RegisteredWithDrawsAndEntry,
|
||||
isWinnerWithUnclaimedPrize: false,
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntryAndNoWinner}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsAndEntryAndWinnerCollectWallet: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
draws: drawsWithWin,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntryAndWinnerCollectWallet}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsAndEntryAndWinnerClaimed: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
draws: drawsWithClaim,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsAndEntryAndWinnerClaimed}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredAndError: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
error: 'Error message text will go here',
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredAndError = ({ children }: { children: React.ReactNode }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredAndError}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
@@ -0,0 +1,66 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export interface ClientId {
|
||||
client_id: string;
|
||||
client_id_signature: string;
|
||||
}
|
||||
|
||||
export interface Registration {
|
||||
id: string;
|
||||
client_id: string;
|
||||
client_id_signature: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface DrawEntryPartial {
|
||||
draw_id: string;
|
||||
client_id: string;
|
||||
client_id_signature: string;
|
||||
}
|
||||
|
||||
export enum DrawEntryStatus {
|
||||
pending = 'Pending',
|
||||
winner = 'Winner',
|
||||
noWin = 'NoWin',
|
||||
claimed = 'Claimed',
|
||||
}
|
||||
|
||||
export interface DrawEntry {
|
||||
id: string;
|
||||
draw_id: string;
|
||||
timestamp: string;
|
||||
status: DrawEntryStatus;
|
||||
}
|
||||
|
||||
export interface DrawWithWordOfTheDay {
|
||||
id: string;
|
||||
start_utc: string;
|
||||
end_utc: string;
|
||||
word_of_the_day?: string;
|
||||
last_modified: string;
|
||||
entry?: DrawEntry;
|
||||
}
|
||||
|
||||
export interface ClaimPartial {
|
||||
draw_id: string;
|
||||
registration_id: string;
|
||||
client_id: string;
|
||||
client_id_signature: string;
|
||||
wallet_address: string;
|
||||
}
|
||||
|
||||
export interface Winner {
|
||||
id: string;
|
||||
client_id: string;
|
||||
draw_id: string;
|
||||
timestamp: string;
|
||||
winner_reg_id: string;
|
||||
winner_wallet_address?: string;
|
||||
winner_claim_timestamp?: string;
|
||||
}
|
||||
|
||||
export interface Draws {
|
||||
current?: DrawWithWordOfTheDay;
|
||||
next?: DrawWithWordOfTheDay;
|
||||
draws: DrawEntry[];
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { FC, useEffect, useRef, useState } from 'react';
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { Box, Paper, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
|
||||
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L4
|
||||
enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
const getLogLevelName = (value: LogLevel) => {
|
||||
switch (value) {
|
||||
case LogLevel.Trace:
|
||||
return 'Trace';
|
||||
case LogLevel.Debug:
|
||||
return 'Debug';
|
||||
case LogLevel.Info:
|
||||
return 'Info';
|
||||
case LogLevel.Warn:
|
||||
return 'Warn';
|
||||
case LogLevel.Error:
|
||||
return 'Error';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L147
|
||||
interface RecordPayload {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const LogViewer: FC = () => {
|
||||
const unlisten = useRef<UnlistenFn>();
|
||||
const messages = useRef<RecordPayload[]>([]);
|
||||
const [messageCount, setMessageCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
listen('log://log', (event) => {
|
||||
console.log(event.payload);
|
||||
const payload = event.payload as RecordPayload;
|
||||
messages.current.unshift(payload);
|
||||
setMessageCount((prev) => prev + 1);
|
||||
}).then((fn) => {
|
||||
unlisten.current = fn;
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (unlisten.current) {
|
||||
unlisten.current();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ height: '100vh', width: '100vw', display: 'grid', gridTemplateRows: '1fr auto' }}>
|
||||
<Box sx={{ overflowY: 'hidden', p: 2 }}>
|
||||
<TableContainer component={Paper} sx={{ maxHeight: '100%' }}>
|
||||
<Table size="small" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Severity</TableCell>
|
||||
<TableCell>Log message</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{messages.current.map((m) => (
|
||||
<TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
<TableCell sx={{ padding: 1 }}>
|
||||
<Chip label={getLogLevelName(m.level)} variant="outlined" size="small" />
|
||||
</TableCell>
|
||||
<TableCell sx={{ padding: 1, fontFamily: 'Monospace' }}>{m.message}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
textAlign: 'right',
|
||||
fontSize: 'small',
|
||||
borderTop: '2px solid',
|
||||
borderTopColor: (theme) => theme.palette.divider,
|
||||
}}
|
||||
>
|
||||
{messageCount} log entries since opening this window
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Autocomplete, Box, Chip, Dialog, DialogProps, TextField, Typography } from '@mui/material';
|
||||
import { ServiceProvider, Service, Services } from '../types/directory';
|
||||
|
||||
export const ServiceProviderPopup: React.FC<
|
||||
DialogProps & { services: Services; onServiceProviderChanged: (sp?: ServiceProvider, s?: Service) => void }
|
||||
> = ({ services, onServiceProviderChanged, ...dialogProps }) => {
|
||||
const options = services.flatMap((s) =>
|
||||
s.items.map((sp) => ({ id: `${s.id}-${sp.id}`, title: sp.description, service: s, sp })),
|
||||
);
|
||||
return (
|
||||
<Dialog {...dialogProps} fullWidth PaperProps={{ sx: { p: 0, m: 0, width: '100%' } }}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
openOnFocus
|
||||
sx={{ p: 1 }}
|
||||
// filterOptions={(filterOptions, { inputValue }) =>
|
||||
// filterOptions.filter((o) => o.title.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
// }
|
||||
options={options}
|
||||
groupBy={(option) => option.service.description}
|
||||
getOptionLabel={(option) => option.title}
|
||||
onChange={(event, value) => onServiceProviderChanged(value?.sp, value?.service)}
|
||||
isOptionEqualToValue={(option, value) => option.id.toLowerCase() === value?.id.toLowerCase()}
|
||||
renderOption={(props, option) => (
|
||||
<Box key={option.id} component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props} fontSize="small">
|
||||
<Typography component="p" sx={{ opacity: 0.5, mr: 2, fontSize: 'inherit' }}>
|
||||
{option.id}
|
||||
</Typography>
|
||||
<p>{option.title}</p>
|
||||
</Box>
|
||||
)}
|
||||
renderInput={(params) => <TextField autoFocus {...params} label="Select a service provider" />}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,342 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ServiceProviderSelector } from './ServiceProviderSelector';
|
||||
import { Services } from '../types/directory';
|
||||
|
||||
export default {
|
||||
title: 'Components/Service Provider Selector',
|
||||
component: ServiceProviderSelector,
|
||||
} as ComponentMeta<typeof ServiceProviderSelector>;
|
||||
|
||||
const width = 240;
|
||||
|
||||
export const Loading = () => (
|
||||
<Box width={width}>
|
||||
<ServiceProviderSelector />
|
||||
</Box>
|
||||
);
|
||||
|
||||
const services: Services = JSON.parse(`[
|
||||
{
|
||||
"id": "keybase",
|
||||
"description": "Keybase",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-keybase",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf",
|
||||
"gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-1",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn",
|
||||
"gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-2",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN",
|
||||
"gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-3",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz",
|
||||
"gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-4",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj",
|
||||
"gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-5",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9",
|
||||
"gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "electrum",
|
||||
"description": "Electrum Wallet",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-electrum",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh",
|
||||
"gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-electrum-1",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd",
|
||||
"gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-electrum-2",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD",
|
||||
"gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "telegram",
|
||||
"description": "Telegram",
|
||||
"items": [
|
||||
{
|
||||
"id": "shipyard-telegram-2",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve",
|
||||
"gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-3",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE",
|
||||
"gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-4",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh",
|
||||
"gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-5",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp",
|
||||
"gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-6",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr",
|
||||
"gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-7",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi",
|
||||
"gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-8",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J",
|
||||
"gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-9",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ",
|
||||
"gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-10",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc",
|
||||
"gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-11",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM",
|
||||
"gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-12",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J",
|
||||
"gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-13",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS",
|
||||
"gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-14",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS",
|
||||
"gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-15",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi",
|
||||
"gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-16",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS",
|
||||
"gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-17",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom",
|
||||
"gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-18",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog",
|
||||
"gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-19",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD",
|
||||
"gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-20",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF",
|
||||
"gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-21",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK",
|
||||
"gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-22",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4",
|
||||
"gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-24",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR",
|
||||
"gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-25",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH",
|
||||
"gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-26",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN",
|
||||
"gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-27",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv",
|
||||
"gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-28",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE",
|
||||
"gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-29",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ",
|
||||
"gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-30",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w",
|
||||
"gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-31",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD",
|
||||
"gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-32",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC",
|
||||
"gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-33",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY",
|
||||
"gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-34",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY",
|
||||
"gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-35",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N",
|
||||
"gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-36",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe",
|
||||
"gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-37",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf",
|
||||
"gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-38",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm",
|
||||
"gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-39",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf",
|
||||
"gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blockstream",
|
||||
"description": "Blockstream Green",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-blockstream",
|
||||
"description": "Nym Blockstream Green Service Provider",
|
||||
"address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW",
|
||||
"gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`);
|
||||
export const Loaded = () => (
|
||||
<Box width={width}>
|
||||
<ServiceProviderSelector services={services} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ServiceAlreadySelected = () => (
|
||||
<Box width={width}>
|
||||
<ServiceProviderSelector
|
||||
services={services}
|
||||
currentSp={services[2].items[2]}
|
||||
onChange={(serviceProvider) => console.log('New service provider selected: ', serviceProvider)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Box, CircularProgress, Stack, TextField, Tooltip, Typography, MenuItem, ListItemIcon } from '@mui/material';
|
||||
import { ServiceProvider, Service, Services } from '../types/directory';
|
||||
import { useTauriEvents } from '../utils';
|
||||
|
||||
type ServiceWithRandomSp = {
|
||||
id: string;
|
||||
@@ -8,13 +9,31 @@ type ServiceWithRandomSp = {
|
||||
sp: ServiceProvider;
|
||||
};
|
||||
|
||||
const defaultServiceValue = { id: '', description: '', items: [] };
|
||||
|
||||
export const ServiceProviderSelector: React.FC<{
|
||||
onChange?: (serviceProvider: ServiceProvider) => void;
|
||||
onChange?: (serviceProvider?: ServiceProvider) => void;
|
||||
services?: Services;
|
||||
currentSp?: ServiceProvider;
|
||||
}> = ({ services, currentSp, onChange }) => {
|
||||
const [service, setService] = React.useState<Service>({ id: '', description: '', items: [] });
|
||||
const [service, setService] = React.useState<Service>(defaultServiceValue);
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>(currentSp);
|
||||
const [resetTrigger, setResetTrigger] = React.useState(new Date().toISOString());
|
||||
|
||||
const handleSelectSp = (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider && newServiceProvider !== currentSp) {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
}
|
||||
};
|
||||
|
||||
// when the user clears local storage, reset the selector
|
||||
useTauriEvents('help://clear-storage', () => {
|
||||
setService(defaultServiceValue);
|
||||
setServiceProvider(undefined);
|
||||
onChange?.(undefined);
|
||||
setResetTrigger(new Date().toISOString());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!serviceProvider && currentSp) {
|
||||
@@ -39,13 +58,6 @@ export const ServiceProviderSelector: React.FC<{
|
||||
}
|
||||
}, [serviceProvider, services]);
|
||||
|
||||
const handleSelectSp = (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider && newServiceProvider !== currentSp) {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
}
|
||||
};
|
||||
|
||||
if (!services) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" sx={{ my: 3 }}>
|
||||
@@ -64,7 +76,7 @@ export const ServiceProviderSelector: React.FC<{
|
||||
description,
|
||||
sp: items[Math.floor(Math.random() * items.length)],
|
||||
})),
|
||||
[services],
|
||||
[services, resetTrigger],
|
||||
);
|
||||
|
||||
if (!service) return null;
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ServiceSelector } from './ServiceSelector';
|
||||
import { Services } from '../types/directory';
|
||||
|
||||
export default {
|
||||
title: 'Components/Service Selector',
|
||||
component: ServiceSelector,
|
||||
} as ComponentMeta<typeof ServiceSelector>;
|
||||
|
||||
const width = 240;
|
||||
|
||||
export const Loading = () => (
|
||||
<Box width={width}>
|
||||
<ServiceSelector />
|
||||
</Box>
|
||||
);
|
||||
|
||||
const services: Services = JSON.parse(`[
|
||||
{
|
||||
"id": "keybase",
|
||||
"description": "Keybase",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-keybase",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf",
|
||||
"gateway": "Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-1",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "D55ksecHzY6vAeqk8MCTzCfj2pqwJeKCKZCUUGnwGnn3.FS42vXS5a6GNTb1qk3aVk5mjSiJCAuawbBVyQZZVfhvt@DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn",
|
||||
"gateway": "DfNMqQRy6pPkU8Z5rBsxRwzDUzAMXHPFwMhjF16ScZqn"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-2",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "DFdDtW7LNBATxQ4ef3jNbqs3cRE8b9wDZTCctHCQRULa.4AbKiTNVUwYFWHhy98o5pT9dELiUrkXoJQ9wHqPgf6GV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN",
|
||||
"gateway": "GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-3",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "6Y1HE1jJ92P9yoHer11TR4A2NdZePrLGaBNFf65MnYGe.FwXoh217odQDWNmViqzNX28fauYrjB3PYLrVvpqnQrX4@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz",
|
||||
"gateway": "5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-4",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "3zzhLtWvaJgn755MkRckG5aRnoTZich8ASn395iSsTgj.J1R5VuxXbh2eNHiaRbrwbKGXrrEQcHKLdzf8eg9HTB6q@3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj",
|
||||
"gateway": "3B7PsbXFuqq6rerYFLw5HPbQb4UmBqAhfWURRovMmWoj"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-keybase-5",
|
||||
"description": "Nym Keybase Service Provider",
|
||||
"address": "CHuXdZJYQ8xH7ekgN9gAuVtQ7ZikjjHEZY5BSN7yc5mN.29dFvqicKQQQvoX1vup44mspmc249RH5xgLibWMwTYGT@CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9",
|
||||
"gateway": "CfWcDJq8QBz6cVAPCYSaLbaJEhVTmHEmyYgQ6C5GdDW9"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "electrum",
|
||||
"description": "Electrum Wallet",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-electrum",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh",
|
||||
"gateway": "2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-electrum-1",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "8Tb73cFQpXCLpgxEA2VSDru2hHrcZ3KQcyMsGbxcTjBp.4x5tu66k8YkHk4tYac1qwEFbNq5WsKiX5kR51q5KKH88@4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd",
|
||||
"gateway": "4WgKhJdmUffz4e1o1ftVAGS3HnG56LiNAxA9dmaekrVd"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-electrum-2",
|
||||
"description": "Nym Electrum Service Provider",
|
||||
"address": "GR6z31MwCsvxHrnvvVN1Cpasd8aQ1giwQqPTZM9dN7VH.5rEiqakSPDrBtKmvpU8Shnhz6gRM85JLoB7AX4h7PJYr@5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD",
|
||||
"gateway": "5Ao1J38frnU9Rx5YVeF5BWExcnDTcW8etNe9W2sRASXD"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "telegram",
|
||||
"description": "Telegram",
|
||||
"items": [
|
||||
{
|
||||
"id": "shipyard-telegram-2",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "C4w6ewbQtoaZEeoaaNw1xVASChqo4WVjNfuYEUFjZxpc.8F1D7rQXf2jGoj1Ken7PiGDM8HS2Ug79wSoc9nZ1iqh1@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve",
|
||||
"gateway": "62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-3",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "DStL3BEUZuQZfbij1KAY3BvJh8rC5jpr9mc6AQ6aTLUu.Ax9foYaKfFgX6g8y393GoNpKkKrnDGFGRZwxDv9R7X6M@FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE",
|
||||
"gateway": "FQon7UwF5knbUr2jf6jHhmNLbJnMreck1eUcVH59kxYE"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-4",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8gRdGTzsDxYzpasRQhsRg59MCgNNhnfag2oFfwwZPXnB.DtDrGz7ScVm4o7sN4K3CYUJveYgz7fcXELBVLNDfMS9Y@3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh",
|
||||
"gateway": "3ojQD6V7skM1bSXJX7fVQvscjmcgptzdixQEaAha2ixh"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-5",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "AR3oEM6Uvmfs6fyddwSehoBUKCFxz7MdFi4z7aahuHuY.3ZKapg9A3Py1PXhyLbCJr8ZbJsEV6NZdN1WJaGGut5tj@EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp",
|
||||
"gateway": "EEyq16v63aotPBCepxUpCgAojrNasZ6Hk1PjpRyBAdEp"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-6",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "7n1BYhsXSwcr8Qim8AqZTAodqFia4QG6T7CRc1ihQHpv.7o4trpGqu2LHMUiXc3dddgfGET1CFFcAK9gKYoHoSn5e@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr",
|
||||
"gateway": "BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYfr"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-7",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Gv4TWhUKrvJfqh1jBRPGEQrikNZvZse2kS3ZgN9Z2nAZ.7KGPaaqUEum2C59jLvw7f8Ug8a48YuZdjjZu3t4JES4U@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi",
|
||||
"gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-8",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8Mqgp12cpF6FSXMeqzxgFgQXvTSapyNqGAi5wy7ub4ge.7z7PDsiJGiGxGz4p77v5L5fZhXBJ5qNZ8CgJwYNr6H6J",
|
||||
"gateway": "3zd3wrCK8Dz5TXrcvk5dG5s9EEdf4Ck1v9VgBPMMFVkR"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-9",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "F3N5eiPDZcGFC985Go4Mpv8p9uxFD1L3jRUdrLCbrZLm.EyTxWwwTwYpPrJBmc97GLd1LpUAphjptS5y1ed182bGk@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ",
|
||||
"gateway": "GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-10",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "G7y7e1nVBr8fmQSzdeAxXnCmmmJb5k8N3E8LBV31KE5g.GRRUCj6t6cCUUjakmTWzidMLiYA7EdCedKnup8osaBC6@AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc",
|
||||
"gateway": "AJad2R9virYEYXEsTcicN5y5tyPoixrhhAGsxoESZVnc"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-11",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2kq9Z7RyDZtb8kxXjyP3ZT8VMWHg6JXFDChGuuNBk7Hw.F5XYbBaGSoF8qAo8faPcaNRPHEq3Y25BDcwESeabUS9S@HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM",
|
||||
"gateway": "HaLyPQrhBTq75dnGeBUdYWeFVA2BBn39MgkhEt3VTMMM"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-12",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "GegdtpNzYj4QCgpih9Kxv7ZVZxmVdxYHsDkiPsbT71XG.E8xtE8mrapjzFtyuziZSrsScAKhwZMH5wNpKWtKfzJ5Y@9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J",
|
||||
"gateway": "9Byd9VAtyYMnbVAcqdoQxJnq76XEg2dbxbiF5Aa5Jj9J"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-13",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "4SsrDQeEtG3mpeD9nN5CDdGaCsxFvNeYMhoviDzNNB9f.GyqG6iK5rBvhe3HXLR11m6ULpf13ATgYvkkidLmteDLs@5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS",
|
||||
"gateway": "5EpkkrMFYAM3XcaztXnZwBWquURHSKsyc9JxUCengDFS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-14",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "9JoHRu2RrSD1fjbj9NSTASgjv9Szep7Nhd9L2PywxbBi.AZhAUDNX6iH8BqXyR5c7TJuzpwMEvDXrabNLGuRukvVf@9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS",
|
||||
"gateway": "9xJM74FwwHhEKKJHihD21QSZnHM2QBRMoFx9Wst6qNBS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-15",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "3K174ijjXqCkhMDT9xLcqjS4MXk2QsqZt4PdgNcuUrnn.BNnHnQmWoj6Uo6kkS1QkPqsdHaXrcwyR9F6MnnzDkZJL@C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi",
|
||||
"gateway": "C7J8SwZQqjWqhBryyjJxLt7FacVuPTwAmR2otGy53ayi"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-16",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BqX5Q3MEcbTnM39hUswQchLW68SrqbhL8K5ucrLmtP39.AWrVsFoVC9s6KjdpcasATmZPA3GtMsUxcfHpAkuNdtFG@Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS",
|
||||
"gateway": "Emswx6KXyjRfq1c2k4d4uD2e6nBSbH1biorCZUei8UNS"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-17",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2tQxccgcqdkuUvLqgiEkEN4rNRZ5QknygnKAFcS4gfoe.EVrY5q5sqDqBUbS3wHsRRZhk2MAw1S17hNoH1Bicyv7n@DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom",
|
||||
"gateway": "DAGQxdxwAkwjaLjTw1B9vndia4YyFD15qRgcTQxrmkom"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-18",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "8YG1rcEauJA814Nd7hSxjNe2UrRwrGsrXTm1Cmd3gRrU.FxYaYqpNN8PciNsySs3zYPrTB1J8AYUu9DBsM2vVDDfF@7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog",
|
||||
"gateway": "7EfEESLo71GUvx3UEW79LgTegHUBPUocUzGyJVv6LHog"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-19",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "HPiXADVFLwLQPNpPtyYefzvYntC6tp9UJ5fJZGfkqvDt.2EUUxmeT3AiaUzAcE5SyXRAk8a2JXBkRz4B8McSdkrST@9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD",
|
||||
"gateway": "9ACTkYraCqE9jMb6zb6ne8EDQGGhZw5ykNiq9YRUdHTD"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-20",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "2QLnEEnTmf2NRWtcQPWBeRcg7Hej5WSPWRWwtTpEEZWF.BheS78ozc8ngvhsXNNnshdJzpoYsmEvhfn3WKUYF5dRU@C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF",
|
||||
"gateway": "C2uyokSPoxhku9GexRxEo1e8KPZ7q6e8FXmK3gtY8kkF"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-21",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "FuBbnwiANfaXZnn683jBapK5XVm5ttgZSykU3vqPSHoD.94MFGv1VcBLTkRwzBDQUkWjvqtZYVBrJg2Q8JGbizcib@CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK",
|
||||
"gateway": "CTqYPY8htdAQMXCzRW9SjZzZuqYwSt2iUh6HPaNgmTvK"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-22",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "9EbQx5jQznSVbftFom7sqUSHAACrsfvMhrzhaFt4A3SZ.D1FQCirL4YKwfcmtMGvB5Rugt5sAzGEhdSjJ3bHVQRZ@7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4",
|
||||
"gateway": "7Zh1Sz5dXpA6s53CbtcdqhQhLqwf4cLynL7KqHKcjrG4"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-24",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6Umawwvf551VyB3Ko46NgKLqJdTFJeToCM67mrTmM3G.3A4sesBac4KGuMTFjvYBwLpksMJvbMbteGJQgmm4PV4Y@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR",
|
||||
"gateway": "AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-25",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "CDtxTeoyqq83JpV9G8cR5HRHRdMMaVspQsCwH3Qnajt3.F5EHK9HFcdGrE2hqA7bK9AUmkbihujYDhtNNqHKxW765@BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH",
|
||||
"gateway": "BDkeNx7JQm5NsQakst9s8htogZXhpTQedFAgZpvsGCqH"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-26",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "HukZkLG2DoarQEqaoDLuqW1GFf2NSHDUMGBZiyJGRYJD.9GyU8wPsyzcvRjcyk8hiNpTJbXCmq5F3VoVhFBZYuHR3@GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN",
|
||||
"gateway": "GsGEZiDBz8SWfHGaK5SDmhfbTEM55v37WCYYcT9wTSxN"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-27",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "773y8iMVJiRk4dRbjQzkJVbrei4TwkePNE5WTEttt77d.3Mw47C9XZj3oAzk1iSqC5Y36tbBsjtaTtdgaHM3Zsdma@7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv",
|
||||
"gateway": "7fiZtNL1RACQTwGrKLBT9nbY77bfwZnX9rqcWqc53qgv"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-28",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6jQJEorCu7YiP9HdDaMeHxcNhxeNmZ1kpd836GnqLZX.HsJqEmNTszGecsKqFB37i84nBXxqf4ETgrKmKmBvMGHC@FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE",
|
||||
"gateway": "FYnDMQzT49ZGM23gVqpTxfih14V6wuedNXirekmt37zE"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-29",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BiCSyovpFMuSnTvF2TdiuZwrytXDrd9AH47ZMcCxscVC.G9YpdicA9BBNoVHDgjWjgt17wv5WYKWcbE3vPJJVpSJD@GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ",
|
||||
"gateway":"GAjhJcrd6f1edaqUkfWCff6zdHoqo756qYrc2TfPuCXJ"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-30",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "AQRRAs9oc8QWXAFBs44YhCKUny7AyLsfLy91pwmGgxuf.CWUKoKA1afSKyw5BnFJJg19UDgnaVATupsFhQpyTEBHJ@EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w",
|
||||
"gateway": "EBT8jTD8o4tKng2NXrrcrzVhJiBnKpT1bJy5CMeArt2w"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-31",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "6YqjAZK3Pr1ngiBLcDkotboB5WiN6k6NPpbXvShH4pR5.9Ss6VW3Xbyi8LuxduNNwnXEv9njHCQ2PLSP1UK6tsoa5@42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD",
|
||||
"gateway": "42XCK9dMS9m5XJLzQd2dBuwimk6ndZnczhZaV5VPFkQD"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-32",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "EmYWLeybmj86Vzr62vxuZ3T15jwMNHggzK7sQwid96yp.GyaF9WprSr56cxUdGf5TpcUvAjb2VbAr8CVBrmBUYAaw@GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC",
|
||||
"gateway": "GL5wESoz4oSbpBaTki9qB9213FGUQXCiRjbzDkhWwoBC"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-33",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "4PDb96cck5btTj6G7rsomqwHJsp4qu8uPvFCbwHfjFUx.C5dKbaoakH7egsZvAueRbwLFbmxnQaVMeSr6QTMpuBAA@58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY",
|
||||
"gateway": "58ceEFaLJh6zAo3cirzT1BDQm7D3L5acnQrxGH1D6TAY"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-34",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "BeZbeMf9vcpUf368qDd85dtLwXLj4Ee5bsHMB2fUD8uX.HELVbppkwU1jmzUAUrCEbHeJfVciSeo8VGAkbJSpwxsb@ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY",
|
||||
"gateway":"ADdHkiTfkpsSt31zVToWW9j3KikH24aLAAwDKtCYE5jY"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-35",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Bp4JRFyf7GB9L9J95AqMPnz9zbGmPnViA5fDXKeNraLJ.D6CTdcjJVxDmH2UQvzXuPWg9Se9xXYe76uDMypXvhzd7@6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N",
|
||||
"gateway": "6UjGEeQZK14C5K2kenycTkqt7qRjEHGLgaQx3FWySo3N"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-36",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "91h7io6BGhVjbtC7dbbRcScyTJcTfnMsTQZ6aWMVsrWR.Epb4hANXCp8cGEY3wSgawux991ti9Z5Y1FHTMzAKFa6E@DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe",
|
||||
"gateway": "DF4TE7V8kJkttMvnoSVGnRFFRt6WYGxxiC2w1XyPQnHe"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-37",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "Cy2wuwKpWZ3iWzKU3ZWL1qqcVfJ5Cq85dU7UHVWwv2gc.9AhC9b2zVcLnXLGriMdxjpsWJpq6iAdCavDi63udbL89@678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf",
|
||||
"gateway": "678qVUJ21uwxZBhp3r56z7GRf6gMh3NYDHruTegPtgMf"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-38",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "GgUeUWW1NRSuquZYeZf3WkppE92EQUHJrFjNZtYU1jow.CSEjwrRi4f4uyw7N6L2LPKw2tB8spcMbFu99wHZzFZSj@77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm",
|
||||
"gateway": "77TSuVU8d1oXKbPzjec2xh4i3Wj5WwUyy9Lr36sm8gZm"
|
||||
},
|
||||
{
|
||||
"id": "shipyard-telegram-39",
|
||||
"description": "Nym Telegram Service Provider",
|
||||
"address": "kz4zWwSkYiQxqxXFPNcGUByTPQWXascD9RfYsmSxY7n.ajp3SjbBVBjrU9nXpSQXAXzbb6EHJJyhbY6cc1ajayx@BTZNB3bkkEePsT14GN8ofVtM1SJae4YLWjpBerrKYf",
|
||||
"gateway": "HyS2UZtZX3kQXdazbdE99DhCjBXjbG61LC9QsmXwbxrU"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blockstream",
|
||||
"description": "Blockstream Green",
|
||||
"items": [
|
||||
{
|
||||
"id": "nym-blockstream",
|
||||
"description": "Nym Blockstream Green Service Provider",
|
||||
"address": "GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW",
|
||||
"gateway": "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW"
|
||||
}
|
||||
]
|
||||
}
|
||||
]`);
|
||||
export const Loaded = () => (
|
||||
<Box width={width}>
|
||||
<ServiceSelector services={services} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ServiceAlreadySelected = () => (
|
||||
<Box width={width}>
|
||||
<ServiceSelector
|
||||
services={services}
|
||||
currentSp={services[2].items[2]}
|
||||
onChange={(serviceProvider) => console.log('New service provider selected: ', serviceProvider)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,179 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Divider,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Service, ServiceProvider, Services } from '../types/directory';
|
||||
import { useTauriEvents } from '../utils';
|
||||
import { ServiceProviderPopup } from './ServiceProviderPopup';
|
||||
|
||||
export const ServiceSelector: React.FC<{
|
||||
onChange?: (serviceProvider?: ServiceProvider) => void;
|
||||
services?: Services;
|
||||
currentSp?: ServiceProvider;
|
||||
}> = ({ services, currentSp, onChange }) => {
|
||||
const [service, setService] = React.useState<Service | undefined>();
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>(currentSp);
|
||||
const [isPopupVisible, setPopupVisible] = React.useState(false);
|
||||
|
||||
const getService = () => {
|
||||
if (!services || !currentSp) {
|
||||
return undefined;
|
||||
}
|
||||
return services.find((s) =>
|
||||
s.items.some(
|
||||
({ id, address, gateway }) =>
|
||||
id === currentSp.id && address === currentSp.address && gateway === currentSp.gateway,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!service && currentSp) {
|
||||
setServiceProvider(currentSp);
|
||||
setService(getService());
|
||||
}
|
||||
}, [currentSp, services]);
|
||||
|
||||
/**
|
||||
* Gets a random service provider from the currently selected service.
|
||||
*
|
||||
* If there is no service selected, or it does not have items, `undefined` is returned.
|
||||
*/
|
||||
const getRandomServiceProviderForService = (serviceToUse?: Service): ServiceProvider | undefined => {
|
||||
if (!serviceToUse?.items.length) {
|
||||
return undefined;
|
||||
}
|
||||
return serviceToUse.items[Math.floor(Math.random() * serviceToUse.items.length)];
|
||||
};
|
||||
|
||||
const handleServiceSelected = React.useCallback(
|
||||
(newService?: Service) => {
|
||||
console.log(newService?.id, service?.id);
|
||||
// if the user has chosen a new service, then pick a random service provider
|
||||
if (newService?.id !== service?.id) {
|
||||
const newServiceProvider = getRandomServiceProviderForService(newService);
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
setService(newService);
|
||||
}
|
||||
},
|
||||
[service],
|
||||
);
|
||||
|
||||
// clears the display and fire on change (to trigger upstream storage clearance)
|
||||
const clearServiceProviderAndFireOnChange = () => {
|
||||
setService(undefined);
|
||||
setServiceProvider(undefined);
|
||||
onChange?.(undefined);
|
||||
};
|
||||
|
||||
// when the user clears local storage, reset the selector
|
||||
useTauriEvents('help://clear-storage', () => {
|
||||
clearServiceProviderAndFireOnChange();
|
||||
});
|
||||
|
||||
const handleAdvancedSpChange = (newServiceProvider?: ServiceProvider, newService?: Service) => {
|
||||
setPopupVisible(false);
|
||||
setService(newService);
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
};
|
||||
|
||||
const handleNewService = (newServiceId?: string) => {
|
||||
const newService = (services || []).find((s) => s.id === newServiceId);
|
||||
setService(newService);
|
||||
};
|
||||
|
||||
if (!services) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="center" sx={{ my: 3 }}>
|
||||
<Typography fontSize={14} fontWeight={700} color={(theme) => theme.palette.common.white}>
|
||||
<CircularProgress size={14} sx={{ mr: 1 }} color="inherit" />
|
||||
Loading services...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ my: 3 }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel
|
||||
id="service-label"
|
||||
sx={{
|
||||
color: 'grey.500',
|
||||
'&.MuiInputLabel-shrink': {
|
||||
marginTop: '16px',
|
||||
marginLeft: '-2px',
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
color: 'grey.500',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Select a service
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="service-label"
|
||||
id="service-id"
|
||||
variant="filled"
|
||||
value={service?.id || ''}
|
||||
onChange={(event) => handleNewService(event.target.value)}
|
||||
fullWidth
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
background: '#383C41',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
'&& .Mui-selected': {
|
||||
backgroundColor: '#FFFFFF33',
|
||||
},
|
||||
'&& .Mui-focusVisible': {
|
||||
backgroundColor: '#FFFFFF33',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{serviceProvider && (
|
||||
<Box px={2} pb={1} sx={{ opacity: 0.5 }}>
|
||||
<Stack direction="column" fontSize="small">
|
||||
<Typography fontSize="inherit">{serviceProvider.description}</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
<code>{serviceProvider.id}</code>
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
{serviceProvider && <Divider />}
|
||||
{services.map((item) => (
|
||||
<MenuItem key={item.id} value={item.id} onClick={() => handleServiceSelected(item)}>
|
||||
<Typography>{item.description}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
<Divider />
|
||||
<Typography ml={2} variant="overline" display="block" sx={{ opacity: 0.5 }}>
|
||||
Advanced
|
||||
</Typography>
|
||||
<MenuItem onClick={() => setPopupVisible(true)}>Choose service provider</MenuItem>
|
||||
<MenuItem onClick={clearServiceProviderAndFireOnChange}>Clear settings</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<ServiceProviderPopup
|
||||
open={isPopupVisible}
|
||||
services={services}
|
||||
onBackdropClick={() => setPopupVisible(false)}
|
||||
onServiceProviderChanged={handleAdvancedSpChange}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -32,7 +32,7 @@ export type TClientContext = {
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
setServiceProvider: (serviceProvider: ServiceProvider) => void;
|
||||
setServiceProvider: (serviceProvider?: ServiceProvider) => void;
|
||||
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
@@ -58,6 +58,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
getAppVersion();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// when mounting, load the connection state (needed for the Growth window, that checks the connection state)
|
||||
(async () => {
|
||||
const currentStatus: ConnectionStatusKind = await invoke('get_connection_status');
|
||||
setConnectionStatus(currentStatus);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
@@ -107,10 +115,12 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
} as any)();
|
||||
};
|
||||
|
||||
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
await setSpInStorage(newServiceProvider);
|
||||
const setServiceProvider = useCallback(async (newServiceProvider?: ServiceProvider) => {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider?.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider?.address });
|
||||
if (newServiceProvider) {
|
||||
await setSpInStorage(newServiceProvider);
|
||||
}
|
||||
setRawServiceProvider(newServiceProvider);
|
||||
}, []);
|
||||
|
||||
@@ -177,6 +187,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
handleShowHelp,
|
||||
}),
|
||||
[
|
||||
appVersion,
|
||||
mode,
|
||||
appVersion,
|
||||
error,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ConnectionStatusKind } from 'src/types';
|
||||
import { ClientContext, TClientContext } from '../main';
|
||||
|
||||
const mockValues: TClientContext = {
|
||||
appVersion: 'v1.x.x',
|
||||
mode: 'dark',
|
||||
appVersion: '1.1.1',
|
||||
connectionStatus: ConnectionStatusKind.disconnected,
|
||||
services: [],
|
||||
showHelp: false,
|
||||
@@ -20,6 +20,9 @@ const mockValues: TClientContext = {
|
||||
startDisconnecting: async () => {},
|
||||
};
|
||||
|
||||
export const MockProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return <ClientContext.Provider value={mockValues}>{children}</ClientContext.Provider>;
|
||||
};
|
||||
export const MockProvider: React.FC<{
|
||||
children?: React.ReactNode;
|
||||
connectionStatus?: ConnectionStatusKind;
|
||||
}> = ({ connectionStatus = ConnectionStatusKind.disconnected, children }) => (
|
||||
<ClientContext.Provider value={{ ...mockValues, connectionStatus }}>{children}</ClientContext.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ClientContextProvider } from './context/main';
|
||||
import { ErrorFallback } from './components/Error';
|
||||
import { NymShipyardTheme } from './theme';
|
||||
import { TestAndEarnPopup } from './components/Growth/TestAndEarnPopup';
|
||||
import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext';
|
||||
|
||||
const root = document.getElementById('root-growth');
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<ClientContextProvider>
|
||||
<TestAndEarnContextProvider>
|
||||
<NymShipyardTheme mode="dark">
|
||||
<TestAndEarnPopup />
|
||||
</NymShipyardTheme>
|
||||
</TestAndEarnContextProvider>
|
||||
</ClientContextProvider>
|
||||
</ErrorBoundary>,
|
||||
root,
|
||||
);
|
||||
@@ -6,17 +6,20 @@ import { ErrorFallback } from './components/Error';
|
||||
import { NymMixnetTheme } from './theme';
|
||||
import { App } from './App';
|
||||
import { AppWindowFrame } from './components/AppWindowFrame';
|
||||
import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<ClientContextProvider>
|
||||
<NymMixnetTheme mode="dark">
|
||||
<AppWindowFrame>
|
||||
<App />
|
||||
</AppWindowFrame>
|
||||
</NymMixnetTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<NymMixnetTheme mode="dark">
|
||||
<AppWindowFrame>
|
||||
<App />
|
||||
</AppWindowFrame>
|
||||
</NymMixnetTheme>
|
||||
</TestAndEarnContextProvider>
|
||||
</ClientContextProvider>
|
||||
</ErrorBoundary>,
|
||||
root,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { IpAddressAndPort } from '../components/IpAddressAndPort';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea';
|
||||
|
||||
export const ConnectedLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
@@ -44,5 +45,6 @@ export const ConnectedLayout: React.FC<{
|
||||
{/* <ConnectionStats stats={stats} /> */}
|
||||
<ConnectionTimer connectedSince={connectedSince} />
|
||||
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
|
||||
<TestAndEarnButtonArea />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Box } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { ConnectionStatus } from 'src/components/ConnectionStatus';
|
||||
import { ConnectionTimer } from 'src/components/ConntectionTimer';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
import { Error } from 'src/types/error';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { ServiceProviderSelector } from '../components/ServiceProviderSelector';
|
||||
import { ServiceSelector } from '../components/ServiceSelector';
|
||||
import { useClientContext } from '../context/main';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
import { Services } from '../types/directory';
|
||||
import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea';
|
||||
|
||||
export const DefaultLayout: React.FC<{
|
||||
error?: Error;
|
||||
@@ -19,13 +19,8 @@ export const DefaultLayout: React.FC<{
|
||||
isError?: boolean;
|
||||
clearError: () => void;
|
||||
onConnectClick?: (status: ConnectionStatusKind) => void;
|
||||
onServiceProviderChange?: (serviceProvider: ServiceProvider) => void;
|
||||
}> = ({ status, error, services, busy, isError, onConnectClick, onServiceProviderChange, clearError }) => {
|
||||
const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => {
|
||||
onServiceProviderChange?.(newServiceProvider);
|
||||
};
|
||||
|
||||
const { serviceProvider: currentSp } = useClientContext();
|
||||
}> = ({ status, error, services, busy, isError, onConnectClick, clearError }) => {
|
||||
const context = useClientContext();
|
||||
|
||||
return (
|
||||
<Box pt={1}>
|
||||
@@ -39,15 +34,16 @@ export const DefaultLayout: React.FC<{
|
||||
This is experimental software. Do not rely on it for strong anonymity (yet).
|
||||
</Typography>
|
||||
</Box>
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} currentSp={currentSp} />
|
||||
<ServiceSelector services={services} onChange={context.setServiceProvider} currentSp={context.serviceProvider} />
|
||||
<ConnectionTimer />
|
||||
<ConnectionButton
|
||||
status={status}
|
||||
disabled={currentSp === undefined}
|
||||
disabled={context.serviceProvider === undefined}
|
||||
busy={busy}
|
||||
isError={isError}
|
||||
onClick={onConnectClick}
|
||||
/>
|
||||
<TestAndEarnButtonArea />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { LogViewer } from './components/LogViewer';
|
||||
import { ErrorFallback } from './components/ErrorFallback';
|
||||
import { NymMixnetTheme } from './theme';
|
||||
|
||||
const Log = () => (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<NymMixnetTheme mode="dark">
|
||||
<LogViewer />
|
||||
</NymMixnetTheme>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
const root = document.getElementById('root-log');
|
||||
|
||||
ReactDOM.render(<Log />, root);
|
||||
@@ -8,12 +8,16 @@ import { ConnectionStatusKind } from '../types';
|
||||
import { DefaultLayout } from '../layouts/DefaultLayout';
|
||||
import { ConnectedLayout } from '../layouts/ConnectedLayout';
|
||||
import { Services } from '../types/directory';
|
||||
import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea';
|
||||
|
||||
export default {
|
||||
title: 'App/Flow',
|
||||
component: AppWindowFrame,
|
||||
} as ComponentMeta<typeof AppWindowFrame>;
|
||||
|
||||
const width = 240;
|
||||
const height = 575;
|
||||
|
||||
export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
@@ -67,45 +71,47 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
context.connectionStatus === ConnectionStatusKind.connecting
|
||||
) {
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={services}
|
||||
clearError={() => {}}
|
||||
/>
|
||||
</AppWindowFrame>
|
||||
<Box width={width} height={height}>
|
||||
<AppWindowFrame>
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={services}
|
||||
clearError={() => {}}
|
||||
/>
|
||||
</AppWindowFrame>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<ConnectedLayout
|
||||
showInfoModal={false}
|
||||
handleCloseInfoModal={() => {
|
||||
return undefined;
|
||||
}}
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={services[0].items[0]}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
totalBytes: 1024,
|
||||
rateBytesPerSecond: 1024 * 1024 * 1024 + 10,
|
||||
},
|
||||
{
|
||||
label: 'out:',
|
||||
totalBytes: 1024 * 1024 * 1024 * 1024 * 20,
|
||||
rateBytesPerSecond: 1024 * 1024 + 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppWindowFrame>
|
||||
<Box width={width} height={height}>
|
||||
<AppWindowFrame>
|
||||
<ConnectedLayout
|
||||
showInfoModal={false}
|
||||
handleCloseInfoModal={() => undefined}
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={services[0].items[0]}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
totalBytes: 1024,
|
||||
rateBytesPerSecond: 1024 * 1024 * 1024 + 10,
|
||||
},
|
||||
{
|
||||
label: 'out:',
|
||||
totalBytes: 1024 * 1024 * 1024 * 1024 * 20,
|
||||
rateBytesPerSecond: 1024 * 1024 + 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</AppWindowFrame>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import { getDesignTokens } from './theme';
|
||||
import { ClientContext } from '../context/main';
|
||||
import '../../../assets/fonts/non-variable/fonts.css';
|
||||
|
||||
/**
|
||||
@@ -17,3 +16,13 @@ export const NymMixnetTheme: React.FC<{ mode: 'light' | 'dark' }> = ({ children,
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const NymShipyardTheme: React.FC<{ mode?: 'light' | 'dark' }> = ({ children, mode = 'dark' }) => {
|
||||
const theme = React.useMemo(() => createTheme(getDesignTokens(mode, true)), [mode]);
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Vendored
+4
@@ -38,6 +38,7 @@ declare module '@mui/material/styles' {
|
||||
light: string;
|
||||
dark: string;
|
||||
};
|
||||
shipyard: string;
|
||||
}
|
||||
|
||||
interface NymPaletteVariant {
|
||||
@@ -52,6 +53,9 @@ declare module '@mui/material/styles' {
|
||||
topNav: {
|
||||
background: string;
|
||||
};
|
||||
shipyard: {
|
||||
main: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ const nymPalette: NymPalette = {
|
||||
light: '#F2F2F2',
|
||||
dark: '#1D2125',
|
||||
},
|
||||
shipyard: '#817FFA',
|
||||
};
|
||||
|
||||
const darkMode: NymPaletteVariant = {
|
||||
@@ -44,6 +45,9 @@ const darkMode: NymPaletteVariant = {
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
},
|
||||
shipyard: {
|
||||
main: '#817FFA',
|
||||
},
|
||||
};
|
||||
|
||||
const lightMode: NymPaletteVariant = {
|
||||
@@ -58,6 +62,9 @@ const lightMode: NymPaletteVariant = {
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
},
|
||||
shipyard: {
|
||||
main: '#817FFA',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -103,6 +110,35 @@ const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Map a Nym palette variant onto the MUI palette for Shipyard
|
||||
*/
|
||||
const variantShipyardToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({
|
||||
text: {
|
||||
primary: variant.text.main,
|
||||
},
|
||||
primary: {
|
||||
main: nymPalette.shipyard,
|
||||
contrastText: '#fff',
|
||||
},
|
||||
secondary: {
|
||||
main: variant.mode === 'dark' ? nymPalette.background.light : nymPalette.background.dark,
|
||||
},
|
||||
success: {
|
||||
main: nymPalette.success,
|
||||
},
|
||||
info: {
|
||||
main: nymPalette.info,
|
||||
},
|
||||
warning: {
|
||||
main: nymPalette.warning,
|
||||
},
|
||||
background: {
|
||||
default: variant.background.main,
|
||||
paper: variant.background.paper,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the Network Explorer palette for light mode.
|
||||
*/
|
||||
@@ -125,6 +161,17 @@ const createDarkModePalette = (): PaletteOptions => ({
|
||||
...variantToMUIPalette(darkMode),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the Shipyard palette for dark mode.
|
||||
*/
|
||||
const createShipyardDarkModePalette = (): PaletteOptions => ({
|
||||
nym: {
|
||||
...nymPalette,
|
||||
...nymMixnetPalette(darkMode),
|
||||
},
|
||||
...variantShipyardToMUIPalette(darkMode),
|
||||
});
|
||||
|
||||
/**
|
||||
* IMPORANT: if you need to get the default MUI theme, use the following
|
||||
*
|
||||
@@ -157,12 +204,19 @@ const createDarkModePalette = (): PaletteOptions => ({
|
||||
*
|
||||
* @param mode The theme mode: 'light' or 'dark'
|
||||
*/
|
||||
export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
|
||||
// first, create the palette from user's choice of light or dark mode
|
||||
export const getDesignTokens = (mode: PaletteMode, isShipyard: boolean = false): ThemeOptions => {
|
||||
let overrides;
|
||||
if (isShipyard) {
|
||||
overrides = createShipyardDarkModePalette();
|
||||
} else {
|
||||
overrides = mode === 'light' ? createLightModePalette() : createDarkModePalette();
|
||||
}
|
||||
|
||||
// create the palette from user's choice of light or dark mode
|
||||
const { palette } = createTheme({
|
||||
palette: {
|
||||
mode,
|
||||
...(mode === 'light' ? createLightModePalette() : createDarkModePalette()),
|
||||
...overrides,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module '*.webp' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
declare module '*.yml' {
|
||||
const content: { [key: string]: any };
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '*.yaml' {
|
||||
const content: { [key: string]: any };
|
||||
export default content;
|
||||
}
|
||||
@@ -1 +1,19 @@
|
||||
// TODO
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { EventName, listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event';
|
||||
|
||||
export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) => {
|
||||
const unlisten = useRef<UnlistenFn>();
|
||||
|
||||
// list for events to clear local storage
|
||||
useEffect(() => {
|
||||
listen(event, handler).then((fn) => {
|
||||
unlisten.current = fn;
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (unlisten.current) {
|
||||
unlisten.current();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,12 @@ const path = require('path');
|
||||
const { mergeWithRules } = require('webpack-merge');
|
||||
const { webpackCommon } = require('@nymproject/webpack');
|
||||
|
||||
const entry = {
|
||||
app: path.resolve(__dirname, 'src/index.tsx'),
|
||||
growth: path.resolve(__dirname, 'src/growth.tsx'),
|
||||
log: path.resolve(__dirname, 'src/log.tsx'),
|
||||
};
|
||||
|
||||
module.exports = mergeWithRules({
|
||||
module: {
|
||||
rules: {
|
||||
@@ -9,10 +15,45 @@ module.exports = mergeWithRules({
|
||||
use: 'replace',
|
||||
},
|
||||
},
|
||||
})(webpackCommon(__dirname, 'public/index.html'), {
|
||||
entry: path.resolve(__dirname, 'src/index.tsx'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: '/',
|
||||
})(
|
||||
webpackCommon(__dirname, [
|
||||
{ filename: 'index.html', chunks: ['app'], template: path.resolve(__dirname, 'public/index.html') },
|
||||
{ filename: 'log.html', chunks: ['log'], template: path.resolve(__dirname, 'public/log.html') },
|
||||
{ filename: 'growth.html', chunks: ['growth'], template: path.resolve(__dirname, 'public/growth.html') },
|
||||
]),
|
||||
{
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.mdx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: '@mdx-js/loader',
|
||||
/** @type {import('@mdx-js/loader').Options} */
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'asset/resource',
|
||||
use: [
|
||||
{
|
||||
loader: 'yaml-loader',
|
||||
options: {
|
||||
asJSON: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
entry,
|
||||
output: {
|
||||
clean: true,
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].bundle.js',
|
||||
publicPath: '/',
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
@@ -2,10 +2,16 @@ const path = require('path');
|
||||
const { default: merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common');
|
||||
|
||||
const entry = {
|
||||
app: path.resolve(__dirname, 'src/index.tsx'),
|
||||
growth: path.resolve(__dirname, 'src/growth.tsx'),
|
||||
log: path.resolve(__dirname, 'src/log.tsx'),
|
||||
};
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
node: {
|
||||
__dirname: false,
|
||||
},
|
||||
entry: path.resolve(__dirname, './src/index'),
|
||||
entry,
|
||||
});
|
||||
|
||||
Generated
+158
-237
@@ -14,12 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.3"
|
||||
@@ -153,20 +147,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "attohttpc"
|
||||
version = "0.19.1"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "262c3f7f5d61249d8c00e5546e2685cd15ebeeb1bc0f3cc5449350a1cb07319e"
|
||||
checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"http",
|
||||
"log",
|
||||
"native-tls",
|
||||
"openssl",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"url",
|
||||
"wildmatch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -438,12 +430,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.11.4"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e270ef0cd868745878982f7ce470aa898d0d4bb248af67f0cf66f54617913ef"
|
||||
checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -1076,16 +1067,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.5.1"
|
||||
@@ -1226,6 +1207,12 @@ dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
@@ -1304,19 +1291,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embed-resource"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936c1354206a875581696369aef920e12396e93bbd251c43a7a3f3fa85023a7d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"rustc_version 0.4.0",
|
||||
"toml",
|
||||
"vswhom",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embed_plist"
|
||||
version = "1.2.2"
|
||||
@@ -1325,9 +1299,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.30"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -1569,21 +1543,6 @@ version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
@@ -2234,12 +2193,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ico"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804"
|
||||
checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"png 0.11.0",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2316,15 +2275,6 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inflate"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -2386,23 +2336,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.18.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24967112a1e4301ca5342ea339763613a37592b8a6ce6cf2e4494537c7a42faf"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
|
||||
checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
@@ -2803,17 +2739,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
@@ -3061,9 +2986,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -3160,12 +3085,6 @@ dependencies = [
|
||||
"system-deps 6.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@@ -3304,9 +3223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
@@ -3512,18 +3431,6 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"deflate",
|
||||
"inflate",
|
||||
"num-iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
@@ -3612,11 +3519,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3868,9 +3775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.4.3"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41"
|
||||
checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
|
||||
dependencies = [
|
||||
"cty",
|
||||
]
|
||||
@@ -3905,9 +3812,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3971,7 +3878,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.7.0",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3987,13 +3894,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f121348fd3b9035ed11be1f028e8944263c30641f8c5deacf57a4320782fb402"
|
||||
checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea"
|
||||
dependencies = [
|
||||
"block",
|
||||
"dispatch",
|
||||
"embed-resource",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
@@ -4007,7 +3913,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows",
|
||||
"windows 0.37.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4248,9 +4154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -4275,9 +4181,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4644,13 +4550,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4693,9 +4599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.12.2"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6fd7725dc1e593e9ecabd9fe49c112a204c8c8694db4182e78b2a5af490b1ae"
|
||||
checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -4715,7 +4621,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"image",
|
||||
"instant",
|
||||
"jni 0.19.0",
|
||||
"jni",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -4726,13 +4632,13 @@ dependencies = [
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"paste",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"raw-window-handle",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
"uuid 1.1.2",
|
||||
"windows",
|
||||
"uuid 1.2.2",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"x11-dl",
|
||||
]
|
||||
@@ -4756,9 +4662,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.0.5"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1a56a8b125069c2682bd31610109b4436c050c74447bee1078217a0325c1add"
|
||||
checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
@@ -4766,9 +4672,9 @@ dependencies = [
|
||||
"cocoa",
|
||||
"dirs-next",
|
||||
"embed_plist",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"futures-util",
|
||||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
@@ -4800,18 +4706,18 @@ dependencies = [
|
||||
"time 0.3.7",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.0.4"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acafb1c515c5d14234a294461bd43c723639a84891a45f6a250fd3441ad2e8ed"
|
||||
checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -4825,16 +4731,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.0.4"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d62a3c8790d6cba686cea6e3f7f569d12c662c3274c2d165a4fd33e3871b72"
|
||||
checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
"plist",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
@@ -4845,15 +4751,15 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time 0.3.7",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.0.4"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7296fa17996629f43081e1c66d554703900187ed900c5bf46f97f0bcfb069278"
|
||||
checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
@@ -4865,29 +4771,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.10.2"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4cff3b4d9469727fa2107c4b3d2eda110df1ba45103fb420178e536362fae4"
|
||||
checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"http-range",
|
||||
"infer",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.10.2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fa8c4edaf01d8b556e7172c844b1b4dd3399adcd1a606bd520fc3e65f698546"
|
||||
checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -4896,24 +4802,25 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
"wry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.0.3"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ff4b68d9faeb57c9c727bf58c9c9768d2b67d8e84e62ce6146e7859a2e9c6b"
|
||||
checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"glob",
|
||||
"heck 0.4.0",
|
||||
"html5ever",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchiki",
|
||||
"memchr",
|
||||
@@ -4927,7 +4834,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"url",
|
||||
"walkdir",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5340,6 +5247,12 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.9"
|
||||
@@ -5351,9 +5264,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
@@ -5379,13 +5292,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
@@ -5404,9 +5316,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.2"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.5",
|
||||
]
|
||||
@@ -5521,32 +5433,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vswhom"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"vswhom-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vswhom-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
@@ -5664,9 +5550,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "0.18.0"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29952969fb5e10fe834a52eb29ad0814ccdfd8387159b0933edf1344a1c9cdcc"
|
||||
checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -5730,13 +5616,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.16.0"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a489a9420acabb3c2ed0434b6f71f6b56b9485ec32665a28dec1ee186d716e0f"
|
||||
checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178"
|
||||
dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
]
|
||||
|
||||
@@ -5753,24 +5639,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com-sys"
|
||||
version = "0.16.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0258c53ee9adc0a4f8ba1c8c317588f7a58c7048a55b621d469ba75ab3709ca1"
|
||||
checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
"windows-bindgen",
|
||||
"windows-metadata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wildmatch"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -5808,7 +5689,6 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows_aarch64_msvc 0.37.0",
|
||||
"windows_i686_gnu 0.37.0",
|
||||
"windows_i686_msvc 0.37.0",
|
||||
@@ -5817,10 +5697,24 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-bindgen"
|
||||
version = "0.37.0"
|
||||
name = "windows"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bed7be31ade0af08fec9b5343e9edcc005d22b1f11859b8a59b24797f5858e8"
|
||||
checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows_aarch64_msvc 0.39.0",
|
||||
"windows_i686_gnu 0.39.0",
|
||||
"windows_i686_msvc 0.39.0",
|
||||
"windows_x86_64_gnu 0.39.0",
|
||||
"windows_x86_64_msvc 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-bindgen"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41"
|
||||
dependencies = [
|
||||
"windows-metadata",
|
||||
"windows-tokens",
|
||||
@@ -5828,9 +5722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.37.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67a1062e555f7d9d66fd1130ed4f7c6ec41a47529ee0850cd0e926d95b26bb14"
|
||||
checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7"
|
||||
dependencies = [
|
||||
"syn",
|
||||
"windows-tokens",
|
||||
@@ -5838,9 +5732,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-metadata"
|
||||
version = "0.37.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f33f2b90a6664e369c41ab5ff262d06f048fc9685d9bf8a0e99a47750bb0463"
|
||||
checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
@@ -5857,9 +5751,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-tokens"
|
||||
version = "0.37.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3263d25f1170419995b78ff10c06b949e8a986c35c208dc24333c64753a87169"
|
||||
checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -5873,6 +5767,12 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
@@ -5885,6 +5785,12 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
@@ -5897,6 +5803,12 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
@@ -5909,6 +5821,12 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
@@ -5921,6 +5839,12 @@ version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
@@ -5930,15 +5854,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
@@ -5950,19 +5865,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.19.0"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce19dddbd3ce01dc8f14eb6d4c8f914123bf8379aaa838f6da4f981ff7104a3f"
|
||||
checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"block",
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dunce",
|
||||
"gdk",
|
||||
"gio",
|
||||
"glib",
|
||||
"gtk",
|
||||
"html5ever",
|
||||
"http",
|
||||
"jni 0.18.0",
|
||||
"kuchiki",
|
||||
"libc",
|
||||
"log",
|
||||
"objc",
|
||||
@@ -5970,13 +5889,15 @@ dependencies = [
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.2",
|
||||
"soup2",
|
||||
"tao",
|
||||
"thiserror",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
]
|
||||
|
||||
@@ -6001,9 +5922,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.19.1"
|
||||
version = "2.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59"
|
||||
checksum = "b1536d6965a5d4e573c7ef73a2c15ebcd0b2de3347bdf526c34c297c00ac40f0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@storybook/react": "^6.5.8",
|
||||
"@tauri-apps/api": "^1.0.2",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"big.js": "^6.2.1",
|
||||
"bs58": "^4.0.1",
|
||||
|
||||
@@ -13,10 +13,10 @@ rust-version = "1.58"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "=1.0.4", features = [] }
|
||||
tauri-build = { version = "=1.2.1", features = [] }
|
||||
|
||||
tauri-codegen = "=1.0.4"
|
||||
tauri-macros = "=1.0.4"
|
||||
tauri-codegen = "=1.2.1"
|
||||
tauri-macros = "=1.2.1"
|
||||
|
||||
[dependencies]
|
||||
bip39 = "1.0"
|
||||
@@ -38,7 +38,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
strum = { version = "0.23", features = ["derive"] }
|
||||
tauri = { version = "=1.0.5", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] }
|
||||
tauri = { version = "=1.2.2", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.10", features = ["full"] }
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
"react-dom": "17",
|
||||
"@nymproject/nym-validator-client": "^0.18.0",
|
||||
"@nymproject/types": "1",
|
||||
"base58": "4"
|
||||
"base58": "4",
|
||||
"bech32": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"flat": "^5.0.2",
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { WalletAddressFormField } from './WalletAddressFormField';
|
||||
|
||||
export default {
|
||||
title: 'Accounts/Wallet Address',
|
||||
component: WalletAddressFormField,
|
||||
} as ComponentMeta<typeof WalletAddressFormField>;
|
||||
|
||||
export const Empty = () => <WalletAddressFormField />;
|
||||
|
||||
export const ErrorValue = () => <WalletAddressFormField initialValue="this is a bad value" />;
|
||||
|
||||
export const ValidValue = () => <WalletAddressFormField initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" />;
|
||||
|
||||
export const ReadOnlyValidValue = () => (
|
||||
<WalletAddressFormField readOnly initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" />
|
||||
);
|
||||
|
||||
export const ReadOnlyErrorValue = () => <WalletAddressFormField readOnly initialValue="this is a bad value" />;
|
||||
|
||||
export const WithLabel = () => (
|
||||
<Box p={2}>
|
||||
<WalletAddressFormField
|
||||
initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec"
|
||||
textFieldProps={{ label: 'Identity Key' }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const WithPlaceholder = () => (
|
||||
<WalletAddressFormField textFieldProps={{ placeholder: 'Please enter an wallet address' }} />
|
||||
);
|
||||
|
||||
export const FullWidth = () => (
|
||||
<WalletAddressFormField fullWidth initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" />
|
||||
);
|
||||
|
||||
export const HideValidTick = () => (
|
||||
<WalletAddressFormField showTickOnValid={false} fullWidth initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" />
|
||||
);
|
||||
@@ -0,0 +1,105 @@
|
||||
import * as React from 'react';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { InputAdornment, TextField } from '@mui/material';
|
||||
import { TextFieldProps } from '@mui/material/TextField/TextField';
|
||||
import { validateWalletAddress } from '@nymproject/types';
|
||||
import DoneIcon from '@mui/icons-material/Done';
|
||||
import { SxProps } from '@mui/system';
|
||||
|
||||
export const WalletAddressFormField: React.FC<{
|
||||
showTickOnValid?: boolean;
|
||||
fullWidth?: boolean;
|
||||
required?: boolean;
|
||||
readOnly?: boolean;
|
||||
initialValue?: string;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
helperText?: string;
|
||||
onChanged?: (newValue: string) => void;
|
||||
onValidate?: (isValid: boolean, error?: string) => void;
|
||||
textFieldProps?: TextFieldProps;
|
||||
errorText?: string;
|
||||
sx?: SxProps;
|
||||
}> = ({
|
||||
required,
|
||||
fullWidth,
|
||||
placeholder,
|
||||
label,
|
||||
readOnly,
|
||||
initialValue,
|
||||
errorText,
|
||||
sx,
|
||||
onChanged,
|
||||
onValidate,
|
||||
textFieldProps,
|
||||
showTickOnValid = true,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState<string | undefined>(initialValue);
|
||||
const [validationError, setValidationError] = React.useState<string | undefined>();
|
||||
|
||||
const doValidation = (newValue?: string): boolean => {
|
||||
if (validateWalletAddress(newValue)) {
|
||||
setValidationError(undefined);
|
||||
if (onValidate) {
|
||||
onValidate(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const newValidationError = 'Account address is not valid';
|
||||
setValidationError(newValidationError);
|
||||
if (onValidate) {
|
||||
onValidate(false, newValidationError);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
// validate initial value (only if set), so that validation error UI hints are set without the user typing
|
||||
if (initialValue) {
|
||||
doValidation(initialValue);
|
||||
}
|
||||
|
||||
if (errorText) {
|
||||
setValidationError(errorText);
|
||||
}
|
||||
}, [initialValue, errorText]);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
|
||||
if (doValidation(newValue)) {
|
||||
setValue(newValue);
|
||||
}
|
||||
|
||||
if (onChanged) {
|
||||
onChanged(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
fullWidth={fullWidth}
|
||||
InputProps={{
|
||||
readOnly,
|
||||
required,
|
||||
endAdornment: showTickOnValid && value && validationError === undefined && (
|
||||
<InputAdornment position="end">
|
||||
<DoneIcon color="success" />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
label={label}
|
||||
sx={sx}
|
||||
{...textFieldProps}
|
||||
aria-readonly={readOnly}
|
||||
error={validationError !== undefined}
|
||||
helperText={validationError}
|
||||
defaultValue={initialValue}
|
||||
onChange={handleChange}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,8 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"peerDependencies": {
|
||||
"@cosmjs/math": "^0.27.1",
|
||||
"bs58": "4"
|
||||
"bs58": "4",
|
||||
"bech32": "^1.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { decode } from 'bech32';
|
||||
|
||||
export const validateWalletAddress = (address?: string, prefix: string = 'n', logErrorToConsole = false): boolean => {
|
||||
if (!address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!address.startsWith(prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// try to decode the address
|
||||
decode(address);
|
||||
} catch (e) {
|
||||
if (logErrorToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to decode address', e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './coin';
|
||||
export * from './keys';
|
||||
export * from './decimal';
|
||||
export * from './accounts';
|
||||
|
||||
@@ -35,7 +35,7 @@ module.exports = (baseDir, htmlPath) => ({
|
||||
use: ['@svgr/webpack'],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|md)$/i,
|
||||
test: /\.(png|jpe?g|gif|md|webp)$/i,
|
||||
// More information here https://webpack.js.org/guides/asset-modules/
|
||||
type: 'asset',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user