diff --git a/Cargo.lock b/Cargo.lock
index 1d7ab8f..b67a992 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1721,6 +1721,15 @@ dependencies = [
"either",
]
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "1.0.6"
@@ -3754,6 +3763,7 @@ dependencies = [
"futures",
"gloo-storage",
"hex",
+ "itertools 0.13.0",
"leptos",
"leptos-qr-scanner",
"leptos-use",
diff --git a/Cargo.toml b/Cargo.toml
index 837bd17..5d63635 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ fedimint-mint-client = "0.2.1-rc1"
fedimint-ln-client = "0.2.1-rc1"
futures = "0.3.28"
hex = "0.4.3"
+itertools = "0.13.0"
leptos = { version = "0.6.5", features = ["csr"] }
leptos-use = "0.10.2"
leptos-qr-scanner = { git = "https://github.com/elsirion/leptos-qr-scanner", rev = "5830bd6f75d7836189ef1434f71a10222a737a44" }
diff --git a/src/components/app.rs b/src/components/app.rs
index 4f7ed90..30a1a9c 100644
--- a/src/components/app.rs
+++ b/src/components/app.rs
@@ -111,6 +111,7 @@ pub fn App() -> impl IntoView {
placeholder="invite code".into()
submit_label="Join".into()
loading=join_action.pending()
+ default_scan=true
/>
diff --git a/src/components/create_wallet.rs b/src/components/create_wallet.rs
index 988d98c..ba1d3dc 100644
--- a/src/components/create_wallet.rs
+++ b/src/components/create_wallet.rs
@@ -28,6 +28,7 @@ where
+
+
}
diff --git a/src/components/joined.rs b/src/components/joined.rs
index 6c757a1..88e7f22 100644
--- a/src/components/joined.rs
+++ b/src/components/joined.rs
@@ -1,6 +1,6 @@
use leptos::*;
-use crate::components::{Balance, ReceiveEcash, ReceiveLn, SendEcash, SendLn, TxList};
+use crate::components::{Balance, Receive, Send, TxList};
use crate::context::ClientContext;
//
@@ -36,20 +36,12 @@ pub fn Joined() -> impl IntoView {
view: view! { },
},
MenuItem {
- title: "Spend".into(),
- view: view! { },
+ title: "Send".into(),
+ view: view! { },
},
MenuItem {
- title: "Redeem".into(),
- view: view! { },
- },
- MenuItem {
- title: "LN Send".into(),
- view: view! { },
- },
- MenuItem {
- title: "LN Receive".into(),
- view: view! { },
+ title: "Receive".into(),
+ view: view! { },
},
];
diff --git a/src/components/mod.rs b/src/components/mod.rs
index cc94870..8fcc24c 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -9,9 +9,13 @@ pub mod ln_receive_form;
pub mod loader_icon;
pub mod logo;
pub mod logo_fedimint;
+pub mod protocol_selector;
pub mod qrcode;
+pub mod receive;
pub mod receive_ecash;
pub mod receive_ln;
+pub mod segmented_button;
+pub mod send;
pub mod send_ecash;
pub mod send_ln;
pub mod service_worker;
@@ -29,9 +33,13 @@ pub use joined::*;
pub use loader_icon::*;
pub use logo::*;
pub use logo_fedimint::*;
+pub use protocol_selector::*;
pub use qrcode::*;
+pub use receive::*;
pub use receive_ecash::*;
pub use receive_ln::*;
+pub use segmented_button::*;
+pub use send::*;
pub use send_ecash::*;
pub use send_ln::*;
pub use submit_button::*;
diff --git a/src/components/protocol_selector.rs b/src/components/protocol_selector.rs
new file mode 100644
index 0000000..a5132cf
--- /dev/null
+++ b/src/components/protocol_selector.rs
@@ -0,0 +1,45 @@
+use leptos::*;
+
+use crate::components::SegmentedButton;
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Protocol {
+ OnChain,
+ Lightning,
+ ECash,
+}
+
+impl Protocol {
+ fn from_idx(idx: usize) -> Protocol {
+ match idx {
+ 0 => Protocol::OnChain,
+ 1 => Protocol::Lightning,
+ 2 => Protocol::ECash,
+ _ => panic!("Out of bounds"),
+ }
+ }
+
+ fn into_idx(self) -> usize {
+ match self {
+ Protocol::OnChain => 0,
+ Protocol::Lightning => 1,
+ Protocol::ECash => 2,
+ }
+ }
+}
+#[component]
+pub fn ProtocolSelector(
+ #[prop(default = Protocol::Lightning)] active_protocol: Protocol,
+ on_change: F,
+) -> impl IntoView
+where
+ F: Fn(Protocol) + 'static + Copy,
+{
+ view! {
+
+ }
+}
diff --git a/src/components/receive.rs b/src/components/receive.rs
new file mode 100644
index 0000000..7ece608
--- /dev/null
+++ b/src/components/receive.rs
@@ -0,0 +1,32 @@
+use leptos::*;
+
+use crate::components::{Protocol, ProtocolSelector, ReceiveEcash, ReceiveLn};
+
+#[component]
+pub fn Receive() -> impl IntoView {
+ const DEFAULT_PROTOCOL: Protocol = Protocol::Lightning;
+ let (active_protocol, set_active_protocol) = create_signal(DEFAULT_PROTOCOL);
+
+ let active_protocol_view = move || match active_protocol.get() {
+ Protocol::OnChain => view! {
+ "TODO"
+ }
+ .into_view(),
+ Protocol::Lightning => view! {
+
+ }
+ .into_view(),
+ Protocol::ECash => view! {
+
+ }
+ .into_view(),
+ };
+
+ view! {
+
+ {active_protocol_view}
+ }
+}
diff --git a/src/components/receive_ecash.rs b/src/components/receive_ecash.rs
index 05cecce..63de8d0 100644
--- a/src/components/receive_ecash.rs
+++ b/src/components/receive_ecash.rs
@@ -24,6 +24,7 @@ pub fn ReceiveEcash() -> impl IntoView {
placeholder="e-cash notes".into()
submit_label="Redeem".into()
loading=submit_action.pending()
+ default_scan=true
/>
{move ||
diff --git a/src/components/segmented_button.rs b/src/components/segmented_button.rs
new file mode 100644
index 0000000..4347bd9
--- /dev/null
+++ b/src/components/segmented_button.rs
@@ -0,0 +1,57 @@
+use itertools::Itertools;
+use leptos::*;
+
+#[component]
+pub fn SegmentedButton(
+ #[prop(default = 0)] active_idx: usize,
+ segments: Vec,
+ on_change: F,
+) -> impl IntoView
+where
+ F: Fn(usize) + 'static + Copy,
+{
+ const ALL_CLASS: &str = "px-4 py-2 focus:outline-none flex-1 text-center";
+ const FIRST_CLASS: &str = "rounded-l-full";
+ const LAST_CLASS: &str = "rounded-r-full";
+ const ACTIVE_CLASS: &str = "text-white bg-blue-500";
+ const INACTIVE_CLASS: &str = "text-blue-500 bg-transparent";
+
+ let (active_idx, set_active_idx) = create_signal(active_idx);
+ let num_segments = segments.len();
+
+ let buttons = segments
+ .into_iter()
+ .enumerate()
+ .map(|(idx, name)| {
+ let class = move || {
+ let is_first = idx == 0;
+ let is_last = idx == num_segments - 1;
+ let is_active = idx == active_idx.get();
+
+ std::iter::once(ALL_CLASS)
+ .chain(is_first.then(|| FIRST_CLASS))
+ .chain(is_last.then(|| LAST_CLASS))
+ .chain(is_active.then(|| ACTIVE_CLASS))
+ .chain((!is_active).then(|| INACTIVE_CLASS))
+ .join(" ")
+ };
+ view! {
+
+ }
+ })
+ .collect::>();
+
+ view! {
+
+ {buttons}
+
+ }
+}
diff --git a/src/components/send.rs b/src/components/send.rs
new file mode 100644
index 0000000..e07ca1d
--- /dev/null
+++ b/src/components/send.rs
@@ -0,0 +1,32 @@
+use leptos::*;
+
+use crate::components::{Protocol, ProtocolSelector, SendEcash, SendLn};
+
+#[component]
+pub fn Send() -> impl IntoView {
+ const DEFAULT_PROTOCOL: Protocol = Protocol::Lightning;
+ let (active_protocol, set_active_protocol) = create_signal(DEFAULT_PROTOCOL);
+
+ let active_protocol_view = move || match active_protocol.get() {
+ Protocol::OnChain => view! {
+ "TODO"
+ }
+ .into_view(),
+ Protocol::Lightning => view! {
+
+ }
+ .into_view(),
+ Protocol::ECash => view! {
+
+ }
+ .into_view(),
+ };
+
+ view! {
+
+ {active_protocol_view}
+ }
+}
diff --git a/src/components/send_ln.rs b/src/components/send_ln.rs
index 5a3e0ac..c449b30 100644
--- a/src/components/send_ln.rs
+++ b/src/components/send_ln.rs
@@ -24,6 +24,7 @@ pub fn SendLn() -> impl IntoView {
placeholder="LN invoice".into()
submit_label="Send".into()
loading=submit_action.pending()
+ default_scan=true
/>
diff --git a/src/components/submit_form.rs b/src/components/submit_form.rs
index e027ae1..4c5341e 100644
--- a/src/components/submit_form.rs
+++ b/src/components/submit_form.rs
@@ -1,6 +1,8 @@
use leptos::ev::KeyboardEvent;
+use leptos::html::Form;
use leptos::*;
use leptos_qr_scanner::Scan;
+use leptos_use::use_element_visibility;
use crate::components::SubmitButton;
@@ -11,6 +13,7 @@ pub fn SubmitForm(
description: String,
submit_label: String,
loading: ReadSignal,
+ #[prop(default = false)] default_scan: bool,
) -> impl IntoView
where
F: Fn(String) + 'static + Copy,
@@ -20,7 +23,7 @@ where
let button_is_disabled = Signal::derive(move || loading.get() || value.get().is_empty());
let scan_disabled = Signal::derive(move || loading.get());
- let (scan, set_scan) = create_signal(false);
+ let (scan, set_scan) = create_signal(default_scan);
let textarea = view! {
};
+ let form_ref = create_node_ref::