fedimint_ui_common/
lib.rs

1pub mod assets;
2pub mod auth;
3
4use axum::response::{Html, IntoResponse};
5use fedimint_core::hex::ToHex;
6use fedimint_core::secp256k1::rand::{Rng, thread_rng};
7use maud::{DOCTYPE, Markup, html};
8use serde::Deserialize;
9
10pub const ROOT_ROUTE: &str = "/";
11pub const LOGIN_ROUTE: &str = "/login";
12
13/// Generic state for both setup and dashboard UIs
14#[derive(Clone)]
15pub struct UiState<T> {
16    pub api: T,
17    pub auth_cookie_name: String,
18    pub auth_cookie_value: String,
19}
20
21impl<T> UiState<T> {
22    pub fn new(api: T) -> Self {
23        Self {
24            api,
25            auth_cookie_name: thread_rng().r#gen::<[u8; 4]>().encode_hex(),
26            auth_cookie_value: thread_rng().r#gen::<[u8; 32]>().encode_hex(),
27        }
28    }
29}
30
31pub fn common_head(title: &str) -> Markup {
32    html! {
33        meta charset="utf-8";
34        meta name="viewport" content="width=device-width, initial-scale=1.0";
35        link rel="stylesheet" href="/assets/bootstrap.min.css" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous";
36        link rel="stylesheet" type="text/css" href="/assets/style.css";
37        link rel="icon" type="image/png" href="/assets/logo.png";
38
39        // Note: this needs to be included in the header, so that web-page does not
40        // get in a state where htmx is not yet loaded. `deref` helps with blocking the load.
41        // Learned the hard way. --dpc
42        script defer src="/assets/htmx.org-2.0.4.min.js" {}
43
44        title { (title) }
45    }
46}
47
48#[derive(Debug, Deserialize)]
49pub struct LoginInput {
50    pub password: String,
51}
52
53pub fn login_layout(title: &str, content: Markup) -> Markup {
54    html! {
55        (DOCTYPE)
56        html {
57            head {
58                (common_head(title))
59            }
60            body {
61                div class="container" {
62                    div class="row justify-content-center" {
63                        div class="col-md-8 col-lg-5 narrow-container" {
64                            header class="text-center" {
65                                h1 class="header-title" { (title) }
66                            }
67
68                            div class="card" {
69                                div class="card-body" {
70                                    (content)
71                                }
72                            }
73                        }
74                    }
75                }
76                script src="/assets/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" {}
77            }
78        }
79    }
80}
81
82pub fn login_form_response(title: &str) -> impl IntoResponse {
83    let content = html! {
84        form method="post" action=(LOGIN_ROUTE) {
85            div class="form-group mb-4" {
86                input type="password" class="form-control" id="password" name="password" placeholder="Your password" required;
87            }
88            div class="button-container" {
89                button type="submit" class="btn btn-primary setup-btn" { "Log In" }
90            }
91        }
92    };
93
94    Html(login_layout(title, content).into_string()).into_response()
95}
96
97pub fn dashboard_layout(content: Markup, title: &str, version: Option<&str>) -> Markup {
98    html! {
99        (DOCTYPE)
100        html {
101            head {
102                (common_head(title))
103            }
104            body {
105                div class="container" {
106                    header class="text-center mb-4" {
107                        h1 class="header-title mb-1" { (title) }
108                        @if let Some(version) = version {
109                            div {
110                                small class="text-muted" { "v" (version) }
111                            }
112                        }
113                    }
114
115                    (content)
116                }
117                script src="/assets/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" {}
118            }
119        }
120    }
121}