|
|
@@ -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) => {
|