Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task(recovery): Redo seed enter recovery page #1915

Merged
merged 12 commits into from
Mar 12, 2024
4 changes: 3 additions & 1 deletion common/locales/en-US/main.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ uplink = Uplink
.go-back = Go Back
.upload-queue = Upload queue
.download-queue = Download Queue
.copy-seed = Copy to Clipboard
.copied-seed = Copied to Clipboard

community = Community
.invited = You're Invited!
Expand Down Expand Up @@ -408,7 +410,7 @@ copy-seed-words = Recovery Seed
.finished = I Saved It

enter-seed-words = Recovery Seed
.instructions = Type your recovery seed here. You may either enter one word at a time or all at once separated by spaces.
.instructions = Type your recovery seed here. Each phrase should go into their respective box. Alternatively you can simply copy past your recovery seed in here.
.submit = Recover Account
.placeholder = Enter Recovery Seed...
.invalid-seed = Hmm, that seed didn't work.
Expand Down
7 changes: 6 additions & 1 deletion kit/src/elements/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use uuid::Uuid;
pub type ValidationError = String;
use crate::elements::label::Label;
use crate::elements::loader::Loader;

use common::icons::outline::Shape as Icon;
use common::icons::Icon as IconElement;

Expand Down Expand Up @@ -151,6 +150,7 @@ pub struct Props<'a> {
select_on_focus: Option<bool>,
onchange: Option<EventHandler<'a, (String, bool)>>,
onreturn: Option<EventHandler<'a, (String, bool, Code)>>,
onfocus: Option<EventHandler<'a, ()>>,
reset: Option<UseState<bool>>,
#[props(default = false)]
disable_onblur: bool,
Expand Down Expand Up @@ -416,6 +416,11 @@ pub fn Input<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
maxlength: "{max_length}",
"type": "{typ}",
placeholder: "{cx.props.placeholder}",
onfocus: move |_| {
if let Some(e) = &cx.props.onfocus {
e.call(())
}
},
onblur: move |_| {
if onblur_active {
emit_return(&cx, val.read().to_string(), *valid.current(), Code::Enter);
Expand Down
31 changes: 29 additions & 2 deletions ui/src/components/settings/sub_pages/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,34 @@ pub fn ProfileSettings(cx: Scope) -> Element {
}
if let Some(phrase) = seed_phrase.as_ref() {
let words = phrase.split_whitespace().collect::<Vec<&str>>();
let words2 = words.clone();
render!(
Button {
text: get_local_text("uplink.copy-seed"),
aria_label: "copy-seed-button".into(),
icon: Icon::BookmarkSquare,
onpress: move |_| {
match Clipboard::new() {
Ok(mut c) => {
match c.set_text(words2.clone().join("\n").to_string()) {
Ok(_) => state.write().mutate(Action::AddToastNotification(
ToastNotification::init(
"".into(),
get_local_text("uplink.copied-seed"),
None,
2,
),
)),
Err(e) => log::warn!("Unable to set text to clipboard: {e}"),
}
},
Err(e) => {
log::warn!("Unable to create clipboard reference: {e}");
}
};
},
appearance: Appearance::Secondary
},
SettingSectionSimple {
aria_label: "seed-words-section".into(),
div {
Expand All @@ -639,7 +666,7 @@ pub fn ProfileSettings(cx: Scope) -> Element {
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 1).to_string()}",
class: "num", ((idx * 2) + 1).to_string()
class: "num disable-select", ((idx * 2) + 1).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 1).to_string()}",
Expand All @@ -650,7 +677,7 @@ pub fn ProfileSettings(cx: Scope) -> Element {
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 2).to_string()}",
class: "num", ((idx * 2) + 2).to_string()
class: "num disable-select", ((idx * 2) + 2).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 2).to_string()}",
Expand Down
46 changes: 42 additions & 4 deletions ui/src/layouts/log_in/copy_seed_words.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use std::time::Duration;

use arboard::Clipboard;
use common::{icons, language::get_local_text, state::State};
use dioxus::prelude::*;
use dioxus_desktop::{use_window, LogicalSize};
use kit::elements::{button::Button, label::Label, Appearance};
use tokio::time::sleep;

use crate::get_app_style;

Expand All @@ -16,7 +20,7 @@ pub fn Layout(cx: Scope, page: UseState<AuthPages>, seed_words: UseRef<String>)
if !matches!(&*page.current(), AuthPages::Success(_)) {
window.set_inner_size(LogicalSize {
width: 500.0,
height: 460.0,
height: 480.0,
});
}

Expand Down Expand Up @@ -58,6 +62,13 @@ pub fn Layout(cx: Scope, page: UseState<AuthPages>, seed_words: UseRef<String>)

#[component]
fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Element {
let copied = use_ref(cx, || false);
use_future(cx, copied, |current| async move {
if *current.read() {
sleep(Duration::from_secs(3)).await;
*current.write() = false;
}
});
render! {
div {
class: "seed-words",
Expand All @@ -66,10 +77,9 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
class: "row",
div {
class: "col",

span {
aria_label: "seed-word-number-{((idx * 2) + 1).to_string()}",
class: "num", ((idx * 2) + 1).to_string()
class: "num disable-select", ((idx * 2) + 1).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 1).to_string()}",
Expand All @@ -80,7 +90,7 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 2).to_string()}",
class: "num", ((idx * 2) + 2).to_string()
class: "num disable-select", ((idx * 2) + 2).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 2).to_string()}",
Expand All @@ -90,6 +100,28 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
}
})
},
div {
class: "controls",
Button {
text: get_local_text("uplink.copy-seed"),
aria_label: "copy-seed-button".into(),
icon: icons::outline::Shape::BookmarkSquare,
onpress: move |_| {
match Clipboard::new() {
Ok(mut c) => {
match c.set_text(words.join("\n").to_string()) {
Ok(_) => *copied.write() = true,
Err(e) => log::warn!("Unable to set text to clipboard: {e}"),
}
},
Err(e) => {
log::warn!("Unable to create clipboard reference: {e}");
}
};
},
appearance: Appearance::Secondary
}
}
div {
class: "controls",
Button {
Expand All @@ -107,5 +139,11 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
}
}
}
copied.read().then(||{
rsx!(div{
class: "copied-toast",
get_local_text("uplink.copied-seed")
})
})
}
}
9 changes: 9 additions & 0 deletions ui/src/layouts/log_in/enter_seed_handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let page = document.getElementById("enter-seed-words-layout");
let inputs = page.getElementsByTagName("input");
for (let input of inputs) {
input.addEventListener("paste", event => {
event.preventDefault();
let paste = (event.clipboardData || window.clipboardData).getData("text");
dioxus.send(paste)
})
}
154 changes: 128 additions & 26 deletions ui/src/layouts/log_in/enter_seed_words.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ use common::{
use dioxus::prelude::*;
use dioxus_desktop::{use_window, LogicalSize};
use futures::{channel::oneshot, StreamExt};
use kit::elements::{button::Button, input, label::Label, Appearance};
use kit::elements::{
button::Button,
input::{self, Options},
label::Label,
Appearance,
};

use crate::get_app_style;

Expand Down Expand Up @@ -38,17 +43,45 @@ struct Cmd {
pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Element {
let state = use_ref(cx, State::load);
let loading = use_state(cx, || false);
let input = use_ref(cx, String::new);
let input: &UseRef<Vec<_>> = use_ref(cx, || (0..12).map(|_| String::new()).collect());
let seed_error = use_state(cx, || None);
let focus = use_ref(cx, || 0);

let window = use_window(cx);

if !matches!(&*page.current(), AuthPages::Success(_)) {
window.set_inner_size(LogicalSize {
width: 500.0,
height: 280.0,
height: 480.0,
});
}

let eval = use_eval(cx);
use_effect(cx, (), move |_| {
to_owned![eval, input];
async move {
if let Ok(eval) = eval(include_str!("./enter_seed_handler.js")) {
loop {
if let Ok(val) = eval.recv().await {
let paste = val
.to_string()
.replace("\\\\", "\\")
.replace("\\r", "\r")
.replace("\\n", "\n");
let paste = &paste[1..(paste.len() - 1)]; // Trim the apostrophes from the input
if !paste.is_empty() {
let phrases = paste.lines().collect::<Vec<_>>();
for i in 0..12 {
if i < phrases.len() {
input.with_mut(|v: &mut Vec<String>| v[i] = phrases[i].into());
}
}
}
}
}
}
}
});
// todo: show toasts to inform user of errors.
let ch = use_coroutine(cx, |mut rx: UnboundedReceiver<Cmd>| {
to_owned![loading, page, seed_error];
Expand Down Expand Up @@ -109,28 +142,97 @@ pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Elem
aria_label: "instructions",
get_local_text("enter-seed-words.instructions")
},
input::Input {
aria_label: "recovery-seed-input".into(),
focus: true,
placeholder: get_local_text("enter-seed-words.placeholder"),
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
*input.write_silent() = x;
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_|{
loading.set(true);
ch.send(Cmd {
seed_words: input.read().clone(),
passphrase: pin.read().clone()
});
}
},
div {
class: "seed-words",
(0..6).map(|idx|{
let idx = idx * 2;
let other = idx + 1;
rsx!(div {
class: "row",
div {
class: "col",
span {
aria_label: "seed-word-number-{(idx + 1).to_string()}",
class: "num disable-select", (idx + 1).to_string()
},
input::Input {
aria_label: "recovery-seed-input-".to_string() + &(idx + 1).to_string(),
value: input.read()[idx].clone(),
select_on_focus: *focus.read() == idx,
focus: *focus.read() == idx, // select class gets removed on focus. this forces an update
placeholder: "".into(),
disable_onblur: true,
options: Options {
clear_on_submit: false,
..Default::default()
},
onfocus: move |_|{
*focus.write() = idx;
},
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
input.with_mut(|v|v[idx] = x);
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_| {
let f = *focus.read();
*focus.write() = (f + 1) % 12;
}
},
},
div {
class: "col",
span {
aria_label: "seed-word-number-{(other + 1).to_string()}",
class: "num disable-select", (other + 1).to_string()
},
input::Input {
aria_label: "recovery-seed-input-".to_string() + &(other + 1).to_string(),
value: input.read()[other].clone(),
focus: *focus.read() == other,
select_on_focus: *focus.read() == other, // select class gets removed on focus. this forces an update
placeholder: "".into(),
disable_onblur: true,
options: Options {
clear_on_submit: false,
..Default::default()
},
onfocus: move |_|{
*focus.write() = other;
},
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
input.with_mut(|v|v[other] = x);
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_| {
if other == 11 {
loading.set(true);
log::debug!("seed {}", input.read().join(" "));
ch.send(Cmd {
seed_words: input.read().join(" ").clone(),
passphrase: pin.read().clone()
});
} else {
let f = *focus.read();
*focus.write() = (f + 1) % 12;
}
}
},
}
})
})
}
seed_error.as_ref().map(|e| rsx!(
span {
aria_label: "input-error",
Expand All @@ -155,7 +257,7 @@ pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Elem
onpress: move |_| {
loading.set(true);
ch.send(Cmd {
seed_words: input.read().clone(),
seed_words: input.read().join(" ").clone(),
passphrase: pin.read().clone()
});
}
Expand Down
Loading
Loading