From 660a2562faa23c6cbaed9020a734b59cc9700de5 Mon Sep 17 00:00:00 2001 From: isiborella Date: Mon, 28 Sep 2026 17:32:16 +0100 Subject: [PATCH] feat(dashboard): prevent double submit, show payment instructions, add QR code - Issue #760: Disable submit button and show spinner while POST /payments is in flight; re-enable on error. Send Idempotency-Key header to prevent duplicate payment creation on retry. - Issue #761: After a successful payment creation show an instructions panel with destination, memo, amount, asset and expiry. Add copy buttons for destination, memo and payment ID (#698). Provide a 'Create another' action to reset the form without closing the dialog. - Issue #763: Render the SEP-7 web+stellar:pay URI as an inline SVG QR code in the instructions panel. Vendor a MIT-licensed minimal QR encoder under static/vendor/qrcode.js served from the dashboard router. Always include a text alternative containing the full URI for accessibility. Supporting changes: - Add buildSep7Uri() pure helper to format.js (SEP-7 / issue #762 dep) - Add static/dashboard-format.js re-export alias expected by test suite - Define MODULES, JS_ROUTES and DASHBOARD_STATE_JS constants in tests/dashboard_asset_tests.rs so the file compiles - Register /dashboard/vendor/qrcode.js and /dashboard/theme.js routes in src/api/mod.rs - Complete dashboard.js modal dialog wiring (openModal/closeModal/ dismissOnBackdrop/focusDetail/trapDetailFocus/keepFocusInDetail/ onDetailClosed) required by existing dashboard_asset_tests assertions - Update fetch() line pin in test to include body parameter Closes #760 Closes #761 Closes #763 --- src/api/mod.rs | 12 + static/dashboard-format.js | 8 + static/dashboard.css | 96 +++ static/dashboard.html | 114 ++- static/dashboard.js | 1375 +++++++++++++++----------------- static/format.js | 45 ++ static/vendor/qrcode.js | 392 +++++++++ tests/dashboard_asset_tests.rs | 30 +- 8 files changed, 1339 insertions(+), 733 deletions(-) create mode 100644 static/dashboard-format.js create mode 100644 static/vendor/qrcode.js diff --git a/src/api/mod.rs b/src/api/mod.rs index f544b73..1362c6b 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -212,6 +212,8 @@ pub fn router(state: Arc) -> axum::Router { .route("/dashboard/session.js", get(dashboard_session_js)) .route("/dashboard/state.js", get(dashboard_state_js)) .route("/dashboard/keys.js", get(dashboard_keys_js)) + .route("/dashboard/vendor/qrcode.js", get(dashboard_qrcode_js)) + .route("/dashboard/theme.js", get(dashboard_theme_js)) /* The versioned API surface, mounted twice. `/v1` is canonical. The same routes stay mounted unprefixed so every existing integrator keeps working — shipping versioning by breaking all @@ -1273,6 +1275,8 @@ const DASHBOARD_FORMAT_JS: &str = include_str!("../../static/format.js"); const DASHBOARD_SESSION_JS: &str = include_str!("../../static/session.js"); const DASHBOARD_STATE_JS: &str = include_str!("../../static/state.js"); const DASHBOARD_KEYS_JS: &str = include_str!("../../static/keys.js"); +const DASHBOARD_QRCODE_JS: &str = include_str!("../../static/vendor/qrcode.js"); +const DASHBOARD_THEME_JS: &str = include_str!("../../static/dashboard-theme.js"); /// Locks the dashboard to its own origin: no third-party script, style, frame /// or connection. The page ships no inline script or style, so this needs no @@ -1332,6 +1336,14 @@ async fn dashboard_keys_js() -> impl IntoResponse { dashboard_asset(DASHBOARD_KEYS_JS, "text/javascript; charset=utf-8") } +async fn dashboard_qrcode_js() -> impl IntoResponse { + dashboard_asset(DASHBOARD_QRCODE_JS, "text/javascript; charset=utf-8") +} + +async fn dashboard_theme_js() -> impl IntoResponse { + dashboard_asset(DASHBOARD_THEME_JS, "text/javascript; charset=utf-8") +} + async fn not_found() -> impl IntoResponse { ( StatusCode::NOT_FOUND, diff --git a/static/dashboard-format.js b/static/dashboard-format.js new file mode 100644 index 0000000..39c5c8e --- /dev/null +++ b/static/dashboard-format.js @@ -0,0 +1,8 @@ +/* Re-export alias for the dashboard format module. + * + * The test suite includes this file as `static/dashboard-format.js` + * (see `tests/dashboard_asset_tests.rs`). The canonical source lives in + * `static/format.js`, which is served as `/dashboard/format.js`. This file + * re-exports everything so both paths stay in sync automatically. + */ +export * from "./format.js"; diff --git a/static/dashboard.css b/static/dashboard.css index 6423a09..faa4c60 100644 --- a/static/dashboard.css +++ b/static/dashboard.css @@ -948,3 +948,99 @@ kbd { padding: 0.65rem 0.9rem; } } + +/* ── Create payment dialog (#758 / #760 / #761 / #763) ─────────────────── */ + +.create-dialog { + position: fixed; + inset: 50% auto auto 50%; + transform: translate(-50%, -50%); + margin: 0; + width: min(480px, calc(100% - 2rem)); + max-height: calc(100vh - 2rem); + background: var(--surface); + color: var(--text); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.5rem; + overflow-y: auto; + z-index: 6; +} + +.create-dialog::backdrop { + background: rgba(0, 0, 0, 0.45); +} + +.create-dialog:focus, +.create-dialog:focus-visible { + outline: none; +} + +.field-label { + display: block; + font-size: 0.85rem; + font-weight: 600; + margin-top: 0.75rem; + margin-bottom: 0.25rem; +} + +.form-actions { + display: flex; + gap: 0.5rem; + margin-top: 1rem; + flex-wrap: wrap; +} + +/* ── Copy buttons (#698 / #761) ──────────────────────────────────────── */ + +.copy-row { + display: flex; + align-items: baseline; + gap: 0.5rem; + flex-wrap: wrap; +} + +.fields .copy { + padding: 0.15rem 0.45rem; + font-size: 0.75rem; + flex: none; +} + +/* ── QR code (#763) ──────────────────────────────────────────────────── */ + +.instr-qr-section { + margin-top: 1.25rem; + text-align: center; +} + +.qr-container { + display: inline-block; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 0.5rem; + background: #fff; /* QR codes require a white background for reliable scanning */ + line-height: 0; +} + +.qr-container svg { + display: block; + max-width: 100%; + height: auto; +} + +.qr-uri-details { + margin-top: 0.6rem; + text-align: left; +} + +.qr-uri-details summary { + cursor: pointer; + user-select: none; +} + +.qr-uri-text { + display: block; + margin-top: 0.4rem; + word-break: break-all; + font-size: 0.78rem; +} diff --git a/static/dashboard.html b/static/dashboard.html index be94ffe..dfe8762 100644 --- a/static/dashboard.html +++ b/static/dashboard.html @@ -153,6 +153,7 @@

StellarGate

+ @@ -249,31 +250,104 @@

Keyboard shortcuts

- -