mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-18 21:25:09 +01:00
cdk-ldk web ui updates (#1027)
This commit is contained in:
@@ -15,7 +15,7 @@ async-trait.workspace = true
|
|||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
cdk-common = { workspace = true, features = ["mint"] }
|
cdk-common = { workspace = true, features = ["mint"] }
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
@@ -29,6 +29,3 @@ tower-http.workspace = true
|
|||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ pub async fn post_open_channel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn close_channel_page(
|
pub async fn close_channel_page(
|
||||||
State(_state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
query: Query<HashMap<String, String>>,
|
query: Query<HashMap<String, String>>,
|
||||||
) -> Result<Html<String>, StatusCode> {
|
) -> Result<Html<String>, StatusCode> {
|
||||||
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
|
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
|
||||||
@@ -229,24 +229,40 @@ pub async fn close_channel_page(
|
|||||||
return Ok(Html(layout("Close Channel Error", content).into_string()));
|
return Ok(Html(layout("Close Channel Error", content).into_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get channel information for amount display
|
||||||
|
let channels = state.node.inner.list_channels();
|
||||||
|
let channel = channels
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.user_channel_id.0.to_string() == channel_id);
|
||||||
|
|
||||||
let content = form_card(
|
let content = form_card(
|
||||||
"Close Channel",
|
"Close Channel",
|
||||||
html! {
|
html! {
|
||||||
p { "Are you sure you want to close this channel?" }
|
p style="margin-bottom: 1.5rem;" { "Are you sure you want to close this channel?" }
|
||||||
div class="info-item" {
|
|
||||||
span class="info-label" { "User Channel ID:" }
|
// Channel details in consistent format
|
||||||
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) }
|
div class="channel-details" {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "User Channel ID" }
|
||||||
|
span class="detail-value-amount" { (channel_id) }
|
||||||
|
}
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Node ID" }
|
||||||
|
span class="detail-value-amount" { (node_id) }
|
||||||
|
}
|
||||||
|
@if let Some(ch) = channel {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Channel Amount" }
|
||||||
|
span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div class="info-item" {
|
|
||||||
span class="info-label" { "Node ID:" }
|
form method="post" action="/channels/close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" {
|
||||||
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) }
|
|
||||||
}
|
|
||||||
form method="post" action="/channels/close" style="margin-top: 1rem;" {
|
|
||||||
input type="hidden" name="channel_id" value=(channel_id) {}
|
input type="hidden" name="channel_id" value=(channel_id) {}
|
||||||
input type="hidden" name="node_id" value=(node_id) {}
|
input type="hidden" name="node_id" value=(node_id) {}
|
||||||
button type="submit" style="background: #dc3545;" { "Close Channel" }
|
a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
|
||||||
" "
|
button type="submit" class="button-destructive" { "Close Channel" }
|
||||||
a href="/balance" { button type="button" { "Cancel" } }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -255,7 +271,7 @@ pub async fn close_channel_page(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn force_close_channel_page(
|
pub async fn force_close_channel_page(
|
||||||
State(_state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
query: Query<HashMap<String, String>>,
|
query: Query<HashMap<String, String>>,
|
||||||
) -> Result<Html<String>, StatusCode> {
|
) -> Result<Html<String>, StatusCode> {
|
||||||
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
|
let channel_id = query.get("channel_id").unwrap_or(&"".to_string()).clone();
|
||||||
@@ -273,32 +289,48 @@ pub async fn force_close_channel_page(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get channel information for amount display
|
||||||
|
let channels = state.node.inner.list_channels();
|
||||||
|
let channel = channels
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.user_channel_id.0.to_string() == channel_id);
|
||||||
|
|
||||||
let content = form_card(
|
let content = form_card(
|
||||||
"Force Close Channel",
|
"Force Close Channel",
|
||||||
html! {
|
html! {
|
||||||
div style="border: 2px solid #d63384; background-color: rgba(214, 51, 132, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" {
|
div style="border: 2px solid #f97316; background-color: rgba(249, 115, 22, 0.1); padding: 1rem; margin-bottom: 1rem; border-radius: 0.5rem;" {
|
||||||
h4 style="color: #d63384; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" }
|
h4 style="color: #f97316; margin: 0 0 0.5rem 0;" { "⚠️ Warning: Force Close" }
|
||||||
p style="color: #d63384; margin: 0; font-size: 0.9rem;" {
|
p style="color: #f97316; margin: 0; font-size: 0.9rem;" {
|
||||||
"Force close should NOT be used if normal close is preferred. "
|
"Force close should NOT be used if normal close is preferred. "
|
||||||
"Force close will immediately broadcast the latest commitment transaction and may result in delayed fund recovery. "
|
"Force close will immediately broadcast the latest commitment transaction and may result in delayed fund recovery. "
|
||||||
"Only use this if the channel counterparty is unresponsive or there are other issues preventing normal closure."
|
"Only use this if the channel counterparty is unresponsive or there are other issues preventing normal closure."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p { "Are you sure you want to force close this channel?" }
|
p style="margin-bottom: 1.5rem;" { "Are you sure you want to force close this channel?" }
|
||||||
div class="info-item" {
|
|
||||||
span class="info-label" { "User Channel ID:" }
|
// Channel details in consistent format
|
||||||
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel_id) }
|
div class="channel-details" {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "User Channel ID" }
|
||||||
|
span class="detail-value-amount" { (channel_id) }
|
||||||
|
}
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Node ID" }
|
||||||
|
span class="detail-value-amount" { (node_id) }
|
||||||
|
}
|
||||||
|
@if let Some(ch) = channel {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Channel Amount" }
|
||||||
|
span class="detail-value-amount" { (format_sats_as_btc(ch.channel_value_sats)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div class="info-item" {
|
|
||||||
span class="info-label" { "Node ID:" }
|
form method="post" action="/channels/force-close" style="margin-top: 1rem; display: flex; justify-content: space-between; align-items: center;" {
|
||||||
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (node_id) }
|
|
||||||
}
|
|
||||||
form method="post" action="/channels/force-close" style="margin-top: 1rem;" {
|
|
||||||
input type="hidden" name="channel_id" value=(channel_id) {}
|
input type="hidden" name="channel_id" value=(channel_id) {}
|
||||||
input type="hidden" name="node_id" value=(node_id) {}
|
input type="hidden" name="node_id" value=(node_id) {}
|
||||||
button type="submit" style="background: #d63384;" { "Force Close Channel" }
|
a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
|
||||||
" "
|
button type="submit" class="button-destructive" { "Force Close Channel" }
|
||||||
a href="/balance" { button type="button" { "Cancel" } }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use axum::http::StatusCode;
|
|||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::web::handlers::AppState;
|
use crate::web::handlers::utils::AppState;
|
||||||
use crate::web::templates::{format_sats_as_btc, layout};
|
use crate::web::templates::{format_sats_as_btc, layout};
|
||||||
|
|
||||||
pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
|
pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
|
||||||
@@ -26,18 +26,35 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
|
|||||||
html! {
|
html! {
|
||||||
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
|
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
|
||||||
|
|
||||||
// Quick Actions section - matching dashboard style
|
// Quick Actions section - individual cards
|
||||||
div class="card" style="margin-bottom: 2rem;" {
|
div class="card" style="margin-bottom: 2rem;" {
|
||||||
h2 { "Quick Actions" }
|
h2 { "Quick Actions" }
|
||||||
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
|
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
|
||||||
a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
// Open Channel Card
|
||||||
button class="button-primary" style="width: 100%;" { "Open Channel" }
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning Network channel to connect with another node." }
|
||||||
|
a href="/channels/open" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Open Channel" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Create Invoice" }
|
// Create Invoice Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments from other users or services." }
|
||||||
|
a href="/invoices" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Create Invoice" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Make Lightning Payment" }
|
// Make Payment Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices. BOLT 11 & 12 supported." }
|
||||||
|
a href="/invoices" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Make Payment" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,18 +90,35 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
|
|||||||
html! {
|
html! {
|
||||||
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
|
h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
|
||||||
|
|
||||||
// Quick Actions section - matching dashboard style
|
// Quick Actions section - individual cards
|
||||||
div class="card" style="margin-bottom: 2rem;" {
|
div class="card" style="margin-bottom: 2rem;" {
|
||||||
h2 { "Quick Actions" }
|
h2 { "Quick Actions" }
|
||||||
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
|
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
|
||||||
a href="/channels/open" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
// Open Channel Card
|
||||||
button class="button-primary" style="width: 100%;" { "Open Channel" }
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Open Channel" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Create a new Lightning channel by connecting with another node." }
|
||||||
|
a href="/channels/open" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Open Channel" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/invoices" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Create Invoice" }
|
// Create Invoice Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Create Invoice" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a Lightning invoice to receive payments." }
|
||||||
|
a href="/invoices" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Create Invoice" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/payments/send" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Make Lightning Payment" }
|
// Make Payment Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Make Lightning Payment" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Lightning payments to other users using invoices." }
|
||||||
|
a href="/payments/send" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Make Payment" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,57 +146,72 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div class="card" {
|
// Channel Details header (outside card)
|
||||||
h2 { "Channel Details" }
|
h2 class="section-header" { "Channel Details" }
|
||||||
|
|
||||||
// Channels list
|
// Channels list
|
||||||
@for channel in &channels {
|
@for (index, channel) in channels.iter().enumerate() {
|
||||||
div class="channel-item" {
|
@let node_id = channel.counterparty_node_id.to_string();
|
||||||
div class="channel-header" {
|
@let channel_number = index + 1;
|
||||||
span class="channel-id" { "Channel ID: " (channel.channel_id.to_string()) }
|
|
||||||
|
div class="channel-box" {
|
||||||
|
// Channel number as prominent header
|
||||||
|
div class="channel-alias" { (format!("Channel {}", channel_number)) }
|
||||||
|
|
||||||
|
// Channel details in left-aligned format
|
||||||
|
div class="channel-details" {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Channel ID" }
|
||||||
|
span class="detail-value-amount" { (channel.channel_id.to_string()) }
|
||||||
|
}
|
||||||
|
@if let Some(short_channel_id) = channel.short_channel_id {
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Short Channel ID" }
|
||||||
|
span class="detail-value-amount" { (short_channel_id.to_string()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Node ID" }
|
||||||
|
span class="detail-value-amount" { (node_id) }
|
||||||
|
}
|
||||||
|
div class="detail-row" {
|
||||||
|
span class="detail-label" { "Status" }
|
||||||
@if channel.is_usable {
|
@if channel.is_usable {
|
||||||
span class="status-badge status-active" { "Active" }
|
span class="status-badge status-active" { "Active" }
|
||||||
} @else {
|
} @else {
|
||||||
span class="status-badge status-inactive" { "Inactive" }
|
span class="status-badge status-inactive" { "Inactive" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div class="info-item" {
|
}
|
||||||
span class="info-label" { "Counterparty" }
|
|
||||||
span class="info-value" style="font-family: monospace; font-size: 0.85rem;" { (channel.counterparty_node_id.to_string()) }
|
// Balance information cards (keeping existing style)
|
||||||
|
div class="balance-info" {
|
||||||
|
div class="balance-item" {
|
||||||
|
div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) }
|
||||||
|
div class="balance-label" { "Outbound" }
|
||||||
}
|
}
|
||||||
@if let Some(short_channel_id) = channel.short_channel_id {
|
div class="balance-item" {
|
||||||
div class="info-item" {
|
div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) }
|
||||||
span class="info-label" { "Short Channel ID" }
|
div class="balance-label" { "Inbound" }
|
||||||
span class="info-value" { (short_channel_id.to_string()) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
div class="balance-info" {
|
div class="balance-item" {
|
||||||
div class="balance-item" {
|
div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) }
|
||||||
div class="balance-amount" { (format_sats_as_btc(channel.outbound_capacity_msat / 1000)) }
|
div class="balance-label" { "Total" }
|
||||||
div class="balance-label" { "Outbound" }
|
|
||||||
}
|
|
||||||
div class="balance-item" {
|
|
||||||
div class="balance-amount" { (format_sats_as_btc(channel.inbound_capacity_msat / 1000)) }
|
|
||||||
div class="balance-label" { "Inbound" }
|
|
||||||
}
|
|
||||||
div class="balance-item" {
|
|
||||||
div class="balance-amount" { (format_sats_as_btc(channel.channel_value_sats)) }
|
|
||||||
div class="balance-label" { "Total" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@if channel.is_usable {
|
}
|
||||||
div style="margin-top: 1rem; display: flex; gap: 0.5rem;" {
|
|
||||||
a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
|
// Action buttons
|
||||||
button style="background: #dc3545;" { "Close Channel" }
|
@if channel.is_usable {
|
||||||
}
|
div class="channel-actions" {
|
||||||
a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
|
a href=(format!("/channels/close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
|
||||||
button style="background: #d63384;" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" }
|
button class="button-secondary" { "Close Channel" }
|
||||||
}
|
}
|
||||||
|
a href=(format!("/channels/force-close?channel_id={}&node_id={}", channel.user_channel_id.0, channel.counterparty_node_id)) {
|
||||||
|
button class="button-destructive" title="Force close should not be used if normal close is preferred. Force close will broadcast the latest commitment transaction immediately." { "Force Close" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,15 +79,26 @@ pub async fn onchain_page(
|
|||||||
let mut content = html! {
|
let mut content = html! {
|
||||||
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
||||||
|
|
||||||
// Quick Actions section - matching dashboard style
|
// Quick Actions section - individual cards
|
||||||
div class="card" style="margin-bottom: 2rem;" {
|
div class="card" style="margin-bottom: 2rem;" {
|
||||||
h2 { "Quick Actions" }
|
h2 { "Quick Actions" }
|
||||||
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
|
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
|
||||||
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
// Receive Bitcoin Card
|
||||||
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
|
||||||
|
a href="/onchain?action=receive" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Receive Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
|
// Send Bitcoin Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
|
||||||
|
a href="/onchain?action=send" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Send Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,15 +124,26 @@ pub async fn onchain_page(
|
|||||||
content = html! {
|
content = html! {
|
||||||
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
||||||
|
|
||||||
// Quick Actions section - matching dashboard style
|
// Quick Actions section - individual cards
|
||||||
div class="card" style="margin-bottom: 2rem;" {
|
div class="card" style="margin-bottom: 2rem;" {
|
||||||
h2 { "Quick Actions" }
|
h2 { "Quick Actions" }
|
||||||
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
|
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
|
||||||
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
// Receive Bitcoin Card
|
||||||
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
|
||||||
|
a href="/onchain?action=receive" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Receive Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
|
// Send Bitcoin Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
|
||||||
|
a href="/onchain?action=send" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Send Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +163,7 @@ pub async fn onchain_page(
|
|||||||
}
|
}
|
||||||
input type="hidden" id="send_action" name="send_action" value="send" {}
|
input type="hidden" id="send_action" name="send_action" value="send" {}
|
||||||
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
|
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
|
||||||
a href="/onchain" { button type="button" { "Cancel" } }
|
a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
|
||||||
div style="display: flex; gap: 0.5rem;" {
|
div style="display: flex; gap: 0.5rem;" {
|
||||||
button type="submit" onclick="document.getElementById('send_action').value='send'" { "Send Payment" }
|
button type="submit" onclick="document.getElementById('send_action').value='send'" { "Send Payment" }
|
||||||
button type="submit" onclick="document.getElementById('send_action').value='send_all'; document.getElementById('amount_sat').value=''" { "Send All" }
|
button type="submit" onclick="document.getElementById('send_action').value='send_all'; document.getElementById('amount_sat').value=''" { "Send All" }
|
||||||
@@ -171,15 +193,26 @@ pub async fn onchain_page(
|
|||||||
content = html! {
|
content = html! {
|
||||||
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
|
||||||
|
|
||||||
// Quick Actions section - matching dashboard style
|
// Quick Actions section - individual cards
|
||||||
div class="card" style="margin-bottom: 2rem;" {
|
div class="card" style="margin-bottom: 2rem;" {
|
||||||
h2 { "Quick Actions" }
|
h2 { "Quick Actions" }
|
||||||
div style="display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap;" {
|
div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
|
||||||
a href="/onchain?action=receive" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
// Receive Bitcoin Card
|
||||||
button class="button-primary" style="width: 100%;" { "Receive Bitcoin" }
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Receive Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Generate a new Bitcoin address to receive on-chain payments from other users or services." }
|
||||||
|
a href="/onchain?action=receive" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Receive Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a href="/onchain?action=send" style="text-decoration: none; flex: 1; min-width: 200px;" {
|
|
||||||
button class="button-primary" style="width: 100%;" { "Send Bitcoin" }
|
// Send Bitcoin Card
|
||||||
|
div class="quick-action-card" {
|
||||||
|
h3 style="font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);" { "Send Bitcoin" }
|
||||||
|
p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.4;" { "Send Bitcoin to another address on the blockchain. Standard on-chain transactions." }
|
||||||
|
a href="/onchain?action=send" style="text-decoration: none;" {
|
||||||
|
button class="button-outline" { "Send Bitcoin" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +224,7 @@ pub async fn onchain_page(
|
|||||||
form method="post" action="/onchain/new-address" {
|
form method="post" action="/onchain/new-address" {
|
||||||
p style="margin-bottom: 2rem;" { "Click the button below to generate a new Bitcoin address for receiving on-chain payments." }
|
p style="margin-bottom: 2rem;" { "Click the button below to generate a new Bitcoin address for receiving on-chain payments." }
|
||||||
div style="display: flex; justify-content: space-between; gap: 1rem;" {
|
div style="display: flex; justify-content: space-between; gap: 1rem;" {
|
||||||
a href="/onchain" { button type="button" { "Cancel" } }
|
a href="/onchain" { button type="button" class="button-secondary" { "Cancel" } }
|
||||||
button class="button-primary" type="submit" { "Generate New Address" }
|
button class="button-primary" type="submit" { "Generate New Address" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +378,7 @@ pub async fn onchain_confirm_page(
|
|||||||
div class="card" {
|
div class="card" {
|
||||||
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
|
div style="display: flex; justify-content: space-between; gap: 1rem; margin-top: 2rem;" {
|
||||||
a href="/onchain?action=send" {
|
a href="/onchain?action=send" {
|
||||||
button type="button" class="button-secondary" { "← Cancel" }
|
button type="button" class="button-secondary" { "Cancel" }
|
||||||
}
|
}
|
||||||
div style="display: flex; gap: 0.5rem;" {
|
div style="display: flex; gap: 0.5rem;" {
|
||||||
a href=(confirmation_url) {
|
a href=(confirmation_url) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ pub fn payment_list_item(
|
|||||||
let status_class = match status {
|
let status_class = match status {
|
||||||
"Succeeded" => "status-active",
|
"Succeeded" => "status-active",
|
||||||
"Failed" => "status-inactive",
|
"Failed" => "status-inactive",
|
||||||
"Pending" => "status-badge",
|
"Pending" => "status-pending",
|
||||||
_ => "status-badge",
|
_ => "status-badge",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 43 KiB |
Reference in New Issue
Block a user