build(nc-android): prepare for apk release (#2943)
* chore(nc-android): prepare for production build * refactor(nc-android): remove dead code * feat(nc-android): update native color theme * feat(nc-android): update native color theme * build(nc-android): fix rfd version issue * build(nc-android): fix dist dir no such file error * fix(nc-android): post rebase changes
This commit is contained in:
committed by
Jon Häggblad
parent
44bec774c3
commit
ba6584e4b6
@@ -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, {
|
||||
|
||||
@@ -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