Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b5cc1bb54 | |||
| e980441e5f | |||
| f1214e29f9 | |||
| 604742bddb | |||
| e935b2c60f | |||
| 4cb04e9192 | |||
| 4fa5c7f2d3 | |||
| 3321331cb0 | |||
| 1e031391fa | |||
| e9f5f0223f | |||
| 8e8adc45f3 | |||
| 158ad15d37 | |||
| 9954334b30 | |||
| 8ddd9e37e0 | |||
| d7deb9819b | |||
| 3ec0fcb0a4 | |||
| ba6584e4b6 | |||
| 44bec774c3 | |||
| a1f15cda16 | |||
| 2a42090eb7 | |||
| d31d4630b7 | |||
| a1890e49cf |
@@ -0,0 +1,112 @@
|
||||
name: Build and upload binaries to CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-client $OUTPUT_DIR
|
||||
cp target/release/nym-gateway $OUTPUT_DIR
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
+14
-3
@@ -4,11 +4,22 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
# [Unreleased]
|
||||
|
||||
# [v1.1.9] (2023-02-07)
|
||||
|
||||
### Added
|
||||
|
||||
- remove coconut feature and unify builds ([#2890])
|
||||
- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964])
|
||||
- Add `host` option to client init ([#2912])
|
||||
- Remove Coconut feature flag ([#2793])
|
||||
- Don't drop in mixnet connection handler ([#2963])
|
||||
|
||||
### Changed
|
||||
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
|
||||
- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963])
|
||||
|
||||
[#2939]: https://github.com/nymtech/nym/pull/2939
|
||||
[#2963]: https://github.com/nymtech/nym/pull/2963
|
||||
|
||||
[#2890]: https://github.com/nymtech/nym/pull/2890
|
||||
|
||||
# [v1.1.8] (2023-01-31)
|
||||
|
||||
@@ -24,7 +35,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- nym-api: an `--id` flag is now always explicitly required ([#2873])
|
||||
|
||||
[#2754]: https://github.com/nymtech/nym/issues/2754
|
||||
[#2839]: https://github.com/nymtech/nym/issues/2810
|
||||
[#2810]: https://github.com/nymtech/nym/issues/2810
|
||||
[#2931]: https://github.com/nymtech/nym/issues/2931
|
||||
[#1902]: https://github.com/nymtech/nym/issues/1902
|
||||
[#2873]: https://github.com/nymtech/nym/issues/2873
|
||||
|
||||
@@ -9,6 +9,7 @@ use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use config::{NymConfig, OptionalSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -104,6 +105,11 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_host(mut self, host: IpAddr) -> Self {
|
||||
self.socket.host = host;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socket.listening_port = port;
|
||||
self
|
||||
@@ -130,6 +136,10 @@ impl Config {
|
||||
self.socket.socket_type
|
||||
}
|
||||
|
||||
pub fn get_listening_ip(&self) -> IpAddr {
|
||||
self.socket.host
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
@@ -180,9 +190,10 @@ impl Config {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
socket_type: SocketType,
|
||||
host: IpAddr,
|
||||
listening_port: u16,
|
||||
}
|
||||
|
||||
@@ -190,6 +201,7 @@ impl Default for Socket {
|
||||
fn default() -> Self {
|
||||
Socket {
|
||||
socket_type: SocketType::WebSocket,
|
||||
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ socket_type = '{{ socket.socket_type }}'
|
||||
# will be listening for incoming requests
|
||||
listening_port = {{ socket.listening_port }}
|
||||
|
||||
# if applicable (for the case of 'WebSocket'), the ip address on which the client
|
||||
# will be listening for incoming requests
|
||||
host = '{{ socket.host }}'
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ impl SocketClient {
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown);
|
||||
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
|
||||
.start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
|
||||
@@ -12,6 +12,7 @@ use crypto::asymmetric::identity;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -46,6 +47,10 @@ pub(crate) struct Init {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Ip for the socket (if applicable) to listen for requests.
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -71,6 +76,7 @@ impl From<Init> for OverrideConfig {
|
||||
nym_apis: init_config.nym_apis,
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
host: init_config.host,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use completions::{fig_generate, ArgShell};
|
||||
use config::OptionalSet;
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
@@ -56,6 +57,7 @@ pub(crate) struct OverrideConfig {
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
disable_socket: Option<bool>,
|
||||
port: Option<u16>,
|
||||
host: Option<IpAddr>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -81,6 +83,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional(Config::with_host, args.host)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::{
|
||||
client::{config::Config, SocketClient},
|
||||
@@ -43,6 +44,10 @@ pub(crate) struct Run {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Ip for the socket (if applicable) to listen for requests.
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -64,6 +69,7 @@ impl From<Run> for OverrideConfig {
|
||||
nym_apis: run_config.nym_apis,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
host: run_config.host,
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use std::net::IpAddr;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::{sync::Notify, task::JoinHandle};
|
||||
@@ -24,10 +25,9 @@ pub(crate) struct Listener {
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(port: u16) -> Self {
|
||||
pub(crate) fn new(host: IpAddr, port: u16) -> Self {
|
||||
Listener {
|
||||
// unless we find compelling reason not to, just listen on local only
|
||||
address: SocketAddr::new("127.0.0.1".parse().unwrap(), port),
|
||||
address: SocketAddr::new(host, port),
|
||||
state: State::AwaitingConnection,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -12,7 +15,7 @@ pub struct Args {
|
||||
pub pool_addr: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub multisig_addr: Option<String>,
|
||||
pub multisig_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_denom: Option<String>,
|
||||
@@ -24,8 +27,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -34,7 +39,7 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
pool_addr: args.pool_addr,
|
||||
multisig_addr,
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::str::FromStr;
|
||||
|
||||
use coconut_dkg_common::msg::InstantiateMsg;
|
||||
use coconut_dkg_common::types::TimeConfiguration;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -14,7 +15,7 @@ pub struct Args {
|
||||
pub group_addr: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub multisig_addr: Option<String>,
|
||||
pub multisig_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub public_key_submission_time_secs: Option<u64>,
|
||||
@@ -44,8 +45,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -86,7 +89,7 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
group_addr: args.group_addr,
|
||||
multisig_addr,
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
time_configuration: Some(time_configuration),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
@@ -6,15 +6,17 @@ use log::{debug, info};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub rewarding_validator_address: Option<String>,
|
||||
pub rewarding_validator_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub vesting_contract_address: Option<String>,
|
||||
pub vesting_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub rewarding_denom: Option<String>,
|
||||
@@ -77,13 +79,17 @@ pub async fn generate(args: Args) {
|
||||
debug!("initial_rewarding_params: {:?}", initial_rewarding_params);
|
||||
|
||||
let rewarding_validator_address = args.rewarding_validator_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Rewarding validator address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Rewarding validator address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting rewarding validator address to AccountId")
|
||||
});
|
||||
|
||||
let vesting_contract_address = args.vesting_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS)
|
||||
.expect("Vesting contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS)
|
||||
.expect("Vesting contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting vesting contract address to AccountId")
|
||||
});
|
||||
|
||||
let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| {
|
||||
@@ -92,8 +98,8 @@ pub async fn generate(args: Args) {
|
||||
});
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
rewarding_validator_address,
|
||||
vesting_contract_address,
|
||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||
vesting_contract_address: vesting_contract_address.to_string(),
|
||||
rewarding_denom,
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmwasm_std::Decimal;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
use log::{debug, info};
|
||||
use multisig_contract_common::msg::InstantiateMsg;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -20,10 +22,10 @@ pub struct Args {
|
||||
pub max_voting_period: u64,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_bandwidth_contract_address: Option<String>,
|
||||
pub coconut_bandwidth_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
pub coconut_dkg_contract_address: Option<AccountId>,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -33,13 +35,18 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let coconut_bandwidth_contract_address =
|
||||
args.coconut_bandwidth_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set")
|
||||
let address =
|
||||
std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting bandwidth contract address to AccountId")
|
||||
});
|
||||
|
||||
let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
|
||||
.expect("Coconut DKG contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
|
||||
.expect("Coconut DKG contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting DKG contract address to AccountId")
|
||||
});
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
@@ -49,8 +56,8 @@ pub async fn generate(args: Args) {
|
||||
.expect("threshold can't be converted to Decimal"),
|
||||
},
|
||||
max_voting_period: Duration::Time(args.max_voting_period),
|
||||
coconut_bandwidth_contract_address,
|
||||
coconut_dkg_contract_address,
|
||||
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use validator_client::nyxd::AccountId;
|
||||
use vesting_contract_common::InitMsg;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mixnet_contract_address: Option<String>,
|
||||
pub mixnet_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_denom: Option<String>,
|
||||
@@ -21,8 +24,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let mixnet_contract_address = args.mixnet_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS)
|
||||
.expect("Mixnet contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS)
|
||||
.expect("Mixnet contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting mixnet address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -30,7 +35,7 @@ pub async fn generate(args: Args) {
|
||||
});
|
||||
|
||||
let instantiate_msg = InitMsg {
|
||||
mixnet_contract_address,
|
||||
mixnet_contract_address: mixnet_contract_address.to_string(),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
## UNRELEASED
|
||||
|
||||
- nothing yet
|
||||
|
||||
## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-07)
|
||||
|
||||
- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
|
||||
[#2923]: https://github.com/nymtech/nym/issues/2923
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
|
||||
## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31)
|
||||
|
||||
- Add routing score on gateway list ([#2913])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
@@ -37,9 +37,19 @@ function formatCellValues(val: string | number, field: string) {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'bond') {
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
|
||||
if (field === 'owner') {
|
||||
return (
|
||||
<Link underline="none" color="inherit" target="_blank" href={`https://mixnet.explorers.guru/account/${val}`}>
|
||||
{val}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
@@ -77,6 +77,7 @@ impl ConnectionHandler {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
@@ -15,7 +15,11 @@ pub(crate) mod routes;
|
||||
|
||||
/// Merges the routes with http information and returns it to Rocket for serving
|
||||
pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: routes::get_circulating_supply]
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::get_full_circulating_supply,
|
||||
routes::get_total_supply,
|
||||
routes::get_circulating_supply
|
||||
]
|
||||
}
|
||||
|
||||
/// Spawn the circulating supply cache refresher.
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use nym_api_requests::models::CirculatingSupplyResponse;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use validator_client::nyxd::Coin;
|
||||
|
||||
// TODO: this is not the best place to put it, it should be more centralised,
|
||||
// but for a quick fix, that's good enough for now...
|
||||
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
|
||||
// value from the mix denom here.
|
||||
const UNYM_RATIO: f64 = 1000000.;
|
||||
|
||||
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
|
||||
// our total supply can't exceed 1B so an overflow here is impossible
|
||||
// (if it happened, then we SHOULD crash)
|
||||
coin.amount as f64 / UNYM_RATIO
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
pub(crate) async fn get_full_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
|
||||
match cache.get_circulating_supply().await {
|
||||
@@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply(
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/total-supply-value")]
|
||||
pub(crate) async fn get_total_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.total_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/circulating-supply-value")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.circulating_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
|
||||
|
||||
let config = override_config(config_from_file, args);
|
||||
|
||||
if already_initialized {
|
||||
if !already_initialized {
|
||||
fs::create_dir_all(Config::default_config_directory(&id))
|
||||
.expect("Could not create config directory");
|
||||
fs::create_dir_all(Config::default_data_directory(&id))
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!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>
|
||||
@@ -41,8 +41,8 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
tap = "1.0.1"
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
# tauri = { version = "2.0.0-alpha.0", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
# TODO swithing to `rfd101` temporarily, untill https://github.com/tauri-apps/tauri/pull/6174 is merged
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "rfd101", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.24.1", features = ["sync", "time"] }
|
||||
@@ -64,3 +64,9 @@ tempfile = "3.3.0"
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
# [profile.dev]
|
||||
# strip = true
|
||||
# opt-level = "s"
|
||||
# lto = true
|
||||
|
||||
|
||||
+91
-90
@@ -1,111 +1,112 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "net.nymtech.nym_connect_android"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "net.nymtech.nym_connect_android"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
// proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
val abiList = findProperty("abiList") as? String
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf(
|
||||
"arm64-v8a", "armeabi-v7a", "x86", "x86_64",
|
||||
)
|
||||
}
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
val abiList = findProperty("abiList") as? String
|
||||
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64",
|
||||
)
|
||||
}
|
||||
}
|
||||
create("arm64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
create("arm") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86_64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86_64")
|
||||
}
|
||||
}
|
||||
create("arm64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf()
|
||||
namespace = "net.nymtech.nym_connect_android"
|
||||
create("arm") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86_64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf()
|
||||
namespace = "net.nymtech.nym_connect_android"
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "../../../../"
|
||||
targets = listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
arches = listOf("arm64", "arm", "x86", "x86_64")
|
||||
rootDirRel = "../../../../"
|
||||
targets = listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
arches = listOf("arm64", "arm", "x86", "x86_64")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.webkit:webkit:1.5.0")
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
implementation("androidx.webkit:webkit:1.5.0")
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.all {
|
||||
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
|
||||
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
|
||||
productFlavors.filter{ it.name != "universal" }.forEach { _ ->
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
android.applicationVariants.all {
|
||||
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
|
||||
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
|
||||
productFlavors.filter { it.name != "universal" }.forEach { _ ->
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/aarch64-linux-android/debug/libnym_connect_android.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/armv7-linux-androideabi/debug/libnym_connect_android.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/i686-linux-android/debug/libnym_connect_android.so
|
||||
+6
-6
@@ -2,15 +2,15 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.nym_connect_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<item name="colorPrimary">@color/grey_800</item>
|
||||
<item name="colorPrimaryVariant">@color/grey_900</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorSecondary">@color/green_500</item>
|
||||
<item name="colorSecondaryVariant">@color/green_900</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
</resources>
|
||||
+4
-5
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="grey_900">#212121</color>
|
||||
<color name="grey_800">#424242</color>
|
||||
<color name="green_500">#4caf50</color>
|
||||
<color name="green_900">#1b5e20</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">nym-connect-android</string>
|
||||
<string name="app_name">Nym Connect</string>
|
||||
</resources>
|
||||
+4
-4
@@ -2,12 +2,12 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.nym_connect_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorPrimary">@color/grey_800</item>
|
||||
<item name="colorPrimaryVariant">@color/grey_900</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorSecondary">@color/green_500</item>
|
||||
<item name="colorSecondaryVariant">@color/green_900</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
|
||||
@@ -40,11 +40,6 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: ClientCoreError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ApiClientError {
|
||||
#[from]
|
||||
source: crate::operations::growth::api_client::ApiClientError,
|
||||
},
|
||||
|
||||
#[error("could not send disconnect signal to the SOCKS5 client")]
|
||||
CoundNotSendDisconnectSignal,
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use the method in `operations::http` instead
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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"],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod api_client;
|
||||
pub mod assets;
|
||||
pub mod test_and_earn;
|
||||
@@ -1,159 +0,0 @@
|
||||
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;
|
||||
#[cfg(desktop)]
|
||||
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);
|
||||
|
||||
#[cfg(desktop)]
|
||||
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?)
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod export;
|
||||
pub mod growth;
|
||||
pub mod help;
|
||||
pub mod http;
|
||||
#[cfg(desktop)]
|
||||
|
||||
@@ -161,7 +161,7 @@ impl State {
|
||||
|
||||
pub fn load_socks5_config(&self) -> Result<Socks5Config> {
|
||||
let id = self.get_config_id()?;
|
||||
let config = Socks5Config::load_from_file(Some(&id))
|
||||
let config = Socks5Config::load_from_file(&id)
|
||||
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
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: FCWithChildren<{ 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>
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const TestAndEarnButtonArea: FCWithChildren = () => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
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>
|
||||
);
|
||||
@@ -1,192 +0,0 @@
|
||||
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: FCWithChildren<{ 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: FCWithChildren<{ 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: FCWithChildren<{ 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: FCWithChildren<{
|
||||
draw?: DrawWithWordOfTheDay;
|
||||
sx?: SxProps;
|
||||
}> = ({ draw }) => {
|
||||
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 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: FCWithChildren<{
|
||||
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} />;
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
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>
|
||||
);
|
||||
@@ -1,196 +0,0 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
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: FCWithChildren<{ 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: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = () => {
|
||||
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: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const drawCount = context.draws?.draws?.length || 0;
|
||||
if (drawCount < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TestAndEarnDraws sx={sx} />;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
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>
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Box, Button, Paper, Stack } from '@mui/material';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
|
||||
export const TestAndEarnEnterWalletAddress: FCWithChildren<{
|
||||
initialValue?: string;
|
||||
placeholder?: string;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
sx?: SxProps;
|
||||
}> = ({ initialValue, placeholder, onSubmit }) => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
|
||||
export const TestAndEarnError: FCWithChildren<{ 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>
|
||||
);
|
||||
@@ -1,162 +0,0 @@
|
||||
/* 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 {
|
||||
MockTestAndEarnProvider_NotRegistered,
|
||||
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: FCWithChildren<{
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ 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: FCWithChildren<{ 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>
|
||||
);
|
||||
@@ -1,118 +0,0 @@
|
||||
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: FCWithChildren<{
|
||||
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: FCWithChildren = () => {
|
||||
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>} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,75 +0,0 @@
|
||||
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 { Registration } from './context/types';
|
||||
|
||||
export const TestAndEarnTakePart: FCWithChildren<{
|
||||
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={{ ...(Array.isArray(sx) ? 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
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>
|
||||
);
|
||||
@@ -1,114 +0,0 @@
|
||||
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: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
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('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: FCWithChildren<{
|
||||
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;
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import Content from './content/TestAndEarn/WinnerEntersWalletAddress.mdx';
|
||||
|
||||
export const TestAndEarnWinnerWalletAddress: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = () => (
|
||||
<Box>
|
||||
<Content />
|
||||
</Box>
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
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>.
|
||||
@@ -1,25 +0,0 @@
|
||||
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>
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
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.
|
||||
@@ -1,3 +0,0 @@
|
||||
## 😕 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.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 498 KiB |
@@ -1,43 +0,0 @@
|
||||
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!
|
||||
@@ -1,272 +0,0 @@
|
||||
/* 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: FCWithChildren = ({ children }) => {
|
||||
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);
|
||||
@@ -1,262 +0,0 @@
|
||||
/* 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: FCWithChildren = ({ children }) => (
|
||||
<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: FCWithChildren = ({ children }) => (
|
||||
<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: FCWithChildren = ({ children }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithAllDrawsAndEntry}>
|
||||
{children}
|
||||
</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDrawsNoCurrent: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
draws: drawsWithClaim,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent: FCWithChildren = ({ children }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredWithDrawsNoCurrent}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
|
||||
const mockValues_RegisteredWithDraws: TTestAndEarnContext = {
|
||||
...mockValues_Registered,
|
||||
draws: {
|
||||
current,
|
||||
draws,
|
||||
},
|
||||
};
|
||||
|
||||
export const MockTestAndEarnProvider_RegisteredWithDraws: FCWithChildren = ({ children }) => (
|
||||
<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: FCWithChildren = ({ children }) => (
|
||||
<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: FCWithChildren = ({ children }) => (
|
||||
<TestAndEarnContext.Provider value={mockValues_RegisteredAndError}>{children}</TestAndEarnContext.Provider>
|
||||
);
|
||||
@@ -1,64 +0,0 @@
|
||||
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[];
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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,
|
||||
);
|
||||
@@ -1,18 +0,0 @@
|
||||
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);
|
||||
@@ -13,11 +13,11 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": false,
|
||||
"jsx": "react-jsx",
|
||||
"sourceMap": false,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@assets/*": ["../assets/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target"]
|
||||
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target", "src-tauri"]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ 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({
|
||||
@@ -18,22 +16,20 @@ module.exports = mergeWithRules({
|
||||
})(
|
||||
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: /\.mdx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: '@mdx-js/loader',
|
||||
/** @type {import('@mdx-js/loader').Options} */
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'asset/resource',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { mergeWithRules } = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
// const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
// const ReactRefreshTypeScript = require('react-refresh-typescript');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const ReactRefreshTypeScript = require('react-refresh-typescript');
|
||||
const commonConfig = require('./webpack.common');
|
||||
|
||||
module.exports = mergeWithRules({
|
||||
@@ -13,7 +13,7 @@ module.exports = mergeWithRules({
|
||||
},
|
||||
})(commonConfig, {
|
||||
mode: 'development',
|
||||
devtool: false,
|
||||
devtool: 'inline-source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -21,9 +21,9 @@ module.exports = mergeWithRules({
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
// getCustomTransformers: () => ({
|
||||
// before: [ReactRefreshTypeScript()],
|
||||
// }),
|
||||
getCustomTransformers: () => ({
|
||||
before: [ReactRefreshTypeScript()],
|
||||
}),
|
||||
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
|
||||
// If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative.
|
||||
transpileOnly: true,
|
||||
@@ -32,14 +32,15 @@ module.exports = mergeWithRules({
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// new ReactRefreshWebpackPlugin(),
|
||||
new ReactRefreshWebpackPlugin(),
|
||||
|
||||
// this can be included automatically by the dev server, however build mode fails if missing
|
||||
// new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
|
||||
// recommended for faster rebuild
|
||||
optimization: {
|
||||
// runtimeChunk: true,
|
||||
runtimeChunk: true,
|
||||
removeAvailableModules: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
@@ -55,14 +56,14 @@ module.exports = mergeWithRules({
|
||||
|
||||
devServer: {
|
||||
port: 9000,
|
||||
// compress: true,
|
||||
compress: true,
|
||||
historyApiFallback: true,
|
||||
// disable this because on android it makes reloading infinity loop
|
||||
hot: false,
|
||||
host: 'local-ipv4',
|
||||
allowedHosts: 'all',
|
||||
// client: {
|
||||
// overlay: false,
|
||||
// },
|
||||
client: {
|
||||
overlay: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,8 +4,6 @@ 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, {
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-07)
|
||||
|
||||
- NC - Button animations ([#2949])
|
||||
- NC - add effect when the button is clicked ([#2947])
|
||||
- NC - UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942])
|
||||
- NC - client health check when connecting ([#2859])
|
||||
|
||||
[#2949]: https://github.com/nymtech/nym/issues/2949
|
||||
[#2947]: https://github.com/nymtech/nym/issues/2947
|
||||
[#2942]: https://github.com/nymtech/nym/issues/2942
|
||||
[#2859]: https://github.com/nymtech/nym/issues/2859
|
||||
|
||||
## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31)
|
||||
|
||||
- Add supported apps in the menu + update guide ([#2868])
|
||||
|
||||
@@ -45,6 +45,8 @@ url = "2.2"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common"}
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
|
||||
@@ -52,6 +52,7 @@ fn main() {
|
||||
crate::operations::connection::status::get_gateway_connection_status,
|
||||
crate::operations::connection::status::start_connection_health_check_task,
|
||||
crate::operations::directory::get_services,
|
||||
crate::operations::directory::get_gateways_detailed,
|
||||
crate::operations::export::export_keys,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
|
||||
use crate::models::{
|
||||
DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult,
|
||||
};
|
||||
use contracts_common::types::Percent;
|
||||
use nym_api_requests::models::GatewayBondAnnotated;
|
||||
|
||||
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";
|
||||
|
||||
static GATEWAYS_DETAILED_URL: &str =
|
||||
"https://validator.nymtech.net/api/v1/status/gateways/detailed";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
|
||||
log::trace!("Fetching services");
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", res);
|
||||
log::trace!("Received: {:#?}", services_res);
|
||||
|
||||
log::trace!("Fetching gateways");
|
||||
let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL)
|
||||
.await?
|
||||
.json::<Vec<GatewayBondAnnotated>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", gateway_res);
|
||||
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
@@ -27,7 +41,7 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
|
||||
let mut filtered: Vec<DirectoryService> = vec![];
|
||||
|
||||
for service in &res {
|
||||
for service in &services_res {
|
||||
let items: _ = service
|
||||
.items
|
||||
.clone()
|
||||
@@ -47,5 +61,32 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
})
|
||||
}
|
||||
|
||||
Ok(filtered)
|
||||
let perf_threshold = Percent::from_percentage_value(90).unwrap();
|
||||
|
||||
// Use only services that are active AND have a performance of >= 90%
|
||||
let services_with_good_performance: Vec<DirectoryServiceProvider> = filtered
|
||||
.iter_mut()
|
||||
.fold(vec![], |mut acc, sp| {
|
||||
acc.append(&mut sp.items);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.filter(|sp| {
|
||||
gateway_res.iter().any(|gateway| {
|
||||
gateway.gateway_bond.gateway.identity_key == sp.gateway
|
||||
&& gateway.performance >= perf_threshold
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(services_with_good_performance)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_gateways_detailed() -> Result<Vec<GatewayBondAnnotated>> {
|
||||
let res = reqwest::get(GATEWAYS_DETAILED_URL)
|
||||
.await?
|
||||
.json::<Vec<GatewayBondAnnotated>>()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useClientContext } from './context/main';
|
||||
import { useTauriEvents } from './utils';
|
||||
import { AppRoutes } from './routes';
|
||||
import { Connected } from './pages/connection/Connected';
|
||||
|
||||
export const App: FCWithChildren = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
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;
|
||||
if (currentStatus === 'connected' || currentStatus === 'disconnected') {
|
||||
setBusy(true);
|
||||
|
||||
// eslint-disable-next-line default-case
|
||||
switch (currentStatus) {
|
||||
case 'disconnected':
|
||||
await context.startConnecting();
|
||||
context.setConnectedSince(DateTime.now());
|
||||
break;
|
||||
case 'connected':
|
||||
await context.startDisconnecting();
|
||||
context.setConnectedSince(undefined);
|
||||
break;
|
||||
}
|
||||
setBusy(false);
|
||||
}
|
||||
}, [context.connectionStatus]);
|
||||
|
||||
if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') {
|
||||
return <AppRoutes />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Connected
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
closeInfoModal={() => context.setShowInfoModal(false)}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
gatewayPerformance={context.gatewayPerformance}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={context.selectedProvider}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
totalBytes: 1024,
|
||||
rateBytesPerSecond: 1024 * 1024 * 1024 + 10,
|
||||
},
|
||||
{
|
||||
label: 'out:',
|
||||
totalBytes: 1024 * 1024 * 1024 * 1024 * 20,
|
||||
rateBytesPerSecond: 1024 * 1024 + 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -83,7 +83,6 @@ export const ConnectionStatus: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, serviceProvider, gatewayPerformance }) => {
|
||||
const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white';
|
||||
console.log(gatewayPerformance);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
if (hover) {
|
||||
return '#FFF';
|
||||
}
|
||||
return '#BBB';
|
||||
case 'connecting':
|
||||
return '#FFF';
|
||||
case 'disconnecting':
|
||||
return '#FFF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#E43E3E';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
export const PowerButton: FCWithChildren<{
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
isError?: boolean;
|
||||
disabled?: boolean;
|
||||
status: ConnectionStatusKind;
|
||||
busy?: boolean;
|
||||
}> = ({ onClick, disabled, status, isError }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
};
|
||||
|
||||
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="190"
|
||||
height="190"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-30, -25) ">
|
||||
<circle cx={131} cy={131} r={70} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" />
|
||||
<circle cx={131} cy={131} r={22} strokeWidth={1} stroke={statusFillColor} filter="url(#blur)" opacity="0.3" />
|
||||
<circle opacity={0.6} cx={131} cy={131} r={68.5} stroke={statusFillColor} />
|
||||
<g filter="url(#filter1_d_944_9033)">
|
||||
<circle cx={131} cy={131} r={64} fill="url(#paint1_radial_944_9033)" />
|
||||
<circle cx={131} cy={131} r={63} stroke={statusFillColor} strokeWidth={2} />
|
||||
</g>
|
||||
<g opacity={0.5} filter="url(#filter2_f_944_9033)">
|
||||
<g clipPath="url(#clip0_944_9033)">
|
||||
<path
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
stroke={statusFillColor}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g clipPath="url(#clip1_944_9033)">
|
||||
<path
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
fill={statusFillColor}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_944_9033"
|
||||
x={0}
|
||||
y={0}
|
||||
width={240}
|
||||
height={240}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation={40} result="effect1_foregroundBlur_944_9033" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_944_9033"
|
||||
x={52}
|
||||
y={58}
|
||||
width={158}
|
||||
height={158}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_944_9033" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_944_9033" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_f_944_9033"
|
||||
x={97}
|
||||
y={97}
|
||||
width={68}
|
||||
height={68}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation={5} result="effect1_foregroundBlur_944_9033" />
|
||||
</filter>
|
||||
<filter id="blur">
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
</filter>
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import './power-button.css';
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
if (hover) {
|
||||
return '#FFF';
|
||||
}
|
||||
return '#BBB';
|
||||
case 'connecting':
|
||||
return '#FFF';
|
||||
case 'disconnecting':
|
||||
return '#FFF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#E43E3E';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
export const PowerButton: FCWithChildren<{
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
isError?: boolean;
|
||||
disabled?: boolean;
|
||||
status: ConnectionStatusKind;
|
||||
busy?: boolean;
|
||||
}> = ({ onClick, disabled, status, isError }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
};
|
||||
|
||||
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
|
||||
|
||||
const getClassName = useCallback(() => {
|
||||
if (hover) {
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
return 'expand';
|
||||
default:
|
||||
return 'contract';
|
||||
}
|
||||
}
|
||||
if (!hover) {
|
||||
switch (status) {
|
||||
case 'connected':
|
||||
return 'expand';
|
||||
default:
|
||||
return 'contract';
|
||||
}
|
||||
}
|
||||
}, [status, hover]);
|
||||
|
||||
const buttonPulse = () => {
|
||||
if (status === 'connecting' || status === 'disconnecting') return 'pulse';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="190"
|
||||
height="190"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-30, -25) ">
|
||||
<circle cx={131} cy={131} r={75} strokeWidth={4} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" />
|
||||
<circle cx={131} cy={131} r={25} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.5" />
|
||||
<g id="Button power">
|
||||
<circle cx="131" cy="131" r="68.5" stroke={statusFillColor} strokeWidth="0.5" />
|
||||
<circle id="ring-one" className={getClassName()} cx="131" cy="131" r="73" stroke={statusFillColor} />
|
||||
<circle id="ring-two" className={getClassName()} cx="131" cy="131" r="77" stroke={statusFillColor} />
|
||||
<circle id="ring-three" className={getClassName()} cx="131" cy="131" r="81" stroke={statusFillColor} />
|
||||
<circle id="ring-four" className={getClassName()} cx="131" cy="131" r="85" stroke={statusFillColor} />
|
||||
<g id="button bg">
|
||||
<circle cx="131" cy="131" r="63" stroke={statusFillColor} strokeWidth="3" className={buttonPulse()} />
|
||||
</g>
|
||||
<g id="Power icon">
|
||||
<g id="Icon">
|
||||
<g id="Group 672_2">
|
||||
<g id="power_settings_new_black_24dp (1) 1_2" clipPath="url(#clip1_944_8739)">
|
||||
<path
|
||||
id="Vector_2"
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
fill={statusFillColor}
|
||||
className={buttonPulse()}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="blur" width="200%" height="200%" x="-50%" y="-50%">
|
||||
<feGaussianBlur stdDeviation="12.5" />
|
||||
</filter>
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
#ring-expand {
|
||||
animation-name: rings-expand;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
#ring-one.expand {
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-two.expand {
|
||||
opacity: 0.2;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-three.expand {
|
||||
opacity: 0.1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-four.expand {
|
||||
opacity: 0.05;
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-one.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-two.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-three.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-four.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
circle,
|
||||
path {
|
||||
transition: stroke 0.5s, fill 0.5s;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation-name: pulse;
|
||||
animation-duration: 0.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { Error } from 'src/types/error';
|
||||
import { TauriEvent } from 'src/types/event';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
import { useEvents } from 'src/hooks/events';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
@@ -57,21 +55,21 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
|
||||
const flattenProviders = (services: Services) => {
|
||||
return services.reduce((a: ServiceProvider[], b) => {
|
||||
return [...a, ...b.items];
|
||||
}, []);
|
||||
};
|
||||
|
||||
const initialiseApp = async () => {
|
||||
const services = await invoke('get_services');
|
||||
const allServiceProviders = flattenProviders(services as Services);
|
||||
const AppVersion = await getAppVersion();
|
||||
console.log(services);
|
||||
|
||||
setAppVersion(AppVersion);
|
||||
setServiceProviders(allServiceProviders);
|
||||
setServiceProviders(services as ServiceProvider[]);
|
||||
};
|
||||
|
||||
useEvents({
|
||||
onError: (error) => setError(error),
|
||||
onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance),
|
||||
onStatusChange: (status) => setConnectionStatus(status),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
initialiseApp();
|
||||
}, []);
|
||||
@@ -84,49 +82,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
// TODO: fix typings
|
||||
listen(TAURI_EVENT_STATUS_CHANGED, (event) => {
|
||||
const { status } = event.payload as any;
|
||||
console.log(TAURI_EVENT_STATUS_CHANGED, { status, event });
|
||||
setConnectionStatus(status);
|
||||
})
|
||||
.then((result) => {
|
||||
unlisten.push(result);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
|
||||
setError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-status-event', (e: TauriEvent) => {
|
||||
if (e.payload.message.includes('slow')) {
|
||||
setGatewayPerformance('Poor');
|
||||
|
||||
if (timerId.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
|
||||
timerId.current = setTimeout(() => {
|
||||
setGatewayPerformance('Good');
|
||||
}, 10000);
|
||||
}
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const startConnecting = useCallback(async () => {
|
||||
try {
|
||||
await invoke('start_connecting');
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from 'src/types';
|
||||
import { Error } from 'src/types/error';
|
||||
import { TauriEvent } from 'src/types/event';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
export const useEvents = ({
|
||||
onError,
|
||||
onStatusChange,
|
||||
onGatewayPerformanceChange,
|
||||
}: {
|
||||
onError: (error: Error) => void;
|
||||
onStatusChange: (status: ConnectionStatusKind) => void;
|
||||
onGatewayPerformanceChange: (status: GatewayPerformance) => void;
|
||||
}) => {
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
// TODO: fix typings
|
||||
listen(TAURI_EVENT_STATUS_CHANGED, (event) => {
|
||||
const { status } = event.payload as any;
|
||||
console.log(TAURI_EVENT_STATUS_CHANGED, { status, event });
|
||||
onStatusChange(status);
|
||||
})
|
||||
.then((result) => {
|
||||
unlisten.push(result);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
onError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-status-event', (e: TauriEvent) => {
|
||||
if (e.payload.message.includes('slow')) {
|
||||
onGatewayPerformanceChange('Poor');
|
||||
|
||||
if (timerId?.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
|
||||
timerId.current = setTimeout(() => {
|
||||
onGatewayPerformanceChange('Good');
|
||||
}, 10000);
|
||||
}
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-connection-fail-event', (e: TauriEvent) => {
|
||||
onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` });
|
||||
onGatewayPerformanceChange('Poor');
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ClientContextProvider } from './context/main';
|
||||
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';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
@@ -10,9 +10,12 @@ import { IpAddressAndPort } from 'src/components/IpAddressAndPort';
|
||||
import { ServiceProvider } from 'src/types/directory';
|
||||
import { ExperimentalWarning } from 'src/components/ExperimentalWarning';
|
||||
import { ConnectionLayout } from 'src/layouts/ConnectionLayout';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { Error } from 'src/types/error';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
|
||||
export const Connected: FCWithChildren<{
|
||||
error?: Error;
|
||||
status: ConnectionStatusKind;
|
||||
showInfoModal: boolean;
|
||||
gatewayPerformance: GatewayPerformance;
|
||||
@@ -23,9 +26,11 @@ export const Connected: FCWithChildren<{
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
closeInfoModal: () => void;
|
||||
}> = ({
|
||||
error,
|
||||
status,
|
||||
showInfoModal,
|
||||
gatewayPerformance,
|
||||
@@ -35,11 +40,13 @@ export const Connected: FCWithChildren<{
|
||||
busy,
|
||||
isError,
|
||||
serviceProvider,
|
||||
clearError,
|
||||
onConnectClick,
|
||||
closeInfoModal,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
|
||||
<IpAddressAndPortModal show={showInfoModal} onClose={closeInfoModal} ipAddress={ipAddress} port={port} />
|
||||
<ConnectionLayout
|
||||
TopContent={
|
||||
@@ -58,7 +65,7 @@ export const Connected: FCWithChildren<{
|
||||
busy={busy}
|
||||
onClick={onConnectClick}
|
||||
isError={isError}
|
||||
disabled={status === 'connecting' || status === 'disconnecting'}
|
||||
disabled={status === 'disconnecting'}
|
||||
/>
|
||||
}
|
||||
BottomContent={
|
||||
|
||||
@@ -2,14 +2,12 @@ import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { ConnectionStatus } from 'src/components/ConnectionStatus';
|
||||
import { ConnectionTimer } from 'src/components/ConntectionTimer';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
import { Error } from 'src/types/error';
|
||||
import { ExperimentalWarning } from 'src/components/ExperimentalWarning';
|
||||
import { ServiceProvider, Services } from 'src/types/directory';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { ConnectionButton } from 'src/components/ConnectionButton';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { Box } from '@mui/system';
|
||||
import { ConnectionLayout } from 'src/layouts/ConnectionLayout';
|
||||
|
||||
@@ -22,7 +20,7 @@ export const Disconnected: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, error, onConnectClick, clearError, serviceProvider }) => {
|
||||
}> = ({ status, error, onConnectClick, clearError }) => {
|
||||
return (
|
||||
<>
|
||||
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
|
||||
@@ -33,7 +31,7 @@ export const Disconnected: FCWithChildren<{
|
||||
<ConnectionTimer />
|
||||
</Box>
|
||||
}
|
||||
ConnectButton={<PowerButton onClick={onConnectClick} status={status} disabled={false} />}
|
||||
ConnectButton={<PowerButton onClick={onConnectClick} status={status} disabled={status === 'connecting'} />}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between" pt={1}>
|
||||
<Typography
|
||||
|
||||
@@ -47,6 +47,8 @@ export const ConnectionPage = () => {
|
||||
if (context.connectionStatus === 'connected')
|
||||
return (
|
||||
<Connected
|
||||
error={context.error}
|
||||
clearError={context.clearError}
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
busy={busy}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Box } from '@mui/system';
|
||||
|
||||
const appsSchema = {
|
||||
messagingApps: ['Telegram', 'Keybase'],
|
||||
wallets: ['Blockstream', 'Electum'],
|
||||
wallets: ['Blockstream', 'Electrum'],
|
||||
};
|
||||
|
||||
export const CompatibleApps = () => (
|
||||
|
||||
@@ -87,6 +87,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Connected
|
||||
clearError={() => {}}
|
||||
gatewayPerformance="Good"
|
||||
showInfoModal={false}
|
||||
closeInfoModal={() => undefined}
|
||||
|
||||
@@ -15,6 +15,7 @@ export default {
|
||||
export const Default: ComponentStory<typeof Connected> = () => (
|
||||
<Box p={2} width={242} sx={{ bgcolor: 'nym.background.dark' }}>
|
||||
<Connected
|
||||
clearError={() => {}}
|
||||
gatewayPerformance="Good"
|
||||
showInfoModal={false}
|
||||
closeInfoModal={() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
export default {
|
||||
@@ -13,7 +13,7 @@ export const Disconnected: ComponentStory<typeof PowerButton> = () => (
|
||||
);
|
||||
|
||||
export const Connecting: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.connecting} />
|
||||
<PowerButton status={ConnectionStatusKind.connecting} disabled />
|
||||
);
|
||||
|
||||
export const Connected: ComponentStory<typeof PowerButton> = () => (
|
||||
@@ -21,9 +21,9 @@ export const Connected: ComponentStory<typeof PowerButton> = () => (
|
||||
);
|
||||
|
||||
export const Disconnecting: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.disconnecting} />
|
||||
<PowerButton status={ConnectionStatusKind.disconnecting} disabled />
|
||||
);
|
||||
|
||||
export const Disabled: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.connecting} disabled />
|
||||
<PowerButton status={ConnectionStatusKind.disconnected} disabled />
|
||||
);
|
||||
|
||||
@@ -10715,6 +10715,18 @@ hex-rgb@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776"
|
||||
integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==
|
||||
|
||||
history@^4.9.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
loose-envify "^1.2.0"
|
||||
resolve-pathname "^3.0.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
value-equal "^1.0.1"
|
||||
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
@@ -10724,7 +10736,7 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
@@ -11685,6 +11697,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
|
||||
dependencies:
|
||||
is-docker "^2.0.0"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@@ -12952,7 +12969,7 @@ longest-streak@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
|
||||
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@@ -15061,6 +15078,13 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
@@ -16036,7 +16060,7 @@ react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0:
|
||||
react-is@^16.10.2, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@@ -16112,6 +16136,34 @@ react-router-dom@6, react-router-dom@^6.7.0:
|
||||
"@remix-run/router" "1.3.0"
|
||||
react-router "6.7.0"
|
||||
|
||||
react-router-dom@^5.2.0:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6"
|
||||
integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.3.4"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5"
|
||||
integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.13"
|
||||
history "^4.9.0"
|
||||
hoist-non-react-statics "^3.1.0"
|
||||
loose-envify "^1.3.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.6.0"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.7.0.tgz#db262684c13b5c2970694084ae9e8531718a0681"
|
||||
@@ -16690,6 +16742,11 @@ resolve-from@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
|
||||
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
|
||||
|
||||
resolve-pathname@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
|
||||
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
|
||||
|
||||
resolve-url@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
@@ -18123,7 +18180,12 @@ timers-browserify@^2.0.4:
|
||||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
tiny-warning@^1.0.2:
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
|
||||
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
|
||||
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
@@ -18923,6 +18985,11 @@ validate-npm-package-name@^3.0.0:
|
||||
dependencies:
|
||||
builtins "^1.0.3"
|
||||
|
||||
value-equal@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
|
||||
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
|
||||
Reference in New Issue
Block a user