Răsfoiți Sursa

Redesign Lightning invoice creation and display with better UX and status handling (#1184)

---------

Co-authored-by: thesimplekid <tsk@thesimplekid.com>
Erik 3 săptămâni în urmă
părinte
comite
db2764c566

+ 67 - 44
crates/cdk-ldk-node/src/web/handlers/dashboard.rs

@@ -140,8 +140,8 @@ pub async fn dashboard(State(state): State<AppState>) -> Result<Html<String>, St
 
         // Balance Summary as metric cards
         div class="card" {
-            h2 { "Balance Summary" }
-            div class="metrics-container" {
+            h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { "Balance Summary" }
+            div class="metrics-container" style="margin-top: 1.5rem;" {
                 div class="metric-card" {
                     div class="metric-value" { (format_sats_as_btc(balances.total_lightning_balance_sats)) }
                     div class="metric-label" { "Lightning Balance" }
@@ -209,8 +209,8 @@ pub async fn dashboard(State(state): State<AppState>) -> Result<Html<String>, St
             // Right side - Connections metrics
             aside class="node-metrics" {
                 div class="card" {
-                    h3 { "Connections" }
-                    div class="metrics-container" {
+                    h3 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { "Connections" }
+                    div class="metrics-container" style="margin-top: 1.5rem;" {
                         div class="metric-card" {
                             div class="metric-value" { (format!("{}/{}", num_connected_peers, num_peers)) }
                             div class="metric-label" { "Connected Peers" }
@@ -224,51 +224,74 @@ pub async fn dashboard(State(state): State<AppState>) -> Result<Html<String>, St
             }
         }
 
-        // Lightning Network Activity as metric cards
+        // Activity Sections - Side by Side Layout
         div class="card" {
-            h2 { "Lightning Network Activity" }
-            div class="metrics-container" {
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.lightning_inflow_24h)) }
-                    div class="metric-label" { "24h LN Inflow" }
-                }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.lightning_outflow_24h)) }
-                    div class="metric-label" { "24h LN Outflow" }
-                }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.lightning_inflow_all_time)) }
-                    div class="metric-label" { "All-time LN Inflow" }
-                }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.lightning_outflow_all_time)) }
-                    div class="metric-label" { "All-time LN Outflow" }
-                }
-            }
-        }
+            h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; margin-bottom: 0;" { "Activity Overview" }
 
-        // On-chain Activity as metric cards
-        div class="card" {
-            h2 { "On-chain Activity" }
-            div class="metrics-container" {
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.onchain_inflow_24h)) }
-                    div class="metric-label" { "24h On-chain Inflow" }
-                }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.onchain_outflow_24h)) }
-                    div class="metric-label" { "24h On-chain Outflow" }
-                }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.onchain_inflow_all_time)) }
-                    div class="metric-label" { "All-time On-chain Inflow" }
+            div class="activity-grid" {
+                // Lightning Network Activity
+                div class="activity-section" {
+                    div class="activity-header" {
+                        div class="activity-icon-box" {
+                            svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" {
+                                path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" {}
+                            }
+                        }
+                        h3 class="activity-title" { "Lightning Network Activity" }
+                    }
+
+                    div class="activity-metrics" {
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "24h Inflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.lightning_inflow_24h)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "24h Outflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.lightning_outflow_24h)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "All-time Inflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.lightning_inflow_all_time)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "All-time Outflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.lightning_outflow_all_time)) }
+                        }
+                    }
                 }
-                div class="metric-card" {
-                    div class="metric-value" { (format_sats_as_btc(metrics.onchain_outflow_all_time)) }
-                    div class="metric-label" { "All-time On-chain Outflow" }
+
+                // On-chain Activity
+                div class="activity-section" {
+                    div class="activity-header" {
+                        div class="activity-icon-box" {
+                            svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" {
+                                path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" {}
+                                path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" {}
+                            }
+                        }
+                        h3 class="activity-title" { "On-chain Activity" }
+                    }
+
+                    div class="activity-metrics" {
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "24h Inflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.onchain_inflow_24h)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "24h Outflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.onchain_outflow_24h)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "All-time Inflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.onchain_inflow_all_time)) }
+                        }
+                        div class="activity-metric-card" {
+                            div class="activity-metric-label" { "All-time Outflow" }
+                            div class="activity-metric-value" { (format_sats_as_btc(metrics.onchain_outflow_all_time)) }
+                        }
+                    }
                 }
             }
-
         }
     };
 

+ 113 - 80
crates/cdk-ldk-node/src/web/handlers/invoices.rs

@@ -10,7 +10,7 @@ use serde::Deserialize;
 use crate::web::handlers::utils::{deserialize_optional_f64, deserialize_optional_u32};
 use crate::web::handlers::AppState;
 use crate::web::templates::{
-    error_message, form_card, format_sats_as_btc, info_card, is_node_running, layout_with_status,
+    error_message, format_sats_as_btc, invoice_display_card, is_node_running, layout_with_status,
     success_message,
 };
 
@@ -34,54 +34,101 @@ pub struct CreateBolt12Form {
 pub async fn invoices_page(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
     let content = html! {
         h2 style="text-align: center; margin-bottom: 3rem;" { "Invoices" }
-        div class="grid" {
-            (form_card(
-                "Create BOLT11 Invoice",
-                html! {
-                    form method="post" action="/invoices/bolt11" {
-                        div class="form-group" {
-                            label for="amount_btc" { "Amount" }
-                            input type="number" id="amount_btc" name="amount_btc" required placeholder="₿0" step="0.00000001" {}
-                        }
-                        div class="form-group" {
-                            label for="description" { "Description (optional)" }
-                            input type="text" id="description" name="description" placeholder="Payment for..." {}
-                        }
-                        div class="form-group" {
-                            label for="expiry_seconds" { "Expiry (seconds, optional)" }
-                            input type="number" id="expiry_seconds" name="expiry_seconds" placeholder="3600" {}
-                        }
-                        button type="submit" { "Create BOLT11 Invoice" }
+
+        div class="card" {
+            // Tab navigation
+            div class="payment-tabs" style="display: flex; gap: 0.5rem; margin-bottom: 1.5rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0;" {
+                button type="button" class="payment-tab active" onclick="switchInvoiceTab('bolt11')" data-tab="bolt11" {
+                    "BOLT11 Invoice"
+                }
+                button type="button" class="payment-tab" onclick="switchInvoiceTab('bolt12')" data-tab="bolt12" {
+                    "BOLT12 Offer"
+                }
+            }
+
+            // BOLT11 tab content
+            div id="bolt11-content" class="tab-content active" {
+                form method="post" action="/invoices/bolt11" {
+                    div class="form-group" {
+                        label for="amount_btc_bolt11" { "Amount" }
+                        input type="number" id="amount_btc_bolt11" name="amount_btc" required placeholder="₿0" step="0.00000001" {}
+                    }
+                    div class="form-group" {
+                        label for="description_bolt11" { "Description (optional)" }
+                        input type="text" id="description_bolt11" name="description" placeholder="Payment for..." {}
+                    }
+                    div class="form-group" {
+                        label for="expiry_seconds_bolt11" { "Expiry (seconds, optional)" }
+                        input type="number" id="expiry_seconds_bolt11" name="expiry_seconds" placeholder="3600" {}
+                    }
+                    div class="form-actions" {
+                        a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
+                        button type="submit" class="button-primary" { "Create BOLT11 Invoice" }
                     }
                 }
-            ))
+            }
 
-            (form_card(
-                "Create BOLT12 Offer",
-                html! {
-                    form method="post" action="/invoices/bolt12" {
-                        div class="form-group" {
-                            label for="amount_btc" { "Amount (optional for variable amount)" }
-                            input type="number" id="amount_btc" name="amount_btc" placeholder="₿0" step="0.00000001" {}
-                        }
-                        div class="form-group" {
-                            label for="description" { "Description (optional)" }
-                            input type="text" id="description" name="description" placeholder="Payment for..." {}
+            // BOLT12 tab content
+            div id="bolt12-content" class="tab-content" {
+                form method="post" action="/invoices/bolt12" {
+                    div class="form-group" {
+                        label for="amount_btc_bolt12" { "Amount (optional for variable amount)" }
+                        input type="number" id="amount_btc_bolt12" name="amount_btc" placeholder="₿0" step="0.00000001" {}
+                        p style="font-size: 0.8125rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;" {
+                            "Leave empty for variable amount offers, specify amount for fixed offers"
                         }
-                        div class="form-group" {
-                            label for="expiry_seconds" { "Expiry (seconds, optional)" }
-                            input type="number" id="expiry_seconds" name="expiry_seconds" placeholder="3600" {}
-                        }
-                        button type="submit" { "Create BOLT12 Offer" }
+                    }
+                    div class="form-group" {
+                        label for="description_bolt12" { "Description (optional)" }
+                        input type="text" id="description_bolt12" name="description" placeholder="Payment for..." {}
+                    }
+                    div class="form-group" {
+                        label for="expiry_seconds_bolt12" { "Expiry (seconds, optional)" }
+                        input type="number" id="expiry_seconds_bolt12" name="expiry_seconds" placeholder="3600" {}
+                    }
+                    div class="form-actions" {
+                        a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
+                        button type="submit" class="button-primary" { "Create BOLT12 Offer" }
                     }
                 }
-            ))
+            }
+        }
+
+        // Tab switching script
+        script type="text/javascript" {
+            (maud::PreEscaped(r#"
+            function switchInvoiceTab(tabName) {
+                console.log('Switching to invoice tab:', tabName);
+
+                // Hide all tab contents
+                const contents = document.querySelectorAll('.tab-content');
+                contents.forEach(content => content.classList.remove('active'));
+
+                // Remove active class from all tabs
+                const tabs = document.querySelectorAll('.payment-tab');
+                tabs.forEach(tab => tab.classList.remove('active'));
+
+                // Show selected tab content
+                const tabContent = document.getElementById(tabName + '-content');
+                if (tabContent) {
+                    tabContent.classList.add('active');
+                    console.log('Activated invoice tab content:', tabName);
+                }
+
+                // Add active class to selected tab
+                const tabButton = document.querySelector('[data-tab="' + tabName + '"]');
+                if (tabButton) {
+                    tabButton.classList.add('active');
+                    console.log('Activated invoice tab button:', tabName);
+                }
+            }
+            "#))
         }
     };
 
     let is_running = is_node_running(&state.node.inner);
     Ok(Html(
-        layout_with_status("Create Invoices", content, is_running).into_string(),
+        layout_with_status("s", content, is_running).into_string(),
     ))
 }
 
@@ -126,7 +173,7 @@ pub async fn post_create_bolt11(
                     .status(StatusCode::BAD_REQUEST)
                     .header("content-type", "text/html")
                     .body(Body::from(
-                        layout_with_status("Create Invoice Error", content, true).into_string(),
+                        layout_with_status(" Error", content, true).into_string(),
                     ))
                     .unwrap());
             }
@@ -161,32 +208,25 @@ pub async fn post_create_bolt11(
                 description_text.clone()
             };
 
+            let invoice_details = vec![
+                ("Payment Hash", invoice.payment_hash().to_string()),
+                ("Amount", format_sats_as_btc(form.amount_btc)),
+                ("Description", description_display),
+                (
+                    "Expires At",
+                    format!("{}", current_time + expiry_seconds as u64),
+                ),
+            ];
+
             html! {
                 (success_message("BOLT11 Invoice created successfully!"))
-                (info_card(
-                    "Invoice Details",
-                    vec![
-                        ("Payment Hash", invoice.payment_hash().to_string()),
-                        ("Amount", format_sats_as_btc(form.amount_btc)),
-                        ("Description", description_display),
-                        ("Expires At", format!("{}", current_time + expiry_seconds as u64)),
-                    ]
-                ))
-                div class="card" {
-                    h3 { "Invoice (copy this to share)" }
-                    textarea readonly style="width: 100%; height: 150px; font-family: monospace; font-size: 0.8rem;" {
-                        (invoice.to_string())
-                    }
-                }
-                div class="card" {
-                    a href="/invoices" { button { "← Create Another Invoice" } }
-                }
+                (invoice_display_card(&invoice.to_string(), &format_sats_as_btc(form.amount_btc), invoice_details, "/invoices"))
             }
         }
         Err(e) => {
             tracing::error!("Web interface: Failed to create BOLT11 invoice: {}", e);
             html! {
-                (error_message(&format!("Failed to create invoice: {e}")))
+                (error_message(&format!("Failed to : {e}")))
                 div class="card" {
                     a href="/invoices" { button { "← Try Again" } }
                 }
@@ -210,15 +250,15 @@ pub async fn post_create_bolt12(
     let description_text = form.description.unwrap_or_else(|| "".to_string());
 
     tracing::info!(
-        "Web interface: Creating BOLT12 offer for amount={:?} btc, description={:?}, expiry={}s",
+        "Web interface: Creating BOLT12 offer for amount={:?} sats, description={:?}, expiry={}s",
         form.amount_btc,
         description_text,
         expiry_seconds
     );
 
     let offer_result = if let Some(amount_btc) = form.amount_btc {
-        // Convert Bitcoin to millisatoshis (1 BTC = 100,000,000,000 msats)
-        let amount_msats = (amount_btc * 100_000_000_000.0) as u64;
+        // Convert satoshis to millisatoshis (1 sat = 1,000 msats)
+        let amount_msats = (amount_btc * 1_000.0) as u64;
         state.node.inner.bolt12_payment().receive(
             amount_msats,
             &description_text,
@@ -246,7 +286,7 @@ pub async fn post_create_bolt12(
 
             let amount_display = form
                 .amount_btc
-                .map(|a| format_sats_as_btc((a * 100_000_000.0) as u64))
+                .map(|a| format_sats_as_btc(a as u64))
                 .unwrap_or_else(|| "Variable amount".to_string());
 
             let description_display = if description_text.is_empty() {
@@ -255,26 +295,19 @@ pub async fn post_create_bolt12(
                 description_text
             };
 
+            let offer_details = vec![
+                ("Offer ID", offer.id().to_string()),
+                ("Amount", amount_display.clone()),
+                ("Description", description_display),
+                (
+                    "Expires At",
+                    format!("{}", current_time + expiry_seconds as u64),
+                ),
+            ];
+
             html! {
                 (success_message("BOLT12 Offer created successfully!"))
-                (info_card(
-                    "Offer Details",
-                    vec![
-                        ("Offer ID", offer.id().to_string()),
-                        ("Amount", amount_display),
-                        ("Description", description_display),
-                        ("Expires At", format!("{}", current_time + expiry_seconds as u64)),
-                    ]
-                ))
-                div class="card" {
-                    h3 { "Offer (copy this to share)" }
-                    textarea readonly style="width: 100%; height: 150px; font-family: monospace; font-size: 0.8rem;" {
-                        (offer.to_string())
-                    }
-                }
-                div class="card" {
-                    a href="/invoices" { button { "← Create Another Offer" } }
-                }
+                (invoice_display_card(&offer.to_string(), &amount_display, offer_details, "/invoices"))
             }
         }
         Err(e) => {

+ 69 - 86
crates/cdk-ldk-node/src/web/handlers/lightning.rs

@@ -26,43 +26,33 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
         html! {
             h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
 
-            // Quick Actions section - individual cards
-            div class="card" style="margin-bottom: 2rem;" {
-                h2 { "Quick Actions" }
-                div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
-                    // Open Channel Card
-                    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" }
-                        }
+            // Inactive channels warning (only show if > 0)
+            @if num_inactive_channels > 0 {
+                div class="card" style="background-color: #fef3c7; border: 1px solid #f59e0b; margin-bottom: 2rem;" {
+                    h3 style="color: #92400e; margin-bottom: 0.5rem;" { "⚠️ Inactive Channels Detected" }
+                    p style="color: #78350f; margin: 0;" {
+                        "You have " (num_inactive_channels) " inactive channel(s). This may indicate a connectivity issue that requires attention."
                     }
+                }
+            }
 
-                    // 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" }
+            // Balance Information with action buttons in header
+            div class="card" {
+                div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" {
+                    h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "Balance Information" }
+                    div style="display: flex; gap: 0.5rem;" {
+                        a href="/payments/send" style="text-decoration: none;" {
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Send" }
                         }
-                    }
-
-                    // 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" }
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Receive" }
+                        }
+                        a href="/channels/open" style="text-decoration: none;" {
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Open Channel" }
                         }
                     }
                 }
-            }
-
-            // Balance Information as metric cards
-            div class="card" {
-                h2 { "Balance Information" }
-                div class="metrics-container" {
+                div class="metrics-container" style="margin-top: 1.5rem;" {
                     div class="metric-card" {
                         div class="metric-value" { (format_sats_as_btc(balances.total_lightning_balance_sats)) }
                         div class="metric-label" { "Lightning Balance" }
@@ -75,9 +65,11 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
                         div class="metric-value" { (format!("{}", num_active_channels)) }
                         div class="metric-label" { "Active Channels" }
                     }
-                    div class="metric-card" {
-                        div class="metric-value" { (format!("{}", num_inactive_channels)) }
-                        div class="metric-label" { "Inactive Channels" }
+                    @if num_inactive_channels > 0 {
+                        div class="metric-card" {
+                            div class="metric-value" style="color: #f59e0b;" { (format!("{}", num_inactive_channels)) }
+                            div class="metric-label" { "Inactive Channels" }
+                        }
                     }
                 }
             }
@@ -90,43 +82,33 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
         html! {
             h2 style="text-align: center; margin-bottom: 3rem;" { "Lightning" }
 
-            // Quick Actions section - individual cards
-            div class="card" style="margin-bottom: 2rem;" {
-                h2 { "Quick Actions" }
-                div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
-                    // Open Channel Card
-                    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" }
-                        }
+            // Inactive channels warning (only show if > 0)
+            @if num_inactive_channels > 0 {
+                div class="card" style="background-color: #fef3c7; border: 1px solid #f59e0b; margin-bottom: 2rem;" {
+                    h3 style="color: #92400e; margin-bottom: 0.5rem;" { "⚠️ Inactive Channels Detected" }
+                    p style="color: #78350f; margin: 0;" {
+                        "You have " (num_inactive_channels) " inactive channel(s). This may indicate a connectivity issue that requires attention."
                     }
+                }
+            }
 
-                    // 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." }
+            // Balance Information with action buttons in header
+            div class="card" {
+                div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" {
+                    h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "Balance Information" }
+                    div style="display: flex; gap: 0.5rem;" {
+                        a href="/payments/send" style="text-decoration: none;" {
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Send" }
+                        }
                         a href="/invoices" style="text-decoration: none;" {
-                            button class="button-outline" { "Create Invoice" }
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Receive" }
                         }
-                    }
-
-                    // 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" }
+                        a href="/channels/open" style="text-decoration: none;" {
+                            button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Open Channel" }
                         }
                     }
                 }
-            }
-
-            // Balance Information as metric cards
-            div class="card" {
-                h2 { "Balance Information" }
-                div class="metrics-container" {
+                div class="metrics-container" style="margin-top: 1.5rem;" {
                     div class="metric-card" {
                         div class="metric-value" { (format_sats_as_btc(balances.total_lightning_balance_sats)) }
                         div class="metric-label" { "Lightning Balance" }
@@ -139,47 +121,48 @@ pub async fn balance_page(State(state): State<AppState>) -> Result<Html<String>,
                         div class="metric-value" { (format!("{}", num_active_channels)) }
                         div class="metric-label" { "Active Channels" }
                     }
-                    div class="metric-card" {
-                        div class="metric-value" { (format!("{}", num_inactive_channels)) }
-                        div class="metric-label" { "Inactive Channels" }
+                    @if num_inactive_channels > 0 {
+                        div class="metric-card" {
+                            div class="metric-value" style="color: #f59e0b;" { (format!("{}", num_inactive_channels)) }
+                            div class="metric-label" { "Inactive Channels" }
+                        }
                     }
                 }
             }
 
             // Channel Details header (outside card)
-            h2 class="section-header" { "Channel Details" }
+            h2 class="section-header" style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5;" { "Channel Details" }
 
-            // Channels list
-            @for (index, channel) in channels.iter().enumerate() {
+                    // Channels list
+                    @for (index, channel) in channels.iter().enumerate() {
                 @let node_id = channel.counterparty_node_id.to_string();
                 @let channel_number = index + 1;
 
                 div class="channel-box" {
-                    // Channel number as prominent header
-                    div class="channel-alias" { (format!("Channel {}", channel_number)) }
+                    // Channel header with number on left and status badge on right
+                    div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 1.5rem;" {
+                        div class="channel-alias" style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { (format!("Channel {}", channel_number)) }
+                        @if channel.is_usable {
+                            span class="status-badge status-active" { "Active" }
+                        } @else {
+                            span class="status-badge status-inactive" { "Inactive" }
+                        }
+                    }
 
-                    // Channel details in left-aligned format
+                    // Channel details - ordered by label length
                     div class="channel-details" {
                         div class="detail-row" {
+                            span class="detail-label" { "Node ID" }
+                            span class="detail-value" { (node_id) }
+                        }
+                        div class="detail-row" {
                             span class="detail-label" { "Channel ID" }
-                            span class="detail-value-amount" { (channel.channel_id.to_string()) }
+                            span class="detail-value" { (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 {
-                                span class="status-badge status-active" { "Active" }
-                            } @else {
-                                span class="status-badge status-inactive" { "Inactive" }
+                                span class="detail-value" { (short_channel_id.to_string()) }
                             }
                         }
                     }

+ 43 - 43
crates/cdk-ldk-node/src/web/handlers/onchain.rs

@@ -33,33 +33,6 @@ pub struct ConfirmOnchainForm {
     confirmed: Option<String>,
 }
 
-fn quick_actions_section() -> maud::Markup {
-    html! {
-        div class="card" style="margin-bottom: 2rem;" {
-            h2 { "Quick Actions" }
-            div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;" {
-                // Receive Bitcoin Card
-                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" }
-                    }
-                }
-
-                // 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" }
-                    }
-                }
-            }
-        }
-    }
-}
-
 pub async fn get_new_address(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
     let address_result = state.node.inner.onchain_payment().new_address();
 
@@ -67,8 +40,8 @@ pub async fn get_new_address(State(state): State<AppState>) -> Result<Html<Strin
         Ok(address) => {
             html! {
                 div class="card" {
-                    h2 { "Bitcoin Address" }
-                    div class="address-display" {
+                    h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { "Bitcoin Address" }
+                    div class="address-display" style="margin-top: 1.5rem;" {
                         div class="address-container" {
                             span class="address-text" { (address.to_string()) }
                         }
@@ -113,13 +86,20 @@ pub async fn onchain_page(
     let mut content = html! {
         h2 style="text-align: center; margin-bottom: 3rem;" { "On-chain" }
 
-        // Quick Actions section - only show on overview
-        (quick_actions_section())
-
-        // On-chain Balance as metric cards
+        // On-chain Balance with action buttons in header
         div class="card" {
-            h2 { "On-chain Balance" }
-            div class="metrics-container" {
+            div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" {
+                h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "On-chain Balance" }
+                div style="display: flex; gap: 0.5rem;" {
+                    a href="/onchain?action=send" style="text-decoration: none;" {
+                        button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Send" }
+                    }
+                    a href="/onchain?action=receive" style="text-decoration: none;" {
+                        button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Receive" }
+                    }
+                }
+            }
+            div class="metrics-container" style="margin-top: 1.5rem;" {
                 div class="metric-card" {
                     div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
                     div class="metric-label" { "Total Balance" }
@@ -162,10 +142,20 @@ pub async fn onchain_page(
                     }
                 ))
 
-                // On-chain Balance as metric cards
+                // On-chain Balance with action buttons in header
                 div class="card" {
-                    h2 { "On-chain Balance" }
-                    div class="metrics-container" {
+                    div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" {
+                        h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "On-chain Balance" }
+                        div style="display: flex; gap: 0.5rem;" {
+                            a href="/onchain?action=send" style="text-decoration: none;" {
+                                button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Send" }
+                            }
+                            a href="/onchain?action=receive" style="text-decoration: none;" {
+                                button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Receive" }
+                            }
+                        }
+                    }
+                    div class="metrics-container" style="margin-top: 1.5rem;" {
                         div class="metric-card" {
                             div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
                             div class="metric-label" { "Total Balance" }
@@ -196,10 +186,20 @@ pub async fn onchain_page(
                     }
                 ))
 
-                // On-chain Balance as metric cards
+                // On-chain Balance with action buttons in header
                 div class="card" {
-                    h2 { "On-chain Balance" }
-                    div class="metrics-container" {
+                    div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" {
+                        h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "On-chain Balance" }
+                        div style="display: flex; gap: 0.5rem;" {
+                            a href="/onchain?action=send" style="text-decoration: none;" {
+                                button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Send" }
+                            }
+                            a href="/onchain?action=receive" style="text-decoration: none;" {
+                                button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Receive" }
+                            }
+                        }
+                    }
+                    div class="metrics-container" style="margin-top: 1.5rem;" {
                         div class="metric-card" {
                             div class="metric-value" { (format_sats_as_btc(balances.total_onchain_balance_sats)) }
                             div class="metric-label" { "Total Balance" }
@@ -324,8 +324,8 @@ pub async fn onchain_confirm_page(
 
         // Transaction Details Card
         div class="card" {
-            h2 { "Transaction Details" }
-            div class="transaction-details" {
+            h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { "Transaction Details" }
+            div class="transaction-details" style="margin-top: 1.5rem;" {
                 div class="detail-row" {
                     span class="detail-label" { "Recipient Address:" }
                     span class="detail-value" { (form.address.clone()) }

+ 100 - 38
crates/cdk-ldk-node/src/web/handlers/payments.rs

@@ -15,7 +15,7 @@ use serde::Deserialize;
 use crate::web::handlers::utils::{deserialize_optional_u64, get_paginated_payments_streaming};
 use crate::web::handlers::AppState;
 use crate::web::templates::{
-    error_message, form_card, format_msats_as_btc, format_sats_as_btc, info_card, is_node_running,
+    error_message, format_msats_as_btc, format_sats_as_btc, info_card, is_node_running,
     layout_with_status, payment_list_item, success_message,
 };
 
@@ -86,7 +86,7 @@ pub async fn payments_page(
         div class="card" {
             div class="payment-list-header" {
                 div {
-                    h2 { "Payment History" }
+                    h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { "Payment History" }
                     @if total_count > 0 {
                         p style="margin: 0.25rem 0 0 0; color: #666; font-size: 0.9rem;" {
                             "Showing " (start_index + 1) " to " (end_index) " of " (total_count) " payments"
@@ -116,14 +116,6 @@ pub async fn payments_page(
                         PaymentDirection::Outbound => "Outbound",
                     };
 
-                    @let status_str = match payment.status {
-                        PaymentStatus::Pending => "Pending",
-                        PaymentStatus::Succeeded => "Succeeded",
-                        PaymentStatus::Failed => "Failed",
-                    };
-
-                    @let amount_str = payment.amount_msat.map(format_msats_as_btc).unwrap_or_else(|| "Unknown".to_string());
-
                     @let (payment_hash, description, payment_type, preimage) = match &payment.kind {
                         PaymentKind::Bolt11 { hash, preimage, .. } => {
                             (Some(hash.to_string()), None::<String>, "BOLT11", preimage.map(|p| p.to_string()))
@@ -147,6 +139,27 @@ pub async fn payments_page(
                         },
                     };
 
+                    @let status_str = {
+                        // Helper function to determine invoice status
+                        fn get_invoice_status(status: PaymentStatus, direction: PaymentDirection, payment_type: &str) -> &'static str {
+                            match status {
+                                PaymentStatus::Succeeded => "Succeeded",
+                                PaymentStatus::Failed => "Failed",
+                                PaymentStatus::Pending => {
+                                    // For inbound BOLT11 payments, show "Unpaid" instead of "Pending"
+                                    if direction == PaymentDirection::Inbound && payment_type == "BOLT11" {
+                                        "Unpaid"
+                                    } else {
+                                        "Pending"
+                                    }
+                                }
+                            }
+                        }
+                        get_invoice_status(payment.status, payment.direction, payment_type)
+                    };
+
+                    @let amount_str = payment.amount_msat.map(format_msats_as_btc).unwrap_or_else(|| "Unknown".to_string());
+
                     (payment_list_item(
                         &payment.id.to_string(),
                         direction_str,
@@ -245,42 +258,91 @@ pub async fn payments_page(
 pub async fn send_payments_page(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
     let content = html! {
         h2 style="text-align: center; margin-bottom: 3rem;" { "Send Payment" }
-        div class="grid" {
-            (form_card(
-                "Pay BOLT11 Invoice",
-                html! {
-                    form method="post" action="/payments/bolt11" {
-                        div class="form-group" {
-                            label for="invoice" { "BOLT11 Invoice" }
-                            textarea id="invoice" name="invoice" required placeholder="lnbc..." style="height: 120px;" {}
-                        }
-                        div class="form-group" {
-                            label for="amount_btc" { "Amount Override (optional)" }
-                            input type="number" id="amount_btc" name="amount_btc" placeholder="Leave empty to use invoice amount" step="1" {}
+
+        div class="card" {
+            // Tab navigation
+            div class="payment-tabs" style="display: flex; gap: 0.5rem; margin-bottom: 1.5rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0;" {
+                button type="button" class="payment-tab active" onclick="switchTab('bolt11')" data-tab="bolt11" {
+                    "BOLT11 Invoice"
+                }
+                button type="button" class="payment-tab" onclick="switchTab('bolt12')" data-tab="bolt12" {
+                    "BOLT12 Offer"
+                }
+            }
+
+            // BOLT11 tab content
+            div id="bolt11-content" class="tab-content active" {
+                form method="post" action="/payments/bolt11" {
+                    div class="form-group" {
+                        label for="invoice" { "BOLT11 Invoice" }
+                        textarea id="invoice" name="invoice" required placeholder="lnbc..." rows="4" {}
+                    }
+                    div class="form-group" {
+                        label for="amount_btc_bolt11" { "Amount Override (optional)" }
+                        input type="number" id="amount_btc_bolt11" name="amount_btc" placeholder="Leave empty to use invoice amount" step="1" {}
+                        p style="font-size: 0.8125rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;" {
+                            "Only specify an amount if you want to override the invoice amount"
                         }
-                        button type="submit" { "Pay BOLT11 Invoice" }
+                    }
+                    div class="form-actions" {
+                        a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
+                        button type="submit" class="button-primary" { "Pay Invoice" }
                     }
                 }
-            ))
-
-            (form_card(
-                "Pay BOLT12 Offer",
-                html! {
-                    form method="post" action="/payments/bolt12" {
-                        div class="form-group" {
-                            label for="offer" { "BOLT12 Offer" }
-                            textarea id="offer" name="offer" required placeholder="lno..." style="height: 120px;" {}
-                        }
-                        div class="form-group" {
-                            label for="amount_btc" { "Amount (required for variable amount offers)" }
-                            input type="number" id="amount_btc" name="amount_btc" placeholder="Required for variable amount offers, ignored for fixed amount offers" step="1" {}
+            }
+
+            // BOLT12 tab content
+            div id="bolt12-content" class="tab-content" {
+                form method="post" action="/payments/bolt12" {
+                    div class="form-group" {
+                        label for="offer" { "BOLT12 Offer" }
+                        textarea id="offer" name="offer" required placeholder="lno..." rows="4" {}
+                    }
+                    div class="form-group" {
+                        label for="amount_btc_bolt12" { "Amount" }
+                        input type="number" id="amount_btc_bolt12" name="amount_btc" placeholder="Amount in satoshis" step="1" {}
+                        p style="font-size: 0.8125rem; color: hsl(var(--muted-foreground)); margin-top: 0.5rem;" {
+                            "Required for variable amount offers, ignored for fixed amount offers"
                         }
-                        button type="submit" { "Pay BOLT12 Offer" }
+                    }
+                    div class="form-actions" {
+                        a href="/balance" { button type="button" class="button-secondary" { "Cancel" } }
+                        button type="submit" class="button-primary" { "Pay Offer" }
                     }
                 }
-            ))
+            }
         }
 
+        // Tab switching script
+        script type="text/javascript" {
+            (maud::PreEscaped(r#"
+            function switchTab(tabName) {
+                console.log('Switching to tab:', tabName);
+
+                // Hide all tab contents
+                const contents = document.querySelectorAll('.tab-content');
+                contents.forEach(content => content.classList.remove('active'));
+
+                // Remove active class from all tabs
+                const tabs = document.querySelectorAll('.payment-tab');
+                tabs.forEach(tab => tab.classList.remove('active'));
+
+                // Show selected tab content
+                const tabContent = document.getElementById(tabName + '-content');
+                if (tabContent) {
+                    tabContent.classList.add('active');
+                    console.log('Activated tab content:', tabName);
+                }
+
+                // Add active class to selected tab
+                const tabButton = document.querySelector('[data-tab="' + tabName + '"]');
+                if (tabButton) {
+                    tabButton.classList.add('active');
+                    console.log('Activated tab button:', tabName);
+                }
+            }
+            "#))
+        }
     };
 
     let is_running = is_node_running(&state.node.inner);

+ 58 - 7
crates/cdk-ldk-node/src/web/templates/components.rs

@@ -3,11 +3,13 @@ use maud::{html, Markup};
 pub fn info_card(title: &str, items: Vec<(&str, String)>) -> Markup {
     html! {
         div class="card" {
-            h2 { (title) }
-            @for (label, value) in items {
-                div class="info-item" {
-                    span class="info-label" { (label) ":" }
-                    span class="info-value" { (value) }
+            h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { (title) }
+            div style="margin-top: 1.5rem;" {
+                @for (label, value) in items {
+                    div class="info-item" {
+                        span class="info-label" { (label) ":" }
+                        span class="info-value" { (value) }
+                    }
                 }
             }
         }
@@ -17,8 +19,10 @@ pub fn info_card(title: &str, items: Vec<(&str, String)>) -> Markup {
 pub fn form_card(title: &str, form_content: Markup) -> Markup {
     html! {
         div class="card" {
-            h2 { (title) }
-            (form_content)
+            h2 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 0;" { (title) }
+            div style="margin-top: 1.5rem;" {
+                (form_content)
+            }
         }
     }
 }
@@ -34,3 +38,50 @@ pub fn error_message(message: &str) -> Markup {
         div class="error" { (message) }
     }
 }
+
+pub fn invoice_display_card(
+    invoice_text: &str,
+    amount: &str,
+    details: Vec<(&str, String)>,
+    back_url: &str,
+) -> Markup {
+    html! {
+        div class="card" {
+            div style="display: flex; justify-content: space-between; align-items: center; padding-bottom: 1rem; border-bottom: 1px solid hsl(var(--border)); margin-bottom: 1.5rem;" {
+                h3 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0;" { "Invoice Details" }
+            }
+
+            // Amount highlight section at the top
+            div class="invoice-amount-section" {
+                div class="invoice-amount-label" { "Amount" }
+                div class="invoice-amount-value" { (amount) }
+            }
+
+            // Invoice display section - under the amount
+            div class="invoice-display-section" {
+                div class="invoice-label" { "Invoice" }
+                div class="invoice-display-container" {
+                    textarea readonly class="invoice-textarea" { (invoice_text) }
+                }
+            }
+
+            // Invoice details section - after the invoice with increased spacing
+            div class="invoice-details-section" style="margin-top: 2.5rem;" {
+                h4 style="font-size: 0.875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.5; margin: 0 0 1rem 0;" { "Details" }
+                @for (label, value) in details {
+                    div class="info-item" {
+                        span class="info-label" { (label) ":" }
+                        span class="info-value" { (value) }
+                    }
+                }
+            }
+
+            // Back button at bottom left - no border lines
+            div style="margin-top: 2rem;" {
+                a href=(back_url) style="text-decoration: none;" {
+                    button class="button-outline" style="padding: 0.5rem 1rem; font-size: 0.875rem;" { "Back" }
+                }
+            }
+        }
+    }
+}

Fișier diff suprimat deoarece este prea mare
+ 635 - 52
crates/cdk-ldk-node/src/web/templates/layout.rs


+ 1 - 0
crates/cdk-ldk-node/src/web/templates/payments.rs

@@ -18,6 +18,7 @@ pub fn payment_list_item(
         "Succeeded" => "status-active",
         "Failed" => "status-inactive",
         "Pending" => "status-pending",
+        "Unpaid" => "status-pending", // Use pending styling for unpaid
         _ => "status-badge",
     };
 

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff